You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Sometimes it is desired to forbid certain types from being considered.
Example: JSON.stringify(function() {}); doesn't make sense and the chance is high it's written like this by mistake. With negated types we could eliminate a chance of it.
typeSerializable=any~Function;// or simply ~ FunctiondeclareinterfaceJSON{stringify(value: Serializable): string;}
Edit: this comment probably belongs in #7993 instead.
Aleksey-Bykov This would allow unions with a catch-all member, without overshadowing the types of the known members.
interfaceA{type: "a",data: number}interfaceB{type: "b",data: string}interfaceUnknown{type: string~"a"|"b",data: any }typeABU=A|B|Unknownvarx : ABU={type: "a",data: 5}if(x.type==="a"){lety=x.data;// y should be inferred to be a number instead of any}
Reacted by ExE Boss, Richard Collins, Max Marrone, Ricardo Fernández Serrata and Gulg
I think you should never put a question of what exactly any - MyClass is. I think negated types should be evaluated loosely lazily and only when it comes to typechecks against certain types.
I agree. Is that not sort of like many types now, e.g. number. You never consider how to construct number because it's infinite: only test that a value belongs to it when you need it. What would be the (lazy) procedure for checking that T belongs to A - B, or !B?
Understood! I guess my question is when you have some concrete types, say A, B, C, and you want to know if A is assignable to B - C, is it something like.
not sure if you can apply sort of a type algebra here, because it's unclear how assignability relates to negation
what you can do is to build a concrete type out of B - C and name it D (provided both B and C are known) and then ask a question whether or not A is assignable to the concrete type D
my naive 5 cents
question still stands what to do when B is too broad like any
It may have been stated before in another ticket but the use case I have for this feature is containing a string so:
Pseudo code
// Where `T` is a union of string literalstypeAnyStringExcept<T>=string&!T;typeNotAllowed="a"|"b";AllowedStrings=string|AnyStringExcept<NotAllowed>;
As its been mentioned before, this behavior can exist in a function but not at a "type-only level".
The project specific use-case for me is constraining the keys that are pressed for a keybinding to only valid characters.
// Every possible universal key across all keyboardsexporttypeUniversalKey=// Control Keys|"BACKSPACE"|"TAB"|"ENTER"|"ESCAPE"|"DELETE"// Modifier Keys|"SHIFT"|"CONTROL"|"ALT"|"META"// Arrow Keys|"ARROWLEFT"|"ARROWUP"|"ARROWRIGHT"|"ARROWDOWN"// Number Keys|"0"|"1"|"2"|"3"|"4"|"5"|"6"|"7"|"8"|"9"// Numpad Keys|"NUMPAD0"|"NUMPAD1"|"NUMPAD2"|"NUMPAD3"|"NUMPAD4"|"NUMPAD5"|"NUMPAD6"|"NUMPAD7"|"NUMPAD8"|"NUMPAD9"|"NUMPADMULTIPLY"|"NUMPADADD"|"NUMPADSUBTRACT"|"NUMPADDECIMAL"|"NUMPADDIVIDE"// Function Keys|"F1"|"F2"|"F3"|"F4"|"F5"|"F6"|"F7"|"F8"|"F9"|"F10"|"F11"|"F12"// Navigation Keys|"HOME"|"END"|"PAGEUP"|"PAGEDOWN"// Other Special Keys|"CAPSLOCK"|"INSERT"|"PAUSE"|"PRINTSCREEN"|"SCROLLLOCK";// a constrained list of universal keys that are acceptable for keybindingsexporttypeValidUniversalKey=Extract<UniversalKey,// Control Keys"BACKSPACE"|"TAB"|"ENTER"|"ESCAPE"|"DELETE"// Modifier Keys|"SHIFT"|"CONTROL"|"ALT"|"META"// Arrow Keys|"ARROWLEFT"|"ARROWUP"|"ARROWRIGHT"|"ARROWDOWN"// Number Keys|"0"|"1"|"2"|"3"|"4"|"5"|"6"|"7"|"8"|"9"// Numpad Keys|"NUMPAD0"|"NUMPAD1"|"NUMPAD2"|"NUMPAD3"|"NUMPAD4"|"NUMPAD5"|"NUMPAD6"|"NUMPAD7"|"NUMPAD8"|"NUMPAD9">;exporttypeInvalidUniversalKey=Exclude<UniversalKey,ValidUniversalKey>;exporttypeValidShortcutKey=(ValidUniversalKey|ValidUniqueKey)&AnyStringExcept<InvalidUniversalKey>;
Having this would prevent me from loosening the type def or defining a massive character list (string literals).
This would be really nice to have because I could use combinators like AND, OR, NOT, for type predicates.
/** * a type predicate AND combinator */exportfunctionandTp<A,BextendsA,CextendsA>(f: (x: A)=>x is B,g: (x: A)=>x is C): (x: A)=>x is B&C{return(x: A)=>f(x)&&g(x);}
/** * a type predicate OR combinator */exportfunctionorTp<A,BextendsA,CextendsA>(f: (x: A)=>x is B,g: (x: A)=>x is C): (x: A)=>x is B|C{return(x: A)=>f(x)||g(x);}
This works great and I'm able to do point-free combinations of type predicates. But I can't see how I can do NEGATION of type predicates without negated types.
// I wish I could do this...exportfunctionnotTp<A>(f: (x: A)=>x is A,): (x: A)=>x is notA{return(x: A)=>!f(x);}
Then I could nicely compose my type predicates and do things like this
I'm trying to determine the current state of this and related suggestions (going back over 10 years). It seems like it's in popular demand, and the lack of such a feature makes some valid JS unrepresentable (if you want it typed).
Is it just not as high a priority as other features (and if so, how are the priorities determined)?
Is it a philosophically problematic addition for some reason?
Is the implementation considered too hard or risky vs perceived utility?
If someone implemented type subtraction or negation (and it didn't break anything!) would that influence the decision to include the feature? Would a PR be welcome (asking for a friend ;) )?
Reacted by KT, Nex Zhu, Ricardo Fernández Serrata, Sʜɪᴍᴜʀᴀ Yū, Yamiteru, Lucas Genzelis, Maru Alka, Matthew Dean, Dylan Pizzo and Nico314159
adrianstephens My guess is that implementing negated types in a duck-typed language like TypeScript would be quite challenging. Since TypeScript allows structural typing, an object of one type can often be used as another type if they share the same attributes—even if their meanings are entirely different.
// A and B are distinct interfaces but have identical attributesconstfn=(a: A,callback: (notA: ~A)=>void)=>{constb: B=a;// Structurally identical, so assignment is allowedcallback(b);// This is validcallback(a);// This is an error};// The issue becomes even more problematic when implicit type conversions occurconstfn2=(b: B,callback: (notA: ~A)=>void)=>{callback(b);// Accepted};consta: A=getA();fn2(a,(notA: ~A)=>console.log('not A',notA));// Unexpectedly valid// Ambiguity arises with inheritance and type intersectionsinterfaceCextendsA,B{}// Alternatively, C could be defined as: type C = A & BfunctiongetC(): C{ ... }constmyCallback: (notA: ~A)=>void= ...
constc1=getC();myCallback(c1);// Errorconstc2: B=getC();myCallback(c2);// Allowed
These cases could pose challenges for those looking to implement negated types, depending on how strictly they need to be enforced. The validity of ~A depends not only on the structure of A but also on how TypeScript resolves assignments and type relations dynamically.
One potential solution would be introducing nominal types (see: #202). If nominal typing were supported, negated types might be more feasible—at least for explicitly nominal types—since they wouldn't rely purely on structural compatibility.
Reacted by Joar Krohn Mauseth and Ricardo Fernández Serrata
Lucas Basquerotto (@lucasbasquerotto) Without thinking about it too hard, it seems to me that if you have a function in your transpiler that determines if one type is assignable to another (say), you can just negate the result for negated types. In other words, just think of the type checker as working on abstract things that obey boolean algebra.
This makes it quite unambiguous - ~A would match anything that A doesn't by the existing rules. So if an instance of B could be passed to a parameter of type A, then it would not match ~A.
I think type subtraction is a bit clearer conceptually, so that's what I tried to implement; but negation would probably've been simpler.
My 2 cents: Even though Rust and TS have very different type-systems, discussions about negative trait-bounds (and specialization) could help reach a consensus about this
Control flow analysis already seems to do this for else clauses, doesn't it?
Control flow analysis does type subtraction, not type negation. And it always goes from concrete type to concrete type, so it can take a number of shortcuts that can't always be taken with negated types.
Reacted by Ricardo Fernández Serrata and Sʜɪᴍᴜʀᴀ Yū
The thing is, all these nuances have already been discussed in this very long thread - probably multiple times! I don't think this feature is pending a specification. Which brings me back to my original question - what is the status, and what can be done to push it forward?
Reacted by Ricardo Fernández Serrata, Claudia Meadows, Dylan Pizzo, Jeff Rooks, Gulg, June, Douglas Gaskell, Craig, Alexey Berezin, Domi and 2 more
I would also love this for multiple projects I'm working on, while it isn't blocking as I already have working implementations, but it would make working with typescript way easier and cleaner.
I'm fairly new to typescript, so probably no one will need this like I did, but if anyone is interested, here are some examples of how I implemented negation for my projects.
/** Given a type `T`, it returns `T` if it extends `A` AND does not extend `N`, if not it returns never */typeNot<N,T,A=any>=[T]extends[N] ? never : ([T]extends[A] ? T : never);// Non empty stringstypeNonEmptyString<T>=Not<'',T,string>;functiontakesNonEmptyString<T>(v: NonEmptyString<T>){};takesNonEmptyString('');// gives a type error as it is an empty stringtakesNonEmptyString('not empty');// works as expectedtakesNonEmptyString(20);// gives a type error as it isn't a string// Non zero numbertypeNonZeroNumber<T>=Not<0,T,number>;functiontakesNonZeroNumber<T>(v: NonZeroNumber<T>){}takesNonZeroNumber(0);// gives a type error as it is 0takesNonZeroNumber(20);// works as expectedtakesNonZeroNumber('str');// gives a type error as it isn't a number// Any string but a settypeAnyStringBut<T>=Not<'a'|'b',T,string>;functiontakesAnyStringBut<T>(v: AnyStringBut<T>){};takesAnyStringBut('a');// gives a type error as it is 'a'takesAnyStringBut('b');// gives a type error as it is 'b'takesAnyStringBut('c');// works as expected as any other stringtakesAnyStringBut(20);// gives an error as it isn't a string// Infer value using generic functionfunctionnot<N,A=any>(){return{infer<T>(v: Not<N,T,A>){returnv}}}constnonEmptyString=not<'',string>().infer('');// gives a type error as it is an empty stringconstnonZeroNumber=not<0,number>().infer(0);// gives a type error as it is 0constanyButFunction=not<Function>().infer(()=>{})// gives a type error as it is a functionconstanyStringBut=not<'a'|'b',string>().infer('b')// gives a type error as it is 'b'
This correctly gives type errors if the value is not allowed:
Reacted by Yamiteru and Daniele Orlando
Reacted by Ricardo Fernández Serrata and Brandon Istenes
I would also love this for multiple projects I'm working on, while it isn't blocking as I already have working implementations, but it would make working with typescript way easier and cleaner.
I'm fairly new to typescript, so probably no one will need this like I did, but if anyone is interested, here are some examples of how I implemented negation for my projects.
type Not<N, T, A = any> = [T] extends [N] ? never : (
[T] extends [A] ? T : never
);
// Non empty strings
type NonEmptyString = Not<'', T, string>;
function takesNonEmptyString(v: NonEmptyString) {};
takesNonEmptyString(''); // gives a type error as it is an empty string
takesNonEmptyString('not empty'); // works as expected
takesNonEmptyString(20); // gives a type error as it isn't a string
// Non zero number
type NonZeroNumber = Not<0, T, number>;
function takesNonZeroNumber(v: NonZeroNumber) {}
takesNonZeroNumber(0); // gives a type error as it is 0
takesNonZeroNumber(20); // works as expected
takesNonZeroNumber('str'); // gives a type error as it isn't a number
// Any string but a set
type AnyStringBut = Not<'a' | 'b', T, string>;
function takesAnyStringBut(v: AnyStringBut) {};
takesAnyStringBut('a'); // gives a type error as it is 'a'
takesAnyStringBut('b'); // gives a type error as it is 'b'
takesAnyStringBut('c'); // works as expected as any other string
takesAnyStringBut(20); // gives an error as it isn't a string
// Infer value using generic function
function not<N, A = any>() {
return { infer(v: Not<N, T, A>) { return v } }
}
const nonEmptyString = not<'', string>().infer(''); // gives a type error as it is an empty string
const nonZeroNumber = not<0, number>().infer(0); // gives a type error as it is 0
const anyButFunction = not().infer(()=>{}) // gives a type error as it is a function
const anyStringBut = not<'a' | 'b', string>().infer('b') // gives a type error as it is 'b'
This correctly gives type errors if the value is not allowed:
is there a way to do this for like Infinity | -Infinity | NaN, im trying to make a "FiniteNumber" type
is there a way to do this for like Infinity | -Infinity | NaN, im trying to make a "FiniteNumber" type
Miyo Sho (@yuri-kiss) I don't think there is a straightforward way to do this at the type level, as Infinity and NaN are not types but values, both are typed as numbers and are not considered as literal types by typescript, so there is no way to distinguish between them and a finite number at the type level. So this won't be fixed even if negated types are added, unless typescript recognizes Infinity and NaN as types.
Tho, after thinking about it and searching for a bit, I did find a way to implement this, tho I prefer to not post here, as it isn't directly related to this issue, and I don't want to clutter this discussion.
As such, I answered your question and explained how I did it in this page.
is there a way to do this for like Infinity | -Infinity | NaN, im trying to make a "FiniteNumber" type
@yuri-kiss I don't think there is a straightforward way to do this at the type level, as Infinity and NaN are not types but values, both are typed as numbers and are not considered as literal types by typescript, so there is no way to distinguish between them and a finite number at the type level. So this won't be fixed even if negated types are added, unless typescript recognizes Infinity and NaN as types.
Tho, after thinking about it and searching for a bit, I did find a way to implement this, tho I prefer to not post here, as it isn't directly related to this issue, and I don't want to clutter this discussion.
As such, I answered your question and explained how I did it in this page.
Activity
wclr commentedon Sep 5, 2016
It is interesting, it is possible to achieve somehow?
siegebell commentedon Nov 11, 2016
Edit: this comment probably belongs in #7993 instead.
Aleksey-Bykov This would allow unions with a catch-all member, without overshadowing the types of the known members.
SalathielGenese commentedon Mar 9, 2018
Following Mohamed Hegazy (@mhegazy) request at #18280, I copy-paste this suggestion here...
I upvote
A & !Bespecially the!Bpart... OverExcludefrom #21847jack-williams commentedon Mar 9, 2018
Do negated types rely on completeness for type-checking?
zpdDG4gta8XKpMCd commentedon Mar 9, 2018
I think you should never put a question of what exactly
any - MyClassis. I think negated types should be evaluatedlooselylazily and only when it comes to typechecks against certain types.jack-williams commentedon Mar 9, 2018
I agree. Is that not sort of like many types now, e.g.
number. You never consider how to constructnumberbecause it's infinite: only test that a value belongs to it when you need it. What would be the (lazy) procedure for checking thatTbelongs toA - B, or!B?SalathielGenese commentedon Mar 9, 2018
zpdDG4gta8XKpMCd commentedon Mar 9, 2018
not until all type parameters (
A,BandTin your example) are resolved to concrete types (string,MyClass,null) can you tell whatA - BiszpdDG4gta8XKpMCd commentedon Mar 9, 2018
so the procedure would be:
jack-williams commentedon Mar 9, 2018
Understood! I guess my question is when you have some concrete types, say
A, B, C, and you want to know ifAis assignable toB - C, is it something like.Ais assignable toBAis not assignable toCAis assignable toB - C.For
Aassignable to!Cit would just be isAnot assignable toC. (Thanks Salathiel Genese (@SalathielGenese))Sorry if that's not clear!
SalathielGenese commentedon Mar 9, 2018
I think you meant
Anot assignable toCzpdDG4gta8XKpMCd commentedon Mar 9, 2018
not sure if you can apply sort of a type algebra here, because it's unclear how assignability relates to negation
what you can do is to build a concrete type out of
B - Cand name itD(provided bothBandCare known) and then ask a question whether or notAis assignable to the concrete typeDmy naive 5 cents
question still stands what to do when B is too broad like
anySalathielGenese commentedon Mar 9, 2018
I think a cleaner way to see
B - Cwould be much like a type constraint rather a type by essence.SalathielGenese commentedon Mar 9, 2018
If by some logic it can be resolved to a type, that would be great, otherwise, it is just a type constraint
46 remaining items
onx2 commentedon Jul 24, 2024
It may have been stated before in another ticket but the use case I have for this feature is containing a string so:
Pseudo code
As its been mentioned before, this behavior can exist in a function but not at a "type-only level".
The project specific use-case for me is constraining the keys that are pressed for a keybinding to only valid characters.
Having this would prevent me from loosening the type def or defining a massive character list (string literals).
adueck commentedon Aug 19, 2024
This would be really nice to have because I could use combinators like AND, OR, NOT, for type predicates.
This works great and I'm able to do point-free combinations of type predicates. But I can't see how I can do NEGATION of type predicates without negated types.
Then I could nicely compose my type predicates and do things like this
Link to the TypeScript type narrowing issue.
adrianstephens commentedon Mar 18, 2025
I'm trying to determine the current state of this and related suggestions (going back over 10 years). It seems like it's in popular demand, and the lack of such a feature makes some valid JS unrepresentable (if you want it typed).
Is it just not as high a priority as other features (and if so, how are the priorities determined)?
Is it a philosophically problematic addition for some reason?
Is the implementation considered too hard or risky vs perceived utility?
If someone implemented type subtraction or negation (and it didn't break anything!) would that influence the decision to include the feature? Would a PR be welcome (asking for a friend ;) )?
lucasbasquerotto commentedon Mar 19, 2025
adrianstephens My guess is that implementing negated types in a duck-typed language like TypeScript would be quite challenging. Since TypeScript allows structural typing, an object of one type can often be used as another type if they share the same attributes—even if their meanings are entirely different.
These cases could pose challenges for those looking to implement negated types, depending on how strictly they need to be enforced. The validity of ~A depends not only on the structure of A but also on how TypeScript resolves assignments and type relations dynamically.
One potential solution would be introducing nominal types (see: #202). If nominal typing were supported, negated types might be more feasible—at least for explicitly nominal types—since they wouldn't rely purely on structural compatibility.
adrianstephens commentedon Mar 19, 2025
Lucas Basquerotto (@lucasbasquerotto) Without thinking about it too hard, it seems to me that if you have a function in your transpiler that determines if one type is assignable to another (say), you can just negate the result for negated types. In other words, just think of the type checker as working on abstract things that obey boolean algebra.
This makes it quite unambiguous - ~A would match anything that A doesn't by the existing rules. So if an instance of B could be passed to a parameter of type A, then it would not match ~A.
I think type subtraction is a bit clearer conceptually, so that's what I tried to implement; but negation would probably've been simpler.
Rudxain commentedon Mar 20, 2025
My 2 cents: Even though Rust and TS have very different type-systems, discussions about negative trait-bounds (and specialization) could help reach a consensus about this
yuhr commentedon Mar 20, 2025
Control flow analysis already seems to do this for
elseclauses, doesn't it?dead-claudia commentedon Mar 20, 2025
Control flow analysis does type subtraction, not type negation. And it always goes from concrete type to concrete type, so it can take a number of shortcuts that can't always be taken with negated types.
adrianstephens commentedon Mar 20, 2025
The thing is, all these nuances have already been discussed in this very long thread - probably multiple times! I don't think this feature is pending a specification. Which brings me back to my original question - what is the status, and what can be done to push it forward?
amine-err commentedon Dec 21, 2025
I would also love this for multiple projects I'm working on, while it isn't blocking as I already have working implementations, but it would make working with typescript way easier and cleaner.
I'm fairly new to typescript, so probably no one will need this like I did, but if anyone is interested, here are some examples of how I implemented negation for my projects.
This correctly gives type errors if the value is not allowed:
yuri-kiss commentedon Dec 24, 2025
is there a way to do this for like
Infinity | -Infinity | NaN, im trying to make a "FiniteNumber" typeamine-err commentedon Dec 25, 2025
Miyo Sho (@yuri-kiss) I don't think there is a straightforward way to do this at the type level, as
InfinityandNaNare not types but values, both are typed as numbers and are not considered as literal types by typescript, so there is no way to distinguish between them and a finite number at the type level. So this won't be fixed even if negated types are added, unless typescript recognizesInfinityandNaNas types.Tho, after thinking about it and searching for a bit, I did find a way to implement this, tho I prefer to not post here, as it isn't directly related to this issue, and I don't want to clutter this discussion.
As such, I answered your question and explained how I did it in this page.
Rudxain commentedon Dec 26, 2025
this almost works (more info here)
yuri-kiss commentedon Dec 27, 2025
thank you both so much