microsoft / microsoft/TypeScript

allow narrowing of types and values outside of a function's parameters

オープン
#43,786 コメント 3 件 リアクション 10 件 担当者 0 名 GitHub で見る

まだ誰も着手していません。

Awaiting More Feedback Suggestion
主要言語
Go
スター
111k
フォーク
14.3k
平均マージ
1日 19時間
マージ済み PR(30日)
117

説明

Suggestion

🔍 Search Terms

narrowing global types assertion function type predicate

✅ Viability Checklist

My suggestion meets these guidelines:

  • This wouldn't be a breaking change in existing TypeScript/JavaScript code
  • This wouldn't change the runtime behavior of existing JavaScript code
  • This could be implemented without emitting different JS based on the types of the expressions
  • This isn't a runtime feature (e.g. library functionality, non-ECMAScript syntax with JavaScript output, new syntax sugar for JS, etc.)
  • This feature would agree with the rest of TypeScript's Design Goals.

⭐ Suggestion

allow assertion functions (and type predicate functions) to:

  • change the type of values outside of the function's parameters
  • extend globally declared interfaces

📃 Motivating Example

it would be very useful for things like prototype extensions, where you have to define the type of the new property twice:

declare global {
    interface String {
        foo: (value: string) => string
    }
}
Object.defineProperty(String.prototype, 'foo', {
  value: (value: string) => value,
  enumerable: false,
})

instead you could define a wrapper function that does both:

export function defineProperty<T, Key extends string | number | symbol, Value>(
  object: T,
  key: Key,
  value: Value
): asserts InstanceType<T> /* this can now be either a value or a type*/ is T & { [K in Key]: Value } {
  Object.defineProperty(object, key, { value, enumerable: false })
}

defineProperty(String.prototype, 'foo', (value: string) => value);

['hi'].foo('value')

💻 Use Cases

for tools like cypress where to add new functions you have to define their signatures twice (see https://docs.cypress.io/api/cypress-api/custom-commands)

declare global {
    interface Chainable {
        foo(bar: number): string
    }
}

Cypress.Commands.add('foo', (bar: number) => "foo")

Instead you'd be able to define both at the same time using an assertion function

//(pseudocode, the actual function signature to make this accurate to how commands work in cypress is like 60 lines long)
function addCommand<Name extends string, Func extends (...args: any[]) => any>(
  name: Name,
  func: Func
): asserts Chainable is Chainable & { [Key in Name]: Func } /* new syntax */ {
  Cypress.Commands.add(name, func)
}

//add the new command
addCommand(cy, 'foo', (bar: number) => "foo")

//use the new command
cy.foo(1)

currently, you can sort of do it like this:

function addCommand<Name extends string, Func extends (...args: any[]) => any>(
  _cy: typeof cy, //need to pass the unused cy object in order for the function to be able to change its type
  name: Name,
  func: Func
): asserts _cy is typeof _cy & { [Key in Name]: Func } {
  Cypress.Commands.add(name, func)
}

problems with the current approach:

  • you have to needlessly pass the global cy object to the function, otherwise its type can't be asserted
  • only the cy Chainable instance is narrowed, instead of the Chainable interface itself, which means you can't use this new method if you're chaining it off a previous command:
    cy.get('foo').foo(1) //error: property 'foo' doesn't exist on Chainable
    
  • the narrowed type is not exportable - see #43772

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

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

はじめの一歩

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

調査の方向性

Start with the prototype-extension and Cypress custom-command examples in the issue, then review the related discussion in #43772. A complete proposal should define how assertion or type-predicate functions affect values and globally declared interfaces, including the syntax and behavior for chained uses such as cy.get('foo').foo(1).

索引モデルが issue の本文から書いたものです。

評価

技術スタック
typescript
領域
compilers
issue の種類
機能追加
難易度
5/5
見積もり時間
1週間以上
活発さ
活発
明瞭さ
おおむね明確
初心者へのやさしさ
35/100

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

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