microsoft / microsoft/TypeScript

Allow object types to have property-like associated types

Aperta
#17,588 36 commenti 74 reazioni 0 assegnatari Vedi su GitHub

Nessuno ha ancora preso questa issue.

In Discussion Suggestion
Lingua principale
Go
Stelle
111k
Fork
14.3k
Merge medio
2g 4h
PR unite (30g)
132

Descrizione

Edit: Add static Type variant for classes, make abstractness a little clearer, clarify things.

This has probably already been asked before, but a quick search didn't turn up anything.

This is similar, and partially inspired by, Swift's/Rust's associated types and Scala's abstract types.


Rationale

  • In complex structures like in virtual DOM components/nodes and in graphs, where there are numerous constraints to check, but they would get cumbersome in a hurry to force off onto the user.
  • In dynamic imports, where you never have the module namespace itself until runtime, yet you still want to be able to use types defined within it. Edit: Already addressed elsewhere.
  • In types where there's a lot of optional parameters, it'd be easier and more flexible to label which ones you're defining as part of the type.
  • In types where you want to specify a single optional parameter, and the required ordering requires you to specify some that you don't want to specify, that would get boilerplatey in a hurry. I've experienced this personally on multiple occasions.
  • It would allow fully typing namespaces and named module imports without hard-coded compiler handling.

Proposed Syntax/Semantics

  • Each interface and object type may have a number of associated types within it.
  • Associated types are checked for assignability like any other usual property, except they live in a different "namespace" from properties. In particular, the type must be assignable to the target's type.
  • To access an associated type, you use TypeName.Type, where Type is the name of an associated type.
    • For sugar and namespace compatibility, object.Type is equivalent to (typeof object).Type
  • To expect an interface with an associated type, you use Foo & {type Type: Value} or Foo with <Type: Value>.
  • To expect an object with an associated type, you use {type Type: Value}.
  • To declare an abstract associated type, you use type Type: * within the interface or object type.
  • To declare a non-abstract associated type, you use type Type: Default within the interface or object type.
  • To constrain an abstract associated type, you use type Type: * extends Super.
  • Associated types may be inherited from other interfaces and/or object types.
  • Namespaces' types are modified to include the exported types as associated types.
  • Classes may also define associated types, optionally with visibility modifiers.
  • Associated types may have defaults, in case they aren't defined or further constrained later.
    • Constraining an associated type with an existing default removes the default if and only if the existing constraint is not assignable to the new constraint. For example, {type Foo: string | number = string} & {type Foo: string | number} is assignable to {type Foo: string}, but {type Foo: string | number = string} & {type Foo: number} is not.

Here's what that would look like in syntax:

// Interfaces
interface Foo {
    type Type: *; // abstract
    type Sub: * extends Type; // abstract, constrained
    type Type: Default;
    type Type: * extends Type = Default; // late-bound default
}

// Objects
type Foo = {
    type Type: Foo,
}

// Classes
abstract class Foo {
    // Note: outer class *must* be abstract for these, and the keyword is required.
    abstract type Type: *; // abstract
    private abstract type Sub: * extends Type; // abstract, constrained
    protected abstract type Type: * extends Type = Default, // late-bound default

    // Note: outer class *may* be not abstract for these.
    type Type: Default;
    private type Type: Default;

    // Declare an associated type in the class
    // Note: type must not be abstract.
    static Type: Foo;
}

Emit

This has no effect on the JavaScript emit as it is purely type-level.

Compatibility

  • This is purely additive, making no observably incompatible changes beyond possibly different error messages.
  • This has no impact on any existing JavaScript-related proposal.

Other

  • It may slow down the type checker a little initially when namespaces are unified, but two optimization points are available, which would recover most of the perf hit, if not all:
    • Associated types could be stored in a per-interface type map.
    • The list of associated types could be initially stored as undefined to avoid generating a large number of empty arrays.
  • It should have little effect on editor tooling.

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

L'issue non indica file, test o punti di ingresso, quindi inizia individuando il codice del parser e del type-checker per interfacce, tipi oggetto, classi, namespace e query di tipo. Leggi i test rilevanti del compilatore prima di proporre un'implementazione. Il lavoro è completato quando la sintassi e la semantica proposte per i tipi associati sono implementate, verificate nei casi elencati e si è confermato che non ci sono modifiche all'emissione JavaScript.

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à
Attiva
Chiarezza
Abbastanza chiara
Idoneità per principianti
30/100

Ricevi le nuove issue nella tua casella

Un breve riepilogo di issue GitHub adatte ai principianti.