microsoft / microsoft/TypeScript

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

Ouverte
#43,786 3 commentaires 10 réactions 0 personnes assignées Voir sur GitHub

Personne n'a encore pris cette issue.

Awaiting More Feedback Suggestion
Langage dominant
Go
Étoiles
111k
Forks
14.4k
Merge moyen
1 j 19 h
PR mergées (30 j)
117

Description

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

Guide de contribution

Ouvrir le guide de contribution

Par où commencer

  1. Lisez l'issue en entier, puis le guide de contribution du projet.
  2. Signalez en commentaire que vous la prenez — cela évite que deux personnes fassent le même travail.
  3. Forkez le dépôt et travaillez sur une branche.
  4. Ouvrez une pull request qui référence le numéro de l'issue.

Piste de recherche

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).

Rédigé par le modèle d'indexation à partir du texte de l'issue.

Évaluation

Stack technique
typescript
Domaine
compilers
Type d'issue
Fonctionnalité
Difficulté
5/5
Temps estimé
Plus d'une semaine
Activité
Active
Clarté
Plutôt claire
Accessibilité débutants
35/100

Recevez les nouvelles issues par e-mail

Un résumé court des issues GitHub adaptées aux débutants.