0
const abc = <T extends object, P extends { [key in keyof T]?: number }>(
  a: T,
  b: P
) => {
  console.log(a, b);
};

const A = { x: "1", y: "2", z: "3" };
const b = { x: 1, y: 2, z: 3 };
const b1 = { x: 1, y: 2 };
const b3 = { m: 5 };
const b4 = { m: 5, x: 1 };

abc(A, b);
abc(A, b1);
abc(A, b3); // Type '{ m: number; }' has no properties in common with type '{ x?: number | undefined; y?: number | undefined; z?: number | undefined; }'
abc(A, b4); // expect type error just like b3 but it is not

since m doesn't exist on A, b4 should error like b3 right? but why it is not error and how to fix it?

here is the sand box codesandbox

Acid Coder
  • 2,047
  • 15
  • 21
  • 1
    Typescript allow extra properties by default, so `{ m: 5 }` is considered valid argument. This post may help you: https://stackoverflow.com/questions/49580725/is-it-possible-to-restrict-typescript-object-to-contain-only-properties-defined – Simon Bruneaud May 04 '20 at 20:00
  • @Simon, thanks for the link, quite informative but the solution in it is not working because not only it need exact member(which I want), it also need also to be exact same type(which I dont want) – Acid Coder May 05 '20 at 04:38

2 Answers2

0

Type '{ m: number; }' has no properties in common with type '{ x?: number | undefined; y?: number | undefined; z?: number | undefined; }.

think its clear, that b4 not error because has either one of x?, y? z? included. thats because you extends from T and T is from the first param which you passed is A.

Sam Yao
  • 31
  • 1
  • 3
0

turn out this is not about generics at all, this should be better way to represent my question:

const abc = <T extends { [index: string]: string }>(
  a: T,
  b: { [key in keyof T]?: number }
) => {
  console.log(a, b);
};

const A = { x: "1", y: "2", z: "3" };
const b4 = { m: 5, x: 1 };

abc(A, b4); // no error, not "expected"
abc(A, { m: 5, x: 1 }); // exact same value as b4 but throw error as "expected"

solution is you need to pass a fresh value (value that is not a variable)

for explanation and other solution(helper type), read this answer and its comments

update, I found the perfect solution:

const abc = <
  T extends { [index: string]: string },
  U extends { [key in keyof T]?: number }
>(
  a: T,
  b: keyof U extends keyof T ? U : never
) => {
  console.log(a, b);
};

const A = { x: "1", y: "2", z: "3" };
const b4 = { x: 1, y: 2, z: 3 };
const b5 = { m: 2, x: 5 };
const b6 = { x: 1, y: 2, z: 3, m: 2 };
const b7 = { x: 1 };

abc(A, b4); // no error, excat member
abc(A, b5); // error, excess member
abc(A, b6); // error, excess member
abc(A, b7); // no error, one of the member exist
abc(A, {}); // no error, since it is optional

// all work as expected

this work regardless of the value is fresh or not

Acid Coder
  • 2,047
  • 15
  • 21