microsoft / TypeScript

TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
https://www.typescriptlang.org
Apache License 2.0
100.95k stars 12.47k forks source link

T.constructor should be of type T #3841

Open LPGhatguy opened 9 years ago

LPGhatguy commented 9 years ago

Given

class Example {
}

The current type of Example.constructor is Function, but I feel that it should be typeof Example instead. The use case for this is as follows:

I'd like to reference the current value of an overridden static property on the current class.

In TypeScript v1.5-beta, doing this requires:

class Example {
    static someProperty = "Hello, world!";

    constructor() {
        // Output overloaded value of someProperty, if it is overloaded.
        console.log(
            (<typeof Example>this.constructor).someProperty
        );
    }
}

class SubExample {
    static someProperty = "Overloaded! Hello world!";

    someMethod() {
        console.log(
            (<typeof SubExample>this.constructor).someProperty
        );
    }
}

After this proposal, the above block could be shortened to:

class Example {
    static someProperty = "Hello, world!";

    constructor() {
        // Output overloaded value of someProperty, if it is overloaded.
        console.log(
            this.constructor.someProperty
        );
    }
}

class SubExample {
    static someProperty = "Overloaded! Hello world!";

    someMethod() {
        console.log(
            this.constructor.someProperty
        );
    }
}

This removes a cast to the current class.

kitsonk commented 9 years ago

Also, for clarity, as discussed in #4356 it is logical based on the ES specification to strongly type .constructor property of an instance and the following is essentially equivalent valid ways of creating instances:

class Foo {
    foo() { console.log('bar'); }
}

let foo1 = new Foo();

let foo2 = new foo1.constructor();
RyanCavanaugh commented 9 years ago

Accepting PRs for this. Anyone interested? :smile:

weswigham commented 9 years ago

I spoke with @ahejlsberg about this - we could type constructor fairly well just with a change to our lib.d.ts now that we have this types (in theory):

interface Constructor<T> {
  new (...args: any[]): T;
  prototype: T;
}

interface Object {
    constructor: Constructor<this>;
}

But! There are two issues - we don't instantiate this types on apparent type members (as in, anything on object) correctly right now, and there would be a performance impact in making every object have a this typed member (it's constructor member). Additionally, this method doesn't capture the arguments of the constructor of the class (or its static members), so it could stand to be improved.

RyanCavanaugh commented 9 years ago

Additionally, this method doesn't capture the arguments of the constructor of the class (or its static members),

This was a dealbreaker for us since it wouldn't even address the problem in the OP. Wiring it up in the compiler the same way we do prototype seems necessary.

jbondc commented 9 years ago

Can take a look, does

1) Removing from lib.d.ts:

interface Object {
    constructor: Function;

2) inferring/adding a "constructor" property/symbol with a type to the classType seem like the right thing to do?

jbondc commented 9 years ago

Nevermind, it's a bit more involved then I thought. Attempt here in case helpful: https://github.com/Microsoft/TypeScript/compare/master...jbondc:this.constructor

Think proper way involves changing ConstructorKeyword in getSymbolAtLocation()

Arnavion commented 8 years ago

As I mentioned in #5933, will this not be a breaking change for assigning object literals to class types?

class Foo {
    constructor(public prop: string) { }
}

var foo: Foo = { prop: "5" };

This is currently allowed, but with this issue fixed it should produce an error that the literal is missing the constructor property? If it didn't produce an error, then it would be allowed to call statics on Foo such as foo.constructor.bar() which would break if foo.constructor wasn't actually Foo.

LPGhatguy commented 8 years ago

Would this be possible to implement solely with the polymorphic this type that's a work in progress? Assuming typeof mechanics would work there as well, could the signature for Object.prototype.constructor be set to:

interface Object {
    constructor: typeof this;
}
ahejlsberg commented 8 years ago

The general problem we face here is that lots of valid JavaScript code has derived constructors that aren't proper subtypes of their base constructors. In other words, the derived constructors "violate" the substitution principle on the static side of the class. For example:

class Base {
    constructor() { }  // No parameters
}

class Derived {
    constructor(x: number, y: number) { }  // x and y are required parameters
}

var factory: typeof Base = Derived;  // Error, Derived constructor signature incompatible
var x = new factory();

If we were to add a strongly typed constructor property to every class, then it would become an error to declare the Derived class above (we'd get the same error for the constructor property that we get for the factory variable above). Effectively we'd require all derived classes to have constructors that are compatible with (i.e. substitutable for) the base constructor. That's simply not feasible, not to mention that it would be a massive breaking change.

In cases where you do want substitutability you can manually declare a "constructor" property. There's an example here. I'm not sure we can do much better than that.

pocesar commented 8 years ago

Well, since the constructor, per spec (and after typescript compilation) is always the defined in the prototype, I don't see why it shouldn't be strongly typed. If people are hacking their prototypes manually, let them typecast their code. this.constructor should be the class definition, and super.constructor the immediate super prototype definition, and so on.

Since non-typescript modules usually have their manual typings (through DefinitelyTyped), it's safe to rewrite their constructor there as needed (in case the module author did something silly like):

function Foo(){
}
Foo.prototype = {
   constructor: Foo,
   someMethod: function() {
   }
}
RyanCavanaugh commented 8 years ago

I don't understand what's going on in this thread. Need to take it back to the design meeting.

uMaxmaxmaximus commented 8 years ago

Cant call static owerriden methods, if i write Animal.run() this is code not use Cat.run owerriden method. i need write this.constructor.run() but it get compilation error.

class Animal {

    static run (){
        alert('Animal run')
    }

    run(){
        this.constructor.run() // compile error
        // if i write Animal.run() this code not use Cat.run owerriden method
    }

}

class Cat extends Animal {

    static run (){
        super.run()
        alert('Cat jump')
    }

}

let cat = new Cat()
cat.run()

solution

add keyword static for access to current static props, static will be alias for this.constructor in self methods, and alias for this in static methods

and code will be:

class Animal {

    static run (){
        alert('Animal run')
    }

    run(){
        static.run() // compile to this.constructor.run()
    }

}

class Cat extends Animal {

    static run (){
        super.run()
        alert('Cat jump')
    }

}

let cat = new Cat()
cat.run() // alert('Animal run') alert('Cat jump')

dangers

keyword static can be to used in the next standards ES, and may have different semantics

P.S.

There is nothing wrong with adding a little sugar. This really is not anything bad. All restrictions that you come up with, to serve for the good of the people. And in this case, the failure to implement the keyword static, just from the fact that it is not in the standard, does more harm than good.

Language is a tool, and, as we understand, should be the keyword static. And we all know that.

TypeScript is a ES with types. ES not has static keyword, and we must not it have

This is nonsense and is an example of how not built right ideology hinders the improvement of living standards of people. The rule for the rule.

We need to add the keyword, since it will allow programmers to better to program. That's the purpose of the language. Am I wrong? Am I wrong???

pocesar commented 8 years ago

static.run() isn't valid javascript, in all senses. using an imaginary keyword that looks like a global. the Class specification does have static as a keyword http://www.ecma-international.org/ecma-262/6.0/#sec-class-definitions one thing is transpiling code that works in ES5, another thing is changing the input to a completely different output. await / async for example are in the spec, and the generated code just makes you able to use ES2016 today. the coffeescript dark days are now past at last.

Typescript does one thing and does really well: having plain JS strongly-typed. they just need to fix the constructor to be strongly typed as well. I have no problem doing this.constructor.some() since using ClassDefinition.some() is awkward and makes inheritance a (even more) pain.

charrondev commented 8 years ago

Has anyone figured out some kind of workaround for this yet?

acrazing commented 7 years ago

Maybe use new keywords is better. For example:

In TypeScript:

class Foo {
  static base() {
    return 'FOO'
  }

  static foo() {
    return self.base()
  }

  static bar() {
    return static.base()
  }

  cfoo() {
    return self.base()
  }

  cbar() {
    return static.base()
  }
}

class Bar extends Foo {
  static base() {
    return 'BAR'
  }

  static more() {
    return parent.base()
  }

  cmore() {
    return parent.base()
  }
}

Expect executing result as follow:

Foo.base() // 'FOO'
Foo.foo() // 'FOO'
Foo.bar() // 'FOO'
Bar.base() // 'BAR'
Bar.foo() // 'FOO'
Bar.bar() // 'BAR'
Bar.more() // 'FOO'
const foo = new Foo()
foo.cfoo() // 'FOO'
foo.cbar() // 'FOO'
const bar = new Bar()
bar.cfoo() // 'FOO'
bar.cbar() // 'BAR'
bar.cmore() // 'FOO'

The three keywords self, static, parent means different access controls as follow:

The compile result is(omit useless codes):

(function (_super) {
  function _ctor() {}
  _ctor.foo = function() {
    _super.bar() // from parent.bar()
    _ctor.bar() // from self.bar()
    this.bar() // from static.bar()
  }

  _ctor.prototype.foo() {
    _super.bar() // from parent.bar()
    _ctor.bar() // from self.bar()
    this.constructor.bar() // from static.bar()
  }
})(function _super() {})
aluanhaddad commented 7 years ago

@acrazing as already discussed in this thread that is a bad idea and it is contrary to the design goals of TypeScript.

To understand why it is a bad idea, consider that static already has a meaning in ECMAScript and that this meaning may expand. What if TC39 adds support for precisely this feature? What if it adds the same syntax with a different meaning? What is static is endowed with a meta-property like new was with target?TypeScript, as a JavaScript superset, would need to implement the new behavior of static. This would break existing code.

That is one reason to not add new syntax in value positions. It breaks compatibility.

Chacha-26 commented 7 years ago

Possible workaround:

class Foo {
    'constructor': typeof Foo; // <-- this line here
    static method() { return 1 }
}
const foo = new Foo();
foo.constructor.method(); // Works
new foo.constructor(); // Works

Having an explicit opt-in isn't necessarily a bad thing either, since it makes it obvious that you're going to be using the .constructor property for something.

It seems a bit odd that the quotes are required, but just having constructor: typeof Foo will give you: [ts] Constructor implementation is missing. if you have no constructor implementation, or [ts] '(' expected. if you do.

As a bonus, you can use this to implement static properties on interfaces:

interface I {
    constructor: {
        staticMethod(): number;
    }
    instanceMethod(): string;
}

class C implements I {
    'constructor': typeof C;
    static staticMethod() { return 1 }
    instanceMethod() { return '' }
}
albertogasparin commented 7 years ago

If someone wants to make it work with prettier (which removes the quotes), wrapping around [] does the trick:

class C {
    ['constructor']: typeof C;
}
k8w commented 6 years ago

@ahejlsberg

Here we just wanna to use this.constructor to retrieve static member. If it must add "constructor": typeof XXX to every class, it also seems too unnecessary.

Since this.constructor have some unavoidable type problem, can we add a new grammer for this. For example: this::staticMember compiles to this.constructor.staticMember. Treat type of this::XXX as (typeof typeof this)['XXX']

Just like it in PHP: self::staticMember. It also use :: in C++.

SalathielGenese commented 6 years ago

I agree with @k8w as introducing a new concept/keyword like constructof would be verbose!

As for the constructor.prototype, it cannot be strongly typed as may developers still use that means to add properties. Moreover, have you notice that though not suggested in autocomplete, when you do use it, no error is triggered?

dbkaplun commented 6 years ago

Any progress on this?

alfaproject commented 6 years ago

I'm trying to call a static property from a Sequelize model as such:

const ModelT = options.target().constructor as typeof Model<T>;
return ModelT.count({ where });

options.target() return type is new () => T and T extends Model<T>

count is a static method of Model<T>, but I'm unable to type that cast properly. Any help would be appreciated.

charrondev commented 6 years ago

Looking at this now, having a built in polymorphic definition of this.constructor would probably do the trick. The biggest issue I have with this:

class Foo {
    'constructor': typeof Foo; // <-- this line here
    static method() { return 1 }
}
const foo = new Foo();
foo.constructor.method(); // Works
new foo.constructor(); // Works

Is that it's not polymorphic. For example the above falls apart when extending classes.

class Foo {
    'constructor': typeof Foo; // <-- this line here
    static method() { return 1 }
}
class FooChild extends Foo {
}
const foo = new Foo();
const fooChild = new FooChild();

foo.constructor.method(); // Works
new foo.constructor(); // Works

fooChild.constructor.method(); // Doesn't Work
new fooChild.constructor(); // Doesn't Work
KSXGitHub commented 6 years ago

Relevant ECMAScript proposal: https://github.com/rbuckton/proposal-class-access-expressions

nieltg commented 6 years ago

It's true what @ahejlsberg's comment here about type incompatibility which will happen if every object has different types of constructor property.

However, after thinking for a while, I think we were providing a little less information than we could provide. Well, I will explain my idea for this issue.

constructor type information from the new operator

type Instance<T, P> = T & { constructor: P }

Instance<T, P> augments T type with the type information of constructor property. We can provide the information somewhere and I think the sweet spot is at the return value of the new operator. Look at the example below. (open in TypeScript playground here)

class Class1 {
  constructor(value: number) {
  }
}

class Class2 extends Class1 {
  constructor(value: string) {
    super(Number(value))
  }
}

let i1 = new Class1(100) as Instance<Class1, typeof Class1>
let i2 = new Class2("1") as Instance<Class2, typeof Class2>
//                       ^ this is what I propose: make this auto-magically happens

i1 = i2               // <-- error because incompatible constructors

let di1: Class1 = i1  // <-- let's reduce information that we have here
let di2: Class2 = i2

di1 = di2             // <-- not even an error happens here :)

This idea will solve the problem here but still allows for more specific typing if needed.

Sidenote

The behavior of returning T from new operator has been around there for a long time. I'm thinking about automagically reduce the information at assignment operation if the variable type not explicitly stated.

let s1 = new Class1(100)  // <-- ReducibleInstance<Class1, typeof Class1>
let s2 = new Class2("1")  // <-- ReducibleInstance<Class2, typeof Class2>

// s1.constructor.           <-- resolve to typeof Class1

s1 = s2   // <-- reduce s1 to Class1 before assignment only if signature is incompatible
          //     s2 is still typed as ReducibleInstance<Class2, typeof Class2>

// s1.constructor.           <-- now resolve to Function
nieltg commented 6 years ago

Well, I'm not finished yet. This alone is not enough to solve the whole problem. Therefore, I'll propose another design idea.

Polymorphic typeof this type

I have an idea to create a new type named typeof this type which is similar to polymorphic this type but for the type of the class instead of for the instance. This is similar to @LPGhatguy comment here. I'll highlight some traits of my concept idea below.

Dissolve to typeof T from the outside

Like polymorphic this type which dissolves to T when accessed from the outside, polymorphic typeof this type dissolves to typeof T when accessed from the outside.

class Class3 {
  method1(): this {         // <-- type: this
    return this
  }

  method2(): typeof this {  // <-- type: typeof this
    return this.constructor
  }
}

let t1 = new Class3()
t1.method1()  // <-- type: Class3
t1.method2()  // <-- type: typeof Class3

Resolve currently available members of the class type from the inside

Like polymorphic this type which resolves currently available members when accessed from the inside, polymorphic typeof this resolves currently available static members of the class.

class Class4 {
  static staticProp1: number
  prop1: number

  method1() {
    let obj = this              // <-- type: this
    // obj.                        <-- resolve prop1, method1

    let cls = this.constructor  // <-- type: typeof this
    // cls.                        <-- resolve to staticProp1, etc
  }
}

class Class5 extends Class4 {
  static staticProp2: number
  prop2: number

  method2() {
    let obj = this              // <-- type: this
    // obj.                        <-- resolve prop1, prop2, method1, method2

    let cls = this.constructor  // <-- type: typeof this
    // cls.                        <-- resolve to staticProp1, staticProp2, etc
  }
}

Don't resolve any construct signatures from the inside

Reason: child's construct signature is allowed to be incompatible with parent's construct signature. We don't have enough information to figure out the signature of the final constructor at the moment.

class Class6 {
  constructor(readonly value: number) {
  }

  method1() {
    new this.constructor(17)  // <-- Error TS2351: Cannot use 'new' with an ...
  }
}

class Class7 extends Class6 {
  constructor(value: string) {
    if (typeof value !== "string") {
      throw new Error("value must be a string")
    }

    super(Number(value))
  }

  // method1() {
  //   new this.constructor(17)  <-- this expr. will break the rule if allowed
  // }
}

Provided by this type

Since we're not changing the type of Object.prototype.constructor, we have to provide access to this feature. I'm thinking about putting this on this type.

class Class8 {
  method1() {
    // this.constructor.   <-- resolve to typeof this
  }
}

let it1: Class8 = new Class8()
// it1.constructor.        <-- resolve to Function

Motivating Problem to Solve

My actual problem is I want to create an extendable class that leverages immutable fluent interface pattern. I took an example from TypeScript documentation here with a few changes. (open in playground)

class Calculator {
  constructor(readonly value = 0) {
  }

  protected newInstance(value: number): this {
    return new (this.constructor as any)(value)   // <-- not type-safe
  }

  add(operand: number) {
    return this.newInstance(this.value + operand)
  }

  // ... other operations go here
}

let v = new Calculator(2)
  .add(1)
  .value

One of my objectives is I want to make this code more type-safe since the constructor's type signature is not checked yet.

It will be possible to resolve to resolve static members of the class from this.constructor attribute and to do type-checking on the constructor of the child class from the parent class using this idea by modifying the Calculator class above to be like this.

class Calculator {
  // ...

  protected newInstance(
    this: this & { constructor: { new(value: number) } },
    value: number
  ): this {
    return new this.constructor(value)
  }

  add(
    this: this & { constructor: { new(value: number) } },
    operand: number
  ) {
    return this.newInstance(this.value + operand)
  }

  // ... other operations go here
}

Every method which associates with the fluent interface will not be able to be called from an instance of the class which doesn't have the required signature.

class BrokenCalculator extends Calculator {
  constructor(value: string) {
    super()
  }
}

let v2 = new BrokenCalculator()
  .add(5)   // <-- this resolves to:
            //     ReducibleInstance<BrokenCalculator, typeof BrokenCalculator>
            //     (error: not compatible with required this by the method)

Thank you. I'm looking forward to hearing about this. I'm sorry for long post here.

I hope this idea will be able to solve our problems. 😃

hinell commented 6 years ago

It's quite boring to write ['constructor']: typeof FooClass every time so I think it worth to fix this.

Solution must be simple. The following code placed inside of the class constructor or its method

...
   this.constructor
...

is always pointing to the class' variable unless someone failed to assign Foo.prototype.constructor = property by Parent class during inheritance.

Friksel commented 5 years ago

Any news on this?

trusktr commented 5 years ago

This would be nice to have! It's a very common case. There are often libs that rely on settings being specified on classes (and extended in subclasses), and those libs retrieve the settings via this.constructor.someSetting.

@ahejlsberg

The general problem we face here is that lots of valid JavaScript code has derived constructors that aren't proper subtypes of their base constructors. In other words, the derived constructors "violate" the substitution principle on the static side of the class.

That's true, but has no bearing on the fact that when we read this.constructor we intuitively expect a value of the type of this's constructor assuming it was defined with class. That case can be satisfied. we don't know which subclass this will be made from.

Now, what we do with the constructor after we've read its value is a different story. Maybe there needs to be two type checks: one prevents it from being substitutable when called (typed as Function), and the other just treats it as a static access when reading properties. Calling new this.constructor is not usual, but reading this.constructor.foo is common.

ayZagen commented 5 years ago

I was OK with adding field 'constructor': typeof ClassName but after upgrading to TS 3.5.1 I receive error Classes may not have a field named 'constructor'. It is really obtrusive that this issue is open for 4 years :)

Chacha-26 commented 5 years ago

@ayZagen That error message was added in #31119 and relates to #31020 There seems to be an allowance for the version with brackets:

class A {
    ['constructor']!: typeof A // ! to avoid 'use before init' error in constructor
    static methodA() { }
    constructor() { this.constructor.methodA(); }
}

new A().constructor.methodA();

You can also use interface merging, which avoids both [] and !

interface C {
    constructor: typeof C
}
class C {
    static methodC() { }
    constructor() { this.constructor.methodC(); }
}
new C().constructor.methodC();

I can't remember if this was always the case, but it works with subclasses now too

class B extends A {
    ['constructor']: typeof B
    static methodB() { }
}

new B().constructor.methodA();
new B().constructor.methodB();
trusktr commented 5 years ago

Most people want to read this.constructor.someProp, they hardly ever want to call new this.constructor(...). Maybe the type checker needs to do two things:

This would prevent us from having to write ['constructor']!: typeof B in every subclass to have proper static inheritance.

trusktr commented 5 years ago

The fact that the following works,

class A {
    ['constructor']!: typeof A
    static methodA() { }
    constructor() { this.constructor.methodA(); }
}

new A().constructor.methodA();

is weird because in JavaScript it is an error. Try it in Chrome console, without the types:

class A {
    ['constructor']
    static methodA() { }
    constructor() { this.constructor.methodA(); }
}

new A().constructor.methodA();

If I recall correctly, one of the goals of TypeScript is that types can be stripped to leave valid JavaScript, but that's not true in this case.

Looking forward to an official solution!

alfaproject commented 5 years ago

@trusktr that's not valid JavaScript and it's also not what TypeScript compiles to. This is what TypeScript compiles to:

class A {
    static methodA() { }
    constructor() { this.constructor.methodA(); }
}

new A().constructor.methodA();
trusktr commented 5 years ago

that's not valid JavaScript

@alfaproject Thanks. I already knew that. That's what I was pointing out: the code in TypeScript is invalid JavaScript (with types), and I know it converts to valid JS after compile.

I'm just saying that it is strange and not obvious what the behavior should be. If I go by my JS knowledge (coming from JS to TS), then I'd be wrong: I would have never thought to define the same property twice like this (because it is invalid JavaScript).

Do you get what I mean? I'm just saying, that an official solution will be nice (and won't involve invalid JavaScript inside of TypeScript).

agrozyme commented 5 years ago

I use this way for inherit class

export function bindConstructor<T>() {
  return <U extends T>(constructor: U) => {
    // noinspection BadExpressionStatementJS
    constructor;
  };
}

export interface BaseClass<T extends typeof BaseClass = typeof BaseClass> {
  constructor: T;
}

export interface BaseClassConstructor<T = {}> {
  new (...items: any[]): T;
}

export interface AnyConstructor<T = {}> extends BaseClassConstructor {
  new (...items: any[]): T;
}

@bindConstructor<BaseClassConstructor>()
export class BaseClass<T extends typeof BaseClass = typeof BaseClass> {
  constructor(...items: any[]) {}
}

interface ChildInterface {
  readonly Zero: number;
}

interface ChildConstructor<T = {}> extends AnyConstructor {
  new (...items: any[]): T;
  Two: number;
}

@bindConstructor<ChildConstructor>()
class Child<T extends typeof Child = typeof Child>
  extends BaseClass<T & AnyConstructor>
  implements ChildInterface {
  readonly Zero = 0;
  protected readonly One = 1;
  static Two = 2;
  protected static Three = 2;
}

@bindConstructor<ChildConstructor>()
class Son<T extends typeof Son = typeof Son> extends Child<T & AnyConstructor>
  implements ChildInterface {
  constructor(...items: any[]) {
    super(...arguments);
    console.log(this.Zero);
    console.log(this.One);
    console.log(this.constructor.Two);
    console.log(this.constructor.Three);
  }
}
agrozyme commented 5 years ago

another way: ps: override type() without return type

export function bindConstructor<T>() {
  return <U extends T>(constructor: U) => {
    // noinspection BadExpressionStatementJS
    constructor;
  };
}

export type BaseType<U> = InstanceType<U & { new (...args: any): any }>;

export interface AnyConstructor<U extends {} = {}> {
  new (...items: any[]): U;
}

//export interface BaseConstructor<U extends typeof Base> extends AnyConstructor<U> {
//  create(...items: any[]): BaseType<U>;
//
//  new (...items: any[]): BaseType<U>;
//}

export abstract class Base {
  constructor(...items: any[]) {
    this.setup();
  }

  static create<U extends typeof Base>(this: U, ...items: any[]): BaseType<U> {
    return Reflect.construct(this, items);
  }

  setup(): void {}

  //noinspection JSMethodCanBeStatic
  type() {
    return Base;
  }
}

  interface YI {}

  interface YC<U extends YI = YI> extends AnyConstructor<U> {
    z(): string;
  }

  abstract class X extends Base implements YI {
    xa!: string;
    xf!: string;
    xfr!: string;

    protected ef: string = 'ex';
    protected readonly efr: string = 'ex';

    protected get ea() {
      return 'ex';
    }

    type() {
      return X;
    }
  }

  @bindConstructor<YC>()
  class Y extends X {
    protected ef: string = 'ey';
    protected readonly efr: string = 'ey';

    protected get ea() {
      return 'ey';
    }

    static z() {
      return 'z';
    }

    setup(): void {
      this.xf = 'ey' === this.ef ? 'TRUE' : 'FALSE';
      this.xfr = 'ey' === this.efr ? 'TRUE' : 'FALSE';
      this.xa = 'ey' === this.ea ? 'TRUE' : 'FALSE';
    }

    type() {
      return Y;
    }
  }
canonic-epicure commented 4 years ago

Any hope for this feature? A long awaited "ergonomic" improvement.

Becold commented 4 years ago

I use this workaround to call a static method inside a class :

class A {
    constructor() { }
    static a(): void {
        console.log("from a static method");
    }
    b(): void {
        this.constructor['a'](); // <= call the a() static method
    }
}

(new A()).b();

And this workdaround to retrieve a static property inside a class :

class A {
    public static a: string = "hello world";
    constructor() { }
    static b(): void {
        console.log(this.a + " from b"); // <= from static method
    }
    c(): void {
        console.log(this.constructor['a'] + " from c"); // <= from non-static method
        this.constructor['b']();
    }
}

(new A()).c();
SephReed commented 4 years ago

VSCode

Screen Shot 2020-02-13 at 4 13 32 PM

Chrome Console

Screen Shot 2020-02-13 at 4 13 54 PM

I'm currently trying to do some type checking (anything really) to see an object extends a class. One notable difference between classes and objects is the list of attributes on their constructor.

The following would be a functional way to tell if something was a class or not:

T extends {constructor: { defineProperty: any}} ? ProbablyNotAClass : ProbablyAClass
Screen Shot 2020-02-13 at 4 28 43 PM
pushkine commented 4 years ago

chrome_2020-04-09_11-03-05

dolsem commented 4 years ago

One thing that no one seems to have called out during this discussion is that there's a difference between T.constructor and T.prototype.constructor. T.constructor type is already accurate - it is the constructor function and not the class object, so it lacks things like static members. T.prototype.constructor is indeed just a reference to the class object, and this is where we have this type problem. Try this on TS Playground:

class Klass {
  static prop = 1
}

console.log(Klass.constructor, (Klass.constructor as any).prop)
console.log(Klass.prototype.constructor, (Klass.prototype.constructor as any).prop)

Playground Link

ExE-Boss commented 4 years ago

T.constructor is the global Function object for classes (and functions in general), so T.prototype.constructor !== T.constructor, because T !== Function.

ljharb commented 4 years ago

@ExE-Boss oops, you're right; i deleted my comment.

JasonHK commented 4 years ago

Finally this is best solution eval('this.constructor.staticFn()')

@Lugeen Then what's the reason you use TypeScript? Why don't you just use plain JavaScript?

trusktr commented 4 years ago

eval('this.constructor.staticFn()')

Isn't this better?

// @ts-ignore
this.constructor.staticFn()
Lugeen commented 4 years ago

@trusktr thank you , your answer is perfect solution .

KisaragiEffective commented 3 years ago

I thought this can be implement if there's exact type

canonic-epicure commented 3 years ago

Every time I type (this.constructor as typeof MyClass).someStaticMethod() I scold the TypeScript with the last words. Optimized for developers stress?

whzx5byb commented 3 years ago

I'd like to share my workaround, for referring static members in a convenient way. But the newable signature is not working well.

type Constructor = new (...args: any[]) => any;

function TypedConstructor<S extends void | Constructor = void>(Super?: S): 
    S extends Constructor 
        ? {
            [K in keyof S]: S[K]
        } & {
            new <U>(...args: ConstructorParameters<S>): InstanceType<S> & { constructor: U }
        } 
        : {
            new <U>(): { constructor: U }
        }
{
    return Super || Object as any;
}

// for base class, use TypedConstructor()<typeof [ThisName]>
class A extends TypedConstructor()<typeof A> {

    static staticA() { return 0; }
    protected static protected_staticA() { return 1; }

    constructor(x: number) {
        if (typeof x !== 'number') {
            throw new Error('Argument type error!')
        }

        super();
    }

    foo() {
        console.log(this.constructor.staticA()) // OK
        console.log(this.constructor.protected_staticA()) // OK
    }
}

// for inherited class, use TypedConstructor(Super)<typeof [ThisName]>
class B extends TypedConstructor(A)<typeof B> {

    static staticB() { return 'bbb'; }
    protected static protected_staticB() { return 'xxx'; }

    constructor(x: string) {
        if (typeof x !== 'string') {
            throw new Error('Argument type error!')
        }
        super(Number(x));
    }

    foo() {
        super.foo();
    }

    bar() {
        console.log(this.constructor.staticA()); // OK
        console.log(this.constructor.protected_staticA()) // OK
        console.log(this.constructor.staticB()); // OK
        console.log(this.constructor.protected_staticB()); // OK
    }
}

var a = new A(0);
var b = new B('1');
b.bar();

// Do not use `new` with `constructor`!
var c = new b.constructor(0) // Compiled, but will throw runtime error.