Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Call class function with generic arguments inside class gives error 2345 #41899

Closed
aponomy opened this issue Dec 9, 2020 · 6 comments
Closed
Labels
Working as Intended The behavior described is the intended behavior; this is not a bug

Comments

@aponomy
Copy link

aponomy commented Dec 9, 2020

TypeScript Version: 4.1.2

Search Terms:
(arg1:keyof T, arg2:T[typeof arg1])

Code

type MyType = {
    foo:string
}

class MyClass<T extends MyType = MyType> {

    /* this is the failing function */
    callMe = (arg1:keyof T, arg12:T[typeof arg1]) => null;

    /* callMe not working when calling inside the class */
    testInsideClass = () => {
        this.callMe('foo', 'not-working'); // <-- Compiler error on second argument
    }
}

/* callMe  working when calling from an instance of the class */
const classInstance = new MyClass<MyType>();
classInstance.callMe('foo', 'working');  // <-- No compiler error here

Expected behavior:
Able to call class function callMe from within the class since T extends MyType

Actual behavior:
Compiler says argument of type 'string' is not assignable to parameter of type 'T[keyof T]'.ts(2345)

Playground Link:

Related Issues:
Maybe #39945 is related

@RyanCavanaugh RyanCavanaugh added the Working as Intended The behavior described is the intended behavior; this is not a bug label Dec 9, 2020
@RyanCavanaugh
Copy link
Member

This is a correct error. Allowing this would produce an unsoundness:

type MyType = {
    foo:string
}

class MyClass<T extends MyType = MyType> {
    callMe = (arg1:keyof T, arg12:T[typeof arg1]) => null;
    testInsideClass = () => {
        this.callMe('foo', 'not-working');
    }
}

const classInstance = new MyClass<{foo: "not that"}>();
// Causes value "not-working" to inhabit parameter arg12, which has type "not that" in this instantiation
classInstance.testInsideClass();

@aponomy
Copy link
Author

aponomy commented Dec 9, 2020

@RyanCavanaugh Thanks a lot! I didn't realize it was allowed for an extended type to change type of one of one of it's properties? But why is this not working then? Sorry if I miss something obvious.

type MyType = {
    foo:string
}

type MySecondType = {
    foo:number
}

class MyClass<T extends MyType = MyType> {}

const classInstance = new MyClass<MySecondType>();    // <- Compiler error here

// Type 'MySecondType' does not satisfy the constraint 'MyType'.
//  Types of property 'foo' are incompatible.
//   Type 'number' is not assignable to type 'string'.ts(2344)

@RyanCavanaugh
Copy link
Member

The example I showed was subtyping, which is what extends is a check for, but in your example MySecondType is not a subtype of MyType.

@aponomy
Copy link
Author

aponomy commented Dec 11, 2020

Yes, my hint was maybe it has to do with another aspect of subtyping - maybe this example is better?

type MyType = {
    foo:string
}

class MyFirstClass<T extends MyType = MyType> {

    func1(arg:keyof T) {}
    func2(arg1:keyof T, arg2:T[typeof arg1]) {}

    constructor() {

        // working
        this.func1('foo'); 

        // Compiler error: Argument of type 'string' is not assignable to parameter of type 'T[keyof T]'.ts(2345)
        this.func2('foo', 'not-working');
    }
}

type MySecondType = MyType & {
    bar: string		
}

class MySecondClass<T extends MySecondType = MySecondType> extends MyFirstClass<T> {

    constructor() {
        super();
		
        // working
        this.func1('bar');

        // Compiler error: Argument of type 'string' is not assignable to parameter of type 'T[keyof T]'.ts(2345)
        this.func2('foo', 'not-working');

        // Compiler error: Argument of type 'string' is not assignable to parameter of type 'T[keyof T]'.ts(2345)
        this.func2('bar', 'not-working');
    }
}

const classInstance = new MySecondClass<MySecondType>(); 
// These are working of course:
classInstance.func2('foo', 'working');
classInstance.func2('bar', 'working');

@typescript-bot
Copy link
Collaborator

This issue has been marked 'Working as Intended' and has seen no recent activity. It has been automatically closed for house-keeping purposes.

@aponomy
Copy link
Author

aponomy commented Dec 15, 2020

If anyone get the same problem, it seams like this limitation is by design and suggested work-around-pattern is described in #30480.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Working as Intended The behavior described is the intended behavior; this is not a bug
Projects
None yet
Development

No branches or pull requests

3 participants