Skip to content

Type inference issue when using generic function and generic constraint #24747

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

Closed
ulrichb opened this issue Jun 7, 2018 · 7 comments
Closed
Labels
Design Limitation Constraints of the existing architecture prevent this from being fixed Rescheduled This issue was previously scheduled to an earlier milestone
Milestone

Comments

@ulrichb
Copy link

ulrichb commented Jun 7, 2018

TypeScript Version: 3.0.0-dev.20180607 and 2.9.1

Search Terms: type inference generic constraint function callback accessor

Code

type GenericFunc<TA, TB> = (x: TA) => TB;

class GenericFuncHolder<TA, TB> {
    constructor(readonly func: GenericFunc<TA, TB>) { }
}

function wrap<TA, TB>(func: GenericFunc<TA, TB>) { return new GenericFuncHolder(func); }

class X { readonly xProperty = "xProperty"; }

class Y<TX> { readonly x: TX | undefined; readonly yProperty = "yProperty"; }
class YDerived<TX> extends Y<TX> { readonly yDerivedProperty = "yDerivedProperty"; }

class GenericTarget<TX, TY> {

    constructor(readonly x: TX, readonly y: TY) { }

    holderParams<TX2, TY2 extends Y<TX2>>(
        holderX: GenericFuncHolder<TX, TX2>, holderY: GenericFuncHolder<TY, TY2>,
        use: (x: TX2, y: TY2) => void,
    ) {
        use(holderX.func(this.x), holderY.func(this.y));
    }

    funcParamsWithoutConstraint<TX2, TY2>(
        funcX: GenericFunc<TX, TX2>, funcY: GenericFunc<TY, TY2>,
        use: (x: TX2, y: TY2) => void,
    ) {
        use(wrap(funcX).func(this.x), wrap(funcY).func(this.y));
    }

    funcParamsWithConstraint_BAD<TX2, TY2 extends Y<TX2>>(
        funcX: GenericFunc<TX, TX2>, funcY: GenericFunc<TY, TY2>,
        use: (x: TX2, y: TY2) => void,
    ) {
        use(wrap(funcX).func(this.x), wrap(funcY).func(this.y));
    }
}


const genericClassTarget = new GenericTarget (new X(), new Y<X>());

// Type inference works when having the constraint but using `wrap()` on the call site:
genericClassTarget.holderParams(wrap(x => new X()), wrap(y => new YDerived<X>()),
    (x2, y2) => console.log(x2.xProperty, y2.yDerivedProperty));

// Type inference works without the `TY2` constraint:
genericClassTarget.funcParamsWithoutConstraint(x => new X(), y => new YDerived<X>(),
    (x2, y2) => console.log(x2.xProperty, y2.yDerivedProperty));

// Type inference doesn't work, TSC emits "TS2339: Property 'xProperty' does not exist on type '{}'"
genericClassTarget.funcParamsWithConstraint_BAD(x => new X(), y => new YDerived<X>(),
    (x2, y2) => console.log(x2.xProperty, y2.yDerivedProperty));

(Sorry for the long repro, it's the simplified version of a production issue and the inference issue seems to be a very special case.)

Expected behavior:
Type inference works for funcParamsWithConstraint_BAD as it works for holderParams and funcParamsWithoutConstraint. It seems that the generic constraint of TY2 narrows TX2 to {}, but this works when using wrap() on the call site. Which is the reason, why I would expect funcParamsWithConstraint_BAD to work just fine.

Actual behavior:
"TS2339: Property 'xProperty' does not exist on type '{}'" in the last line.

Playground Link: Playground Link

EDIT: Extended repro to make it more realistic.

@mhegazy mhegazy added the Needs Investigation This issue needs a team member to investigate its status. label Jun 7, 2018
@RyanCavanaugh
Copy link
Member

TY2 in _BAD isn't doing anything. The signature should be written as:

funcParamsWithConstraint_BAD<TX2>(
    funcX: GenericFunc<TX, TX2>, funcY: GenericFunc<TY, Y<TX2>>,
    useX2: (x: TX2) => void,
) {
    wrap(funcX); wrap(funcY);
}

This works as expected.

@ulrichb
Copy link
Author

ulrichb commented Jun 7, 2018

@RyanCavanaugh Okay, maybe I oversimplified the repro sample. I updated it (now indeed using TY2).

The real code is a fluent API, where I need the type inference and the TY2 extends Y<TX2> constraint.

@ulrichb
Copy link
Author

ulrichb commented Jun 7, 2018

@RyanCavanaugh Also added YDerived to motivate TY2 better.

@ulrichb
Copy link
Author

ulrichb commented Jul 20, 2018

@RyanCavanaugh Any news on this?

I have now a second but much easier sample using a conditional type instead of a generic constraint:

type GenericFunc<TA, TB> = (x: TA) => TB;

class GenericFuncHolder<TA, TB> {
    constructor(readonly func: GenericFunc<TA, TB>) { }
}

function wrap<TA, TB>(func: GenericFunc<TA, TB>) { return new GenericFuncHolder(func); }

//

export type PropertyToTypeName<TProperty> =
    TProperty extends string | undefined ? "text" :
        TProperty extends number | undefined ? "number" :
            never;

function withFunc<TProperty>(
    func: GenericFunc<string, TProperty>, typeName: PropertyToTypeName<TProperty>) {
    console.log('func points to', typeName);
}

function withHolder<TProperty>(
    holder: GenericFuncHolder<string, TProperty>, typeName: PropertyToTypeName<TProperty>) {
    console.log('holder points to', typeName);
}


withHolder(wrap(x => x.length), "number"); // works fine

// emits TS2345: Argument of type '"number"' is not assignable to parameter of type 'never'
withFunc(x => x.length, "number");

Playground link

... like in the sample above the problem is that in the withFunc-case TProperty infers to {} which you can see when adding TProperty extends {} ? "[is empty object]" to the conditional type.

@ulrichb
Copy link
Author

ulrichb commented Jul 20, 2018

One more observation: When adding TProperty extends string | number constraints in the second example it works (also for withFunc). It seems that the inference works correctly because it cannot infer down to {}.

@ulrichb ulrichb changed the title Type inferrence issue when using generic function and generic constraint Type inference issue when using generic function and generic constraint Jul 22, 2018
@RyanCavanaugh RyanCavanaugh added Bug A bug in TypeScript and removed Needs Investigation This issue needs a team member to investigate its status. labels Aug 23, 2019
@RyanCavanaugh RyanCavanaugh added this to the TypeScript 3.7.0 milestone Aug 23, 2019
@RyanCavanaugh
Copy link
Member

I feel like I'm missing something, because this example really shouldn't error, but has since 2.8

type TypeName<T> = T extends number ? "number" : "something else";
function fn<T>(func: (x: string) => T, p: TypeName<T>): void { }
// Error
fn(x => x.length, "number");

@RyanCavanaugh
Copy link
Member

RyanCavanaugh commented Oct 29, 2019

The sample above now produces a crash in the LS in the Playground...

Uncaught Error: Cannot read property 'kind' of undefined

TypeError: Cannot read property 'kind' of undefined
    at tE (unpkg.com/@typescript-deploys/[email protected]/min/vs/language/typescript/tsWorker.js:7)
    at Mh (unpkg.com/@typescript-deploys/[email protected]/min/vs/language/typescript/tsWorker.js:7)
    at Object.getTypeAtLocation (unpkg.com/@typescript-deploys/[email protected]/min/vs/language/typescript/tsWorker.js:7)
    at i (unpkg.com/@typescript-deploys/[email protected]/min/vs/language/typescript/tsWorker.js:7)
    at Object.getCodeActions (unpkg.com/@typescript-deploys/[email protected]/min/vs/language/typescript/tsWorker.js:7)
    at unpkg.com/@typescript-deploys/[email protected]/min/vs/language/typescript/tsWorker.js:7
    at Object.f.flatMap (unpkg.com/@typescript-deploys/[email protected]/min/vs/language/typescript/tsWorker.js:7)
    at Object.e.getFixes (unpkg.com/@typescript-deploys/[email protected]/min/vs/language/typescript/tsWorker.js:7)
    at unpkg.com/@typescript-deploys/[email protected]/min/vs/language/typescript/tsWorker.js:7
    at Object.f.flatMap (unpkg.com/@typescript-deploys/[email protected]/min/vs/language/typescript/tsWorker.js:7)
    at editor.main.js:38

@RyanCavanaugh RyanCavanaugh added Design Limitation Constraints of the existing architecture prevent this from being fixed and removed Bug A bug in TypeScript labels Nov 6, 2019
@RyanCavanaugh RyanCavanaugh added the Rescheduled This issue was previously scheduled to an earlier milestone label Aug 31, 2020
@RyanCavanaugh RyanCavanaugh removed their assignment Jan 26, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Design Limitation Constraints of the existing architecture prevent this from being fixed Rescheduled This issue was previously scheduled to an earlier milestone
Projects
None yet
Development

No branches or pull requests

5 participants