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

Generic type inferance not working #60981

Open
aceArt-GmbH opened this issue Jan 16, 2025 · 3 comments
Open

Generic type inferance not working #60981

aceArt-GmbH opened this issue Jan 16, 2025 · 3 comments
Labels
Working as Intended The behavior described is the intended behavior; this is not a bug

Comments

@aceArt-GmbH
Copy link

aceArt-GmbH commented Jan 16, 2025

πŸ”Ž Search Terms

"generic type inferance"

πŸ•— Version & Regression Information

  • This is the behavior in version 5.5 & 5.7

⏯ Playground Link

https://www.typescriptlang.org/play/?jsx=0#code/MYGwhgzhAEAiCWYQHsDmBlApgJwG72E2gG8AoaC6ZAB0wDtoBeaAHgSTVjABcwA+ABTlKI4MgC21ZHXrcAXNADCEqTLrcAKgE9aLbbQAm7FKjaITXXnz4AaYSIpi6AM3ioFxzjzD3oASiY+EgBfAG5SYNJSMAAjCG5sMGBuaFBIGH1MI3M0Mw5US34SXwMcgu8AQg8ywvDI0jSoJRVpWWhMAA9uegMMnSzPUzIRMGxMMAU6AFdxGJxwygB6Rehu+IV47Hg6VFDoZegpujFxcTbuZGhUTBScbGRsaFjkKZTR8eg57dRoaZBwGIgTARILDSjvCa-GZzR7MbgAC3gEAAdKV8oVkRC6lEnPFoBAcPhCExfpgAO5wMpYPAETACPzhBrSPEGbwkgBMTxg01mOGgAB8of9GQSaYRkTR6AJlJJWuobMURmNIazeAqDsAptgxuoQFpfpc7g8FRB4S8QAZPkQAAaZaAAch5MIFQpA9ugSINbygbjosSBq0u3H6DqdOHtyOtEQZUQMmDSYw96hwziSRBlqlkmT0oN8MgpAmRRdGqAgCjAdC0AG0ALp+BQaOpAA

πŸ’» Code

class DialogService {
    open = <DialogData>(
        component: ComponentType<TypedDialog<DialogData>>,
        config: DialogData
    ) => {};
}

abstract class TypedDialog<DialogData> {
    dialogData!: DialogData;
}

class Component extends TypedDialog<{
    area: number;
   // test: string; // uncomment to get error about area being nullable
}> {
    area: number = this.dialogData.area;
}

const service = new DialogService();

const data = 2 as number | null;

service.open(Component, {
    area: data, // currently no error, should be `Type 'number | null' is not assignable to type 'number'.`
});

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

πŸ™ Actual behavior

when test is not there, no error about type mismatch of area: data

πŸ™‚ Expected behavior

type error at any time

Additional information about the issue

this seems like a bug as adding new properties show an already existing error.
but maybe I am just misunderstanding how typescript generics work?

@whzx5byb
Copy link

It seems that the generic type was inferred from the second argument config: DialogData instead of the first one, without the additional property test. I'm not sure why it behaved like this, but if you want it to be always inferred from the first one, you can use the NoInfer
utility type
.

class DialogService {
    open = <DialogData>(
        component: ComponentType<TypedDialog<DialogData>>,
-        config: DialogData
+        config: NoInfer<DialogData>
    ) => {};
}

@RyanCavanaugh
Copy link
Member

This is basically the same behavior as seen in e.g.

interface Foo {
    s: number;
}
interface FooNull {
    s: number | undefined;
}

function f<T>(arg1: T, arg2: T) {
    
}

f({} as Foo, {} as FooNull);

There's no error because aliased property writes in TS are generally unsound and the implied initialization of TypeDialog.dialogData is effectively one of those

@RyanCavanaugh RyanCavanaugh added the Working as Intended The behavior described is the intended behavior; this is not a bug label Jan 16, 2025
@aceArt-GmbH
Copy link
Author

@whzx5byb thanks for mentioning NoInfer<>. This seems to indeed help

@RyanCavanaugh Sorry but I still don't quite understand the situation.
In the introduction example of NoInfer<> TypeScript can not understand the intend of the developer so it widens the type.
In your example, the wider type is also chosen, so the developer has to check for undefined.

But in my example the type is not widened but rather is TypeScript lying to me. It is not inferring the type of the wrong argument.

class DialogService {
    open = <DialogData>(
        component: ComponentType<TypedDialog<DialogData>>,
        config: DialogData
    ) => {};
}

abstract class TypedDialog<DialogData> {
    dialogData!: DialogData;
}

class Component extends TypedDialog<{
    area: Date;
   // test: string; // uncomment to get error about area being nullable
}> {
    area: string = this.dialogData.area.toISOString(); // <-- runtime error
}

const service = new DialogService();

let data: Date | null = null;

if (2 as any == "cantMatch") {
    data = new Date();
}

service.open(Component, {
    area: data,
});

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

This example throws no compile error but fails hard at runtime.
Could TypeScript not at least widen the type to Date | null?
And how does adding another property change the situation?

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