Skip to content

Commit

Permalink
Fix broken object and lazy types
Browse files Browse the repository at this point in the history
  • Loading branch information
nvie committed Dec 28, 2023
1 parent d8a21fd commit 4c1334b
Show file tree
Hide file tree
Showing 4 changed files with 14 additions and 21 deletions.
23 changes: 7 additions & 16 deletions src/lib/_helpers.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,9 @@
/* eslint-disable @typescript-eslint/no-explicit-any */

/**
* Returns all the keys from T where `undefined` can be assigned to.
*/
type OptionalKeys<T> = {
[K in keyof T]-?: K extends any ? (undefined extends T[K] ? K : never) : never;
type RequiredKeys<T extends object> = {
[K in keyof T]: undefined extends T[K] ? never : K;
}[keyof T];

type RequiredKeys<T> = Exclude<keyof T, OptionalKeys<T>>;

/**
* Transforms an object type, by marking all fields that contain "undefined"
* with a question mark, i.e. allowing implicit-undefineds when
Expand All @@ -21,21 +16,17 @@ type RequiredKeys<T> = Exclude<keyof T, OptionalKeys<T>>;
* age: number | null | undefined;
* }
*
* Then AllowImplicit<User> will become equivalent to:
* Then UndefinedToOptional<User> will become equivalent to:
*
* {
* name: string;
* age?: number | null;
* age?: number | null | undefined;
* ^
* Note the question mark
* }
*/
export type AllowImplicit<T> = Resolve<
{ [K in RequiredKeys<T>]-?: T[K] } & {
[K in OptionalKeys<T>]+?: Exclude<T[K], undefined>;
}
export type UndefinedToOptional<T extends object> = Resolve<
Pick<Required<T>, RequiredKeys<T>> & Partial<T>
>;

export type Resolve<T> = T extends (...args: unknown[]) => unknown
? T
: { [K in keyof T]: T[K] };
export type Resolve<T> = T extends Function ? T : { [K in keyof T]: T[K] };
7 changes: 3 additions & 4 deletions src/lib/objects.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,11 @@ import { define } from '../Decoder';
import { subtract, isPojo } from '../_utils';
import type { Annotation } from '../annotate';
import type { Decoder, DecodeResult } from '../Decoder';
import type { UndefinedToOptional } from './_helpers';

// TODO: Restore AllowImplicit here somehow
// import type { AllowImplicit } from './_helpers';
type ObjectDecoderType<T> = {
type ObjectDecoderType<T> = UndefinedToOptional<{
[K in keyof T]: T[K] extends Decoder<infer V> ? V : never;
};
}>;

/**
* Accepts any "plain old JavaScript object", but doesn't validate its keys or
Expand Down
3 changes: 3 additions & 0 deletions test-d/inference.test-d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -244,6 +244,9 @@ expectType<string | Date | null | undefined>(
expectType<never>(x.qux);
expectType<undefined>(x.quxx);

// With "unknown" fields (which implicitly contain "undefined")
expectType<{ a?: unknown }>(test(object({ a: unknown })));

// With "never" fields
expectType<{ nope: never }>(test(object({ nope: fail('not allowed') })));
expectType<{ nope?: never }>(test(object({ nope: optional(fail('not allowed')) })));
Expand Down
2 changes: 1 addition & 1 deletion test/utilities.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ describe('lazy', () => {

test('build self-referential types with variables', () => {
type Tree<T> = {
node: T;
node?: T;
children: Tree<T>[];
};

Expand Down

0 comments on commit 4c1334b

Please sign in to comment.