`PlainMessage<Empty>` is `{}`, meaning any non-nullish value
I think it's a little bit unfortunate that PlainMessage<Empty> (or PlainMessage of any user-defined empty message) reduces to {}, because to quote typescript-eslint
Don't use
{}as a type.{}actually means "any non-nullish value".
- If you want a type meaning "any object", you probably want
objectinstead.- If you want a type meaning "any value", you probably want
unknowninstead.- If you want a type meaning "empty object", you probably want
Record<string, never>instead.- If you really want a type meaning "any non-nullish value", you probably want
NonNullable<unknown>instead.
The problem is that TypeScript won't prevent you from doing nonsensical things like
const empty: PlainMessage<Empty> = 42;
I don't think it's really a bug, given that you can also do stuff like new Empty(42) without a runtime error, but it might be nicer if PlainMessage<Empty> produced Record<string, never> instead?
The lint rule is neat, I only became aware of this behavior because of it. But I didn't connect the dots to PlainMessage - great find.
I think this should avoid the issue and map to empty object:
type ReallyPlainMessage<T extends Message<T>> = keyof PlainMessage<T> extends never ? Record<string, never> : PlainMessage<T>;
I am not sure that it would be wise to make this change, though. It's very possible that it breaks something.
This is fixed with version 2:
import type { Empty } from "./gen/example_pb";
const empty: Empty = 42; // TS2322: Type number is not assignable to type Message<"Empty">
Awesome, thanks @timostamm!