Repository navigation
Allow type parameters in base class expressions #26542
Description
Activity
mattmccutchen commented
on Aug 20, 2018 ContributorMore actionsThis alternative is working for me (playground):type Constructor<T = {}> = new (...args: any[]) => T; class Point { constructor( public readonly x: number, public readonly y: number, ) {} } const Tagged = (Base: Constructor) => class <T> extends Base { getTag(): T { // Just imagine I have a function that returns objects, // and I want to case them to T. return {} as T } }; class UsesTheTag<T> extends Tagged(Point)<T> { useTag(): T { const tag: T = this.getTag() return tag } }
- addedQuestionAn issue which isn't directly actionable in codeAn issue which isn't directly actionable in code
on Aug 20, 2018 RyanCavanaugh commented
on Aug 20, 2018 MemberMore actionsSee #26154 - trying to use an instance type parameter in a base class expression is a conceptual error
mattmccutchen commented
on Aug 20, 2018 ContributorMore actionsOops, my code above doesn't actually work because
UsesTheTag<T>isn't seen as a subtype ofPoint. Never mind. This is what I'd like to write, but it gives an error:type Constructor<T = {}> = new (...args: any[]) => T; class Point { constructor( public readonly x: number, public readonly y: number, ) {} } const Tagged = <B extends Constructor>(Base: B) => // Error: A mixin class must have a constructor // with a single rest parameter of type 'any[]'. class <T> extends Base { getTag(): T { // Just imagine I have a function that returns objects, // and I want to case them to T. return {} as T } }; class UsesTheTag<T> extends Tagged(Point) { useTag(): T { const tag: T = this.getTag() console.log(this.x); return tag } }
Why aren't mixin classes allowed to have type parameters?
thejohnfreeman commented
on Aug 20, 2018 AuthorMore actionsSomething else that confuses me, why does this first declaration work but not the second? Why that error at that source location?
const Tagged = (Base: Constructor) => class <T> extends Base { getTag(): T { // Just imagine I have a function that returns objects, // and I want to case them to T. return {} as T } } const Tagged = <B extends Constructor> (Base: B) => class <T> extends Base { //~~~~~ // error: A mixin class must have a constructor with a single rest parameter of type 'any[]'. getTag(): T { // Just imagine I have a function that returns objects, // and I want to case them to T. return {} as T } }
mattmccutchen commented
on Aug 20, 2018 ContributorMore actionsI think I answered my own question: the original superclass could be generic, and any type arguments in the extends clause are passed to the original superclass, not to the mixin. The mixin is not allowed to have type parameters because there would be no way to pass type arguments. Clearly someone thought about this but didn't see fit to write any documentation.
☹️ Hopefully this thread will serve as documentation for the next person to search for the error message.type Constructor<T = {}> = new (...args: any[]) => T; class Point<U> { constructor( public readonly x: U, public readonly y: U, ) {} } const Tagged = <B extends Constructor>(Base: B) => class extends Base { getTag(): string { return {} as string } }; class UsesTheTag<U> extends Tagged(Point)<U> { useTag(): string { const tag: string = this.getTag() console.log(this.x); return tag } }
So if we want a derived class that passes a type argument to a mixin, we'll have to go with this approach.
Reacted by Beth Hitch and codeconfessionsthejohnfreeman commented
on Aug 20, 2018 AuthorMore actionsI think we're getting off track. Here is the fixed version of what you gave earlier, in which I can use the facilities of both
TaggedandPointon the final type. The only piece you forgot was to passPointas the type argument toConstructor(playground).type Constructor<T = {}> = new (...args: any[]) => T; class Point { constructor( public readonly x: number, public readonly y: number, ) {} } const Tagged = (Base: Constructor<Point>) => class <T> extends Base { getTag(): T { // I can use the facilities of the base class // (in this case, Point) because I declared that `Base` // is a `Constructor` of that class. const x: number = this.x // Constructions of my class assert the type that I return, // so that they get type safety when working with the // objects I return. return { tag: 'it' } as any as T } }; class UsesTheTag<T> extends Tagged(Point)<T> { useTag(): T { // I can use the facilities of the base class (Tagged) // and *its* base class (Point). const tag: T = this.getTag() const x: number = this.x return tag } } // When I construct an object, I declare the type that its // `getTag` and `useTag` methods return. const tp = new UsesTheTag<{ tag: string }>(1, 2) // When I call `getTag` or `useTag`, TypeScript checks that // I use the return value in accordance with the tag type // I declared. const tag = tp.getTag() console.log(tag.tag) const sameTag = tp.useTag() console.log(tag.tag) // `tp` should also be a Point. These should work, just as they // did within the implementation of `getTag` and `useTag`. const x: number = tp.x const y: number = tp.y
I'm going to consider my original question answered. Thanks for the help! In this case, a type parameter in a base class expression is not what I wanted; I wanted a type parameter in the class expression returned by the mixin.
There is a follow-up question that I think brushes up against the higher-kinded types limitation that has been documented elsewhere. I haven't seen a well put-together example, though, so I'm going to take this opportunity to try to contribute my own (playground).
type Constructor<T = {}> = new (...args: any[]) => T; // The base class. class Point { constructor ( public readonly x: number, public readonly y: number, ) {} } // An extended base class with additional facilities. class WeightedPoint extends Point { public readonly weight: number = 1 } // My mixin just needs the facilities of the base class. const SummedSquared = (Base: Constructor<Point>) => class extends Base { getSumOfSquares(): number { return this.x * this.x + this.y * this.x } }; // My final subtype expects the extended base class // because it needs those additional facilities, // but it also wants the mixin applied. class SummedSquaredWeightedPoint extends SummedSquared(WeightedPoint) { getWeightedSumOfSquares(): number { // I want to use the facilities of the base class of // the base class (`WeightPointed`), but TypeScript only // knows that the base class is a `Point` because that is // what the mixin declared. return ( this.x * this.x * this.weight + this.y * this.y * this.weight // ~~~~~~ // error: Property 'weight' does not exist on type 'SummedSquaredWeightedPoint'. ) } }
John Freeman (@thejohnfreeman) This playground solves your follow-up question.
Before:
const SummedSquared = (Base: Constructor<Point>) => class extends Base { getSumOfSquares(): number { return this.x * this.x + this.y * this.x } };
After:
const SummedSquared = <T extends Constructor<Point>>(Base: T) => class extends Base { getSumOfSquares(): number { return this.x * this.x + this.y * this.x } };
Pulls the types quite nicely:
i have the same question
import * as mongoose from 'mongoose'; export declare type InstanceType<T> = T & mongoose.Document; export declare type ModelType<T, typeofT> = mongoose.Model<InstanceType<T>> & typeofT; declare var _Model1: <T>(t?: T) => mongoose.Model<InstanceType<T>>; export declare class Model<T, DocOmit={}> extends _Model1() { createdAt?: Date; updatedAt?: Date; } type FooModelType = ModelType<Foo, typeof Foo>; class Foo extends Model<Foo>{ static async method() { //findOne is defined in mongoose Model // interface Model<T extends Document> extends NodeJS.EventEmitter, ModelProperties { // findOne(conditions?: any, // callback?: (err: any, res: T | null) => void): DocumentQuery<T | null, T>; // } let rs = await this.findOne(); //and now the result is mongoose.Document //but i want to get InstanceType<Foo> let self = this as FooModelType; let rs2 = await self.findOne(); //what i need is //class Model<T, DocOmit={}> extends _Model1<T>() {} } }
- locked as resolved and limited conversation to collaborators
on Oct 21, 2025




Search Terms
typescript type parameters in base class expressions
Suggestion
Type parameters in base class expressions.
Use Cases and Examples
Let's start with a simple mixin [playground] (borrowed from proposal #13743):
Now if I want to specify the type argument for
Tagged, that's easy enough 1 [playground]:But if I want to pass a derived class's type parameter as that argument, I cannot [playground]:
Checklist
My suggestion meets these guidelines:
Footnotes
Note, this requires the default type argument
= Bshown above, but I have never seen it used in documentation, Stack Overflow, or blogs on the issue of TypeScript mixins. Is that because TypeScript is not actually deducing the type parameter from the function argument? ↩