Typescript: Exact Types

Created on 15 Dec 2016  ·  171Comments  ·  Source: microsoft/TypeScript

This is a proposal to enable a syntax for exact types. A similar feature can be seen in Flow (https://flowtype.org/docs/objects.html#exact-object-types), but I would like to propose it as a feature used for type literals and not interfaces. The specific syntax I'd propose using is the pipe (which almost mirrors the Flow implementation, but it should surround the type statement), as it's familiar as the mathematical absolute syntax.

interface User {
  username: string
  email: string
}

const user1: User = { username: 'x', email: 'y', foo: 'z' } //=> Currently errors when `foo` is unknown.
const user2: Exact<User> = { username: 'x', email: 'y', foo: 'z' } //=> Still errors with `foo` unknown.

// Primary use-case is when you're creating a new type from expressions and you'd like the
// language to support you in ensuring no new properties are accidentally being added.
// Especially useful when the assigned together types may come from other parts of the application 
// and the result may be stored somewhere where extra fields are not useful.

const user3: User = Object.assign({ username: 'x' }, { email: 'y', foo: 'z' }) //=> Does not currently error.
const user4: Exact<User> = Object.assign({ username: 'x' }, { email: 'y', foo: 'z' }) //=> Will error as `foo` is unknown.

This syntax change would be a new feature and affect new definition files being written if used as a parameter or exposed type. This syntax could be combined with other more complex types.

type Foo = Exact<X> | Exact<Y>

type Bar = Exact<{ username: string }>

function insertIntoDb (user: Exact<User>) {}

Apologies in advance if this is a duplicate, I could not seem to find the right keywords to find any duplicates of this feature.

Edit: This post was updated to use the preferred syntax proposal mentioned at https://github.com/Microsoft/TypeScript/issues/12936#issuecomment-267272371, which encompasses using a simpler syntax with a generic type to enable usage in expressions.

Awaiting More Feedback Suggestion

Most helpful comment

We talked about this for quite a while. I'll try to summarize the discussion.

Excess Property Checking

Exact types are just a way to detect extra properties. The demand for exact types dropped off a lot when we initially implemented excess property checking (EPC). EPC was probably the biggest breaking change we've taken but it has paid off; almost immediately we got bugs when EPC didn't detect an excess property.

For the most part where people want exact types, we'd prefer to fix that by making EPC smarter. A key area here is when the target type is a union type - we want to just take this as a bug fix (EPC should work here but it's just not implemented yet).

All-optional types

Related to EPC is the problem of all-optional types (which I call "weak" types). Most likely, all weak types would want to be exact. We should just implement weak type detection (#7485 / #3842); the only blocker here is intersection types which require some extra complexity in implementation.

Whose type is exact?

The first major problem we see with exact types is that it's really unclear which types should be marked exact.

At one end of the spectrum, you have functions which will literally throw an exception (or otherwise do bad things) if given an object with an own-key outside of some fixed domain. These are few and far between (I can't name an example from memory). In the middle, there are functions which silently ignore
unknown properties (almost all of them). And at the other end you have functions which generically operate over all properties (e.g. Object.keys).

Clearly the "will throw if given extra data" functions should be marked as accepting exact types. But what about the middle? People will likely disagree. Point2D / Point3D is a good example - you might reasonably say that a magnitude function should have the type (p: exact Point2D) => number to prevent passing a Point3D. But why can't I pass my { x: 3, y: 14, units: 'meters' } object to that function? This is where EPC comes in - you want to detect that "extra" units property in locations where it's definitely discarded, but not actually block calls that involve aliasing.

Violations of Assumptions / Instantiation Problems

We have some basic tenets that exact types would invalidate. For example, it's assumed that a type T & U is always assignable to T, but this fails if T is an exact type. This is problematic because you might have some generic function that uses this T & U -> T principle, but invoke the function with T instantiated with an exact type. So there's no way we could make this sound (it's really not OK to error on instantiation) - not necessarily a blocker, but it's confusing to have a generic function be more permissive than a manually-instantiated version of itself!

It's also assumed that T is always assignable to T | U, but it's not obvious how to apply this rule if U is an exact type. Is { s: "hello", n: 3 } assignable to { s: string } | Exact<{ n: number }>? "Yes" seems like the wrong answer because whoever looks for n and finds it won't be happy to see s, but "No" also seems wrong because we've violated the basic T -> T | U rule.

Miscellany

What is the meaning of function f<T extends Exact<{ n: number }>(p: T) ? :confused:

Often exact types are desired where what you really want is an "auto-disjointed" union. In other words, you might have an API that can accept { type: "name", firstName: "bob", lastName: "bobson" } or { type: "age", years: 32 } but don't want to accept { type: "age", years: 32, firstName: 'bob" } because something unpredictable will happen. The "right" type is arguably { type: "name", firstName: string, lastName: string, age: undefined } | { type: "age", years: number, firstName: undefined, lastName: undefined } but good golly that is annoying to type out. We could potentially think about sugar for creating types like this.

Summary: Use Cases Needed

Our hopeful diagnosis is that this is, outside of the relatively few truly-closed APIs, an XY Problem solution. Wherever possible we should use EPC to detect "bad" properties. So if you have a problem and you think exact types are the right solution, please describe the original problem here so we can compose a catalog of patterns and see if there are other solutions which would be less invasive/confusing.

All 171 comments

I would suggest the syntax is arguable here. Since TypeScript now allows leading pipe for union type.

class B {}

type A = | number | 
B

Compiles now and is equivalent to type A = number | B, thanks to automatic semicolon insertion.

I think this might not I expect if exact type is introduced.

If the {| ... |} syntax was adopted, we could build on mapped types so that you could write

type Exact<T> = {|
    [P in keyof T]: P[T]
|}

and then you could write Exact<User>.

This is probably the last thing I miss from Flow, compared to TypeScript.

The Object.assign example is especially good. I understand why TypeScript behaves the way it does today, but most of the time I'd rather have the exact type.

@HerringtonDarkholme Thanks. My initial issue has mentioned that, but I omitted it in the end as someone would have a better syntax anyway, turns out they do 😄

@DanielRosenwasser That looks a lot more reasonable, thanks!

@wallverb I don't think so, though I'd also like to see that feature exist 😄

What if I want to express a union of types, where some of them are exact, and some of them are not? The suggested syntax would make it error-prone and difficult to read, even If extra attention is given for spacing:

|Type1| | |Type2| | Type3 | |Type4| | Type5 | |Type6|

Can you quickly tell which members of the union are not exact?

And without the careful spacing?

|Type1|||Type2||Type3||Type4||Type5||Type6|

(answer: Type3, Type5)

@rotemdan See the above answer, there's the generic type Extact instead which is a more solid proposal than mine. I think this is the preferred approach.

There's also the concern of how it would look in editor hints, preview popups and compiler messages. Type aliases currently just "flatten" to raw type expressions. The alias is not preserved so the incomperhensible expressions would still appear in the editor, unless some special measures are applied to counteract that.

I find it hard to believe this syntax was accepted into a programming language like Flow, which does have unions with the same syntax as Typescript. To me it doesn't seem wise to introduce a flawed syntax that is fundamentally in conflict with existing syntax and then try very hard to "cover" it.

One interesting (amusing?) alternative is to use a modifier like only. I had a draft for a proposal for this several months ago, I think, but I never submitted it:

function test(a: only string, b: only User) {};

That was the best syntax I could find back then.

_Edit_: just might also work?

function test(a: just string, b: just User) {};

_(Edit: now that I recall that syntax was originally for a modifier for nominal types, but I guess it doesn't really matter.. The two concepts are close enough so these keywords might also work here)_

I was wondering, maybe both keywords could be introduced to describe two slightly different types of matching:

  • just T (meaning: "exactly T") for exact structural matching, as described here.
  • only T (meaning: "uniquely T") for nominal matching.

Nominal matching could be seen as an even "stricter" version of exact structural matching. It would mean that not only the type has to be structurally identical, the value itself must be associated with the exact same type identifier as specified. This may or may not support type aliases, in addition to interfaces and classes.

I personally don't believe the subtle difference would create that much confusion, though I feel it is up to the Typescript team to decide if the concept of a nominal modifier like only seems appropriate to them. I'm only suggesting this as an option.

_(Edit: just a note about only when used with classes: there's an ambiguity here on whether it would allow for nominal subclasses when a base class is referenced - that needs to be discussed separately, I guess. To a lesser degree - the same could be considered for interfaces - though I don't currently feel it would be that useful)_

This seems sort of like subtraction types in disguise. These issues might be relevant: https://github.com/Microsoft/TypeScript/issues/4183 https://github.com/Microsoft/TypeScript/issues/7993

@ethanresnick Why do you believe that?

This would be exceedingly useful in the codebase I'm working on right now. If this was already part of the language then I wouldn't have spent today tracking down an error.

(Perhaps other errors but not this particular error 😉)

I don't like the pipe syntax inspired by Flow. Something like exact keyword behind interfaces would be easier to read.

exact interface Foo {}

@mohsen1 I'm sure most people would use the Exact generic type in expression positions, so it shouldn't matter too much. However, I'd be concerned with a proposal like that as you might be prematurely overloading the left of the interface keyword which has previously been reserved for only exports (being consistent with JavaScript values - e.g. export const foo = {}). It also indicates that maybe that keyword is available for types too (e.g. exact type Foo = {} and now it'll be export exact interface Foo {}).

With {| |} syntax how would extends work? will interface Bar extends Foo {| |} be exact if Foo is not exact?

I think exact keyword makes it easy to tell if an interface is exact. It can (should?) work for type too.

interface Foo {}
type Bar = exact Foo

Exceedingly helpful for things that work over databases or network calls to databases or SDKs like AWS SDK which take objects with all optional properties as additional data gets silently ignored and can lead to hard to very hard to find bugs :rose:

@mohsen1 That question seems irrelevant to the syntax, since the same question still exists using the keyword approach. Personally, I don't have a preferred answer and would have to play with existing expectations to answer it - but my initial reaction is that it shouldn't matter whether Foo is exact or not.

The usage of an exact keyword seems ambiguous - you're saying it can be used like exact interface Foo {} or type Foo = exact {}? What does exact Foo | Bar mean? Using the generic approach and working with existing patterns means there's no re-invention or learning required. It's just interface Foo {||} (this is the only new thing here), then type Foo = Exact<{}> and Exact<Foo> | Bar.

We talked about this for quite a while. I'll try to summarize the discussion.

Excess Property Checking

Exact types are just a way to detect extra properties. The demand for exact types dropped off a lot when we initially implemented excess property checking (EPC). EPC was probably the biggest breaking change we've taken but it has paid off; almost immediately we got bugs when EPC didn't detect an excess property.

For the most part where people want exact types, we'd prefer to fix that by making EPC smarter. A key area here is when the target type is a union type - we want to just take this as a bug fix (EPC should work here but it's just not implemented yet).

All-optional types

Related to EPC is the problem of all-optional types (which I call "weak" types). Most likely, all weak types would want to be exact. We should just implement weak type detection (#7485 / #3842); the only blocker here is intersection types which require some extra complexity in implementation.

Whose type is exact?

The first major problem we see with exact types is that it's really unclear which types should be marked exact.

At one end of the spectrum, you have functions which will literally throw an exception (or otherwise do bad things) if given an object with an own-key outside of some fixed domain. These are few and far between (I can't name an example from memory). In the middle, there are functions which silently ignore
unknown properties (almost all of them). And at the other end you have functions which generically operate over all properties (e.g. Object.keys).

Clearly the "will throw if given extra data" functions should be marked as accepting exact types. But what about the middle? People will likely disagree. Point2D / Point3D is a good example - you might reasonably say that a magnitude function should have the type (p: exact Point2D) => number to prevent passing a Point3D. But why can't I pass my { x: 3, y: 14, units: 'meters' } object to that function? This is where EPC comes in - you want to detect that "extra" units property in locations where it's definitely discarded, but not actually block calls that involve aliasing.

Violations of Assumptions / Instantiation Problems

We have some basic tenets that exact types would invalidate. For example, it's assumed that a type T & U is always assignable to T, but this fails if T is an exact type. This is problematic because you might have some generic function that uses this T & U -> T principle, but invoke the function with T instantiated with an exact type. So there's no way we could make this sound (it's really not OK to error on instantiation) - not necessarily a blocker, but it's confusing to have a generic function be more permissive than a manually-instantiated version of itself!

It's also assumed that T is always assignable to T | U, but it's not obvious how to apply this rule if U is an exact type. Is { s: "hello", n: 3 } assignable to { s: string } | Exact<{ n: number }>? "Yes" seems like the wrong answer because whoever looks for n and finds it won't be happy to see s, but "No" also seems wrong because we've violated the basic T -> T | U rule.

Miscellany

What is the meaning of function f<T extends Exact<{ n: number }>(p: T) ? :confused:

Often exact types are desired where what you really want is an "auto-disjointed" union. In other words, you might have an API that can accept { type: "name", firstName: "bob", lastName: "bobson" } or { type: "age", years: 32 } but don't want to accept { type: "age", years: 32, firstName: 'bob" } because something unpredictable will happen. The "right" type is arguably { type: "name", firstName: string, lastName: string, age: undefined } | { type: "age", years: number, firstName: undefined, lastName: undefined } but good golly that is annoying to type out. We could potentially think about sugar for creating types like this.

Summary: Use Cases Needed

Our hopeful diagnosis is that this is, outside of the relatively few truly-closed APIs, an XY Problem solution. Wherever possible we should use EPC to detect "bad" properties. So if you have a problem and you think exact types are the right solution, please describe the original problem here so we can compose a catalog of patterns and see if there are other solutions which would be less invasive/confusing.

The main place I see people get surprised by having no exact object type is in the behaviour of Object.keys and for..in -- they always produce a string type instead of 'a'|'b' for something typed { a: any, b: any }.

As I mentioned in https://github.com/Microsoft/TypeScript/issues/14094 and you described in Miscellany section it's annoying that {first: string, last: string, fullName: string} conforms to {first: string; last: string} | {fullName: string}.

For example, it's assumed that a type T & U is always assignable to T, but this fails if T is an exact type

If T is an exact type, then presumably T & U is never (or T === U). Right?

Or U is a non-exact subset of T

My use case that lead me to this suggestion are redux reducers.

interface State {
   name: string;
}
function nameReducer(state: State, action: Action<string>): State {
   return {
       ...state,
       fullName: action.payload // compiles, but it's an programming mistake
   }
}

As you pointed out in the summary, my issue isn't directly that I need exact interfaces, I need the spread operator to work precisely. But since the behavior of the spread operator is given by JS, the only solution that comes to my mind is to define the return type or the interface to be exact.

Do I understand correctly that assigning a value of T to Exact<T> would be an error?

interface Dog {
    name: string;
    isGoodBoy: boolean;
}
let a: Dog = { name: 'Waldo', isGoodBoy: true };
let b: Exact<Dog> = a;

In this example, narrowing Dog to Exact<Dog> would not be safe, right?
Consider this example:

interface PossiblyFlyingDog extends Dog {
    canFly: boolean;
}
let c: PossiblyFlyingDog = { ...a, canFly: true };
let d: Dog = c; // this is okay
let e: Exact<Dog> = d; // but this is not

@leonadler Yes, that'd be the idea. You could only assign Exact<T> to Exact<T>. My immediate use-case is that validation functions would be handling the Exact types (e.g. taking request payloads as any and outputting valid Exact<T>). Exact<T>, however, would be assignable to T.

@nerumo

As you pointed out in the summary, my issue isn't directly that I need exact interfaces, I need the spread operator to work precisely. But since the behavior of the spread operator is given by JS, the only solution that comes to my mind is to define the return type or the interface to be exact.

I have bumped on the same issue and figured out this solution which for me is quite elegant workaround :)

export type State = {
  readonly counter: number,
  readonly baseCurrency: string,
};

// BAD
export function badReducer(state: State = initialState, action: Action): State {
  if (action.type === INCREASE_COUNTER) {
    return {
      ...state,
      counterTypoError: state.counter + 1, // OK
    }; // it's a bug! but the compiler will not find it 
  }
}

// GOOD
export function goodReducer(state: State = initialState, action: Action): State {
  let partialState: Partial<State> | undefined;

  if (action.type === INCREASE_COUNTER) {
    partialState = {
      counterTypoError: state.counter + 1, // Error: Object literal may only specify known properties, and 'counterTypoError' does not exist in type 'Partial<State>'. 
    }; // now it's showing a typo error correctly 
  }
  if (action.type === CHANGE_BASE_CURRENCY) {
    partialState = { // Error: Types of property 'baseCurrency' are incompatible. Type 'number' is not assignable to type 'string'.
      baseCurrency: 5,
    }; // type errors also works fine 
  }

  return partialState != null ? { ...state, ...partialState } : state;
}

you can find more in this section of my redux guide:

Note that this could be solved in userland using my constraint types proposal (#13257):

type Exact<T> = [
    case U in U extends T && T extends U: T,
];

Edit: Updated syntax relative to proposal

@piotrwitek thank you, the Partial trick works perfectly and already found a bug in my code base ;) that's worth the little boilerplate code. But still I agree with @isiahmeadows that an Exact would be even better

@piotrwitek using Partial like that _almost_ solved my problem, but it still allows the properties to become undefined even if the State interface clams they aren't (I'm assuming strictNullChecks).

I ended up with something slightly more complex to preserve the interface types:

export function updateWithPartial<S extends object>(current: S, update: Partial<S>): S {
    return Object.assign({}, current, update);
}

export function updateWith<S extends object, K extends keyof S>(current: S, update: {[key in K]: S[key]}): S {
    return Object.assign({}, current, update);
}

interface I {
    foo: string;
    bar: string;
}

const f: I = {foo: "a", bar: "b"}
updateWithPartial(f, {"foo": undefined}).foo.replace("a", "x"); // Compiles, but fails at runtime
updateWith(f, {foo: undefined}).foo.replace("a", "x"); // Does not compile
updateWith(f, {foo: "c"}).foo.replace("a", "x"); // Compiles and works

@asmundg that is correct, the solution will accept undefined, but from my point of view this is acceptable, because in my solutions I'm using only action creators with required params for payload, and this will ensure that no undefined value should ever be assigned to a non-nullable property.
Practically I'm using this solution for quite some time in production and this problem never happened, but let me know your concerns.

export const CHANGE_BASE_CURRENCY = 'CHANGE_BASE_CURRENCY';

export const actionCreators = {
  changeBaseCurrency: (payload: string) => ({
    type: CHANGE_BASE_CURRENCY as typeof CHANGE_BASE_CURRENCY, payload,
  }),
}

store.dispatch(actionCreators.changeBaseCurrency()); // Error: Supplied parameters do not match any signature of call target.
store.dispatch(actionCreators.changeBaseCurrency(undefined)); // Argument of type 'undefined' is not assignable to parameter of type 'string'.
store.dispatch(actionCreators.changeBaseCurrency('USD')); // OK => { type: "CHANGE_BASE_CURRENCY", payload: 'USD' }

DEMO - enable strictNullChecks in options

you can also make a nullable payload as well if you need to, you can read more in my guide: https://github.com/piotrwitek/react-redux-typescript-guide#actions

When Rest Types get merged in, this feature can be easily made syntactic sugar over them.

Proposal

The type equality logic should be made strict - only types with the same properties or types which have rest properties that can be instantiated in such a way that their parent types have the same properties are considered matching. To preserve backward compatibility, a synthetic rest type is added to all types unless one already exists. A new flag --strictTypes is also added, which suppresses the addition of synthetic rest parameters.

Equalities under --strictTypes:

type A = { x: number, y: string };
type B = { x: number, y: string, ...restB: <T>T };
type C = { x: number, y: string, z: boolean, ...restC: <T>T };

declare const a: A;
declare const b: B;
declare const c: C;

a = b; // Error, type B has extra property: "restB"
a = c; // Error, type C has extra properties: "z", "restC"
b = a; // OK, restB inferred as {}
b = c; // OK, restB inferred as { z: boolean, ...restC: <T>T }

c = a; // Error, type A is missing property: "z"
       // restC inferred as {}

c = b; // Error, type B is missing property: "z"
       // restC inferred as restB 

If --strictTypes is not switched on a ...rest: <T>T property is automatically added on type A. This way the lines a = b; and a = c; will no longer be errors, as is the case with variable b on the two lines that follow.

A word on Violations of Assumptions

it's assumed that a type T & U is always assignable to T, but this fails if T is an exact type.

Yes, & allows bogus logic but so is the case with string & number. Both string and number are distinct rigid types that cannot be intersected, however the type system allows it. Exact types are also rigid, so the inconsistency is still consistent. The problem lies in the & operator - it's unsound.

Is { s: "hello", n: 3 } assignable to { s: string } | Exact<{ n: number }>.

This can be translated to:

type Test = { s: string, ...rest: <T>T } | { n: number }
const x: Test = { s: "hello", n: 3 }; // OK, s: string; rest inferred as { n: number }

So the answer should be "yes". It's unsafe to union exact with non-exact types, as the non-exact types subsume all exact types unless a discriminator property is present.

Re: the function f<T extends Exact<{ n: number }>(p: T) in @RyanCavanaugh's comment above, in one of my libraries I would very much like to implement the following function:

const checkType = <T>() => <U extends Exact<T>>(value: U) => value;

I.e. a function that returns it's parameter with its exact same type, but at the same time also check whether it's type is also exactly the same type as another (T).

Here is a bit contrived example with three of my failed tries to satisfy both requirements:

  1. No excess properties with respect to CorrectObject
  2. Assignable to HasX without specifying HasX as the object's type
type AllowedFields = "x" | "y";
type CorrectObject = {[field in AllowedFields]?: number | string};
type HasX = { x: number };

function objectLiteralAssignment() {
  const o: CorrectObject = {
    x: 1,
    y: "y",
    // z: "z" // z is correctly prevented to be defined for o by Excess Properties rules
  };

  const oAsHasX: HasX = o; // error: Types of property 'x' are incompatible.
}

function objectMultipleAssignment() {
  const o = {
    x: 1,
    y: "y",
    z: "z",
  };
  const o2 = o as CorrectObject; // succeeds, but undesirable property z is allowed

  type HasX = { x: number };

  const oAsHasX: HasX = o; // succeeds
}

function genericExtends() {
  const checkType = <T>() => <U extends T>(value: U) => value;
  const o = checkType<CorrectObject>()({
    x: 1,
    y: "y",
    z: "z", // undesirable property z is allowed
  }); // o is inferred to be { x: number; y: string; z: string; }

  type HasX = { x: number };

  const oAsHasX: HasX = o; // succeeds
}

Here HasX is a greatly simplified type (the actual type maps o against a schema type) which is defined in a different layer than the constant itself, so I can't make o's type to be (CorrectObject & HasX).

With Exact Types, the solution would be:

function exactTypes() {
  const checkType = <T>() => <U extends Exact<T>>(value: U) => value;
  const o = checkType<CorrectObject>()({
    x: 1,
    y: "y",
    // z: "z", // undesirable property z is *not* allowed
  }); // o is inferred to be { x: number; y: string; }

  type HasX = { x: number };

  const oAsHasX: HasX = o; // succeeds
}

@andy-ms

If T is an exact type, then presumably T & U is never (or T === U). Right?

I think T & U should be never only if U is provably incompatible with T, e.g. if T is Exact<{x: number | string}> and U is {[field: string]: number}, then T & U should be Exact<{x: number}>

See the first response to that:

Or U is a non-exact subset of T

I would say, if U is assignable to T, then T & U === T. But if T and U are different exact types, then T & U === never.

In your example, why is it necessary to have a checkType function that does nothing? Why not just have const o: Exact<CorrectObject> = { ... }?

Because it loses the information that x definitely exists (optional in CorrectObject) and is number (number | string in CorrectObject). Or perhaps I've misunderstood what Exact means, I thought it would just prevent extraneous properties, not that it would recurively mean all types must be exactly the same.

One more consideration in support for Exact Types and against the current EPC is refactoring - if Extract Variable refactoring was available, one would lose EPC unless the extracted variable introduced a type annotation, which could become very verbose.

To clarify why I supoort for Exact Types - it's not for discriminated unions but spelling errors and erronously extraneous properties in case the type costraint cannot be specified at the same time as the object literal.

@andy-ms

I would say, if U is assignable to T, then T & U === T. But if T and U are different exact types, then T & U === never.

The & type operator is intersection operator, the result of it is the common subset of both sides, which doesn't necessarily equal either. Simplest example I can think of:

type T = Exact<{ x?: any, y: any }>;
type U = { x: any, y? any };

here T & U should be Exact<{ x: any, y: any }>, which is a subset of both T and U, but neither T is a subset of U (missing x) nor U is a subset of T (missing y).

This should work independent of whether T, U, or T & U are exact types.

@magnushiie You have a good point -- exact types can limit assignability from types with a greater width, but still allow assignability from types with a greater depth. So you could intersect Exact<{ x: number | string }> with Exact<{ x: string | boolean }> to get Exact<{ x: string }>. One problem is that this isn't actually typesafe if x isn't readonly -- we might want to fix that mistake for exact types, since they mean opting in to stricter behavior.

Exact types could also be used for type arguments relations issues to index signatures.

interface T {
    [index: string]: string;
}

interface S {
    a: string;
    b: string;
}

interface P extends S {
    c: number;
}

declare function f(t: T);
declare function f2(): P;
const s: S = f2();

f(s); // Error because an interface can have more fields that is not conforming to an index signature
f({ a: '', b: '' }); // No error because literals is exact by default

Here's a hacky way to check for exact type:

// type we'll be asserting as exact:
interface TextOptions {
  alignment: string;
  color?: string;
  padding?: number;
}

// when used as a return type:
function getDefaultOptions(): ExactReturn<typeof returnValue, TextOptions> {
  const returnValue = { colour: 'blue', alignment: 'right', padding: 1 };
  //             ERROR: ^^ Property 'colour' is missing in type 'TextOptions'.
  return returnValue
}

// when used as a type:
function example(a: TextOptions) {}
const someInput = {padding: 2, colour: '', alignment: 'right'}
example(someInput as Exact<typeof someInput, TextOptions>)
  //          ERROR: ^^ Property 'colour' is missing in type 'TextOptions'.

Unfortunately it's not currently possible to make the Exact assertion as a type-parameter, so it has to be made during call time (i.e. you need to remember about it).

Here are the helper utils required to make it work (thanks to @tycho01 for some of them):

type Exact<A, B extends Difference<A, B>> = AssertPassThrough<Difference<A, B>, A, B>
type ExactReturn<A, B extends Difference<A, B>> = B & Exact<A, B>

type AssertPassThrough<Actual, Passthrough, Expected extends Actual> = Passthrough;
type Difference<A, Without> = {
  [P in DiffUnion<keyof A, keyof Without>]: A[P];
}
type DiffUnion<T extends string, U extends string> =
  ({[P in T]: P } &
  { [P in U]: never } &
  { [k: string]: never })[T];

See: Playground.

Nice one! @gcanti (typelevel-ts) and @pelotom (type-zoo) might be interested as well. :)

To anyone interested, I found a simple way of enforcing exact types on function parameters. Works on TS 2.7, at least.

function myFn<T extends {[K in keyof U]: any}, U extends DesiredType>(arg: T & U): void;

EDIT: I guess for this to work you must specify an object literal directly into the argument; this doesn't work if you declare a separate const above and pass that in instead. :/ But one workaround is to just use object spread at the call site, i.e., myFn({...arg}).

EDIT: sorry, I didn't read that you mentioned TS 2.7 only. I will test it there!

@vaskevich I can't seem to get it to work, i.e. it's not detecting colour as an excess property:

When conditional types land (#21316) you can do the following to require exact types as function parameters, even for "non-fresh" object literals:

type Exactify<T, X extends T> = T & {
    [K in keyof X]: K extends keyof T ? X[K] : never
}

type Foo = {a?: string, b: number}

declare function requireExact<X extends Exactify<Foo, X>>(x: X): void;

const exact = {b: 1}; 
requireExact(exact); // okay

const inexact = {a: "hey", b: 3, c: 123}; 
requireExact(inexact);  // error
// Types of property 'c' are incompatible.
// Type 'number' is not assignable to type 'never'.

Of course if you widen the type it won't work, but I don't think there's anything you can really do about that:

const inexact = {a: "hey", b: 3, c: 123} as Foo;
requireExact(inexact);  // okay

Thoughts?

Looks like progress is being made on function parameters. Has anyone found a way to enforce exact types for a function return value?

@jezzgoodwin not really. See #241 which is the root cause of function returns not being properly checked for extra properties

One more use case. I've just almost run into a bug because of the following situation that is not reported as an error:

interface A {
    field: string;
}

interface B {
    field2: string;
    field3?: string;
}

type AorB = A | B;

const fixture: AorB[] = [
    {
        field: 'sfasdf',
        field3: 'asd' // ok?!
    },
];

(Playground)

The obvious solution for this could be:

type AorB = Exact<A> | Exact<B>;

I saw a workaround proposed in #16679 but in my case, the type is AorBorC (may grow) and each object have multiple properties, so I it's rather hard to manually compute set of fieldX?:never properties for each type.

@michalstocki Isn't that #20863? You want excess property checking on unions to be stricter.

Anyway, in the absence of exact types and strict excess property checking on unions, you can do these fieldX?:never properties programmatically instead of manually by using conditional types:

type AllKeys<U> = U extends any ? keyof U : never
type ExclusifyUnion<U> = [U] extends [infer V] ?
 V extends any ? 
 (V & {[P in Exclude<AllKeys<U>, keyof V>]?: never}) 
 : never : never

And then define your union as

type AorB = ExclusifyUnion<A | B>;

which expands out to

type AorB = (A & {
    field2?: undefined;
    field3?: undefined;
}) | (B & {
    field?: undefined;
})

automatically. It works for any AorBorC also.

Also see https://github.com/Microsoft/TypeScript/issues/14094#issuecomment-373780463 for exclusive or implementation

@jcalz The advanced type ExclusifyUnion isn't very safe:

const { ...fields } = o as AorB;

fields.field3.toUpperCase(); // it shouldn't be passed

The fields of fields are all non-optional.

I don't think that has much to do with Exact types, but with what happens when you spread and then destructure a union-typed object . Any union will end up getting flattened out into a single intersection-like type, since it's pulling apart an object into individual properties and then rejoining them; any correlation or constraint between the constituents of each union will be lost. Not sure how to avoid it... if it's a bug, it might be a separate issue.

Obviously things will behave better if you do type guarding before the destructuring:

declare function isA(x: any): x is A;
declare function isB(x: any): x is B;

declare const o: AorB;
if (isA(o)) {
  const { ...fields } = o;
  fields.field3.toUpperCase(); // error
} else {
  const { ...fields } = o;
  fields.field3.toUpperCase(); // error
  if (fields.field3) {
    fields.field3.toUpperCase(); // okay
  }
}

Not that this "fixes" the issue you see, but that's how I'd expect someone to act with a constrained union.

i might be late to the party, but here is how you can at least make sure your types exactly match:

type AreSame<A, B> = A extends B
    ? B extends A ? true : false
    : false;
const sureIsTrue: (fact: true) => void = () => {};
const sureIsFalse: (fact: false) => void = () => {};



declare const x: string;
declare const y: number;
declare const xAndYAreOfTheSameType: AreSame<typeof x, typeof y>;
sureIsFalse(xAndYAreOfTheSameType);  // <-- no problem, as expected
sureIsTrue(xAndYAreOfTheSameType);  // <-- problem, as expected

wish i could do this:

type Exact<A, B> = A extends B ? B extends A ? B : never : never;
declare function needExactA<X extends Exact<A, X>>(value: X): void;

Would the feature described in this issue help with a case where an empty/indexed interface matches object-like types, like functions or classes?

interface MyType
{
    [propName: string]: any;
}

function test(value: MyType) {}

test({});           // OK
test(1);            // Fails, OK!
test('');           // Fails, OK!
test(() => {});     // Does not fail, not OK!
test(console.log);  // Does not fail, not OK!
test(console);      // Does not fail, not OK!

Interface MyType only defines an index signature and is used as the type of the only parameter of the function test. Parameter passed to the function of type:

  • Object literal {}, passes. Expected behavior.
  • Numeric constant 1 does not pass. Expected behavior (_Argument of type '1' is not assignable to parameter of type 'MyType'._)
  • String literal '' does not pass. Expected behavior (_`Argument of type '""' is not assignable to parameter of type 'MyType'._)
  • Arrow function declaration () => {}: Passes. Not expected behavior. Probably passes because functions are objects?
  • Class method console.log Passes. Not expected behavior. Similar to arrow function.
  • Class console passes. Not expected behavior. Probably because classes are objects?

The point is to only allow variables that exactly match the interface MyType by being of that type already (and not implicitly converted to it). TypeScript seems to do a lot of implicit conversion based on signatures so this might be something that cannot be supported.

Apologies if this is off-topic. So far this issue is the closest match to the problem I explained above.

@Janne252 This proposal could help you indirectly. Assuming you tried the obvious Exact<{[key: string]: any}>, here's why it would work:

  • Object literals pass as expected, as they already do with {[key: string]: any}.
  • Numeric constants fail as expected, since literals aren't assignable to {[key: string]: any}.
  • String literals fail as expected, since they aren't assignable to {[key: string]: any}.
  • Functions and class constructors fail because of their call signature (it's not a string property).
  • The console object passes because it's just that, an object (not a class). JS makes no separation between objects and key/value dictionaries, and TS is no different here apart from the added row-polymorphic typing. Also, TS doesn't support value-dependent types, and typeof is simply sugar for adding a few extra parameters and/or type aliases - it's not nearly as magical as it looks.

@blakeembrey @michalstocki @aleksey-bykov
This is my way of doing exact types:

type Exact<A extends object> = A & {__kind: keyof A};

type Foo = Exact<{foo: number}>;
type FooGoo = Exact<{foo: number, goo: number}>;

const takeFoo = (foo: Foo): Foo => foo;

const foo = {foo: 1} as Foo;
const fooGoo = {foo: 1, goo: 2} as FooGoo;

takeFoo(foo)
takeFoo(fooGoo) // error "[ts]
//Argument of type 'Exact<{ foo: number; goo: number; }>' is not assignable to parameter of type 'Exact<{ //foo: number; }>'.
//  Type 'Exact<{ foo: number; goo: number; }>' is not assignable to type '{ __kind: "foo"; }'.
//    Types of property '__kind' are incompatible.
//      Type '"foo" | "goo"' is not assignable to type '"foo"'.
//        Type '"goo"' is not assignable to type '"foo"'."

const takeFooGoo = (fooGoo: FooGoo): FooGoo => fooGoo;

takeFooGoo(fooGoo);
takeFooGoo(foo); // error "[ts]
// Argument of type 'Exact<{ foo: number; }>' is not assignable to parameter of type 'Exact<{ foo: number; // goo: number; }>'.
//  Type 'Exact<{ foo: number; }>' is not assignable to type '{ foo: number; goo: number; }'.
//    Property 'goo' is missing in type 'Exact<{ foo: number; }>'.

It works for functions parameters, returns and even for assingments.
const foo: Foo = fooGoo; // error
No runtime overhead. Only issue is that whenever you create new exact object you have to cast it against its type, but it's not a big deal really.

I believe the original example has the correct behavior: I expect interfaces to be open. In contrast, I expect types to be closed (and they are only closed sometimes). Here is an example of surprising behavior when writing a MappedOmit type:
https://gist.github.com/donabrams/b849927f5a0160081db913e3d52cc7b3

The MappedOmit type in the example only works as intended for discriminated unions. For non discriminated unions, Typescript 3.2 is passing when any intersection of the types in the union is passed.

The workarounds above using as TypeX or as any to cast have the side effect of hiding errors in construction!. We want our typechecker to help us catch errors in construction too! Additionally, there are several things we can generate statically from well defined types. Workarounds like the above (or the nominal type workarounds described here: https://gist.github.com/donabrams/74075e89d10db446005abe7b1e7d9481) stop those generators from working (though we can filter _ leading fields, it's a painful convention that's absolutely avoidable).

@aleksey-bykov fyi i think your implementation is 99% of the way there, this worked for me:

type AreSame<A, B> = A extends B
    ? B extends A ? true : false
    : false;
type Exact<A, B> = AreSame<A, B> extends true ? B : never;

const value1 = {};
const value2 = {a:1};

// works
const exactValue1: Exact<{}, typeof value1> = value1;
const exactValue1WithTypeof: Exact<typeof value1, typeof value1> = value1;

// cannot assign {a:number} to never
const exactValue1Fail: Exact<{}, typeof value2> = value2;
const exactValue1FailWithTypeof: Exact<typeof value1, typeof value2> = value2;

// cannot assign {} to never
const exactValue2Fail: Exact<{a: number}, typeof value1> = value1;
const exactValue2FailWithTypeof: Exact<typeof value2, typeof value1> = value1;

// works
const exactValue2: Exact<{a: number}, typeof value2> = value2;
const exactValue2WithTypeof: Exact<typeof value2, typeof value2> = value2;

wow, please leave the flowers over here, presents go in that bin

One small improvement that can be made here:
By using the following definition of Exact effectively creates a subtraction of B from A as A & never types on all of B's unique keys, you can get more granular errors on the invalid properties:

type Omit<T, K> = Pick<T, Exclude<keyof T, keyof K>>;
type Exact<A, B = {}> = A & Record<keyof Omit<B, A>, never>;

Lastly, I wanted to be able to do this without having to add explicit template usage of the second B template argument. I was able to make this work by wrapping with a method- not ideal since it affects runtime but it is useful if you really really need it:

function makeExactVerifyFn<T>() {
  return <C>(x: C & Exact<T, C>): C => x;
}

Sample usage:

interface Task {
  title: string;
  due?: Date;
}

const isOnlyTask = makeExactVerifyFn<Task>();

const validTask_1 = isOnlyTask({
    title: 'Get milk',
    due: new Date()  
});

const validTask_2 = isOnlyTask({
    title: 'Get milk'
});

const invalidTask_1 = isOnlyTask({
    title: 5 // [ts] Type 'number' is not assignable to type 'string'.
});

const invalidTask_2 = isOnlyTask({
    title: 'Get milk',
    procrastinate: true // [ts] Type 'true' is not assignable to type 'never'.
});

@danielnmsft It seems weird to leave B in Exact<A, B> optional in your example, especially if it's required for proper validation. Otherwise, it looks pretty good to me. It looks better named Equal, though.

@drabinowitz Your type Exact does not actually represent what has been proposed here and probably should be renamed to something like AreExact. I mean, you can't do this with your type:

function takesExactFoo<T extends Exact<Foo>>(foo: T) {}

However, your type is handy to implement the exact parameter type!

type AreSame<A, B> = A extends B
    ? B extends A ? true : false
    : false;
type Exact<A, B> = AreSame<A, B> extends true ? B : never;

interface Foo {
    bar: any
}

function takesExactFoo <T>(foo: T & Exact<Foo, T>) {
                    //  ^ or `T extends Foo` to type-check `foo` inside the function
}

let foo = {bar: 123}
let foo2 = {bar: 123, baz: 123}

takesExactFoo(foo) // ok
takesExactFoo(foo2) // error

UPD1 This will not create +1 runtime function as in the solution of @danielnmsft and of course is much more flexible.

UPD2 I just realized that Daniel in fact made basically the same type Exact as @drabinowitz did, but a more compact and probably better one. I also realized that I did the same thing as Daniel had done. But I'll leave my comment in case if someone finds it useful.

That definition of AreSame/Exact does not seem to work for union type.
Example: Exact<'a' | 'b', 'a' | 'b'> results in never.
This can apparently be fixed by defining type AreSame<A, B> = A|B extends A&B ? true : false;

@nerumo definitely found this for the same type of reducer function you showed.

Couple additional options from what you had:

1 You can set the return type to be the same as the input type with typeof. More useful if it's a very complicated type. To me when I look at this it's more explicitly obvious the intent is prevent extra properties.

interface State {
   name: string;
}
function nameReducer(state: State, action: Action<string>): typeof state {
   return {
       ...state,
       fullName: action.payload        // THIS IS REPORTED AS AN ERROR
   };
}

2 For reducers, instead of a temporary variable, assign it back to itself before returning:

interface State {
   name: string;
}
function nameReducer(state: State, action: Action<string>) {
   return (state = {
       ...state,
       fullName: action.payload        // THIS IS REPORTED AS AN ERROR
   });
}

3 If you really want a temporary variable, don't give it an explicit type, use typeof state again

interface State {
   name: string;
}
function nameReducer(state: State, action: Action<string>) {

   const newState: typeof state = {
       ...state,
       fullName: action.payload         // THIS IS REPORTED AS AN ERROR
   };

   return newState;
}

3b If your reducer doesn't contain ...state you can use Partial<typeof state> for the type:

interface State {
   name: string;
}
function nameReducer(state: State, action: Action<string>) {

   const newState: Partial<typeof state> = {
       name: 'Simon',
       fullName: action.payload         // THIS IS REPORTED AS AN ERROR
   };

   return newState;
}

I do feel this whole conversation (and I just read the whole thread) missed the crux of the issue for most people and that is that to prevent errors all we want is a type assertion to prevent disallow a 'wider' type:

This is what people may try first, which doesn't disallow 'fullName':

 return <State> {
       ...state,
       fullName: action.payload         // compiles ok :-(
   };

This is because <Dog> cat is you telling the compiler - yes I know what I'm doing, its a Dog! You're not asking permission.

So what would be most useful to me is a stricter version of <Dog> cat that would prevent extraneous properties:

 return <strict State> {
       ...state,
       fullName: action.payload     // compiles ok :-(
   };

The whole Exact<T> type thing has many ripple through consequences (this is a long thread!). It reminds me of the whole 'checked exceptions' debate where it's something you think you want but it turns out it has many issues (like suddenly five minutes later wanting an Unexact<T>).

On the other hand <strict T> would act more like a barrier to prevent 'impossible' types getting 'through'. It's essentially a type filter that passes through the type (as has been done above with runtime functions).

However it would be easy for newcomers to assume it prevented 'bad data' getting through in cases where it would be impossible for it to do so.

So if I had to make a proposal syntax it would be this:

/// This syntax is ONLY permitted directly in front of an object declaration:
return <strict State> { ...state, foooooooooooooo: 'who' };

Back to the OP: in theory[1] with negated types you could write type Exact<T> = T & not Record<not keyof T, any>. Then an Exact<{x: string}> would forbid any types with keys other than x from being assigned to it. Not sure if that's enough to satisfy what's being asked by everyone here, but it does seem to perfectly fit the OP.

[1] I say in theory because that's predicated on better index signatures as well

Curious to know if I have the issue described here. I have code like:

const Layers = {
  foo: 'foo'
  bar: 'bar'
  baz: 'baz'
}

type Groups = {
  [key in keyof Pick<Layers, 'foo' | 'bar'>]: number
}

const groups = {} as Groups

then it allows me to set unknown properties, which is what I don't want:

groups.foo = 1
groups.bar = 2
groups.anything = 2 // NO ERROR :(

Setting anything still works, and key value type is any. I was hoping it would be an error.

Is this what will be solved by this issue?

Turns out, I should have been doing

type Groups = {
  [key in keyof Pick<typeof Layers, 'foo' | 'bar'>]: number
}

Note the added use of typeof.

The Atom plugin atom-typescript was trying hard not to fail, and eventually crashed. When I added typeof, things went back to normal, and unknown props were no longer allowed which is what I was expecting.

In other words, when I was not using typeof, atom-typescript was trying to figure the type in other places of the code where I was using the objects of type Groups, and it was allowing me to add unknown props and showing me a type hint of any for them.

So I don't think I have the issue of this thread.

Another complication might be how to handle optional properties.

If you have a type that has optional properties what would Exact<T> for those properties mean:

export type PlaceOrderResponse = { 
   status: 'success' | 'paymentFailed', 
   orderNumber: string
   amountCharged?: number
};

Does Exact<T> mean every optional property must be defined? What would you specify it as? Not 'undefined' or 'null' because that's has a runtime effect.

Does this now require a new way to specify a 'required optional parameter'?

For example what do we have to assign amountCharged with in the following code sample to get it to satisfy the 'exactness' of the type? We're not being very 'exact' if we don't enforce this property to be at least 'acknowledged' somehow. Is it <never>? It can't be undefined or null.

const exactOrderResponse: Exact<PlaceOrderResponse> = 
{
   status: 'paymentFailed',
   orderNumber: '1001',
   amountCharged: ????      
};

So you may be thinking - it's still optional, and it is now exactly optional which just translates to optional. And certainly at runtime it would need to not be set, but it looks to me like we just 'broke' Exact<T> by sticking in a question mark.

Maybe it is only when assigning a value between two types that this check needs to be made? (To enforce that they both include amountCharged?: number)

Let's introduce a new type here for a dialog box's input data:

export type OrderDialogBoxData = { 
   status: 'success' | 'paymentFailed', 
   orderNumber: string
   amountCharge?: number      // note the typo here!
};

So let's try this out:

// run the API call and then assign it to a dialog box.
const serverResponse: Exact<PlaceOrderResponse> = await placeOrder();
const dialogBoxData: Exact<OrderDialogBoxData> = serverResponse;    // SHOULD FAIL

I would expect this to fail of course because of the typo - even though this property is optional in both.

So then I came back to 'Why are we wanting this in the first place?'.
I think it would be for these reasons (or a subset depending upon the situation):

  • Avoid typos of property names
  • If we add a property to some 'component', we want to make sure everything that uses it has to add that property too
  • If we remove a property from some 'component' we need to remove it everywhere.
  • Make sure we don't provide extra properties unnecessarily (maybe we're sending it to an API and we want to keep payload lean)

If 'exact optional properties' aren't handled properly then some of these benefits are broken or greatly confused!

Also in the above example we've just 'shoehorned' Exact in to try to avoid typos but only succeeded in making a huge mess! And it's now even more brittle than ever before.

I think what I often need isn't an actually an Exact<T> type at all, it is one of these two :

NothingMoreThan<T> or
NothingLessThan<T>

Where 'required optional' is now a thing. The first allows nothing extra to be defined by the RHS of the assignment, and the second makes sure everything (including optional properties) is specified on the RHS of an assignment.

NothingMoreThan would be useful for payloads sent across the wire, or JSON.stringify() and if you were to get an error because you had too many properties on RHS you'd have to write runtime code to select only the needed properties. And that's the right solution - because that's how Javascript works.

NothingLessThan is kind of what we already have in typescript - for all normal assignments - except it would need to consider optional (optional?: number) properties.

I don't expect these names to make any traction, but I think the concept is clearer and more granular than Exact<T>...

Then, perhaps (if we really need it):

Exact<T> = NothingMoreThan<NothingLessThan<T>>;

or would it be:

Exact<T> = NothingLessThan<NothingMoreThan<T>>;   // !!

This post is a result of a real problem I'm having today where I have a 'dialog box data type' that contains some optional properties and I want to make sure what's coming from the server is assignable to it.

Final note: NothingLessThan / NothingMoreThan have a similar 'feel' to some of the comments above where type A is extended from type B, or B is extended from A. The limitation there is that they wouldn't address optional properties (at least I don't think they could today).

@simeyla You could just get away with the "nothing more than" variant.

  • "Nothing less than" is just normal types. TS does this implicitly, and every type is treated as equivalent to a for all T extends X: T.
  • "Nothing more than" is basically the opposite: it's an implicit for all T super X: T

A way to pick one or both explicitly would be sufficient. As a side effect, you could specify Java's T super C as your proposed T extends NothingMoreThan<C>. So I'm pretty convinced this is probably better than standard exact types.

I feel this should be syntax though. Maybe this?

  • extends T - The union of all types assignable to T, i.e. equivalent to just plain T.
  • super T - The union of all types T is assignable to.
  • extends super T, super extends T - The union of all types equivalent to T. This just falls out of the grid, since only the type can be both assignable and assigned to itself.
  • type Exact<T> = extends super T - Sugar built-in for the common case above, to aid readability.
  • Since this just toggles assignability, you could still have things like unions that are exact or super types.

This also makes it possible to implement #14094 in userland by just making each variant Exact<T>, like Exact<{a: number}> | Exact<{b: number}>.


I wonder if this also makes negated types possible in userland. I believe it does, but I'd need to do some complicated type arithmetic first to confirm that, and it's not exactly an obvious thing to prove.

I wonder if this also makes negated types possible in userland, since (super T) | (extends T) is equivalent to unknown. I believe it is, but I'd need to do some complicated type arithmetic first to confirm that, and it's not exactly an obvious thing to prove.

For (super T) | (extends T) === unknown to hold assignability would need to be a total order.

@jack-williams Good catch and fixed (by removing the claim). I was wondering why things weren't working out initially when I was playing around a bit.

@jack-williams

"Nothing less than" is just normal types. TS does this implicitly, and every type is treated as equivalent

Yes and no. But mostly yes... ...but only if you're in strict mode!

So I had a lot of situations where I needed a property to be logically 'optional' but I wanted the compiler to tell me if I had 'forgotten it' or misspelled it.

Well that's exactly what you get with lastName: string | undefined whereas I had mostly got lastName?: string, and of course without strict mode you won't be warned of all the discrepancies.

I've always known about strict mode, and I can't for the life of me find a good reason why I didn't turn it on until yesterday - but now that I have (and I'm still wading through hundreds of fixes) it's much easier to get the behavior I wanted 'out of the box'.

I had been trying all kinds of things to get what I wanted - including playing with Required<A> extends Required<B>, and trying to remove optional ? property flags. That sent me down a whole different rabbit hole - (and this was all before I turned strict mode on).

The point being that if you're trying to get something close to 'exact' types today then you need to start with enabling strict mode (or whatever combination of flags gives the right checks). And if I needed to add middleName: string | undefined later then boom - I'd suddenly find everywhere I needed to 'consider it' :-)

PS. thanks for your comments - was very helpful. I'm realizing I've seen A LOT of code that clearly isn't using strict mode - and then people run into walls like I did. I wonder what can be done to encourage its use more?

@simeyla I think your feedback and thanks should be directed at @isiahmeadows!

I figured I'd write up my experiences with Exact types after implementing a basic prototype. My general thoughts are that the team were spot on with their assessment:

Our hopeful diagnosis is that this is, outside of the relatively few truly-closed APIs, an XY Problem solution.

I don't feel that the cost of introducing yet another object type is repaid by catching more errors, or by enabling new type relationships. Ultimately, exact types let me _say_ more, but they didn't let me _do_ more.

Examining some of the potential uses cases of exact types:

Strong typing for keys and for ... in.

Having more precise types when enumerating keys seems appealing, but in practice I never found myself enumerating keys for things that were conceptually exact. If you precisely know the keys, why not just address them directly?

Hardening optional property widening.

The assignability rule { ... } <: { ...; x?: T } is unsound because the left type may include an incompatible x property that was aliased away. When assigning from an exact type, this rule becomes sound. In practice I never use this rule; it seems more suited for legacy systems that would not have exact types to begin with.

React and HOC

I had pinned my last hope on exact types improving props passing, and simplification of spread types. The reality is that exact types are the antithesis of bounded polymorphism, and fundamentally non-compositional.

A bounded generic lets you specify props you care about, and pass the rest through. As soon as the bound becomes exact, you completely lose width subtyping and the generic becomes significantly less useful. Another problem is that one of the main tools of composition in TypeScript is intersection, but intersection types are incompatible with exact types. Any non-trivial intersection type with an exact component is going to be vacuous: _exact types do not compose_. For react and props you probably want row types and row polymorphism, but that is for another day.

Almost all the interesting bugs that might be solved by exact types are solved by excess property checking; The biggest problem is that excess property checking does not work for unions without a discriminant property; solve this and almost all of the interesting problems relevant for exact types go away, IMO.

@jack-williams I do agree it's not generally very useful to have exact types. The excess property checking concept is actually covered by my super T operator proposal, just indirectly because the union of all types T is assignable to notably does not include proper subtypes of T.

I'm not heavily in support of this personally apart from maybe a T super U*, since about the only use case I've ever encountered for excess property checking were dealing with broken servers, something you can usually work around by using a wrapper function to generate the requests manually and remove the excess garbage. Every other issue I've found reported in this thread so far could be resolved simply by using a simple discriminated union.

* This would basically be T extends super U using my proposal - lower bounds are sometimes useful for constraining contravariant generic types, and workarounds usually end up introducing a lot of extra type boilerplate in my experience.

@isiahmeadows I certainly agree the lower bounded types can be useful, and if you can get exact types out of that, then that's a win for those that want to use them. I guess I should add a caveat to my post that is: I'm primarily addressing the concept of adding a new operator specifically for exact object types.

@jack-williams I think you missed my nuance that I was primarily referring to the exact types and related part of excess property checking. The bit about lower bounded types was a footnote for a reason - it was a digression that's only tangentially related.

I managed to write an implementation for this that will work for function arguments that require varying degrees of exactness:

// Checks that B is a subset of A (no extra properties)
type Subset<A extends {}, B extends {}> = {
   [P in keyof B]: P extends keyof A ? (B[P] extends A[P] | undefined ? A[P] : never) : never;
}

// This can be used to implement partially strict typing e.g.:
// ('b?:' is where the behaviour differs with optional b)
type BaseOptions = { a: string, b: number }

// Checks there are no extra properties (Not More, Less fine)
const noMore = <T extends Subset<BaseOptions, T>>(options: T) => { }
noMore({ a: "hi", b: 4 })        //Fine
noMore({ a: 5, b: 4 })           //Error 
noMore({ a: "o", b: "hello" })   //Error
noMore({ a: "o" })               //Fine
noMore({ b: 4 })                 //Fine
noMore({ a: "o", b: 4, c: 5 })   //Error

// Checks there are not less properties (More fine, Not Less)
const noLess = <T extends Subset<T, BaseOptions>>(options: T) => { }
noLess({ a: "hi", b: 4 })        //Fine
noLess({ a: 5, b: 4 })           //Error
noLess({ a: "o", b: "hello" })   //Error
noLess({ a: "o" })               //Error  |b?: Fine
noLess({ b: 4 })                 //Error
noLess({ a: "o", b: 4, c: 5 })   //Fine

// We can use these together to get a fully strict type (Not More, Not Less)
type Strict<A extends {}, B extends {}> = Subset<A, B> & Subset<B, A>;
const strict = <T extends Strict<BaseOptions, T>>(options: T) => { }
strict({ a: "hi", b: 4 })        //Fine
strict({ a: 5, b: 4 })           //Error
strict({ a: "o", b: "hello" })   //Error
strict({ a: "o" })               //Error  |b?: Fine
strict({ b: 4 })                 //Error
strict({ a: "o", b: 4, c: 5 })   //Error

// Or a fully permissive type (More Fine, Less Fine)
type Permissive<A extends {}, B extends {}> = Subset<A, B> | Subset<B, A>;
const permissive = <T extends Permissive<BaseOptions, T>>(options: T) => { }
permissive({ a: "hi", b: 4 })        //Fine
permissive({ a: 5, b: 4 })           //Error
permissive({ a: "o", b: "hello" })   //Error
permissive({ a: "o" })               //Fine
permissive({ b: 4 })                 //Fine
permissive({ a: "o", b: 4, c: 5 })   //Fine


Exact type for variable assignment that I realised doesn't actually do anything...

// This is a little unweildy, there's also a shortform that works in many cases:
type Exact<A extends {}> = Subset<A, A>
// The simpler Exact type works for variable typing
const options0: Exact<BaseOptions> = { a: "hi", b: 4 }        //Fine
const options1: Exact<BaseOptions> = { a: 5, b: 4 }           //Error
const options2: Exact<BaseOptions> = { a: "o", b: "hello" }   //Error
const options3: Exact<BaseOptions> = { a: "o" }               //Error |b?: Fine
const options4: Exact<BaseOptions> = { b: 4 }                 //Error
const options5: Exact<BaseOptions> = { a: "o", b: 4, c: 5 }   //Error

// It also works for function typing when using an inline value
const exact = (options: Exact<BaseOptions>) => { }
exact({ a: "hi", b: 4 })        //Fine
exact({ a: 5, b: 4 })           //Error
exact({ a: "o", b: "hello" })   //Error
exact({ a: "o" })               //Error  |b?: Fine
exact({ b: 4 })                 //Error
exact({ a: "o", b: 4, c: 5 })   //Error

// But not when using a variable as an argument even of the same type
const options6 = { a: "hi", b: 4 }
const options7 = { a: 5, b: 4 }
const options8 = { a: "o", b: "hello" }
const options9 = { a: "o" }
const options10 = { b: 4 }
const options11 = { a: "o", b: 4, c: 5 }
exact(options6)                 //Fine
exact(options7)                 //Error
exact(options8)                 //Error
exact(options9)                 //Error |b?: Fine
exact(options10)                //Error
exact(options11)                //Fine  -- Should not be Fine

// However using strict does work for that
// const strict = <T extends Strict<BaseOptions, T>>(options: T) => { }
strict(options6)                //Fine
strict(options7)                //Error
strict(options8)                //Error
strict(options9)                //Error |b?: Fine
strict(options10)               //Error
strict(options11)               //Error -- Is correctly Error

See

https://www.npmjs.com/package/ts-strictargs
https://github.com/Kotarski/ts-strictargs

I feel like I have a use case for this when wrapping React components, where I need to "pass through" props: https://github.com/Microsoft/TypeScript/issues/29883. @jack-williams Any thoughts on this?

@OliverJAsh Looks relevant, but I must admit I don't know React as well as most. I guess it would be helpful to work through how exact types can precisely help here.

type MyComponentProps = { foo: 1 };
declare const MyComponent: ComponentType<MyComponentProps>;

type MyWrapperComponent = MyComponentProps & { myWrapperProp: 1 };
const MyWrapperComponent: ComponentType<MyWrapperComponent> = props => (
    <MyComponent
        // We're passing too many props here, but no error!
        {...props}
    />
);

Please correct me at any point I say something wrong.

I'm guessing the start would be to specify MyComponent to accept an exact type?

declare const MyComponent: ComponentType<Exact<MyComponentProps>>;

In that case then we would get an error, but how do you fix the error? I'm assuming here that the wrapper components don't just have the same prop type all the way down, and at some point you really do need to dynamically extract a prop subset. Is this a reasonable assumption?

If MyWrapperComponent props is also exact then I think it would be sufficient to do a destructuring bind. In the generic case this would require an Omit type over an exact type, and I really don't know the semantics there. I'm guessing it could work like a homomorphic mapped type and retain the exact-ness, but I think this would require more thought.

If MyWrapperComponent is not exact then it will require some run-time check to prove the exactness of the new type, which can only be done by explicitly selecting the properties you want (which doesn't scale as you say in your OP). I'm not sure how much you gain in this case.

Things that I haven't covered because I don't know how likely they are is the generic case, where props is some generic type, and where you need to combine props like { ...props1, ...props2 }. Is this common?

@Kotarski Did you publish it by any chance in NPM registry?

@gitowiec

@Kotarski Did you publish it by any chance in NPM registry?

https://www.npmjs.com/package/ts-strictargs
https://github.com/Kotarski/ts-strictargs

I have this use-case:

type AB = { a: string, b: string }
type CD = { c: string, d: string }
type ABCD = AB & CD

// I want this to error, because the 'c' should mean it prevents either AB or ABCD from being satisfied.
const foo: AB | ABCD = { a, b, c };

// I presume that I would need to do this:
const foo: Exact<AB> | Exact<ABCD> = { a, b, c };

@ryami333 That does not need exact types; that just needs a fix to excess property checking: #13813.

@ryami333 If you are willing to use an extra type, I have a type that will do what you want it to, namely force a stricter version of unions :

type AB = { a: string, b: string }
type CD = { c: string, d: string }
type ABCD = AB & CD


type UnionKeys<T> = T extends any ? keyof T : never;
type StrictUnionHelper<T, TAll> = T extends any ? T & Partial<Record<Exclude<UnionKeys<TAll>, keyof T>, never>> : never;
type StrictUnion<T> = StrictUnionHelper<T, T>

// Error now.
const foo: StrictUnion<AB | ABCD> = { a: "", b: "", c: "" };

@dragomirtitian Fascinating. It's curious to me why

type KeyofV1<T extends object> = keyof T

produces a different result than

type KeyofV2<T> = T extends object ? keyof T : never

Could someone explain this to me?

type AB = { a: string, b: string }
type CD = { c: string, d: string }
type ABCD = AB & CD

KeyofV1< AB | ABCD > // 'a' | 'b'
KeyofV2< AB | ABCD > // 'a' | 'b' | 'c' | 'e'

V1 gets the common keys of the union, V2 gets the keys of each union member and unions the result.

@weswigham Is there a reason they should be returning different results?

Yes? As I said - V1 gets the _common keys_ to every union member, because the argument to keyof ends up being keyof (AB | ABCD), which is just "A" | "B", while the version within the conditional only receives one union member at a time, thanks to the conditional distributing over its input, so it's essentially keyof AB | keyof ABCD.

@weswigham So the conditional evaluates it more like this, like via some implicit loop?

type Union =
    (AB extends object ? keyof AB : never) |
    (ABCD extends object ? keyof ABCD : never)

When I'm reading that code, I'd normally expect the (AB | ABCD) extends object check to operate as a single unit, checking that (AB | ABCD) is assignable to object, then it returning keyof (AB | ABCD) as a unit, 'a' | 'b'. The implicit mapping seems really strange to me.

@isiahmeadows You can look at distributive conditional types as a foreach for unions. They apply the conditional type to each member of the union in turn and the result is the union of each partial result.

So UnionKeys<A | B> = UnionKeys<A> | UnionKeys<B> =(keyof A) | (keyof B)

But only if the conditional type distributes, and it distributes only if the tested type is a naked type parameter. So:

type A<T> = T extends object ? keyof T : never // distributive
type B<T> = [T] extends [object] ? keyof T : never // non distributive the type parameter is not naked
type B<T> = object extends T ? keyof T : never // non distributive the type parameter is not the tested type

Thanks guys, I think I got it. I re-arranged it for my understanding; I believe that the NegativeUncommonKeys is useful on its own as well. Here it is in case it is useful to someone else as well.

type UnionKeys<T> = T extends any ? keyof T : never;
type NegateUncommonKeys<T, TAll> = (
    Partial<
        Record<
            Exclude<
                UnionKeys<TAll>,
                keyof T
            >,
            never
        >
    >
) 

type StrictUnion<T, TAll = T> = T extends any 
  ? T & NegateUncommonKeys<T, TAll>
  : never;

I also understand why T and TAll are both there. The "loop effect", where T is tested and naked, means that each item in the union for T is applied whereas the untested TAll contains the original and complete union of all items.

@weswigham Yeah .. except I feel that section reads like it was written by one compiler engineer for another compiler engineer.

Conditional types in which the checked type is a naked type parameter are called distributive conditional types.

What are naked type parameters ? (and why don't they put some clothes on 😄)

i.e. T refers to the individual constituents after the conditional type is distributed over the union type)

Just yesterday I had a discussion about what this particular sentence means and why there was an emphasis on the word 'after'.

I think the documentation is written assuming prior knowledge and terminology that users might not always have.

The handbook section does make sense to me and it explains it much better, but I still am skeptical of the design choice there. It just doesn't logically make sense to me how that behavior would naturally follow from a set theoretic and type-theoretic perspective. It just comes across as a little too hackish.

naturally follow from a set theoretic and type-theoretic perspective

Take each item in a set and partition it according to a predicate.

That's a distributive operation!

Take each item in a set and partition it according to a predicate.

Although that only makes sense when you're talking about sets of sets (ie, a union type) which starts sounding an awful lot more like category theory.

@RyanCavanaugh Okay, so let me clarify: I intuitively read T extends U ? F<T> : G<T> as T <: U ⊢ F(T), (T <: U ⊢ ⊥) ⊢ G(T), with the comparison done not piecewise, but as a complete step. That's distinctly different from "the union of for all {if t ∈ U then F({t}) else G({t}) | t ∈ T}, which is what's currently the semantics.

(Pardon if my syntax is a bit off - my type theory knowledge is entirely self-taught, so I know I don't know all the syntactic formalisms.)

Which operation is more intuitive is up for infinite debate, but with the current rules it's easy to make a distributive type non-distributive with [T] extends [C]. If the default were non-distributive, you'd need some new incantation at a different level to cause distributivity. That's also a separate question from which behavior is more often preferred; IME I almost never want a non-distributing type.

Ye there is no strong theoretical grounding for distribution because it’s a syntactic operation.

The reality is that it is very useful and trying to encode it some other way would be painful.

As it stands, I'll go ahead and trail off before I drive the conversation too far off topic.

there are so many issues about distrubutivness already, why won't we face it that new syntax is required?

30572

Here is an example problem:

I want to specify that my users API endpoint/serice must NOT return any extra properties (like e.g. password) other than the ones specified in the service interface. If I accidentally return an object with extra properties, I want a compile time error, regardless of whether the result object has been produced by an object literal or otherwise.

A run time check of every returned object can be costly, especially for arrays.

Excess property checking doesn't help in this case. Honestly, I think its a wonky one-trick-pony solution. In theory it should've provided an "it just works" kind of experience - in practice its also a source of confusion Exact object types should have been implemented instead, they would have covered both use cases nicely.

@babakness Your type NoExcessiveProps is a no-op. I think they mean something like this:

interface API {
    username: () => { username: string }
}

const api: API = {
    username: (): { username: string } => {
        return { username: 'foobar', password: 'secret'} // error, ok
    }
}

const api2: API = {
    username: (): { username: string } => {
        const id: <X>(x: X) => X = x => x;
        const value = id({ username: 'foobar', password: 'secret' });
        return value  // no error, bad?
    }
}

As the writer of the API type you want to enforce that username just returns the username, but any implementer can get around that because object types have no width restriction. That can only be applied at the initialisation of a literal, which the implementer may, or may not, do. Though, I would heavily discourage anyone from trying to use exact types as language based security.

@spion

Excess property checking doesn't help in this case. Honestly, I think its a wonky one-trick-pony solution. In theory they should've provided an "it just works" kind of experience

EPC is a reasonably sensible and lightweight design choice that covers are large set of problem. The reality is that Exact types do not 'just work'. To implement in a sound way that supports extensibility requires a completely different type system.

@jack-williams Of course there would be other ways to verify present as well (runtime checks where performance is not an issue, tests etc) but an additional compile-time one is invaluable for fast feedback.

Also, I didn't mean that exact types "just work". I meant that EPC was meant to "just work" but in practice its just limited, confusing and unsafe. Mainly because if try to "deliberately" use it you generally end up shooting yourself in the foot.

edit: Yep, I edited to replace "they" with "it" as I realized its confusing.

@spion

Also, I didn't mean that exact types "just work". I meant that EPC was meant to "just work" but in practice its just limited, confusing and unsafe. Mainly because if try to "deliberately" use it you generally end up shooting yourself in the foot.

My mistake. Read the original comment as

In theory they should've provided an "it just works" kind of experience [which would have been exact types instead of EPC]

commentary in [] being my reading.

The revised statement:

In theory it should've provided an "it just works" kind of experience

is much clearer. Sorry for my misinterpretation!

type NoExcessiveProps<O> = {
  [K in keyof O]: K extends keyof O ? O[K] : never 
}

// no error
const getUser1 = (): {username: string} => {
  const foo = {username: 'foo', password: 'bar' }
  return foo 
} 

// Compile-time error, OK
const foo: NoExcessiveProps<{username: string}>  = {username: 'a', password: 'b' }

// No error? 🤔
const getUser2 = (): NoExcessiveProps<{username: string}> => {
  const foo = {username: 'foo', password: 'bar' }
  return foo 
}


The result for getUser2 is surprising, it feels inconsistent and like it should produce a compile-time error. Whats the insight on why it doesn't?

@babakness Your NoExcessiveProps just evaluates back to T (well a type with the same keys as T). In [K in keyof O]: K extends keyof O ? O[K] : never, K will always be a key of O since you are mapping over keyof O. Your const example errors because it triggers EPC just as it would have if you would have typed it as {username: string}.

If you don't mind calling an extra function we can capture the actual type of the object passed in, and do a custom form of excess property checks. (I do realize the whole point is to automatically catch this type of error, so this might be of limited value):

function checked<T extends E, E>(o: T & Record<Exclude<keyof T, keyof E>, never>): E {
    return o;
}

const getUser2 = (): { username: string } => {
    const foo = { username: 'foo', password: 'bar' }
    return checked(foo) //error
}
const getUser3 = (): { username: string } => {
    const foo = { username: 'foo' }
    return checked(foo) //ok
}

@dragomirtitian Ah... right... good point! So I'm trying to understand your checked function. I'm particularly puzzled

const getUser2 = (): { username: string } => {
    const foo = { username: 'foo', password: 'bar' }
    const bar = checked(foo) // error
    return checked(foo) //error
}
const getUser3 = (): { username: string } => {
    const foo = { username: 'foo' }
    const bar = checked(foo) // error!?
    return checked(foo) //ok
}

The bar assignment in getUser3 fails. The error seems to be at foo
image

Details of the error

image

The type for bar here is {}, which seems as though it is because on checked

function checked<T extends E, E>(o: T & Record<Exclude<keyof T, keyof E>, never>): E {
    return o;
}

E is not assigned anywhere. Yet if we replace typeof E with typeof {}, it doesn't work.

What is the type for E? Is there some kind of context-aware thing happening?

@babakness If there is no other place to infer a type parameter from, typescript will infer it from the return type. So when we are assigning the result of checked to the return of getUser*, E will be the return type of the function, and T will be the actual type of the value you want to return. If there is no place to infer E from it will just default to {} and so you will always get an error.

The reason I did it like this was to avoid any explicit type parameters, you could create a more explicit version of it:

function checked<E>() {
    return function <T extends E>(o: T & Record<Exclude<keyof T, keyof E>, never>): E {
        return o;
    }
}

const getUser2 = (): { username: string } => {
    const foo = { username: 'foo', password: 'bar' }
    return checked<{ username: string }>()(foo) //error
}
const getUser3 = (): { username: string } => {
    const foo = { username: 'foo' }
    return checked<{ username: string }>()(foo) //ok
}

Note: The curried function approach is necessary since we don't yet have partial argument inference (https://github.com/Microsoft/TypeScript/pull/26349) so we can't specify some type parameter and have others inferred in the same call. To get around this we specify E in the first call and let T be inferred in the second call. You could also cache the cache function for a specific type and use the cached version

function checked<E>() {
    return function <T extends E>(o: T & Record<Exclude<keyof T, keyof E>, never>): E {
        return o;
    }
}
const checkUser = checked<{ username: string }>()

const getUser2 = (): { username: string } => {
    const foo = { username: 'foo', password: 'bar' }
    return checkUser(foo) //error
}
const getUser3 = (): { username: string } => {
    const foo = { username: 'foo' }
    return checkUser(foo) //ok
}

FWIW this is a WIP / sketch tslint rule that solves the specific problem of not accidentally returning extra properties from "exposed" methods.

https://gist.github.com/spion/b89d1d2958f3d3142b2fe64fea5e4c32

For the spread use case – see https://github.com/Microsoft/TypeScript/issues/12936#issuecomment-300382189 – could a linter detect a pattern like this and warn that it's not type-safe?

Copying code example from the aforementioned comment:

interface State {
   name: string;
}
function nameReducer(state: State, action: Action<string>): State {
   return {
       ...state,
       fullName: action.payload // compiles, but it's an programming mistake
   }
}

cc @JamesHenry / @armano2

Would very much like to see that happen. We use generated TypeScript definitions for GraphQL endpoints and it's a problem that TypeScript does not raise an error when I pass an object with more fields than necessary to a query because GraphQL will fail to execute such a query at runtime.

how much of this is now addressed with the 3.5.1 update w/ better checking for extra properties during assignment? we got a bunch of known problem areas flagged as errors the way we wanted them to be after upgrading to 3.5.1

if you have a problem and you think exact types are the right solution, please describe the original problem here

https://github.com/microsoft/TypeScript/issues/12936#issuecomment-284590083

Here's one involving React refs: https://github.com/microsoft/TypeScript/issues/31798

/cc @RyanCavanaugh

One use case for me is

export const mapValues =
  <T extends Exact<T>, V>(object: T, mapper: (value: T[keyof T], key: keyof T) => V) => {
    type TResult = Exact<{ [K in keyof T]: V }>;
    const result: Partial<TResult> = { };
    for (const [key, value] of Object.entries(object)) {
      result[key] = mapper(value, key);
    }
    return result as TResult;
  };

This is unsound if we don't use exact types, since if object has extra properties, it's not safe to call mapper on those extra keys and values.

The real motivation here is that I want to have the values for an enum somewhere that I can reuse in the code:

const choices = { choice0: true, choice1: true, choice2: true };
const callbacksForChoices = mapValues(choices, (_, choice) => () => this.props.callback(choice));

where this.props.callback has type (keyof typeof choices) => void.

So really it's about the type system being able to represent the fact that I have a list of keys in code land that exactly matches a set (e.g., a union) of keys in type land, so that we can write functions that operate on this list of keys and make valid type assertions about the result. We can't use an object (choices in my previous example) because as far as the type system knows, the code-land object could have extra properties beyond whatever object type is used. We can't use an array (['choice0', 'choice1', 'choice2'] as const, because as far as the type system knows, the array might not contain all of the keys allowed by the array type.

Maybe exact shouldn't be a type, but only a modifier on function's inputs and/or output? Something like flow's variance modifier (+/-)

I want to add on to what @phaux just said. The real use I have for Exact is to have the compiler guarantee the shape of functions. When I have a framework, I may want either of these: (T, S): AtMost<T>, (T, S): AtLeast<T>, or (T, S): Exact<T> where the compiler can verify that the functions a user defines will fit exactly.

Some useful examples:
AtMost is useful for config (so we don't ignore extra params/typos and fail early).
AtLeast is great for things like react components and middleware where a user may shove whatever extra they want onto an object.
Exact is useful for serialisation/deserialization (we can guarantee we don't drop data and these are isomorphic).

Would this help to prevent this from happening?

interface IDate {
  year: number;
  month: number;
  day: number;
}

type TBasicField = string | number | boolean | IDate;

 // how to make this generic stricter?
function doThingWithOnlyCorrectValues<T extends TBasicField>(basic: T): void {
  // ... do things with basic field of only the exactly correct structures
}

const notADate = {
  year: 2019,
  month: 8,
  day: 30,
  name: "James",
};

doThingWithOnlyCorrectValues(notADate); // <- this should not work! I want stricter type checking

We really need a way in TS to say T extends exactly { something: boolean; } ? xxx : yyy.

Or otherwise, something like:

const notExact = {
  something: true,
  name: "fred",
};

Will still return xxx there.

Maybe const keyword can be used? e.g.T extends const { something: boolean }

@pleerock it might be slightly ambiguous, as in JavaScript / TypeScript we can define a variable as const but still add / remove object properties. I think the keyword exact is pretty to the point.

I'm not sure if it's exactly related, but i'd expect at least two errors in this case:
playground
Screen Shot 2019-08-08 at 10 15 34

@mityok I think that is related. I'm guessing you would like to do something along the lines of:

class Animal {
  makeSound(): exact Foo {
     return { a: 5 };
  }
}

If the exact made the type stricter - then it shouldn't be extendable with an extra property, as you've done in Dog.

taking advantage of the const (as const) and using before interfaces and types, like

const type WillAcceptThisOnly = number

function f(accept: WillAcceptThisOnly) {
}

f(1) // error
f(1 as WillAcceptThisOnly) // ok, explicit typecast

const n: WillAcceptThisOnly = 1
f(n) // ok

would be really verbose having to assign to const variables, but would avoid a lot of edge cases when you pass a typealias that wasn't exact what you were expecting

I have came up with pure TypeScript solution for Exact<T> problem that, I believe, behaves exactly like what has been requested in the main post:

// (these two types MUST NOT be merged into a single declaration)
type ExactInner<T> = <D>() => (D extends T ? D : D);
type Exact<T> = ExactInner<T> & T;

function exact<T>(obj: Exact<T> | T): Exact<T> {
    return obj as Exact<T>;
};

The reason ExactInner must be not included in the Exact is due to #32824 fix not being released yet (but already merged in !32924).

It's only possible to assign a value to the variable or function argument of type Exact<T>, if the right hand expression is also Exact<T>, where T is exactly identical type in both parts of assignment.

I haven't achieved automatic promotion of values into Exact types, so that's what exact() helper function is for. Any value can be promoted to be of exact type, but assignment will only succeed if TypeScript can prove that underlying types of both parts of expression are not just extensible, but exactly the same.

It works by exploiting the fact that TypeScript uses extend relation check to determine if right hand type can be assigned to the left hand type — it only can if right hand type (source) _extends_ the left hand type (destination).

Quoting checker.ts,

// Two conditional types 'T1 extends U1 ? X1 : Y1' and 'T2 extends U2 ? X2 : Y2' are related if
// one of T1 and T2 is related to the other, U1 and U2 are identical types, X1 is related to X2,
// and Y1 is related to Y2.

ExactInner<T> generic uses the described approach, substituting U1 and U2 with underlying types that require exactness checks. Exact<T> adds an intersection with plain underlying type, which allows TypeScript to relax exact type when its target variable or function argument are not an exact type.

From programmer's perspective, Exact<T> behaves as if it sets an exact flag on T, without inspecting T or changing it, and without creating an independent type.

Here are playground link and gist link.

Possible future improvement would be to allow auto-promotion of non-exact types into exact types, completely removing the need in exact() function.

Amazing work @toriningen!

If anyone is able to find a way to make this work without having to wrap your value in a call to exact it would be perfect.

Not sure if this is the right issue, but here is an example of something I'd like to work.

https://www.typescriptlang.org/play/#code/KYOwrgtgBAyg9gJwC4BECWDgGMlriKAbwCgooBBAZyygF4oByAQ2oYBpSoVhq7GATHlgbEAvsWIAzMCBx4CTfvwDyCQQgBCATwAU-DNlz4AXFABE5GAGEzUAD7mUAUWtmAlEQnjiilWuCauvDI6Jhy+AB0VFgRSHAAqgAOiQFWLMA6bm4A3EA

enum SortDirection {
  Asc = 'asc',
  Desc = 'desc'
}
function addOrderBy(direction: "ASC" | "DESC") {}
addOrderBy(SortDirection.Asc.toUpperCase());

@lookfirst That's different. This is asking for a feature for types that don't admit extra properties, like some type exact {foo: number} where {foo: 1, bar: 2} isn't assignable to it. That's just asking for text transforms to apply to enum values, which likely doesn't exist.

Not sure if this is the right issue, but [...]

In my experience as a maintainer elsewhere, if you're in doubt and couldn't find any clear existing issue, file a new bug and worst case scenario, it gets closed as a dupe you didn't find. This is pretty much the case in most major open source JS projects. (Most of us bigger maintainers in the JS community are actually decent people, just people who can get really bogged down over bug reports and such and so it's hard not to be really terse at times.)

@isiahmeadows Thanks for the response. I didn't file a new issue because I was searching for duplicate issues first, which is the correct thing to do. I was trying to avoid bogging people down because I wasn't sure if this was the right issue or not or even how to categorize what I was talking about.

EDITED: Check @aigoncharov solution bellow, because I think is even faster.

type Exact<T, R> = T extends R
  ? R extends T
    ? T
    : never
  : never

Don't know if this can be improved more.

type Exact<T, Shape> =
    // Check if `T` is matching `Shape`
    T extends Shape
        // Does match
        // Check if `T` has same keys as `Shape`
        ? Exclude<keyof T, keyof Shape> extends never
            // `T` has same keys as `Shape`
            ? T
            // `T` has more keys than `Shape`
            : never
        // Does not match at all
        : never;

type InexactType = {
    foo: string
}

const obj = {
    foo: 'foo',
    bar: 'bar'
}

function test1<T>(t: Exact<T, InexactType>) {}
function test2(t: InexactType) {}

test1(obj) // $ExpectError
test2(obj)

Without comments

type ExactKeys<T1, T2> = Exclude<keyof T1, keyof T2> extends never
    ? T1
    : never

type Exact<T, Shape> = T extends Shape
    ? ExactKeys<T, Shape>
    : never;

Don't know if this can be improved more.

type Exact<T, Shape> =
    // Check if `T` is matching `Shape`
    T extends Shape
        // Does match
        // Check if `T` has same keys as `Shape`
        ? Exclude<keyof T, keyof Shape> extends never
            // `T` has same keys as `Shape`
            ? T
            // `T` has more keys than `Shape`
            : never
        // Does not match at all
        : never;

type InexactType = {
    foo: string
}

const obj = {
    foo: 'foo',
    bar: 'bar'
}

function test1<T>(t: Exact<T, InexactType>) {}
function test2(t: InexactType) {}

test1(obj) // $ExpectError
test2(obj)

Without comments

type ExactKeys<T1, T2> = Exclude<keyof T1, keyof T2> extends never
    ? T1
    : never

type Exact<T, Shape> = T extends Shape
    ? ExactKeys<T, Shape>
    : never;

Love that idea!

Another trick that could do the job is to check assignability in both directions.

type Exact<T, R> = T extends R
  ? R extends T
    ? T
    : never
  : never

type A = {
  prop1: string
}
type B = {
  prop1: string
  prop2: string
}
type C = {
  prop1: string
}

type ShouldBeNever = Exact<A, B>
type ShouldBeA = Exact<A, C>

http://www.typescriptlang.org/play/#code/C4TwDgpgBAogHgQwMbADwBUA0UBKA+KAXinSgjmAgDsATAZ1wCgooB+XMi6+k5lt3vygAuKFQgA3CACc+o8VNmNQkKAEEiUAN58w0gPZgAjKLrBpASyoBzRgF9l4aACFNOlnsMmoZyzd0GYABMpuZWtg4q0ADCbgFeoX4RjI6qAMoAFvoArgA2NM4QAHKSMprwyGhq2M54qdCZOfmFGsQVKKjVUNF1QkA

Another playground from @iamandrewluca https://www.typescriptlang.org/play/?ssl=7&ssc=6&pln=7&pc=17#code/C4TwDgpgBAogHgQwMbADwBUA0UBKA+KAXinSgjmAgDsATAZ1wCgooB+XMi6+k5lt3vygAuKFQgA3CACc+o8VNmNQkKAElxiFOnDRiAbz4sAZgHtTousGkBLKgHNGAX0aMkpqlaimARgCsiKEMhMwsoAHJQ8MwjKB8EaVFw+Olw51djAFcqFBsPKEorAEYMPAAKYFF4ZDQsdU0anUg8AEoglyyc4DyqAogrACYK0Q1yRt02-RdlfuAist8-NoB6ZagAEnhIFBhpaVNZQuAhxZagA

A nuance here is whether Exact<{ prop1: 'a' }> should be assignable to Exact<{ prop1: string }>. In my use cases, it should.

@jeremybparagon your case is covered. Here are some more cases.

type InexactType = {
    foo: 'foo'
}

const obj = {
    // here foo is infered as `string`
    // and will error because `string` is not assignable to `"foo"`
    foo: 'foo',
    bar: 'bar'
}

function test1<T>(t: Exact<T, InexactType>) {}
function test2(t: InexactType) {}

test1(obj) // $ExpectError
test2(obj) // $ExpectError
type InexactType = {
    foo: 'foo'
}

const obj = {
    // here we cast to `"foo"` type
    // and will not error
    foo: 'foo' as 'foo',
    bar: 'bar'
}

function test1<T>(t: Exact<T, InexactType>) {}
function test2(t: InexactType) {}

test1(obj) // $ExpectError
test2(obj)
type Exact<T, R> = T extends R
  ? R extends T
    ? T
    : never
  : never

I think anybody using this trick (and I'm not saying there aren't valid uses for it) should be acutely aware that it is very very easy to get more props in the "exact" type. Since InexactType is assignable to Exact<T, InexactType> if you have something like this, you break out of exactness without realizing it:

function test1<T>(t: Exact<T, InexactType>) {}

function test2(t: InexactType) {
  test1(t); // inexactType assigned to exact type
}
test2(obj) // but 

Playground Link

This is the reason (at least one of them) that TS does not have exact types, as it would require a complete forking of object types in exact vs non-exact types where an inexact type is never assignable to an exact one, even if at face value they are compatible. The inexact type may always contain more properties. (At least this was one of the reasons @ahejlsberg mentioned as tsconf).

If asExact were some syntactic way of marking such an exact object, this is what such a solution might look like:

declare const exactMarker: unique symbol 
type IsExact = { [exactMarker]: undefined }
type Exact<T extends IsExact & R, R> =
  Exclude<keyof T, typeof exactMarker> extends keyof R? T : never;

type InexactType = {
    foo: string
}
function asExact<T>(o: T): T & IsExact { 
  return o as T & IsExact;
}

const obj = asExact({
  foo: 'foo',
});


function test1<T extends IsExact & InexactType>(t: Exact<T, InexactType>) {

}

function test2(t: InexactType) {
  test1(t); // error now
}
test2(obj) 
test1(obj);  // ok 

const obj2 = asExact({
  foo: 'foo',
  bar: ""
});
test1(obj2);

const objOpt = asExact < { foo: string, bar?: string }>({
  foo: 'foo',
  bar: ""
});
test1(objOpt);

Playground Link

@dragomirtitian that's why I came up with the solution a bit earlier https://github.com/microsoft/TypeScript/issues/12936#issuecomment-524631270 that doesn't suffer from this.

@dragomirtitian it's a matter of how you type your functions.
If you do it a little differently, it works.

type Exact<T, R> = T extends R
  ? R extends T
    ? T
    : never
  : never

type InexactType = {
    foo: string
}

const obj = {
    foo: 'foo',
    bar: 'bar'
}

function test1<T>(t: Exact<T, InexactType>) {}

function test2<T extends InexactType>(t: T) {
  test1(t); // fails
}
test2(obj)

https://www.typescriptlang.org/play/#code/C4TwDgpgBAogHgQwMbADwBUA0UBKA+KAXinSgjmAgDsATAZ1wCgooB+XMi6+k5lt3vygAuKFQgA3CACc+o8VNmNQkKAElxiFOnDRiAbz4sAZgHtTousGkBLKgHNGAX0aMkpqlaimARgCsiKEMhMwsoAHJQ8MwjKB8EaVFw+Olw51djAFcqFBsPKEorAEYMPAAKYFF4ZDQsdU0anUg8AEogl0YsnOA8qgKIKwAmDE5KWgYNckbdcsqSNuD+4oqWgG4oAHoNqGMEGwAbOnTC4EGy3z82oA

@jeremybparagon your case is covered.

@iamandrewluca I think the solutions here and here differ on how they treat my example.

type Exact<T, R> = T extends R
  ? R extends T
    ? T
    : never
  : never

type A = {
  prop1: 'a'
}
type C = {
  prop1: string
}

type ShouldBeA = Exact<A, C> // This evaluates to never.

const ob...

Playground Link

@aigoncharov The problem is you need to be aware of that so one could easily not do this and test1 could still get called with extra properties. IMO any solution that can so easily allow an accidental inexact assignment has already failed as the whole point is to enforce exactness in the type system.

@toriningen yeah your solution seems better, I was just referring to the last posted solution. Your solution has going for it the fact that you don't need the extra function type parameter, however it does not seem to work well for optional properties:

// (these two types MUST NOT be merged into a single declaration)
type ExactInner<T> = <D>() => (D extends T ? D : D);
type Exact<T> = ExactInner<T> & T;
type Unexact<T> = T extends Exact<infer R> ? R : T;

function exact<T>(obj: Exact<T> | T): Exact<T> {
    return obj as Exact<T>;
};

////////////////////////////////
// Fixtures:
type Wide = { foo: string, bar?: string };
type Narrow = { foo: string };
type ExactWide = Exact<Wide>;
type ExactNarrow = Exact<Narrow>;

const ew: ExactWide = exact<Wide>({ foo: "", bar: ""});
const assign_en_ew: ExactNarrow = ew; // Ok ? 

Playground Link

@jeremybparagon I'm not sure @aigoncharov 's solution does a good job on optional properties though. Any solution based on T extends S and S extends T will suffer from the simple fact that

type A = { prop1: string }
type C = { prop1: string,  prop2?: string }
type CextendsA = C extends A ? "Y" : "N" // Y 
type AextendsC = A extends C ? "Y" : "N" // also Y 

Playground Link

I think @iamandrewluca of using Exclude<keyof T, keyof Shape> extends never is good, my type is quite similar (I edited my original answer to add the &R to ensure T extends R without any extra checks).

type Exact<T extends IsExact & R, R> =
  Exclude<keyof T, typeof exactMarker> extends keyof R? T : never;

I would not stake my reputation that my solution does not have holes though, I haven't looked that hard for them but welcome any such findings 😊

we should have a flag where this is enabled globally. In this way, who wants to loose type can keep doing the same. Way too many bugs caused by this issue. Now I try to try to avoid spread operator and use pickKeysFromObject(shipDataRequest, ['a', 'b','c'])

Here's a use case for exact types I recently stumbled on:

type PossibleKeys = 'x' | 'y' | 'z';
type ImmutableMap = Readonly<{ [K in PossibleKeys]?: string }>;

const getFriendlyNameForKey = (key: PossibleKeys) => {
    switch (key) {
        case 'x':
            return 'Ecks';
        case 'y':
            return 'Why';
        case 'z':
            return 'Zee';
    }
};

const myMap: ImmutableMap = { x: 'foo', y: 'bar' };

const renderMap = (map: ImmutableMap) =>
    Object.keys(map).map(key => {
        // Argument of type 'string' is not assignable to parameter of type 'PossibleKeys'
        const friendlyName = getFriendlyNameForKey(key);
        // No index signature with a parameter of type 'string' was found on type 'Readonly<{ x?: string | undefined; y?: string | undefined; z?: string | undefined; }>'.    
        return [friendlyName, map[key]];
    });
;

Because types are inexact by default, Object.keys has to return a string[] (see https://github.com/microsoft/TypeScript/pull/12253#issuecomment-263132208), but in this case, if ImmutableMap was exact, there's no reason it couldn't return PossibleKeys[].

@dallonf note that this example requires extra functionality besides just exact types -- Object.keys is just a function and there'd need to be some mechanism for describing a function that returns keyof T for exact types and string for other types. Simply having the option to declare an exact type wouldn't be sufficient.

@RyanCavanaugh I think that was the implication, exact types + the ability to detect them.

Use case for the react typings:

forwardRef<T, P>(render: (props: P, ref: Ref<T>) => ReactElement<P> & { displayName?: string }) => ComponentType<P>.

It's tempting to pass a regular component to forwardRef which is why React issues runtime warnings if it detects propTypes or defaultProps on the render argument. We'd like to express this at the type level but have to fallback to never:

- forwardRef<T, P>(render: (props: P, ref: Ref<T>) => ReactElement<P> & { displayName?: string }) => ComponentType<P>
+ forwardRef<T, P>(render: (props: P, ref: Ref<T>) => ReactElement<P> & { displayName?: string, propTypes?: never, defaultProps?: never }) => ComponentType<P>

The error message with never is not helpful ("{} is not assignable to undefined").

Can someone help me out on how @toriningen's solution would look like with a union of different event object shapes? I want to restrict my event shapes in redux-dispatch calls, e.g.:

type StoreEvent =
  | { type: 'STORE_LOADING' }
  | { type: 'STORE_LOADED'; data: unknown[] }

It's unclear how I could make a typed dispatch() function which only accepts the exact shape of an event.

(UPDATE: I figured it out: https://gist.github.com/sarimarton/d5d539f8029c01ca1c357aba27139010)

Use case:

Missing Exact<> support leads to runtime problems with GraphQL mutations. GraphQL accepts exact list of permitted properties. If you provide excessive props, it throws an error.

So when we obtain some data from the form, then Typescript cannot validate excess (extra) properties. And we will get an error at runtime.

The following example illustrates imaginary safety

  • in the first case tracked all input params
  • but in real life (like the second case when we got data from the form and save it to some variable) Typescript

Try in Playground

Screen Shot 2020-03-05 at 13 04 38

According to the article https://fettblog.eu/typescript-match-the-exact-object-shape/ and similar solutions provided above we can use the following ugly solution:

Screen Shot 2020-03-05 at 12 26 57

Why this savePerson<T>(person: ValidateShape<T, Person>) solution is Ugly?

Assume you have deeply nested input type eg.:

// Assume we are in the ideal world where implemented Exact<>

type Person {
  name: string;
  address: Exact<Address>;
}

type Address {
   city: string
   location: Exact<Location>
}

type Location {
   lon: number;
   lat: number; 
}

savePerson(person: Exact<Person>)

I cannot imagine what spaghetti we should write to get the same behavior with the currently available solution:

savePerson<T, TT, TTT>(person: 
  ValidateShape<T, Person keyof ...🤯...
     ValidateShape<TT, Address keyof ...💩... 
         ValidateShape<TTT, Location keyof ...🤬... 
> > >)

So, for now, we have big holes in static analysis in our code, which works with complex nested input data.

The case described in the first image, where TS doesn't validate excess properties because "freshness" is lost, has also been a bit of a pain point for us.

Writing

doSomething({
  /* large object of options */
})

often feels much less readable than

const options = {
  /* large object of options */
}
doSomething(options)

Explicitly annotating const options: DoSomethingOptions = { helps, but it's a bit cumbersome and hard to spot and enforce in code reviews.

This is bit of an offtopic idea and wouldn't solve most of the use cases for exactness described here, but would it be possible to keep an object literal fresh when it's only used once inside the enclosing scope?

@RyanCavanaugh thanks for explaining EPC...is the difference between EPC and exact types discussed in more detail anywhere? Now I feel like I ought to get a better understanding of why EPC allows some cases that exact types don't.

Hi @noppa I think that would be a great idea. I have just stumbled on this when I noticed the difference between assigning directly versus assingning to a variable first - even asked a question on SO that brought me here. The current behavior is surprising, at least to me...

I believe I have the same problem as the example of GraphQL mutations (exact nested typing, no extra properties should be allowed). In my case, I am thinking of typing API responses in a common module (shared between frontend and backend):

export type ProductsSlashResponse = {
  products: Array<{
    id: number;
    description: string;
  }>,
  total: number;
};

On the server side, I would like to make sure that the response respects that type signature:

router.get("products/", async () =>
  assertType<ProductsSlashResponse>(getProducts())));

I have tried solutions from here. One that seems to work is T extends U ? U extends T ? T : never : never, along with a curried function which is not ideal. The major problem with it is that you get no feedback on missing or extra properties (perhaps we could improve on that, but it becomes difficult to do when we get into nested properties). Other solutions don't work with deeply nested objects.

Of course, the frontend usually won't crash if I send more information than what is specified, however, this could lead to information leak if the API sends more information than it should (and because of the foggy nature of reading data from a database which types aren't necessarily in sync with the code all the time, this could happen).

@fer22f GraphQL doesn't send fields the client didn't request...unless you're using a JSON scalar type for products or for the array elements, nothing to worry about there.

Sorry I misread, I thought you meant you were using GraphQL

Someone already mentioned GraphQL, but just in terms of "collecting use cases" (@DanielRosenwasser mentioned several years ago in the thread :-) of "not having any use cases off-hand"), two use cases where I've wanted to use Exact are:

  1. Passing data into data stores / databases / ORMs--any extra fields that are passed will be silently dropped / not stored.

  2. Passing data into wire calls / RPCs / REST / GraphQL--again any extra fields that are passed will be silently dropped / not sent.

(Well, maybe not silently dropped, they can be runtime errors.)

In both cases I'd like to tell the programmer/myself (via a compile error) "...you really shouldn't give me this extra property, b/c if you're expecting it to get 'stored' or 'sent', it will not be".

This is particularly needed in "partial update" style APIs, i.e. weak types:

type Data = { firstName:? string; lastName?: string; children?: [{ ... }] };
const data = { firstName: "a", lastNmeTypo: "b" };
await saveDataToDbOrWireCall(data);

Passes the weak type check b/c at least one param matched, firstName, so it's not 100% disjoint, however there is still an "obvious" typo of lsatNmeTypo that is not getting caught.

Granted, EPC works if I do:

await saveDataToDbOrWireCall({ firstName, lastNmeTypo });

But having to destructure + re-type every field is pretty tedious.

Solutions like @jcalz 's Exactify work on 1st-level property, but the recursive case (i.e. children is an array and the array elements should be exact) I'm struggling with once it hits "real world" use cases with generics / like Exact<Foo<Bar<T>>.

It'd be great to have this built-in, and just wanted to note these explicit use cases (basically wire calls with partial/weak types), if that helps with the prioritization / roadmapping.

(FWIW https://github.com/stephenh/joist-ts/pull/35/files has my current attempt at a deep Exact and also an Exact.test.ts that is passing trivial cases, but the PR itself has compile errors on the more esoteric usages. Disclaimer I don't really expect anyone to look into this specific PR, but am just providing it as a "here's where Exact would be useful" + "AFAICT this is hard to do in user-land" data point.)

Hey,

Was wondering what's the thoughts of the TS team regarding exact types records and tuples proposal here? https://github.com/tc39/proposal-record-tuple

Does it make sense to introduce exact types for those new primitives?

@slorber Not TS, but that's orthogonal. That proposal concerns immutability, and concerns are nearly identical between that and libraries like Immutable.js.

I iterated on @stephenh recursive version. I had some trouble correctly handling undefined cases with recursion, I'm opened to cleaner solution. It probably don't works on some edge cases with arrays or complex data structure.

export type Exact<Expected, Actual> = Expected &
  Actual & // Needed to infer `Actual`
  (null extends Actual
    ? null extends Expected
      ? Actual extends null // If only null stop here, because NonNullable<null> = never
        ? null
        : CheckUndefined<Expected, Actual>
      : never // Actual can be null but not Expected: forbid the field
    : CheckUndefined<Expected, Actual>);

type CheckUndefined<Expected, Actual> = undefined extends Actual
  ? undefined extends Expected
    ? Actual extends undefined // If only undefined stop here, because NonNullable<undefined> = never
      ? undefined
      : NonNullableExact<NonNullable<Expected>, NonNullable<Actual>>
    : never // Actual can be undefined but not Expected: forbid the field
  : NonNullableExact<NonNullable<Expected>, NonNullable<Actual>>;

type NonNullableExact<Expected, Actual> = {
  [K in keyof Actual]: K extends keyof Expected
    ? Actual[K] extends (infer ActualElement)[]
      ? Expected[K] extends (infer ExpectedElement)[] | undefined | null
        ? Exact<ExpectedElement, ActualElement>[]
        : never // Not both array
      : Exact<Expected[K], Actual[K]>
    : never; // Forbid extra properties
};

playground

Exact would be very useful to us when returning API responses. Currently, this is what we resolve to:

const response = { companies };

res.json(exact<GetCompaniesResponse, typeof response>(response));
export function exact<S, T>(object: Exact<S, T>) {
  return object;
}

Here the Exact type is what @ArnaudBarre provided above.

Thanks @ArnaudBarre for unblocking me and teaching me some ts.
Riffing on your solution:

export type Exact<Expected, Actual> =
  keyof Expected extends keyof Actual
    ? keyof Actual extends keyof Expected
      ? Expected extends ExactElements<Expected, Actual>
        ? Expected
        : never
      : never
    : never;

type ExactElements<Expected, Actual> = {
  [K in keyof Actual]: K extends keyof Expected
    ? Expected[K] extends Actual[K]
      ? Actual[K] extends Expected[K]
        ? Expected[K]
        : never
      : never
    : never
};

// should succeed (produce exactly the Expected type)
let s1: Exact< { a: number; b: string }, { a: number; b: string } >;
let s2: Exact< { a?: number; b: string }, { a?: number; b: string } >;
let s3: Exact< { a?: number[]; b: string }, { a?: number[]; b: string } >;
let s4: Exact< string, string >;
let s5: Exact< string[], string[] >;
let s6: Exact< { a?: number[]; b: string }[], { a?: number[]; b: string }[] >;

// should fail (produce never)
let f1: Exact< { a: string; b: string }, { a: number; b: string } >;
let f2: Exact< { a: number; b: string }, { a?: number; b: string } >;
let f3: Exact< { a?: number; b: string }, { a: number; b: string } >;
let f4: Exact< { a: number[]; b: string }, { a: string[]; b: string } >;
let f5: Exact< { a?: number[]; b: string }, { a: number[]; b: string } >;
let f6: Exact< { a?: number; b: string; c: string }, { a?: number; b: string } >;
let f7: Exact< { a?: number; b: string }, { a?: number; b: string; c: string } >;
let f8: Exact< { a?: number; b: string; c?: string }, { a?: number; b: string } >;
let f9: Exact< { a?: number; b: string }, { a?: number; b: string; c?: string } >;
let f10: Exact< never, string >;
let f11: Exact< string, never >;
let f12: Exact< string, number >;
let f13: Exact< string[], string >;
let f14: Exact< string, string[] >;
let f15: Exact< string[], number[] >;
let f16: Exact< { a?: number[]; b: string }[], { a?: number[]; b: string } >;

The previous solution 'succeeded' for f6, f8 and f9.
This solution also returns 'cleaner' results; when it matches, you get the 'Expected' type back.
As with @ArnaudBarre 's comment ... not sure if all the edge cases are handled, so ymmv ...

@heystewart Your Exact does not give a symmetric result:

let a: Exact< { foo: number }[], { foo: number, bar?: string }[] >;
let b: Exact< { foo: number, bar?: string }[], { foo: number }[] >;

a = [{ foo: 123, bar: 'bar' }]; // error
b = [{ foo: 123, bar: 'bar' }]; // no error

Edit: @ArnaudBarre's version also has the same issue

@papb Yes effectively my typing doesn't work is the entry point is an array. I needed it for our graphQL API where variables is always an object.

To solve it you need to isolated ExactObject and ExactArray and have an entry point that goes into one or the other.

So what the best way to make sure that object has exact properties, no less, no more ?

@captain-yossarian convince TypeScript team to implement this. No solution presented here works for all expected cases, and almost all of them lack clarity.

@toriningen can't imagine how many issues will be closed if TS team will implement this feature

@RyanCavanaugh
At present, I have one use-case that brought me here, and it runs right into your topic “Miscellany”. I want a function that:

  1. takes a parameter that implements an interface with optional parameters
  2. returns an object typed to the narrower actual interface of the given parameter, so that

Those immediate goals serve these ends:

  1. I get excess property checking for the input
  2. I get auto completion and property type-safety for the output

Example

I have reduced my case to this:

type X = {
    red?: number,
    green?: number,
    blue?: number,
}

function y<
    Y extends X
>(
    y: (X extends Y ? Y : X)
) {
    if ((y as any).purple) throw Error('bla')

    return y as Y
}

const z = y({
    blue: 1,
    red: 3,
    purple: 4, // error
})
z.green // error

type Z = typeof z

That setup works and accomplishes all the desired goals, so from a pure feasibility standpoint and so far as this goes, I'm good. However, EPC is achieved through the parameter typing (X extends Y ? Y : X). I basically stumbled on that by chance, and I was somewhat surprised that it worked.

Proposal

And that is why I'd like to have an implements keyword that can be used in place of extends in order to mark the intention that the type here is not supposed to have excess properties. Like so:

type X = {
    red?: number,
    green?: number,
    blue?: number,
}

function x<
    Y implements X
>( y: Y ) {
    if ((y as any).purple) throw Error('bla')

    return y as Y
}

const z = y({
    blue: 1,
    red: 3,
    purple: 4, // error
})
z.green // error

type Z = typeof z

This seems far clearer to me than my current workaround. Apart from being more concise, it locates the whole constraint with the generics declaration as opposed to my current split between the generics and the parameters.

That may also enable further use-cases that are currently impossible or impractical, but that is presently only a gut feeling.

Weak Type Detection as an Alternative

Notably, Weak Type Detection as per #3842 should fix that just as well, and might be favorable on account of not requiring additional syntax, if it worked in connection with extends, as per my use-case.

Regarding Exact<Type> etc.

Finally, implements, as I envision it, should be pretty straight-forward regarding your point about function f<T extends Exact<{ n: number }>(p: T) since it does not try to solve the more general case of Exact<Type>.

Generally, Exact<Type> seems to be of rather little utility next to EPC, and I cannot envision a valid generally useful case that falls outside these groups:

  • function calls: these can be easily handled now as per my example, and would benefit from implements
  • assignments: just use literals, so EPC applies
  • data from outside your domain of control: type-checking cannot guard you against that, you have to handle that at runtime, at which point you're back to safe casts

Obviously, there will be cases when you cannot assign literals, but these should also be of a finite set:

  • if you are given the assignment data in a function, handle the type check in the call signature
  • if you merge several objects, as per OP, then assert each source object's type properly and you can safely cast as DesiredType

Summary: implements would be nice but otherwise we're good

In summary, I'm confident that with implements and fixing EPC (if and when issues arise), exact types should really be handled.

Question to all interested parties: is anything actually open here?

Having looked through the use-cases here, I think that almost all repros are properly handled by now, and the rest can be made to work with my little example above. That begs the question: does anybody still have issues concerning this today with up-to-date TS?

I have an immature idea About type annotations. Matching an object is divided into members can be exactly equal, no more and no less, more or less, no more but less, more but no less. For each of the above cases, there should be one expression.

exactly equal, i.e. no more and no less:

function foo(p:{|x:any,y:any|})

//it matched 
foo({x,y})
//no match
foo({x})
foo({y})
foo({x,y,z})
foo({})

more but no less:

function foo(p:{|x:any,y:any, ...|})

//it matched 
foo({x,y})
foo({x,y,z})

//no matched
foo({x})
foo({y})
foo({x,z})

no more but less:

function foo(p:{x:any,y:any})

//it matched 
foo({x,y})
foo({x})
foo({y})

//no match
foo({x,z})
foo({x,y,z})

more or less:

function foo(p:{x:any,y:any, ...})

//it matched 
foo({x,y})
foo({x})
foo({y})
foo({x,z})
foo({x,y,z})

conclusion:

With a vertical line indicates that there is no less, without a vertical line means that there can be less. With an ellipsis sign means that there can be more, without an ellipsis sign means that there can be no more. Arrays match is the same idea.

function foo(p:[|x,y|]) // p.length === 2
function foo(p:[|x,y, ... |]) // p.length >= 2
function foo(p:[x,y]) // p.length >= 0
function foo(p:[x,y,...]) // p.length >= 0

@rasenplanscher using your example, this compiles:

const x = { blue: 1, red: 3, purple: 4 };
const z = y(x);

However with exact types, it should not. I.e. the ask here is to not depend on EPC.

@xp44mm "more but no less" is already the behaviour and "more or less" is the behaviour if you mark all properties optional

function foo(p:{x?: any, y?: any}) {}
const x = 1, y = 1, z = 1
// all pass
foo({x,y})
foo({x})
foo({y})
const p1 = {x,z}
foo(p1)
const p2 = {x,y,z}
foo(p2)

Similarily, if we had exact types, exact type + all properties optional would essentially be "no more but less" .

Another example to this issue. A good demonstration for this proposal I think. In this case I use rxjs to work with Subject but want to return a ("locked") Observable (which has no next, error, etc. method to manipulate the value.)

someMethod(): Observable<MyType> {
  const subject = new Subject<MyType>();

  // This works, but should not. (if this proposal is implemented.)
  return subject;

  // Only Observable should be allowed as return type.
  return subject.asObservable();
}

I always want only return the exact type Observable and not Subject which extends it.

Proposal:

// Adding exclamation mark `!` (or something else) to match exact type. (or some other position `method(): !Foo`, ...)
someMethod()!: Observable<MyType> {
  // ...
}

But I'm sure you have better ideas. Especially because this does not only affects return values, right? Anyway, just a pseudo code demo. I think that would a nice feature to avoid errors and lacks. Like in the case described above. Another solution could be adding a new Utility Type.
Or did I miss something? Does this already work? I use TypeScript 4.

Was this page helpful?
0 / 5 - 0 ratings