0

I realize the "object literal may only specify known properties" doesn't work in all cases. In particular, it looks like it doesn't work when I pass the object literal through an identity-like function (playground link)

declare function setUser(arg: {name: string}): void;
declare function identity<T>(v: T): T;

setUser({name: 'a', age: 12}); // Error, good!

setUser(identity({name: 'a', age: 12})); // No error, sad.

const u = {name: 'a', age: 12};
setUser(u); // No error, but I'm used to this case already.

Is there a way to write identity in a way that will get back the error?

In my actual codebase, I'm not using the identity function, but a slight variant (playground link):

declare function setUser(arg: {name: string}): void;

type NonNullable<T> = T extends null ? never : T;
export type NullableToOptional<T> = {
    [K in keyof T]: null extends T[K] ? NonNullable<T[K]> | undefined : T[K];
};
export function toOptional<T>(x: T): NullableToOptional<T> {
    return x as NullableToOptional<T>;
}

setUser({name: 'a', age: 12}); // Error, good!

setUser(toOptional({name: 'a', age: 12})); // No error, sad.
Kannan Goundan
  • 4,962
  • 3
  • 24
  • 31
  • Typescript doesn't have exact types, which would solve this. Maybe you could hack it with e.g. https://stackoverflow.com/a/57117594/13065068 ? – Joonas Oct 09 '21 at 14:41

1 Answers1

0

Here you have a naive implementation. You will find explanation in comments


type Base = { name: string }

// credits goes to https://github.com/microsoft/TypeScript/issues/40248
type IsNever<T> = [T] extends [never] ? true : false;

/**
 * Obtain extra keys
 */
type ExtraKeys<T, U> = Exclude<keyof U, keyof T>

type Validator<Obj> = (
    Obj extends Base
    ? (
        Base extends Obj
        ? Obj : (
            /**
             * If there are no extra keys
             */
            IsNever<ExtraKeys<Base, Obj>> extends true
            /**
             * Return source object
             */
            ? Obj
            /**
             * Otherwise return source object where all extra keys are [never]
             * It helps TS compiler to highlight only invalid props
             */
            : Base & Record<ExtraKeys<Base, Obj>, never>
        )
    )
    : never
)

/**
 * Validator - validates the argument
 */
declare function setUser<
    Name extends string,
    Obj extends { name: Name }
>(arg: Validator<Obj>): void;

type NonNullable<T> = T extends null ? never : T;

export type NullableToOptional<T> = {
    [K in keyof T]: null extends T[K] ? NonNullable<T[K]> | undefined : T[K];
};

/**
 * We need to infer each key and value pair in order to be alphabetic
 * to validate return type
 */
export function toOptional<
    Prop extends PropertyKey,
    Value extends string | number,
    Obj extends Record<Prop, Value>>(x: Obj) {
    return x as NullableToOptional<Obj>;
}

setUser({ name: 'a', age: 2 }); // Error
setUser(toOptional({ name: 'a', age: 12 })); // Error

setUser({ name: 'a' }); // Error
setUser(toOptional({ name: 'a' })); // Error

Playground