Open RyanCavanaugh opened 4 years ago
@olee the ability to specify a generic static interface on a class is an advantage of the static-implements proposal. However, the static-implements proposal falls short of delivering the key capability that has prevented this feature request's implementation since the beginning. To illustrate under my abstract/concrete static proposal syntax:
export type Event = {
// ...
};
export abstract class Loggable {
abstract static events: Event[];
concrete static logEvents() {
this.events.forEach(formatAndLog);
function formatAndLog() {
// ...
}
}
}
Here we are able to share reusable logic, the logEvents
method, that depends on a static property that is only instantiated in inheriting concrete sub-classes. This can't be achieved with the static-implements proposal, which is why I advocate for a more comprehensive solution to the problem.
I would also argue that my solution not enabling generic static interfaces is not a "basic flaw" anymore than the static-implements proposal's inability to cover the above use case is a "basic flaw". They are solving two different problems, but happen to overlap in the ability to mandate implementation of static members in sub-classes. These proposals are actually compatible as far as I can tell. The only caveat would be that members declared abstract static
or concrete static
should satisfy any requirements imposed by an implements static
statement.
@GusBuonv no matter what, I do not see any chance in any proposal which would require defining a static contract in an abstract base class for the reasons I mentioned above (eg. extending a class from a library where you have no access to etc.) so I don't think we should try to continue in this direction.
@Wikiemol you are adding some good thoughts there, but I think you might be trying to overengineer the problem. In fact, the only thing which is required here is to enforce a kind of contract (interface) on the constructor of a class.
Actually I think your example with assigning a class to a typed constant already achieves this goal, just not in a way which would be really usable for developers:
interface IAnimal {
readonly age: number;
}
interface IAnimalStatic {
getBiomes(): string[];
}
class Lion implements IAnimal {
public static getBiomes() {
return ['savanna'];
}
public get age() {
return 3;
}
}
const _enforceLionStatic: IAnimalStatic = Lion;
So this is why I think the alternative solutions here would be to provide the means to enforce the same semantic as in the above example with the proposed static implements syntax:
class Lion implements IAnimal, static IAnimalStatic {
// same as above
}
EDIT: I just noticed that in fact in https://github.com/microsoft/TypeScript/issues/33892#issuecomment-542440546 @RyanCavanaugh already summarized a few of the thoughts I had regarding this really nicely
@olee Could you provide an example of how importing third party modules would break my proposal? There is no need under my proposal to modify the original abstract class.
abstract class A {
concrete static useFoo() {
if (this.Foo === undefined) {
throw new Error()
}
console.log(this.Foo);
}
abstract static Foo: string;
}
class B extends A {
static Foo = 'Hello World!';
}
B.useFoo();
// A.useFoo(); // CompilerError: concrete static method useFoo may not be accessed on abstract class A
Would compile to an equivalent of the following valid JavaScript:
class A {
static useFoo() {
if (this.Foo === undefined) {
throw new Error()
}
console.log(this.Foo);
}
}
class B extends A {
static Foo = 'Hello World';
}
B.useFoo();
Class A could be defined in and exported from a third party module, and you can extend with B perfectly safely in your own code.
@GusBuonv I think the issue is that I saw static abstract
and implements static
as two opposing proposals with only one of them being implemented.
If it was like that, there would have been issues to implement various features.
But if one considers this proposal only in the aspect of adding a static abstract
modifier, I think there is no issue and what is proposed makes a lot of sense!
In fact, both could be combined easily like this:
interface IAnimal {
readonly age: number;
}
interface IAnimalStatic {
getBiomes(): string[];
}
abstract class Animal implements IAnimal, static IAnimalStatic {
static abstract getBiomes(): string[];
abstract get age(): number;
}
class Lion extends Animal {
public static getBiomes() {
return ['savanna'];
}
public get age() {
return 3;
}
}
@olee You are probably right that I am over-engineering the problem haha.
Regardless of whether my specific proposal is good or not, for me the point is that, I feel like there is not much to be gained from this unless, in addition to forcing my class to implement static methods, the following are implemented (in order of importance)
MyStaticContract
, Be able to specify that a given type parameter of a type is intended to be an instance of a class which implements MyStaticContract
without specifying the class explicitly. EDIT (And be able to use these static contract methods for its particular implementation on the instance)For me, I probably wouldn't use this feature much if at least one of these things weren't part of the implementation. As many have already said, you can already force your class to implement static methods. But this doesn't really get you anywhere on its own (I think this is why Ryan has trouble understanding the use cases. There really aren't many if that's all there is to it!) I think if all we wanted was features that already existed, this wouldn't be so highly requested.
For me, the above three things are "really" what I want most. At the very least, I think 1 should be considered.
(EDIT Also, I think @GusBunov's solution does well for satisfying 1, but it doesn't really provide a way to do 2 or 3 that I can think of. It solves the this
problem, but as far as I can tell it introduces another problem which is that it becomes very hard to abstract over these static methods if we ban being able to do A.method()
for an abstract class A
)
@Wikiemol
I think @GusBunov's solution does well for satisfying 1, but it doesn't really provide a way to do 2 or 3 that I can think of. It solves the
this
problem, but as far as I can tell it introduces another problem which is that it becomes very hard to abstract over these static methods if we ban being able to doA.method()
for an abstract classA
.
You are correct. My proposal is solving a fundamentally different problem than 2 & 3. I'm focussed on the question @RyanCavanaugh raised here. The issue of static interfaces/contracts is wholly separate.
However, I do think these approaches (ie my proposal and the implements static
proposal) pair extremely nicely. Expanding on @olee's example here, we can implement reusable generic static logic by combining both proposals:
interface IAnimal {
readonly age: number;
}
interface IBiome {
// ...
}
interface ITerrestrialBiomes extends IBiome {
// ...
}
interface IAnimalStatic<T extends IBiome> {
concrete getBiomes(): T[];
}
abstract class Mammal implements IAnimal, static IAnimalStatic<ITerrestrialBiomes> {
static concrete getBiomes(): ITerrestrialBiomes[] {
return [...this.biomes];
}
private static abstract biomes: ITerrestrialBiomes[];
abstract get age(): number;
}
const savanna: ITerrestrialBiomes = {
// ...
};
class Lion extends Mammal {
private static biomes = [savanna];
public get age() {
return 3;
}
}
Lion.getBiomes() // returns `[savanna]` typed as ITerrestrialBiomes[];
EDIT: For those keeping up with the conversation, I realized my example here cannot work in practice, and I've updated the comment accordingly.
@GusBuonv Its a nice solution for specifically problem 1, but I suppose my point is that not only does it not solve problem 2, it makes it actively difficult to implement 2 in the future.
I would argue that being able to abstract over these things is the primary reason we use abstract classes, and is intimately related with @RyanCavanaugh's problems with Option 2. Consider, for example, the following situation using your example
function getBiomes(mammals: Array<Mammal>): Array<ITerrestrialBiomes[]> {
return mammals.map(x => /*???*/)
}
The question is, what do we put in map function? We cannot call x.getBiomes()
since getBiomes
is static.
We cannot call Lion.getBiomes()
since obviously, not all of the x
's are necessarily Lions
My first instinct would be something like this (changing getBiomes to optionally take an instance)
function getBiomes(mammals: Array<Mammal>, mammalType: typeof Mammal): Array<ITerrestrialBiomes[]> {
return mammals.map(x => mammalType.getBiomes(x))
}
But this will give us a compiler error since we aren't sure that mammalType is concrete. Moreover, this doesn't make much sense in the first place, we need all members of the array to be the same type.
But Note that this is currently not allowed
function getBiomes<T extends Mammal>(mammals: Array<T>, mammalType: typeof T): Array<ITerrestrialBiomes[]> {
return mammals.map(x => mammalType.getBiomes(x))
}
This will give us a compiler error along the lines of "T
is a type but is being used as a value" on typeof T
. Even if it were allowed, in this specific case, we still cannot know if typeof T has implemented the field biomes or not.
With abstract methods of any kind, we want to be able to do things like the above, but its hard to do that if we make the static fields themselves abstract. This is primarily the type of thing we want to be able to do with abstract classes, if we can't do this type of thing, then what is the point?
Ryan's solution fixes this problem, but then we lose the pro of your solution, which is that we all want to be able to explicitly declare that an abstract contract needs to be implemented statically.
@Wikiemol You would use the following:
function getBiomes(mammals: Array<concrete typeof Mammal>): Array<ITerrestrialBiomes[]> {
return mammals.map(x => x.getBiomes());
}
EDIT: Added the missing concrete
modifier to typeof Mammal
. See my original proposal for its meaning in this context.
More generally you could use the following:
function getBiomes<B extends IBiome, T extends concrete IAnimalStatic<B>>(animals: Array<T>): Array<B[]> {
return animals.map(x => x.getBiomes());
}
@GusBuonv This is not quite the same, here you are calling getBiomes() on an "array of classes", not an array of instances of mammals. This level of abstraction can already be accomplished in existing typescript.
I.e. we can already have functions which take instances of things (including classes) which satisfy some constraints as the TS maintainers have already pointed out.
abstract class Mammal {
abstract get age(): number;
}
abstract class BiomeAnimal {
abstract getBiomes(): string[];
}
const Lion: BiomeAnimal = class extends Mammal {
static getBiomes() {
return ["savannah"]
}
public get age() {
return 3;
}
}
then we can do, as you have essentially done
function getBiomes(mammals: Array<BiomeAnimal>): Array<string[]> {
return mammals.map(x => x.getBiomes());
}
The only difference here is that we have separated the abstract static part of Mammal into a separate class.
I am arguing that what is not currently expressible in typescript at all is something like
function sort<T extends Mammal : Comparable<T>>(mammals: Array<T>, comparator: Comparable<T>): Array<string[]> {
return mammals.sort((x, y) => comparator.compare(x, y));
}
Where, by implementing Comparable, we are saying for example that the collection of all T
s forms an ordered set of animals (by comparing age for example), as opposed to saying that each individual T
is comparable to any other T
that implements Comparable in whatever way they choose.
I.e. we are asserting that all of the instances of T
have statically implemented Comparable, and moreover that the argument comparator
is that specific implementation
comparator
essentially represents some information about the type of all of the T
s in the array.
@Wikiemol I agree that your example isn't supported, but supporting that use case is, in my mind, an entirely different feature request than abstract static
support. Combining static implements
with abstract static
/concrete static
declarations satisfies the nearly all of use cases brought up in this thread over the years, and it does so with easy to understand syntax that mirrors what is already in the language.
I fully agree with @GusBuonv and I think we should try to focus on the actual proposal which I agree would be really nice to have.
It's 2022 and Q1 is almost over any update on this ? What is the timeline for it ?
This would be great to have, looking forward to updates on this feature request.
Can people engage with the OP? Which proposed behavior do you want to see, and why?
I would love to be able to do things like this:
abstract class Base {
static abstract fromInt(value: number): this;
}
class Impl extends Base {
public static fromInt(value: number): Impl {
return new Impl(); // just placeholder for this example
}
}
and like this:
abstract class Tile {
public static abstract name: string;
public static abstract texture: string;
public static abstract solid: boolean;
}
class Rock extends Tile {
public static name = "Rock";
public static texture = "rock.png";
public static solid = true;
}
As a side-change, and since this ensures something known at compile-time, this would allow this with the previous example:
const rock = new Rock();
const tile: Tile = rock;
const tileName = tile.name; // known static property, so it can be used here non-statically as well
This would help me out a lot in practice, as well. For example, I wrote https://github.com/rigidity/bls-signatures, and in doing so I had to copy the definition of several static methods among every subclass of Field
and had no way to know that they existed on the superclass, or use them with property access. There was also no type checking on those methods existing (which they had to in the library), which is literally the whole purpose of TypeScript. I ended up duplicating so much code because of this, but with this feature the code would be much cleaner and more statically typed.
For the first feature, overriding static members, I think there's no reason that they can't be added, since it's just a compile-time requirement like anything else in the language. It also adds no code to the underlying JavaScript.
For the second, I can understand not having static fields or methods be usable on instances, but if it's possible, these two combined can be very powerful and clean code up a lot.
I hope that this can be considered!
Any update or thoughts on this?
@Rigidity no, the problems with all the possible approaches outlined in the OP remain effectively unsolved
If you know that every subclass of a class with an abstract static method will implement it, you can assume it exists.
Being able to call new this
and use abstract methods in a static initializer method would be incredibly useful for my project as well, and here is my proposition for how that would work:
Am I missing something here? This seems possible to do. I'd be willing to look into implementing and unit testing this myself if that would help...
If you attempt to call it on the super-class...
OK, but how does that work in practice?
Let's say you took something that could operate on a class with a static create
method
class Foo {
abstract static create(): Foo;
}
class Derived extends Foo {
static create() {
return new Derived();
}
}
function fn(x: typeof Foo) {
return x.create();
}
fn(Foo);
Where's the type error in this program that crashes, and why?
x.create
, then there's no way to refer to the general idea of "a class constructor that looks like Foo
but has no abstract static members"Foo
to typeof Foo
, what exactly does typeof Foo
mean anymore?Ryan, last year there were some comments proposing a concrete
keyword. It's been a long time, and I didn't fully re-read the many-page conversation, so this is based on fuzzy memory and a quick skim, but: I believe the error would be at return x.create()
because x
is typeof Foo
not concrete typeof Foo
. typeof Foo
would mean Foo
without abstract static members, basically. If you declared the parameter as x: concrete typeof Foo
, then calling fn(Foo)
would be an error because Foo does not implement all abstract static methods.
As an aside: doesn't a class with abstract
members have to be abstract
itself?
I would say abstract static
makes more sense to be in line with typescript requiring TS specific keywords (public
, protected
, private
) to be put before the static
keyword.
Dear god, don't make me write a singleton class as a workaround 😭😭😭
I think that a modified version of Option 2
on the original proposal would be the closest to what a programmer would expects. I do not think that it should be valid to call any static members on an abstract class.
abstract class Base {
abstract static getName(): string
static logName() {
console.log(this.getName())
}
}
Base.getName() // Should error: Cannot call static method 'getName' on an abstract class
Base.logName() // Should error: Cannot call static method 'logName' on an abstract class
This will ensure that the function should never cause a runtime error without knowing the contents of the function (e.g. the compiler only has a type definition file). However, this implementation is impossible to use in any helpful manner:
abstract class Base {
abstract static getName(): string
static logName() {
console.log(this.getName())
}
}
class Subclass extends Base {
static getName(): string {
return 'Subclass'
}
static logName() {
console.log(this.getName())
}
}
function doMagic(baseSubclass: typeof Base) {
let name = baseSubclass.getName() // Should error: Cannot call static method 'getName' on an abstract class
let instance = new baseSubclass() // Already errors: Cannot create an instance of an abstract class.
// Do something here....
}
doMagic(Subclass)
doMagic(Base)
The main problem here is the limitation of typeof Base
. This is still the abstract class, when the programmer intends it to be a class that extends Base
. There was a proposal early in the thread to add Concrete<typeof T>
which would handle this expectation. I like this idea, although Extends<typeof T>
might be easier to read.
abstract class Base {
abstract static getName(): string
static logName() {
console.log(this.getName())
}
}
class Subclass extends Base {
static getName(): string {
return 'Subclass'
}
static logName() {
console.log(this.getName())
}
}
function doMagic(baseSubclass: Extends<typeof Base>) {
let name = baseSubclass.getName()
let instance = new baseSubclass()
// Do something here....
}
doMagic(Subclass)
doMagic(Base) // Should error: Base is an abstract class. Try entering one of its children
Would this be a potential solution, or am I missing something?
abstract class
with static methods. I am not entirely sure what the policy on breaking changes is, but I suspect that it is something like "avoid them"Extends<T>
to be a keyword instead (e.g. extends typeof Base
)?Extends<T>
vs Concrete<T>
vs something else?I have a suggestion of my own to reconcile it, going off option 5 by taking the type-driven part of option 4 and tweaking it to both eliminate the failure modes and the need for generics while ensuring they can still be fully manipulated by the type system and minimizing breakage: abstract properties.
Here's how abstract properties would work:
--exactOptionalPropertyTypes
was passed for the purpose of property access and assignability, except this is done regardless of whether that flag is passed.abstract
descriptor) properties are assignable to abstract properties, but abstract properties are not assignable to concrete properties. This ensures that abstractness can be handled transparently as needed.abstract
just like readonly
and other similar descriptors. (This is necessary to allow mapped types to manipulate them.)type Concrete<T> = {-abstract [P in keyof T]: T[P]}
to open up propertiestype ConcreteClass<T extends abstract new (...args: any[]) => any, A extends any[]> = Concrete<T> & (new (...args: A) => InstanceType<T>)
to allow dynamic constructor accessabstract
.abstract
(which among other things causes them to automatically inherit the definitions) or declare them explicitly (optionally as abstract properties if desired).
abstract
to have abstract static properties. (This can be enforced by a linter instead.) All the examples below with static properties are declared as abstract
for clarity for similar reasons.This also gives people the ability to allow some methods to be exposed if only some abstract properties are implemented while disallowing others, if they really wanted to.
To take the examples from OP:
Pros:
Cons:
this
types for any method that can access such abstract properties.Still waiting
Interesting to note / might provide some helpful contrast - Dart is chatting about the same problem https://github.com/dart-lang/language/issues/356#issuecomment-1227522887
Three years later and no action? I can't believe this wasn't baked-in in when TypeScript was designed...
Comments to the tune of "I can't believe this isn't done yet" will be hidden; there are real problems outlined in the OP and other comments that need constructive feedback.
Hello. I would like to provide quick answer for situations from op's question.
Let's say you wrote a trivial hierarchy
abstract class A {
static abstract doSomething(): void;
}
class B extends A {
static doSomething() { }
}
Because this
isn't generic in static
members, we should simply allow all invocations of abstract static methods. Otherwise code like this would be illegal:
and want to do:
abstract class A {
static abstract initialize(self: A): void;
static createInstance() {
const a = new this();
this.initialize(a);
return a;
}
}
Let's define type InstantiableSubclasses
which would literally mean: static side of all subclasses of abstract class we can use with new
operator
Valid behavior would be to throw error that initialize
could NOT be called on this
because this
refers to typeof A
and we can't call abstract methods.
abstract class A {
static abstract initialize(self: A): void;
static createInstance() {
const a = new this();
this.initialize(a); // ERROR: initialize can't be called on type `typeof A`, initialize is abstract
return a;
}
}
But a user may narrow this
by explicitly providing it like this:
abstract class A {
static abstract initialize(self: A): void;
static createInstance(this: InstantiableSubclasses<A>) {
const a = new this();
this.initialize(a); // VALID
return a;
}
}
A.createInstance() // ERROR: initialize could not be called: 'this' type mismatch
This looks like we provide default implementation for method, but only allow it's usage in fully concrete context
abstract class Complicated {
static abstract setup(): void;
static abstract print(): void;
static abstract ship(): void;
static abstract shutdown(): void;
}
// does not allow to pass Complicated itself
function fn(x: InstantiableSubclasses<Complicated>) {
// will work
x.setup();
x.print();
x.ship();
x.shutdown();
}
Is there a status update on this? I've run into this issue when trying to replicate some of our back-end to the front-end, since abstract static methods are used there, I would like to duplicate this logic in a similar way using TS. A small sample of how I would want it to look:
export default abstract class Category extends Period {
year: number;
number: number;
constructor(start: Date, stop: Date, year: number, number: number) {
super(start, stop);
this.year = year;
this.number = number;
}
abstract static periodsInYear(year: number): number;
abstract static nth(year: number, number: number): Category;
static forYear(year: number): Category[] {
return [...Array(this.periodsInYear(year)).keys()].map(number => this.nth(year, number));
}
previous(): Category {
if (this.number > 1) {
return Category.nth(this.year, this.number-1);
}
return Category.nth(this.year-1, Category.periodsInYear(this.year-1));
}
next(): Category {
if (this.number < Category.periodsInYear(this.year)) {
return Category.nth(this.year, this.number+1);
}
return this.constructor.nth(this.year+1, 1);
}
};
Where each concrete subclass of Category
would implement the static methods periodsInYear
and nth
, but not need to define their own implementations of forYear
, previous
and next
. I understand there are some issues with the above, and changing the necessary parts to this.constructor
or some generic Extends<...>
type would be no real issue for me syntactically.
The discussion is pretty long though, and I've failed to understand the gist of the problem, and if and when this will be implemented.
Edit: The approach suggested here (https://github.com/microsoft/TypeScript/issues/34516#issuecomment-642087219) satisfies my use case, but it somehow feels a bit hacky, and doesn't provide type checking in the child class to the extent that I want it: it doesn't signal that the "abstract static" methods aren't defined, for example. I had to use the "trick" from Kamil's answer to https://stackoverflow.com/questions/13955157/how-to-define-static-property-in-typescript-interface to actually make the type checker succeed.
Ideally I'd like to access public abstract static variables with a public static function in the base class. Would this be possible if this issue gets resolved?
bump
@Distortedlogic Please do not "bump" issues if you do not have anything of substance to add, this only serves to annoy everyone who are subscribed to the issue (as GitHub sends emails for each and every comment made) and to make the discussion unreadable.
@QuintenPeulingCodesharpNl the issue with being able to use abstract static
class fields inside concrete abstract class's methods is amongst the unresolved questions outlined in the OP, unfortunately. Depending on what the team settles with, it might or might not be possible. The problem with allowing abstract fields to be used in concrete static methods of an abstract (base) class is that the compiler will count blatant runtime errors as valid code (if the programmer, say, attempts to call the method on the abstract class directly). The OP has potential solutions to that, but all have significant drawbacks that seem to be still in discussion.
Establishing the need for a type whose members can only be concrete classes:
Establishing the need a three-way abstractness distinction between static methods:
Showing examples of this system in action:
I think this solves all the issues in the OP? It at least provides a solution to whether abstract fields are allowed to be used in static methods.
TL;DR:
Concrete<typeof MyClass>
/ concrete typeof myclass
is needed to know which static methods can be called.abstract
into a three-way distinction between (none)/default
/abstract
or such.The actual terms I've chosen to represent these language features don't matter much, what matters is the idea behind them, which I think is sound in both concept and usage (though I am open to being proven wrong).
This is a continuation of #14600 which had two separate features proposed in the same issue (static members in interfaces and abstract static class members)
Search Terms
static abstract method property properties implement concrete
Suggestion
Currently, this code is illegal:
It should be legal to have
abstract static
(static abstract
?) members.Use Cases
(what are they?)
Unresolved Questions
What calls of abstract static methods are allowed?
Let's say you wrote a trivial hierarchy
For an expression
x.doSomething()
, what are validx
s?Option 1: All of them
Because
this
isn't generic instatic
members, we should simply allow all invocations of abstract static methods. Otherwise code like this would be illegal:However, this means that TypeScript would miss straight-up crashes:
A.doSomething()
, which seems like a fairly large design deficitOption 2: None of them
Allowing crashes is bad, so the rule should be that
static abstract
methods simply don't exist from a type system perspective except to the extent that they enforce concrete derived class constraints:This is unergonomic because it'd be impossible to write a function that dealt with an arbitrary complex constructor function without tedious rewriting:
We know this is a problem because people get tripped up by it constantly when they try to
new
an abstract class:https://www.reddit.com/r/typescript/comments/bcyt07/dynamically_creating_instance_of_subclass/ https://stackoverflow.com/questions/57402745/create-instance-inside-abstract-class-of-child-using-this https://stackoverflow.com/questions/49809191/an-example-of-using-a-reference-to-an-abstract-type-in-typescript https://stackoverflow.com/questions/53540944/t-extends-abstract-class-constructor https://stackoverflow.com/questions/52358162/typescript-instance-of-an-abstract-class https://stackoverflow.com/questions/53692161/dependency-injection-of-abstract-class-in-typescript https://stackoverflow.com/questions/52358162/typescript-instance-of-an-abstract-class
For
abstract
constructor signatures, the recommended fix of using{ new(args): T }
is pretty good because a) you need to be explicit about what arguments you're actually going to provide anyway and b) there's almost always exactly one signature you care about, but forstatic abstract
methods/properties this is much more problematic because there could be any number of them.This also would make it impossible for concrete
static
methods to invokeabstract static
methods:On the one hand, this is good, because
A.createInstance()
definitely does crash. On the other hand, this literally the exact kind of code you want to write with abstract methods.One solution would be the existence of an
abstract static
method with a body, which would be allowed to invoke otherabstract static
methods but would be subject to invocation restrictions but not require a derived class implementation. This is also confusing because it would seem like this is just a "default implementation" that would still require overriding (that is the bare meaning ofabstract
, after all):An alternative would be to say that you can't call any
static
method on anabstract
class, even though that would ban trivially-OK code for seemingly no reason:static
methods from calling same-classabstract
methodsOption 3: Indirection is sufficient
Why not just split the baby and say that the direct form
A.doSomething()
is illegal, butexpr.doSomething()
whereexpr
is of typetypeof A
is OK as long asexpr
isn't exactlyA
.This creates the dread inconsistency that a trivial indirection is sufficient to defeat the type system and cause a crash:
It's also not entirely clear what "indirection" means. Technically if you write
then
foo
isn't exactly the declaration of SomeStaticAbstractClass itself - it's an alias. But there isn't really anything distinguishing that fromconst p = A
above.Option 4: Indirection, but with generics
Maybe a trivial indirection as described in Option 3 isn't "good enough" and we should require you to use a constrained generic instead:
This turns out to be a bad option because many subclasses don't actually meet their base class static constraints due to constructor function arity differences:
This isn't even code we want people to write -- a generic type parameter used in exactly one position is something we explicitly discourage because it doesn't "do anything".
Option 5: Something else?
Anyone able to square this circle?
Checklist
My suggestion meets these guidelines: