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

Add `EmptyObject` (the type you'd expect for `{}`)

Open fregante opened this issue 2 years ago • 8 comments

https://stackoverflow.com/a/60112239/288906

type EmptyObject = Record<string, never>;

const one: EmptyObject = {}; // yes ok
const two: EmptyObject = {a: 1}; // error

fregante avatar May 07 '22 05:05 fregante

Makes sense for discoverability 👍

sindresorhus avatar May 08 '22 17:05 sindresorhus

I like this idea. However there may some issues due to unexpected behavior in niche instances. I have just one of those and an adjustment.

First, this would be more complete:

type EmptyObject = Record<keyof any, never>;

Second, I saw this example from someone on stack overflow, which seems to violate the type safety of this.

type EmptyObject = Record<keyof any, never>;

type Union = EmptyObject | { id: string }; 

const a: Union = {}; 
const b: string = a.id // no error
const c = a.id; // inferred type: `never`;

A Typescript playground for the above.

HunterKohler avatar May 09 '22 03:05 HunterKohler

Try Record<never, never>

https://www.typescriptlang.org/play?#code/C4TwDgpgBAogtmUB5ARgKwgY2FAvFAJSwHsAnAEwB4A7CANwlIBopaHSA+AbgFgAofqEhQAqtQCWxanlgJk6LDgA+UAN5Rx5AFxQAzsFLjqAcygBfLlH79MU-VACGOsZOn5VFq31vV7KHfqGJjIOAHSaUAD0kazEUIykZDZ2OJgh4eSW0RrUAGYJEORQQhA6AAZsjGVcQA

Screen Shot 16

fregante avatar May 09 '22 03:05 fregante

@fregante Record<never, never> doesn't solve the original problem:

type EmptyObject = Record<never, never>;

const foo: EmptyObject = {a: 1}; // should error, but is allowed

In fact, the tooltip suggests Record<never, never> is just {}.

Playground link

bbrk24 avatar May 24 '22 16:05 bbrk24

Record<any, InvariantOf<unknown>> seems to work:

import type { InvariantOf } from 'type-fest';

type EmptyObject = Record<any, InvariantOf<unknown>>;

// test cases
const foo: EmptyObject = {a: 1}; // errors
const bar: EmptyObject = {}; // does not error
const baz: string = bar.a; // errors
// for comparison, both `Record<keyof any, never>` and `Record<never, never>` fail one of these

Playground link

bbrk24 avatar May 24 '22 16:05 bbrk24

Coming back to this a while later, { [tag]?: never } (using the const tag: unique symbol from Opaque) seems even better than that. Compare:

import type { InvariantOf } from 'type-fest';
type EmptyObject = Record<any, InvariantOf<unknown>>;

const foo: EmptyObject = {};
const bar = foo.a; // doesn't error, but should
declare const tag: unique symbol;
type EmptyObject = { [tag]?: never };

const foo: EmptyObject = {};
const bar = foo.a; // errors as expected

Playground link demonstrating the difference between these two suggestions. The errors generated by the latter are also easier to understand.

bbrk24 avatar Jun 21 '22 18:06 bbrk24

@bbrk24 Interested in submitting a pull request for it? No worries if not.

sindresorhus avatar Jul 24 '22 17:07 sindresorhus

I don't want to right now, if only because I'm not sure how much to put in the doc comment.

bbrk24 avatar Jul 24 '22 18:07 bbrk24

https://github.com/sindresorhus/type-fest/pull/447

sindresorhus avatar Aug 29 '22 07:08 sindresorhus