question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Request: Allow abstract classes to implement MappedTypes that are instantiated with type parameters.

See original GitHub issue

Ok, so the title is a mouthful, but here’s an example of what we’re trying to accomplish:

interface DeferredProperty<T> {
    get(): T;
}

type Deferred<T> = {
    [P in keyof T]: DeferredProperty<T[P]>
};

Here we use mapped types to express the idea that a interface type might be wrapped with a new type that exposes the same properties as it, except as functions instead of data properties. So, for example:

interface Person {
    age: number,
    name: string
}
var deferredPerson: Deferred<Person>;
var age = deferredPerson.age.get();


interface Vehicle {
    numWheels: number,
    cost: number
}
var deferredVehicle: Deferred<Vehicle>;
var cost = deferredVehicle.cost.get();

We’d like to make a lot of our types deferable in our system, so we create a simple abstract base class that will help us out by doing some of the work for us. In other words, we’d like to be able to write:

abstract class BaseDeferred<T> implements Deferred<T> { //<-- note: currently not legal
    // some common stuff, including concreate methods

    // Maybe some abstract methods.
    // protected abstract whatever(): void;

    // etc.
}

We could then do the following:

class DeferredPerson extends BaseDeferred<Person> {
}
class DeferredVehicle extends BaseDeferred<Vehicle> {
}

At this point TypeScript would say: "Hey, DeferredPerson doesn’t properly implement BaseDeferred<Person>, it is missing age: DeferredProperty<number> and name: DeferredProperty<string>. (And likewise for DeferredVehicle)

However, this isn’t currently allowed as we cannot say: abstract class BaseDeferred<T> implements Deferred<T>

This is a somewhat understandable restriction. After all, how can the compiler actually validate that BaseDeferred<T> is implementing Deferred<T> when it cannot know (at this point) how the Deferred<T> lookup type will expand.

While understandable, it would be nice if this restriction could potentially be lifted for abstract types. Because the type is abstract, we would like it if the check was only actually done at the time the type was concretely derived from. So, for example, when someone wrote:

class DeferredPerson extends BaseDeferred<Person> {
	// Now the compiler create the full type signature for BaseDeferred<Person> and then checked DeferredPerson against it.
}

The workaround today is to do the following:

abstract class BaseDeferred<T> { // <-- note: no implements
}

class DeferredPerson extends BaseDeferred<Person> implements Deferred<Person> {
}
class DeferredVehicle extends BaseDeferred<Vehicle> implements Deferred<Vehicle> {
}

The compiler now appropriately does the right checks. This is unpleasant though as it’s a very simple thing to miss. Because all subclasses must implement this type, we would very much like to push that requirement up to the base class and have the enforcement applied uniformly across all subtypes.

Thanks much, and i hope everyone is doing great! We’re having a blast with TS, especially (ab)using the type system to express some very interesting things. The more crazy stuff that can be expressed (especially around constraints and variadic types) the happier we are 🙂

Issue Analytics

  • State:open
  • Created 6 years ago
  • Reactions:24
  • Comments:9 (5 by maintainers)

github_iconTop GitHub Comments

15reactions
dragomirtitiancommented, Apr 29, 2019

@xxxtonixxx still not possible directly , might I suggest a workaround, use a separate constructor declaration that returns the apropriate intersection:

class _Context<T>  { /* members */ }

const Context: new<T>() => _Context<T> & T = _Context as any;
type Context<T> =  _Context<T> & T

const ctx = new Context<{ id: number, name: string }>();

ctx.id
ctx.name

4reactions
Llorxcommented, Dec 21, 2020

Any update on this?

Read more comments on GitHub >

github_iconTop Results From Across the Web

Handbook - Classes - TypeScript
Abstract classes are base classes from which other classes may be derived. They may not be instantiated directly. Unlike an interface, an ...
Read more >
java - Instantiating a generic extension of an abstract class
I would suggesting using a factory method in your Genotype class public abstract class Genotype { public abstract GenoType newInstance(); }.
Read more >
Can We Instantiate an Abstract Class in Java? - GeeksforGeeks
Abstract class, we have heard that abstract class are classes which can have abstract methods and it can't be instantiated.
Read more >
Advanced TypeScript 4.8 Concepts: Classes and Types
We wouldn't be able to, because it's an anonymous type assigned to a function parameter. To solve this problem, we can use an...
Read more >
Calling Java from Kotlin
When you call methods on variables of platform types, Kotlin does not issue nullability errors at ... Let's annotate the type parameter of...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found