0

[I want to]


interface T {
    a?: string;
    b?: number;
    c?: boolean;
    d?: object;
    e?: null;
}

type A = {
    a: string;
    b: number;
    q: string; // not exist in type T
}


type B = {
    a: string;
    b: number;
}

type G<U extends T> = {
  data: U;
  creator: () => void;
}

const someFunc = <U>(data: U): G<U> => {
  return {
      data,
      creator: () => { console.log(data ) }
  }
}

const funcA = (data: A) => {
    const something = someFunc<A>(data); // I want to occur error
}

const funcB = (data: B) => {
    const something = someFunc<B>(data); // I want to do well
}

Type 'T' has optional property ( every property ).

One type is subset of type "T" ( is "B" )

Another one is not subset of type "T" ( is "A" - has property "q" )

But, using type "B" is not occured error.

How to do?

Thank you.

hyundeock
  • 445
  • 1
  • 6
  • 15

2 Answers2

2

The reason of no error is what we mean by U extends T, and this means - "I accept any U which has all properties of T". Our T is very loose type, every property is optional, then types with only part of these properties are fully ok, as the original T never said that fields are mandatory. Lets do the type level check to prove my words:

type AextendsT = A extends T ? true : false; // true
type BextendsT = B extends T ? true : false; // true

You can ask - why if A has additional field. Yes it has but also it matches all requirements of T, this additional field doesn't ruin the fact that all properties of T are in A. Also from the practical way of thinking, if we use property lets say a and b, then if object has q, its not a problem, as we don't use this property, so nothing bad can happen.

FYI. Forbidding of additional properties has no real practical sense outside of iteration over keys. But if you are interested in such, here is full solution - Advanced TypeScript Exercises - Answer 7

Maciej Sikora
  • 19,374
  • 4
  • 49
  • 50
2

FYI Typescript allows excessive properties if the object is stored in a variable:

One final way to get around these checks, which might be a bit surprising, is to assign the object to another variable: Since squareOptions won’t undergo excess property checks, the compiler won’t give you an error. https://www.typescriptlang.org/docs/handbook/interfaces.html#excess-property-checks

So even if your function was expecting T it would work:

interface T {
  a?: string;
  b?: number;
  c?: boolean;
  d?: object;
  e?: null;
}

interface A {
  a: string;
  b: number;
  q: string; // not exist in type T
}

const foo: A = {
  a: "a",
  b: 1,
  q: "q",
};

const someFunc = (data: T) => console.log(data);

someFunc(foo);

In your example it's "a little bit more valid", because you are expecting anything that extends T (in this line type G<U extends T> = {), that means anything that respect the interface T and A does.

Other remark: Your function someFunc has no constraints on the type U, so it will accept anything:

const funcC = (data: number) => {
    const something = someFunc<number>(data); // Also valid
}

And to finally answer your question and to be able to forbid extra properties there is a detailed answer here: https://stackoverflow.com/a/57117594/3292234

antoinestv
  • 3,286
  • 2
  • 23
  • 39