Home > OS >  Can somebody explain why this doesn't work?
Can somebody explain why this doesn't work?

Time:10-05

The idea is to enforce not using certain fields in the object passed to the function, and you can use anything else (that's why the extends)

type ForbiddenFields = 'a' | 'b' | 'c';
type Data = { [K in ForbiddenFields]?: never };

function foobar<T extends Data>(data: T): void {
  //...
}

const obj = { x: 1, y: 2 };
foobar(obj); // Type '{ x: 1, y: 2 }' has no properties in common with type Data

Why do I get the

Type '{ x: 1, y: 2 }' has no properties in common with type Data

if the function is just expecting an object extending Data, wouldn't it be the same as saying <T extends {}> but just enforcing that if a ForbiddenField is specified it has to be never -- therefore, can't be there?

CodePudding user response:

I couldn't tell you why Typescript doesn't consider { x: number, y: number } to extend { a?: never } - for some reason it requires the 'a' key to be present on the first type for some reason.

to solve your problem, however, requires only a simple modification of the function signature:

function foobar<T>(data: T & Data): void

CodePudding user response:

This is running afoul of the weak type detection introduced in TypeScript 2.4. Since Data's properties are all optional, the compiler considers it a "weak type" and therefore not compatible with any other type unless there's at least one overlapping property. This, like excess property detection for object literals isn't really a type system soundness feature; it's more of a linter rule that stops things that it thinks are probably mistakes.

The release notes for weak type detection say:

Since this is a breaking change, you may need to know about the workarounds which are the same as those for strict object literal checks:

  • Declare the properties if they really do exist.
  • Add an index signature to the weak type (i.e. [propName: string]: {}).
  • Use a type assertion (i.e. opts as Options).

We can use the second suggestion and add a string index signature; since we don't want to restrict any of these other properties, we should probably make the values the any type (since even the type safe unknown would end up excluding interface types without index signatures, which we don't want to do). It could look like this:

interface AnyData extends Data {
  [k: string]: any;
}

function foobar<T extends AnyData>(data: T): void {
  //...
}

And you can verify that things behave as desired:

const obj = { x: 1, y: 2 };
foobar(obj); // okay

const obj2 = { x: 1, y: 2, c: 3 };
foobar(obj2); // error!  Types of property 'c' are incompatible.

interface Baz {
  x: 1,
  y: 2
}
declare const baz: Baz;
foobar(baz); // okay

Playground link to code

  • Related