Search code examples
typescriptsymbolsmapped-types

Why Typescript Omit/Pick erase Symbols


First I'm pretty new to TS. And trying to work on some utilities for my own projects. But when I work on type mapping. I found that Pick/Omit/Exclude and other typing instructions removes fields with symbol keys. as the code here:

    interface T { a: number;[Symbol.iterator](): IterableIterator<number>; }
    type NoA=Omit<T,'a'>;

NoA would be a empty type. But I'am expacting a iterator in it.

Why is this happening? And are there any walk around?

for more info, the related codes in my lib is:

export type Merg<U> = (U extends any ? (k: U) => void : never) extends (k: infer I) => void ? I : never;
export type MergO<U extends object> 
  = (U extends object ? (k: U) => void : never) extends 
    (k: infer I) => void ? (I extends object ? I : object) : object;

export type Alter<T extends object, U extends object> 
  = Pick<T, Exclude<keyof T, keyof U>> & Pick<U, Extract<keyof T, keyof U>>;

export type Extra<T extends object, U extends object> = Pick<T, Exclude<keyof T, keyof U>>;
export type Common<T extends object, U extends object> = Pick<T, Extract<keyof T, keyof U>>;
export type Extend<T extends object, U extends object> = T & Omit<U, keyof T>;
export type Override<T extends object, U extends object> = Omit<T, keyof U> & U;


export type AlterOver<T extends object, U extends object, X extends object> = Alter<T, Extra<U, X>>;
export type ExtendOver<T extends object, U extends object, X extends object> = Extend<T, Extra<U, X>>;
export type OverrideOver<T extends object, U extends object, X extends object> = Override<T, Extra<U, X>>;

export type AlterLike<T extends object, U extends object, X extends object> = Alter<T, Common<U, X>>;
export type ExtendLike<T extends object, U extends object, X extends object> = Extend<T, Common<U, X>>;
export type OverrideLike<T extends object, U extends object, X extends object> = Override<T, Common<U, X>>;

based on @hackape's workaround, a new Exclude by type can be:

export type Exclude2<T extends object, U extends object> = 
U extends { [Symbol.iterator]: any } ? Omit<T, keyof U>:
T extends { [Symbol.iterator]: infer IT } ? { [Symbol.iterator]: IT } & Omit<T, keyof U> : Omit<T, keyof U>;

Solution

  • Like @jcalz mentioned in comment, Symbol.iterator is treated as "well known symbol" and excluded from resolved mapped types. Currently, typescript sees any expression of the form Symbol.whatever as "well known symbol".

    Workaround:

    interface T { b: boolean; a: number; [Symbol.iterator](): IterableIterator<number>; }
    
    type Omit2<T, K extends keyof T> = T extends { [Symbol.iterator]: infer U } ? { [Symbol.iterator]: U } & Omit<T, K> : Omit<T, K>
    type NoA = Omit2<T, 'a'>;