stc icon indicating copy to clipboard operation
stc copied to clipboard

Tracking of `super()` call in constructor is wrong

Open kdy1 opened this issue 2 years ago • 12 comments

https://github.com/dudykr/stc/blob/273356f921c6152b5a45c41527923a86361ebe13/crates/stc_ts_type_checker/tests/conformance/es6/classDeclaration/superCallBeforeThisAccessing1.ts#L9-L12

The code above should pass, but currently, stc fails to validate it.

The validation logic for it lives at

https://github.com/dudykr/stc/blob/273356f921c6152b5a45c41527923a86361ebe13/crates/stc_ts_file_analyzer/src/analyzer/scope/mod.rs#L663-L681

Not sure why doesn't it work, though.

kdy1 avatar Feb 02 '23 08:02 kdy1

Can I try to fix this one? 😁

mtluiz avatar Feb 09 '23 23:02 mtluiz

Of course, thank you!

kdy1 avatar Feb 10 '23 02:02 kdy1

I think that ploblem is reorder logic.

This code flow is next

// origin
class D extends Base {
    private _t: any;
    constructor() {
        super(i);
        var s = {
            t: this._t
        }
        var i = Factory.create(s);
    }
}

// reorder
class D extends Base {
    private _t: any;
    constructor() {
        var s = {
            t: this._t
        }
        var i = Factory.create(s);
        super(i);
    }
}

//tsc
class D extends Base {
    private _t: any;
    constructor() {
        var s, i;
        super(i); // i value is undefined, type is undefinable
        var s = {
            t: this._t
        }
        var i = Factory.create(s);
    }
}

sunrabbit123 avatar Mar 22 '23 19:03 sunrabbit123

Hello, how's it going! I think, STC seems to validate all hoisted variables by var keywords, at the beginning of function.

First of all, to help our understanding, here's some other examples.

  1. more simplified version of above example (same error)
class Base {
    constructor(c) { }
}
class D extends Base {
    private _t;
    constructor() {
        super(s);
        let s = {
            t: this._t
        }
    }
}
  1. tsc hoisting rules
// error case
function A() { 
   console.log(a)  // Error: Variable 'a' is used before being assigned.
    var a: number
}

// non error case
function B() {
    console.log(a)
    var a: number | undefined
}

https://github.com/dudykr/stc/blob/3bee2888c6033ce859296ddf62fc06ccca883701/crates/stc_ts_file_analyzer/src/analyzer/hoisting/mod.rs#L46

on this line, STC always validates all hoisted variables.

could you please give me more information about this issue?

ytw0728 avatar Mar 22 '23 19:03 ytw0728

If it's a problem of reordering, we can add some code to https://github.com/dudykr/stc/blob/3bee2888c6033ce859296ddf62fc06ccca883701/crates/stc_ts_ordering/src/stmt.rs#L24-L29

kdy1 avatar Mar 23 '23 01:03 kdy1

It seems that the 'reorder' function is working as intended. However, what I am trying to convey is that, for certain Var::Decl related tasks, we need to first declare and register types to a certain extent, and then iterate through the entire block sequentially.

sunrabbit123 avatar Mar 23 '23 01:03 sunrabbit123

so example

class D extends Base {
    private _t: any;
    constructor() {
        var s, i;
		// declare_var `s, i` with subset of undefined
		// when this example, Factory create return type declared any, we shouldn't know s type
		// maybe, i declared any, s declared undefined or temp object literal

        super(i); // i value is undefined, type is undefinable
        var s = {
            t: this._t
        } // Overwriting the type after declaration
        var i = Factory.create(s); // Overwriting the type after declaration
    }
}

sunrabbit123 avatar Mar 23 '23 01:03 sunrabbit123

There are much more complex test cases, but feel free to give it a try

kdy1 avatar Mar 23 '23 01:03 kdy1

I have looked at other test cases, but I don't see any for more complex cases. What cases are you concerned about?

declare var Factory: {create: <T>(arg:T) => T};

class Base {
    constructor(c) { }
}
class D extends Base {
    private _t;
    constructor() {
        super(i); // error Variable 'i' is used before being assigned.(2454)
        var s = {
            t: this._t
        }
        var i = Factory.create(s);
    }
}

class E extends Base {
    private _t;
    constructor() {
        let x = {
            j: this._t, // error 'super' must be called before accessing 'this' in the constructor of a derived class
        }
        super(undefined);
    }
}

class F extends Base {
    private _t;
    constructor() {
        let x = () => { this._t };
        x();  // no error; we only check super is called before this when the container is a constructor
        this._t;  // error 'super' must be called before accessing 'this' in the constructor of a derived class
        super(undefined);
    }
}

sunrabbit123 avatar Mar 23 '23 02:03 sunrabbit123

If all the rhs expressions of var are checked, eventually the following code and result will be the same:

class D extends Base {
    private _t;
    constructor() {
        let s = {
            t: this._t // error 'super' must be called before accessing 'this' in the constructor of a derived class.(17009)
        }
        let i = Factory.create(s);
        super(i);
    }
}

That's why an error occurs.

sunrabbit123 avatar Mar 23 '23 02:03 sunrabbit123

I'm not sure about the way to validate declarations partially. Any ideas?

kdy1 avatar Mar 24 '23 07:03 kdy1

If you're only going to fix this issue, you're delaying the parameter check when you check the function's return value

sunrabbit123 avatar Mar 24 '23 09:03 sunrabbit123