microsoft / microsoft/TypeScript

`Omit` helper loses type information when used with extended Records.

オープン
#36,981 コメント 8 件 リアクション 27 件 担当者 1 名 GitHub で見る

@weswigham がすでに取り組んでいます。

2020年3月10日 から。

Needs Investigation Rescheduled
主要言語
Go
スター
111k
フォーク
14.3k
平均マージ
2日 4時間
マージ済み PR(30日)
132

説明

TypeScript Version: 3.8 & Nightly

Search Terms:

  • Omit
  • Record
  • Any
  • Loses type information

Expected behavior:

I expect the following types to be as described in the inline comments.

type AnyRecord = Record<string, any>;
interface ExtendsAny extends AnyRecord {
   myKey1: string;
   myKey2: string;
}
// ExtendsAny['myKey1'] === string
// ExtendsAny['otherKey'] === any

type OmitsKey = Omit<ExtendsAny, "myKey2">;
// OmitsKey['myKey'] === string
// OmitsKey['otherKey'] === any

Please note that this works without the omit, or without the record, but not when both are used together.

Actual behavior:

What actually happens is that after using omit, all keys become "any" and any information for code completion, etc that the specifically defined keys even exist, are removed, even thought they were working in the original extension.

type AnyRecord = Record<string, any>;
interface ExtendsAny extends AnyRecord {
   myKey1: string;
   myKey2: string;
}
// ExtendsAny['myKey1'] === string
// ExtendsAny['otherKey'] === any

type OmitsKey = Omit<ExtendsAny, "myKey2">;
// 🚨Problem here 🚨
// OmitsKey['myKey1'] === any
// OmitsKey['otherKey'] === any


const instance: OmitsKey = {
    myKey1: "test",
}
// 🚨 When typing this no autocomplete is provided for myKey1 🚨
instance.myKey1.

Related Issues:

Potential Workaround

In the meantime, I am redefining the base interface (very large) because it comes from a library without the record type mixed in. I do my omit, then add it back in afterwards.

Code

/**
 * @copyright 2020 Adam (charrondev) Charron
 * @license MIT
 */

export interface IBase<T = number> {
    baseProp: T;
    baseProp2: T;
    baseFunction: () => T;
}

const base: IBase<number> = {
    baseProp: 0,
    baseProp2: 0,
    baseFunction: () => 0,
};

// number
base.baseProp;

///
/// Without the omit
///
type AnyRecord = Record<string, any>;

interface IExtend<T> extends IBase<T>, AnyRecord {
    extendProp: T;
}

const extended: IExtend<number> = {
    baseProp: 0,
    baseProp2: 0,
    baseFunction: () => 0,
    extendProp: 0,
};

// number
extended.baseProp2;

///
/// With an Omit
///
interface IOmittedInterface extends Omit<IExtend<number>, "baseProp"> {}

const omitted: IOmittedInterface = {
    baseProp2: 0,
    baseFunction: () => 0,
};

// any
omitted.baseProp2;

Output
/**
 * @copyright 2020 Adam (charrondev) Charron
 * @license MIT
 */
const base = {
    baseProp: 0,
    baseProp2: 0,
    baseFunction: () => 0,
};
// number
base.baseProp;
const extended = {
    baseProp: 0,
    baseProp2: 0,
    baseFunction: () => 0,
    extendProp: 0,
};
// number
extended.baseProp2;
const omitted = {
    baseProp2: 0,
    baseFunction: () => 0,
};
// any
omitted.baseProp2;

Compiler Options
{
  "compilerOptions": {
    "noImplicitAny": true,
    "strictNullChecks": true,
    "strictFunctionTypes": true,
    "strictPropertyInitialization": true,
    "strictBindCallApply": true,
    "noImplicitThis": true,
    "noImplicitReturns": true,
    "useDefineForClassFields": false,
    "alwaysStrict": true,
    "allowUnreachableCode": false,
    "allowUnusedLabels": false,
    "downlevelIteration": false,
    "noEmitHelpers": false,
    "noLib": false,
    "noStrictGenericChecks": false,
    "noUnusedLocals": false,
    "noUnusedParameters": false,
    "esModuleInterop": true,
    "preserveConstEnums": false,
    "removeComments": false,
    "skipLibCheck": false,
    "checkJs": false,
    "allowJs": false,
    "declaration": true,
    "experimentalDecorators": false,
    "emitDecoratorMetadata": false,
    "target": "ES2017",
    "module": "ESNext"
  }
}

Playground Link: Provided

コントリビューションガイド

コントリビューションガイドを開く

はじめの一歩

  1. issue を最後まで読み、次にプロジェクトのコントリビューションガイドを読みます。
  2. 着手することを issue にコメントします — 二人が同じ作業をするのを防げます。
  3. リポジトリをフォークし、ブランチを切って変更します。
  4. issue 番号を参照したプルリクエストを送ります。

評価

この issue はまだ評価されていません。

新しい issue をメールで受け取る

初心者向けの GitHub issue を短くまとめたダイジェスト。