What is the return type of a generic function of a partial type which returns only the provided keys?

320 views Asked by At

So my goal is to have something like this, such that the return type includes only the keys that were passed into the function:

function doTheThings<T, U = Partial<T>>(values: U): { [P in keyof U]: U[P] };

Currently, what I get instead assumes that my argument and my return types are both independent Partial<T>, and so keys not present on the argument are available on the return type, like this:

type Thing = { name: string, age: number };
const seuss = doTheThings<Thing>({ name: 'seuss'});

// Should work, but TypeScript raises error
//   Type 'string | undefined' is not assignable to type 'string'.
//   Type 'undefined' is not assignable to type 'string'.
const name: string = seuss.name;

// Should work, but TypeScript raises error
//   Type 'number | undefined' is not assignable to type 'undefined'.
//   Type 'number' is not assignable to type 'undefined'.
const age: undefined = seuss.age;

Is what I'm trying to do even possible today in TypeScript?

See playground: https://www.typescriptlang.org/play?#code/GYVwdgxgLglg9mABAEzgFQBYFNMzAcwGcAeNAGkQFVEBeRABQEMAnWRgG1ID4uAKANw4gshAFxUAlOIDeiANr1EeRAGssATzjAqAXXGUFOxAF9E0gFCJEzLFBDMkg9sMIBuc8fPmo6gA5ZEXAJaM0QwRgBbLHFCKGY8fApGfGiwkAiAIyxmE3dzCARYxEIsEEJCENRMHAwEkiD8PllwqPEAchKywjbjCTyAen7EKFqKp2FijDgQdmRELMRGYriE4rwIAJgoRAB3Rgr152QsOf3FpBZ8dKwwbag4YexEUEhYBHzC7ZbU2Pjguk65QAdN8BkMRjAxkIAoQpjM5gtwMdgHgTmtIJttnsKmA4NtDiBjqcKowLswrlFbsMHiMAi9oPAwB8wEVkqkkVgUWA0QDSsC2e4gA

1

There are 1 answers

0
Titian Cernicova-Dragomir On

The problem is how inference works with explicit and default type parameters. If you specify one type parameter, no inference will happen for the rest of the parameter (their default values will be used)

The usual workaround for this is to use function currying:

function doTheThings<T>() {
  return function <U extends  Partial<T>>(values: U): U {
    return values;
  }
}

type Thing = { name: string, age: number };

const seuss = doTheThings<Thing>()({ name: 'seuss'});

// this value should be a string since it was included as an argument to the function
const name: string = seuss.name;

//  error age does not exist 
const age: undefined = seuss.age;

Playground Link

The function above will only return an object with the keys that were passed in. If you want to preserve the unpassed keys as well you can intersect with Partial<T>:

function doTheThings<T>() {
  return function <U extends  Partial<T>>(values: U): U & Partial<T> {
    return values;
  }
}

const age: number | undefined = seuss.age;

Playground Link