1

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>;
TonyG
  • 1,432
  • 12
  • 31
Guo Lieene
  • 113
  • 1
  • 7
  • It's just well-known symbols that have this issue. See [microsoft/TypeScript#24622](https://github.com/microsoft/TypeScript/issues/24622) (the issue is listed as "fixed" but it's still open; that "fixed" just means that someone has opened a PR to fix it) – jcalz Nov 11 '19 at 12:27

1 Answers1

4

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'>;
hackape
  • 18,643
  • 2
  • 29
  • 57
  • 2
    Nice workaround. I imagine it'd be a big headache to keep applying it every time you manipulate a type that *might* have that symbol key, and supporting the dozen-or-so well-known symbols makes it even worse. I think OP might want to consider going to [#24622](https://github.com/microsoft/TypeScript/issues/24622) and giving it a or expanding upon the use case, if they want to see it actually addressed. – jcalz Nov 11 '19 at 13:05
  • So we can do this `type ItrType = T extends { [Symbol.iterator]: infer U } ? U : never; type numItr=ItrType>;` ... alike @hackape did.Exclude2 and Extract2 can be done similarly. But Pick can nerver work as it uses keyof. – Guo Lieene Nov 12 '19 at 05:03