From bffcb69a95099e8dc896ab23877c561f87271af9 Mon Sep 17 00:00:00 2001 From: SAY-5 Date: Sat, 11 Apr 2026 18:44:51 -0700 Subject: [PATCH] feat: HKT prototype - parser support for > syntax Phase 1 of Higher-Kinded Types (#1213): - Parser: recognize > in type parameter positions - Types: isHigherKinded/hktParameters on TypeParameterDeclaration - RFC: design doc with phased plan - Tests: Functor, Monad, Bifunctor syntax cases --- rfcs/higher-kinded-types.md | 233 ++++++++++++++++++ src/compiler/parser.ts | 23 ++ src/compiler/types.ts | 13 + tests/cases/compiler/higherKindedTypes.ts | 94 +++++++ .../compiler/higherKindedTypesBasicParsing.ts | 58 +++++ 5 files changed, 421 insertions(+) create mode 100644 rfcs/higher-kinded-types.md create mode 100644 tests/cases/compiler/higherKindedTypes.ts create mode 100644 tests/cases/compiler/higherKindedTypesBasicParsing.ts diff --git a/rfcs/higher-kinded-types.md b/rfcs/higher-kinded-types.md new file mode 100644 index 0000000000000..f65d5b10cdca4 --- /dev/null +++ b/rfcs/higher-kinded-types.md @@ -0,0 +1,233 @@ +# RFC: Higher-Kinded Types for TypeScript + +**Issue:** [#1213](https://github.com/microsoft/TypeScript/issues/1213) (4000+ reactions) +**Status:** Draft / Prototype +**Author:** TypeScript Community Contribution +**Date:** 2026-04-11 + +--- + +## Summary + +This RFC proposes adding Higher-Kinded Types (HKTs) to TypeScript, enabling type +constructors to be passed as type parameters. This is the most requested TypeScript +feature by a wide margin and would unlock powerful abstractions for generic +programming, including Functor, Monad, and Traversable patterns. + +## Motivation + +Currently, TypeScript has no way to abstract over type constructors. You cannot write: + +```ts +// DESIRED: A generic "map" that works over any container type +type Mappable> = { + map(fa: F, f: (a: A) => B): F; +}; +``` + +Instead, developers must resort to workarounds: +- Module augmentation (fp-ts approach) +- URI-based type-level dictionaries +- Conditional type encoding (HKT emulation) + +All workarounds have significant ergonomic and type-safety drawbacks. + +## Proposed Syntax + +### Type Parameter Declaration with Kind Annotation + +A type parameter that itself accepts type parameters uses the `>` syntax: + +```ts +// Single-parameter type constructor +type Functor> = { + map(fa: F, f: (a: A) => B): F; +}; + +// Multi-parameter type constructor +type Bifunctor> = { + bimap(fab: F, f: (a: A) => C, g: (b: B) => D): F; +}; + +// Named HKT parameters for clarity +type Transform> = { + apply(input: F): F; +}; +``` + +### Usage at Call Sites + +```ts +// Implementing Functor for Array +const ArrayFunctor: Functor = { + map: (fa, f) => fa.map(f), +}; + +// Implementing Functor for Promise +const PromiseFunctor: Functor = { + map: (fa, f) => fa.then(f), +}; + +// Implementing Functor for a custom type +type Maybe = T | null; +const MaybeFunctor: Functor = { + map: (fa, f) => fa === null ? null : f(fa), +}; +``` + +### Constraints on Higher-Kinded Parameters + +```ts +// F must be a type constructor that produces something with a .length +type Sized> = { + size(fa: F): number; +}; + +// F must be a Functor to be a Monad +type Monad> = Functor & { + of(a: A): F; + flatMap(fa: F, f: (a: A) => F): F; +}; +``` + +## Design Details + +### Kind System + +We introduce a simple kind system: + +| Kind | Meaning | Example | +|------|---------|---------| +| `*` | A concrete type | `number`, `string`, `Array` | +| `* -> *` | A type constructor taking one type | `Array`, `Promise`, `Set` | +| `* -> * -> *` | A type constructor taking two types | `Map`, `Either` | +| `(* -> *) -> *` | A type constructor taking a type constructor | `Fix`, `Free` | + +In our syntax: +- `` declares a parameter of kind `*` +- `>` declares a parameter of kind `* -> *` +- `>` declares a parameter of kind `* -> * -> *` + +### Parser Changes + +The parser is modified to recognize when a type parameter itself has type +parameters. When parsing `>`, the parser: + +1. Parses `F` as the type parameter name (Identifier) +2. Detects `<` immediately after the identifier +3. Speculatively parses inner type parameters (`_` or named identifiers) +4. Sets `isHigherKinded = true` on the `TypeParameterDeclaration` node +5. Stores the inner parameters as `hktParameters` + +Key implementation points: +- We reuse the existing `TypeParameterDeclaration` node kind rather than adding + a new `SyntaxKind`, minimizing changes across the codebase +- The `isHigherKinded` and `hktParameters` properties are `@internal` +- `_` is treated as a valid identifier (placeholder) for unnamed HKT parameters + +### Type Checker Changes (Future) + +The checker needs these additions: + +1. **Kind checking**: When a type argument is provided for an HKT parameter, verify + it has the correct number of type parameters. E.g., `Functor` is valid + (Array has 1 type param), `Functor` is an error (Map has 2 type params). + +2. **Application**: When `F` appears in the body where `F` is an HKT parameter, + the checker must produce the applied type. If `F = Array`, then `F` = `Array`. + +3. **Inference**: The checker should infer HKT arguments from usage. Given + `map(promise, f)` where `map` expects `Functor`, infer `F = Promise`. + +4. **Partial application**: Support `F` to partially apply a multi-parameter + type constructor, producing a type constructor of lower kind. + +### Compatibility + +- **Backward compatible**: All existing code continues to work. The `>` syntax + currently produces a parse error, so no existing code uses it. +- **Emit**: HKTs are erased at runtime, like all TypeScript types. +- **Declaration files**: `.d.ts` output must preserve HKT syntax. + +## Implementation Phases + +### Phase 1: Parser Support (This PR) +- [x] Parse `>` syntax in type parameter positions +- [x] Attach `isHigherKinded` and `hktParameters` to `TypeParameterDeclaration` +- [x] Add test cases for parsing +- [ ] Update `emitter.ts` to emit HKT syntax in declarations + +### Phase 2: Basic Type Checking +- [ ] Kind checking for HKT arguments +- [ ] Type application: resolving `F` when `F` is an HKT parameter +- [ ] Error messages for kind mismatches + +### Phase 3: Inference and Advanced Features +- [ ] Infer HKT arguments from usage +- [ ] Partial application of multi-parameter type constructors +- [ ] Constraints on HKT parameters (`F<_> extends Iterable<_>`) +- [ ] Higher-order HKTs (`>>`) + +### Phase 4: Ecosystem Integration +- [ ] Language service support (completions, quick info) +- [ ] Declaration emit +- [ ] API documentation +- [ ] Lib updates (built-in Functor, Monad, etc.) + +## Alternatives Considered + +### 1. Type-Level `typeof` for Generics +```ts +type Functor> = { ... } +``` +Rejected: Requires a new `Generic` built-in and doesn't compose well. + +### 2. Module-Level Type Lambdas +```ts +type Functor = { ... } +``` +This is the fp-ts/Effect approach. It works but is extremely verbose and requires +manual registration of every type constructor. + +### 3. `~` Syntax for Kind Annotation +```ts +type Functor *)> = { ... } +``` +Rejected: Introduces new syntax that doesn't feel like TypeScript. + +### 4. `extends` with Application Syntax +```ts +type Functor => Type> = { ... } +``` +Rejected: Conflicts with arrow function syntax in type positions. + +## Impact on Existing Libraries + +- **fp-ts / Effect**: Could replace the URI-based HKT emulation with native HKTs +- **io-ts**: Codec type constructors become first-class +- **rxjs**: `Observable` can be treated as a proper Functor +- **Prisma / Drizzle**: Type-safe query builders benefit from type constructor abstraction + +## Open Questions + +1. **Should `_` be reserved as a type parameter name?** Currently `_` is a valid + identifier. We may want to treat it specially only in HKT positions. + +2. **How should partial application work?** Should `Map` produce a + `* -> *` type constructor? This requires type-level currying. + +3. **Should we support kind annotations explicitly?** E.g., ` *>` for + cases where the arity cannot be inferred from usage. + +4. **How does this interact with conditional types and mapped types?** + `F extends F ? ... : ...` when F is an HKT parameter needs careful design. + +5. **Variance annotations on HKT parameters?** Should `>` be valid? + +## References + +- [TypeScript #1213](https://github.com/microsoft/TypeScript/issues/1213) - Original issue +- [Haskell Kind System](https://wiki.haskell.org/Kind) - Inspiration +- [Scala 3 Type Lambdas](https://docs.scala-lang.org/scala3/reference/new-types/type-lambdas.html) +- [fp-ts HKT](https://gcanti.github.io/fp-ts/modules/HKT.ts.html) - Current workaround +- [Effect HKT](https://effect.website/) - Modern workaround diff --git a/src/compiler/parser.ts b/src/compiler/parser.ts index 68133ac5f1e40..9d787e97edafb 100644 --- a/src/compiler/parser.ts +++ b/src/compiler/parser.ts @@ -3956,6 +3956,24 @@ namespace Parser { const pos = getNodePos(); const modifiers = parseModifiers(/*allowDecorators*/ false, /*permitConstAsModifier*/ true); const name = parseIdentifier(); + + // Higher-kinded type parameter support (experimental): + // Detect `F<_>` or `F<_, _>` syntax where a type parameter itself takes type parameters. + // This parses patterns like `>`, `>` in type parameter lists. + let isHigherKinded = false; + let hktParameters: NodeArray | undefined; + if (token() === SyntaxKind.LessThanToken) { + // Speculatively parse as HKT parameters: F<_> or F + const savedPos = getNodePos(); + hktParameters = parseBracketedList(ParsingContext.TypeParameters, parseTypeParameter, SyntaxKind.LessThanToken, SyntaxKind.GreaterThanToken); + if (hktParameters && hktParameters.length > 0) { + isHigherKinded = true; + } + else { + hktParameters = undefined; + } + } + let constraint: TypeNode | undefined; let expression: Expression | undefined; if (parseOptional(SyntaxKind.ExtendsKeyword)) { @@ -3981,6 +3999,11 @@ namespace Parser { const defaultType = parseOptional(SyntaxKind.EqualsToken) ? parseType() : undefined; const node = factory.createTypeParameterDeclaration(modifiers, name, constraint, defaultType); node.expression = expression; + // Attach HKT metadata if this is a higher-kinded type parameter + if (isHigherKinded) { + (node as any).isHigherKinded = true; + (node as any).hktParameters = hktParameters; + } return finishNode(node, pos); } diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 4d8d22afb54e6..2b9c591fc6177 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -1835,6 +1835,14 @@ export interface TypeParameterDeclaration extends NamedDeclaration, JSDocContain // For error recovery purposes (see `isGrammarError` in utilities.ts). expression?: Expression; + + // Higher-kinded type support (experimental) + /** When true, this type parameter is higher-kinded: it accepts type parameters itself, e.g. `F<_>` */ + /** @internal */ + readonly isHigherKinded?: boolean; + /** The type parameters of this higher-kinded type parameter, e.g. the `_` in `F<_>` */ + /** @internal */ + readonly hktParameters?: NodeArray; } export interface SignatureDeclarationBase extends NamedDeclaration, JSDocContainer { @@ -6878,6 +6886,11 @@ export interface TypeParameter extends InstantiableType { isThisType?: boolean; /** @internal */ resolvedDefaultType?: Type; + // Higher-kinded type support (experimental) + /** @internal */ + isHigherKinded?: boolean; + /** @internal */ + hktParameters?: TypeParameter[]; } /** @internal */ diff --git a/tests/cases/compiler/higherKindedTypes.ts b/tests/cases/compiler/higherKindedTypes.ts new file mode 100644 index 0000000000000..b8aa9d7e27135 --- /dev/null +++ b/tests/cases/compiler/higherKindedTypes.ts @@ -0,0 +1,94 @@ +// @strict: true +// @noEmit: true + +// ============================================================================= +// Higher-Kinded Types - Parser Test Cases +// ============================================================================= +// These test cases demonstrate the desired syntax for HKTs in TypeScript. +// Phase 1 focuses on parser support; type checking is deferred to Phase 2. + +// --------------------------------------------------------------------------- +// 1. Basic HKT syntax: single-parameter type constructor +// --------------------------------------------------------------------------- + +// A Functor abstracts over any single-parameter type constructor F +type Functor> = { + map(fa: F, f: (a: A) => B): F; +}; + +// --------------------------------------------------------------------------- +// 2. Multi-parameter type constructor +// --------------------------------------------------------------------------- + +// A Bifunctor abstracts over a two-parameter type constructor +type Bifunctor> = { + bimap(fab: F, f: (a: A) => C, g: (b: B) => D): F; +}; + +// --------------------------------------------------------------------------- +// 3. Named HKT parameters (equivalent to `_` but more readable) +// --------------------------------------------------------------------------- + +type Transform> = { + apply(input: F): F; +}; + +// --------------------------------------------------------------------------- +// 4. HKT with constraints via extends +// --------------------------------------------------------------------------- + +type Monad> = Functor & { + of(a: A): F; + flatMap(fa: F, f: (a: A) => F): F; +}; + +// --------------------------------------------------------------------------- +// 5. Function using HKT parameter +// --------------------------------------------------------------------------- + +declare function lift>(functor: Functor): (f: (a: A) => B) => (fa: F) => F; + +// --------------------------------------------------------------------------- +// 6. Interface with HKT +// --------------------------------------------------------------------------- + +interface Traversable> { + traverse, A, B>(ta: T, f: (a: A) => F): F>; +} + +// --------------------------------------------------------------------------- +// 7. Concrete instantiation (Phase 2 will type-check these) +// --------------------------------------------------------------------------- + +// These demonstrate the intended usage at call sites: +// type ArrayFunctor = Functor; +// type PromiseFunctor = Functor; +// type SetFunctor = Functor; + +// --------------------------------------------------------------------------- +// 8. Mixing regular and HKT type parameters +// --------------------------------------------------------------------------- + +type Apply, A> = F; + +type MapOver, Tuple extends readonly unknown[]> = { + [K in keyof Tuple]: F; +}; + +// --------------------------------------------------------------------------- +// 9. Natural transformation (function between type constructors) +// --------------------------------------------------------------------------- + +type NaturalTransformation, G<_>> = (fa: F) => G; + +// A natural transformation from Array to Set (Phase 2): +// type ArrayToSet = NaturalTransformation; + +// --------------------------------------------------------------------------- +// 10. Higher-order: type constructor that takes a type constructor +// --------------------------------------------------------------------------- + +type Fix> = F>; + +// Free monad construction +type Free, A> = A | { tag: "roll"; value: F> }; diff --git a/tests/cases/compiler/higherKindedTypesBasicParsing.ts b/tests/cases/compiler/higherKindedTypesBasicParsing.ts new file mode 100644 index 0000000000000..d4775d94e8921 --- /dev/null +++ b/tests/cases/compiler/higherKindedTypesBasicParsing.ts @@ -0,0 +1,58 @@ +// @strict: true +// @noEmit: true + +// ============================================================================= +// Higher-Kinded Types - Basic Parsing Verification +// ============================================================================= +// These tests verify that the parser correctly handles HKT syntax without errors. +// The focus is on syntactic acceptance, not semantic correctness. + +// Single placeholder parameter +type HKT1> = {}; + +// Multiple placeholder parameters +type HKT2> = {}; +type HKT3> = {}; + +// Named parameters +type HKT4> = {}; +type HKT5> = {}; + +// Mixed HKT and regular type parameters +type HKT6, T> = {}; +type HKT7> = {}; +type HKT8, B> = {}; + +// Multiple HKT parameters +type HKT9, G<_>> = {}; +type HKT10, G<_>> = {}; + +// HKT parameter used in body +type HKT11> = F; +type HKT12, T> = F; + +// HKT in function signatures +declare function fn1>(x: F): F; +declare function fn2, G<_>>(x: F): G; +declare function fn3, T>(x: F): F; + +// HKT in interface declarations +interface I1> { + value: F; + transform(fa: F, f: (a: A) => B): F; +} + +// HKT in class declarations (for future support) +// class C1> { +// value!: F; +// } + +// HKT with constraints +type WithConstraint extends Iterable> = { + iterate(fa: F): Iterator; +}; + +// Nested usage +type Compose, G<_>> = { + composed(x: G): F>; +};