Sometimes it's useful to put a limit on what a type parameter can be. In a way it is a counterpart of the extends constraint.
Consider an example, a classic function that takes whatever and returns void:
function ignore<a>(value: a) : void {};
However we must not apply this function to Promises, because it might get us a temporal leak if we do.
function readFileAsync(): Promise<string>;
ignore(readFileAsync()); // <-- untracked promise, temporal leak
Unfortunately it is way too easy to get into a situation when a promise is passed to that function unintentionally as a result of refactoring:
// before
function readFileSync(): string;
ignore(readFileSync()); // typechecks, works as intended, no problem
// after refactoring
function readFileAsync(): Promise<string>; // <-- went async here
ignore(readFileAsync()); // typechecks, unintended temporal leak, big problem
The situation above could have been avoided if TypeScript allowed negating constraints:
function ignore<a unlike Promise<any>>(value: a): void {} // <-- hypothetical syntax
You can already get the behavior you want today
interface MyPromise {
resolve(): void;
}
function ignore(x: { resolve?: {'no promises allowed!': string}}) {
}
var x: MyPromise;
var y: string;
ignore(x); // Error
ignore(y); // OK
function shallowCopy<a unlike number | string | null | undefined | Regex | Date>(value: a): a {
const result = {};
for (var key in value) {
if (value.hasOwnProperty(key)) {
result[key] = value[key];
}
}
return result;
}
Seems like you're looking for #1809?
function map<a, b unlike void>(values: a, map: (value: a) => b) : b[] { // <-- it doesn't make sense to map to void[]
}
This issue is now awaiting more feedback. This means we'd like to hear from more people who would be helped by this feature, understand their use cases, and possibly contrast with other proposals that might solve the problem in a simpler way (or solve many other problems at once).
If this feature looks like a good fit for you, please use the :+1: reaction on the original post. Comments outlining different scenarios that would be benefited from the feature are also welcomed.
one more scenario: https://github.com/Microsoft/TypeScript/issues/8545#issuecomment-218737345
one more case for scenarios where, say, null is booked for internal purposes and the calling code cannot use it
function asValid<a unlike null>(value: a, isValid: (value: a) => boolean) : a | null {
return isValid(value) ? value : null;
}
similar but distinct
// here we want to eliminate a chance of getting a default undefined from the JS runtime
export function tryAt<a unlike undefined>(values: a[], index: number): a | undefined {
return values[index];
}
I'd propose less radical approach that already has all the machinery to be implemented: https://github.com/Microsoft/TypeScript/issues/9776#issuecomment-233191082
function String<a unlike string>(value: a): string {}
function Number<a unlike number>(value: a): number {}
Negating types could allow unions with a catch-all member, without overshadowing the types of the known members.
interface A { type: "a", data: number }
interface B { type: "b", data: string }
interface Unknown { type: string unlike "a"|"b", data: any }
type ABU = A | B | Unknown
var x : ABU = {type: "a", data: 5}
if(x.type === "a") {
let y = x.data; // y should be inferred to be a number instead of any
}
type Not<T> = {
[key in keyof T]?: never
}
@vvscode phenomenal. I used this to build a static destructuring exhaustiveness check:
interface ExtraProps {
store: StoreType
otherProp: OtherType
}
type Not<T> = { [key in keyof T]?: never }
function someHOC<OwnProps>(Component: React.FC<OwnProps>) {
return ({ store, /* otherProp, */ ...ownProps }: ExtraProps & OwnProps) => (
<Provider store={store}>
<Component {...ownProps as Not<ExtraProps> /* error */ as OwnProps} />
</Provider>
)
}
Most helpful comment
Negating types could allow unions with a catch-all member, without overshadowing the types of the known members.