Sometimes we may want to explicitly assert whether the type of a expression is compatible with a certain type, but type assertion does not satisfy the type safety we want:
interface SomeEventData {
foo: number;
bar: string;
}
this.emit('some-event', {foo: 123} as SomeEventData);
The behavior is desired but it would be nice if we have an elegant way for more secure type checking instead of:
let data: SomeEventData = {foo: 123};
E.g.:
this.emit('some-event', {foo: 123}: SomeEventData);
Even better: make sure that incompatible types will raise a type error
class MyEvent<A> {
readonly _A: A
constructor(readonly name: string) {}
}
type Handler<A> = (a: A) => void
declare class MyEventEmitter {
on<A>(event: MyEvent<A>): (handler: Handler<A>) => void
emit<A>(event: MyEvent<A>): (a: A) => void
}
interface SomeEventData {
foo: number
bar: string
}
const emitter = new MyEventEmitter()
const someEvent = new MyEvent<SomeEventData>('some-event')
emitter.emit(someEvent)({}) // error
emitter.emit(someEvent)({ foo: 'a' }) // error
emitter.emit(someEvent)({ foo: 1, bar: 's' }) // ok
emitter.on(someEvent)(() => {}) // ok
emitter.on(someEvent)((foo: number) => {}) // error
emitter.on(someEvent)(foo => {}) // ok
emitter.on(someEvent)((foo: SomeEventData) => {}) // ok
@gcanti Looks nice (sharing data type between emit and on)! Though creating a wrapper that persists at runtime does not sound ideal to me, especially when we have a lot of libs adapting to implementations like EventEmitter.
Yeah, maybe a branded type instead of a class?
type MyEvent<A> = string & { _A: A } // branded type, the runtime representation is still a string
function createEvent<A>(name: string): MyEvent<A> {
return name as any
}
const someEvent = createEvent<SomeEventData>('some-event')
I cannot find other discussion on this, but I have also thought it would be useful to have a type-assertion like operator that doesn't actually coerce the type of the expression.
This operator:
For example:
interface Foo {
bar(x: string): void;
}
// 'x' has type
//
// {
// bar(a: string): void,
// baz(): number
// }
//
const x = {
bar(a) {
console.log(a.toLowerCase());
}
baz() {
return 100;
}
} compatibleWith Foo;
@gcanti Nice try, but it would still be weird working with existing event emitters. :(
migrated from #20188
in my code i often use, let's call them, type invariants assertions:
function mustBe<T>(_: () => T): void {}
interface A {
isThat: true;
text: string;
}
interface B {
isThat: false;
value: number;
}
type C = A | B;
declare const C: C;
mustBe<true>(() => C.isThat); // <-- type error as expected
now i wish i could do type invariant assertions without emitting any useless code, making it a zero cost abstraction:
makesure C.isThat sameas true;
where makesure is a new TS syntax/construct that participates in type checking, but doesn't get emitted
few more examples
makesure typeof c.isThat subtypeof boolean
makesure MyClass subtypeof BaseClass
// ...
c.f. #7481
Tracking at #7481 / #26064
Most helpful comment
I cannot find other discussion on this, but I have also thought it would be useful to have a type-assertion like operator that doesn't actually coerce the type of the expression.
This operator:
For example: