Matthew James Davis
Matthew James Davis

Reputation: 12295

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

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

Upvotes: 2

Views: 333

Answers (1)

Titian Cernicova-Dragomir
Titian Cernicova-Dragomir

Reputation: 250106

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

Upvotes: 2

Related Questions