Generic and discriminated types?

Hey an idea came to mind but I don't know how to type it or even if it is possible at all.

How can we define the SomeType type so the first property can be anything of the T but the second property can't be the same as first?

type SomeType<T> = {
  first: T; // any option based on T
  second: T; // everything on T except what `first` is using
};

// Example:
const value: SomeType<1 | 2 | 3> = {
  first: 1,
  second: 2 // can be 2 or 3 but not the same as `first`
};

Here's a solution using a distributive conditional type and Exclude. The type parameter _T is the same as T but doesn't get distributed.

type SomeType<T, _T = T> = T extends unknown ? {
  first: T;
  second: Exclude<_T, T>;
} : never

Playground Link

Note that this only works correctly when T is a union type; you can't use e.g. SomeType<string> to get an object type requiring two distinct string properties.


I'm not sure that you can do this strictly with types, but you could use a functional abstraction to accomplish the kind of validation that you're asking about.

After creating the validator function from the initial union type, it will require that, for the argument object provided when invoking it, the type of the first property is assignable the initial union type, and the type of the second property is assignable to the union type (excluding the first).

The primary benefit of this approach is that the resulting value will be the exact type of the object passed in, rather than the second property being all of the union excluding the first.

TS Playground

function makeValidator <T>() {
  return <First extends T, Second extends Exclude<T, First>>(input: { first: First; second: Second; }) {
    return input;
  };
}

const validate = makeValidator<1 | 2 | 3>();

validate({first: 1, second: 1}); /*
                    ^^^^^^
Type '1' is not assignable to type '2 | 3'.(2322) */

validate({first: 1, second: 2}); // { first: 1; second: 2; }