0

I am trying to create an interface / a type definition, which when used in union types, prohibits a certain key in a child of the interface (here, I want to find the correct definition for Abc):

type Abc = {
    someField: {
        prohibited?: never,
    },
};
type UsedHere = {someField: {anotherField: string}} & Abc;

I have tried the types never, undefined and void for prohibited, with and without the ?, but typescript always tells me that I need a value for prohibited when I create a variable of type UsedHere. How can I tell typescript that this value in fact cannot exist? An example for an object which should be valid is:

let x: UsedHere = {
    someField: {
        anotherField: ""
    }
}

EDIT: this problem only occurs in typescript versions prior to 3.6

Lukor
  • 1,485
  • 2
  • 14
  • 25
  • Does this answer your question? [How to exclude a key from an interface in TypeScript](https://stackoverflow.com/questions/44983560/how-to-exclude-a-key-from-an-interface-in-typescript) – Pac0 Mar 02 '20 at 15:58
  • @Pac0 not really, since I don't know where my interface is going to be included, so I don't have a type to exclude from (if I understood the `Exclude` thing correctly...) – Lukor Mar 02 '20 at 15:59
  • In my humble opinion your type works correctly - https://www.typescriptlang.org/play/index.html#code/C4TwDgpgBAggRgYygXigbwLACgq6gZwHsBbCAMQEsIAbAEwC51s8WowAnQgCwrguAi0A-IwB2EAG4R2AGmZ4AvnKwKA3NlCQoAVXyCAEtOio0BEuSp1GpgIajCwLtMo0GBYOwqiA5lAV+oADJYRHUsbGoIYCgAD0ZdAyMUJhw8IlIXKxTWPDsHJ3ZMtwAiYvlcBWxK8KxI6JB4vVpDdmNstPMi63LWDm5efkFGUqgAelHpTnYe6oUgA – Maciej Sikora Mar 02 '20 at 16:01
  • [I cannot reproduce the issue](https://www.typescriptlang.org/play/#code/C4TwDgpgBAggRgYygXigbwLACgq6gZwHsBbCAMQEsIAbAEwC51s8WowAnQgCwrguAi0A-IwB2EAG4R2AGmZ4AvnKwKA3NlCQoAVXyCAEtOio0BEuSp1GpgIajCwLtMo0GBYOwqiA5lAV+oADJYRHUsaghgKAAPRl0DIxQmHDwiUhcrZNY8Owcndgy3ACIi+VwFbAUgA). Please consider editing the above code into a [mcve] as described by [ask] which can be dropped into a standalone IDE to demonstrate the problem you're seeing. – jcalz Mar 02 '20 at 16:01
  • @Lukor but a specific string can be considered as a Type in TypeScript – Pac0 Mar 02 '20 at 16:26
  • @jcalz apparently that works in a newer version of TS; however, I sadly have to use TS@3.5, where that doesn't work (and I cannot upgrade due to dependencies...) – Lukor Mar 03 '20 at 07:39
  • Does [this](https://www.typescriptlang.org/play/?ts=3.5.1#code/C4TwDgpgBAggRgYygXigbwLACgq6gZwHsBbCAMQEsIAbAEwC51s8WowAnQgCwrguAi0A-IwB2EAG4R2AGmZ4AvnKwKA3NmyhIUALLSA5hACSoge3wQEwCoVH4APABUAfCiiOoEAB4DRtfFCEcABWlsBQQuhQANoA0lAUolAA1hAghABm7gC6jHrshiZmFlY2dk5x2a4KUIyO6lia4NAAqha0ABLS0Kj5habSJda2DmgEJORUdIxjAIaihMBc0pQ0DATA7In6UDU1AGSwiM4N2NQQ4V6MbYJd7D1MOHhEpKvTj6x484vL7G-rACIAfJcApsAogA) work for you? Please edit the question to include the version of TS you care about. Note that the TypeScript Playground does allow you to select older versions. – jcalz Mar 03 '20 at 15:26
  • @jcalz that fix seems reasonable; if you write it as an answer, I'll accept it – Lukor Mar 03 '20 at 16:01

1 Answers1

1

As you noticed, the code in your example no longer results in an error starting in TypeScript 3.6. Excess property checking was not behaving properly on sufficiently nested intersections. This was reported as a bug in microsoft/TypeScript#30715 and fixed in microsoft/TypeScript#32582.

For versions of TypeScript prior to 3.6 you will need some sort of workaround. One possible workaround is to take the nested intersection type and use a conditional and mapped type to walk down through object properties and join the intersections into single objects. That is, something like {a: string} & {b: number} should become the equivalent {a: string; b: number}.

Here's one way to define that:

type MergeIntersections<T> =
    T extends object ? { [K in keyof T]: MergeIntersections<T[K]> } : T;

And we use it to wrap the definition of UsedHere:

type UsedHere =
    MergeIntersections<{ someField: { anotherField: string } } & Abc>;
/* type UsedHere = {
    someField: {
        anotherField: string;
        prohibited?: undefined;
    };
} */

As you can see, UsedHere is now a single object type whose someField property is also a single object type. The property prohibited?: undefined is the same as prohibited?: never since | undefined is automatically added to any optional properties with --strictNullChecks enabled.

And then the rest of your code compiles with no error:

let x: UsedHere = {
    someField: {
        anotherField: ""
    }
}; // okay

Okay, hope that helps. Good luck and hopefully you can eventually upgrade to a newer TypeScript version!

Playground link to code

jcalz
  • 264,269
  • 27
  • 359
  • 360