Repository: TypeScript
Issue: Allow object types to have property-like associated types
Labels: Suggestion,In Discussion

**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](https://github.com/Microsoft/TypeScript/issues/22445).
- 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:

```ts
// 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.