microsoft / microsoft/TypeScript

Proposal: `decoratedWith` type operator and generic 'value' parameters

Aperta
#56,124 6 commenti 0 reazioni 0 assegnatari Vedi su GitHub

Nessuno ha ancora preso questa issue.

Awaiting More Feedback Suggestion
Lingua principale
Go
Stelle
111k
Fork
14.3k
Merge medio
1g 19h
PR unite (30g)
117

Descrizione

🔍 Search Terms

Decorators
TypeScript 5

✅ Viability Checklist
  • 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 our Design Goals: https://github.com/Microsoft/TypeScript/wiki/TypeScript-Design-Goals
⭐ Suggestion

WIP: Goal is to get this to canonical ASAP, all help appreciated.

This is a proposal introduces two things a type operator named: decoratoredWith and generic 'value' parameters.

First off, the decoratedWith type operator.
This type operator would check if a method, property/field, getter, setter, auto-accessor or class is decorated with a specific decorator function. (I think it shouldn't support legacy decorators)

To illustrate:

// Decorator functions
function EntityOnly<C, T>(_target: undefined, context: ClassFieldDecoratorContext<C, T>) {
  // Does something
}

function SchemaProperty<C, T>(schema: any) {
  return function(_target: undefined, context: ClassFieldDecoratorContext<C, T>) {
    // Does something
  }
}

// Class with some fields decorated
class User {
  id: number;
  @SchemaProperty({ type: string, })
  username: string;
  @SchemaProperty({ type: string, })
  displayName: string;
  @EntityOnly
  hashedPasssword: any;
  @EntityOnly
  IBAN: string;
}

// A simple example of the decoratedWith type operator albeit this is not where this operator would shine.
type IsEntityOnly = User['hashedPassword'] decoratedWith EntityOnly ? true : false;

// Should also work with functions returning decorators.
type IsEntityOnly = User['username'] decoratedWith SchemaProperty ? true : false;

Should work the same with methods, properties/fields, getters, setters, auto-accessors and classes too.

Revision 1: Generic 'value' parameters

Disclaimer: I understand that the following might be such a substantial addition to the proposal that it should be a separate proposal but I am looking for feedback first before adding yet another proposal. Perhaps this part is already covered in another proposal, if so, great, if not then perhaps there is a reason this hasn't been proposed yet.

My initial proposal featured some code using the decoratedWith proposal with a generic type which wasn't really a type but rather a value pointing to a decorator function. (as pointed out by @jcalz) And although this is currently not possible in TypeScript I think it could be very beneficial to be able to generalize decorator functions as values. Perhaps these "generic 'value' parameters should be supported by their own group of 'value' operators which would refer to a value rather than a type. These values must be constant in nature, so would be limited to functions, const variables, enums, classes, etc. Naturally they cannot refer to types. Here's the list of 'value' operators:

  • valueOf - The one 'value' operator to rule them all. This 'value' operator accepts any value constant in nature.
  • funcOf - This 'value' operator accepts functions.
  • constOf - This 'value' operator refers to const variables.
  • enumOf - This 'value' operator refers to enums.
  • classOf- This 'value' operator refers to classes.

These new operators could streamline some processes like referring to an actual class rather than just it's constructor.
Let me start by illustrating some problems that generic 'value' operators could solve on their own:

The first problem would be that you can't enforce (at least not in a simple way as far as my knowledge goes, any examples that contradict this are appreciated as I could use that to enhance my proposal) a generic type parameter to be a class. The problem illustrated:

type ClassType<T> = new () => T;

// This user class takes a generic type parameter.
// The constructor has a parameter which requires a value with a constructor returning type T.
// Yet you cannot demand the generic parameter to be a class.
class User<T> {
  private _classType: ClassType<T>;
  constructor(classType: ClassType<T>) {
    this._classType = classType;
  }
}

interface Entity {

}
class Item implements Entity {
}
// As shown here, generic parameter T can be an interface while you might to enforce the type being a class.
new User<Entity>(Item);

What my proposal would do is, it would enable users to do the following:

class User<classOf T> {
  private _classType = T;
}

interface Entity {
}
class Item extends Entity {
}
// This shouldn't work because Entity is not a class.
new User<Entity>();
// This should work because Item is a class.
new User<Item>();

Here the 'value' operator classOf is used to denote a generic 'value' parameter (limited to class values). Generic 'value' parameters accept values rather than types and can be used throughout a class or function as value. The User class as described in the problem would translate to a class similar as that of the User class described in the previously illustrated problem which the classOf operator should solve. The only difference being that the classOf operator shortens the code and could restrict the generic parameter to actual classes ,

The other 'value' operators operate the same way where valueOf is the swiss army knife of the bunch which accepts every value accepted by all the other 'value' operators.

Generic 'value' parameters powered by 'value' operators can enable you to use constant values in type declarations.
To take my proposed decoratedWith for example:

// Together with the 'value' operators and generic 'value' parameters this is where `decoratedWith` could truly shine.
// Here it only returns the key names of the members of generic parameter T that are decorated with the decorator function provided in the generic 'value' parameter DT which must be a function.
type DecoratedMembers<T, funcOf DT> = keyof { [P in keyof T as T[P] decoratedWith DT ? P : never]: any; };

// This variable would have the union type: "hashedPassword" | "IBAN"
const entityOnlyMembers: DecoratedMembers<User, EntityOnly>;

// This variable would have the union type: "username" | "displayName"
const entityOnlyMembers: DecoratedMembers<User, SchemaProperty>;

This is all theoretically possible within TypeScript without requiring any change in the JavaScript spec. I am open for feedback. Also I am no longer sure if this is indeed not a breaking change (as it adds keywords), so if anyone could comment on that, that be great too.

📃 Motivating Example

You can now work with types that are computed based on the fact whether methods, properties/fields, getters, setters, auto-accessors or classes include certain decorators or not.

💻 Use Cases
  1. What do you want to use this for?

To be able to act on the inclusion or exclusion of decorators on methods, properties/fields, getters, setters auto-accessors and classes.

  1. What shortcomings exist with current approaches?

There are not really ways to handle such cases right now.

  1. What workarounds are you using in the meantime?

There aren't really any workarounds right now as far as my knowledge goes.

Guida per i contributori

Apri la guida per i contributori

Come iniziare

  1. Leggi tutta la issue e poi la guida ai contributi del progetto.
  2. Commenta sulla issue per dire che te ne occupi tu — evita che due persone facciano lo stesso lavoro.
  3. Fai un fork del repository e lavora su un branch.
  4. Apri una pull request che faccia riferimento al numero della issue.

Direzione di ricerca

La issue contiene una proposta ampia per un operatore di tipo decoratedWith e parametri di valore generici, ma non indica file di implementazione, punti di ingresso o test. Inizia leggendo l’architettura del type-checker e del parser di TypeScript, quindi determina se la proposta può essere circoscritta a una funzionalità testabile in modo indipendente. Per considerare il lavoro completato servirebbero un design concordato, un’implementazione e test del compilatore che coprano la sintassi e il comportamento dei tipi proposti.

Scritto dal modello di indicizzazione a partire dal testo della issue.

Valutazione

Stack tecnologico
typescript
Ambito
compilers
Tipo di issue
Funzionalità
Difficoltà
5/5
Tempo stimato
Più di una settimana
Stato di attività
Ferma
Chiarezza
Da chiarire
Idoneità per principianti
20/100

Ricevi le nuove issue nella tua casella

Un breve riepilogo di issue GitHub adatte ai principianti.