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

Specify whether generic is allowed to be infered as {} #15968

Closed
NN--- opened this issue May 20, 2017 · 14 comments
Closed

Specify whether generic is allowed to be infered as {} #15968

NN--- opened this issue May 20, 2017 · 14 comments
Labels
Declined The issue was declined as something which matches the TypeScript vision Suggestion An idea for TypeScript

Comments

@NN---
Copy link

NN--- commented May 20, 2017

I would like to specify that 'To' is not allowed to be inferred as '{}'.
Perhaps some compilation flag, --noInferObjectFromGeneric, or something else.

function convert<To, From>(from: From): To {
    return from as any as To; // doesn't really matter
}

var x = convert(1);  // To = {}
@NN--- NN--- changed the title Specify whether Object is allowed to be infered Specify whether generic is allowed to be infered as {} May 20, 2017
@kitsonk
Copy link
Contributor

kitsonk commented May 21, 2017

Are you suggesting that the generic be inferred as any? Essentially {} is equivalent to any anyways.

Default generics allow you to suggest something else to inferred:

function convert<To = string, From = string>(from: From): To {
    return from as any as To; // doesn't really matter
}

var x = convert(1);  // To = string

@NN---
Copy link
Author

NN--- commented May 21, 2017

No, I mean to get a compiler error in such case.

@mhegazy
Copy link
Contributor

mhegazy commented May 22, 2017

To here is rather meaningless as a type parameter. there is no way for the code at run-time to observe what To is. so this is merely a type cast at compile time. The language already has support for this in the form of type assertions. I would recommend using that instead.

@mhegazy mhegazy added the Working as Intended The behavior described is the intended behavior; this is not a bug label May 22, 2017
@NN---
Copy link
Author

NN--- commented May 22, 2017

It is just a sample.
I want an option to disallow using such functions.

@mhegazy
Copy link
Contributor

mhegazy commented May 22, 2017

Can you shed some light on the scenario.

@NN---
Copy link
Author

NN--- commented May 22, 2017

Only if all generic parameters can be deduced either manually or from parameters, the function is allowed to called.
Similar to way it is implemented in C#

@mhegazy
Copy link
Contributor

mhegazy commented May 22, 2017

i understand. can you share a code sample where you feel you need this?

@NN---
Copy link
Author

NN--- commented May 22, 2017

Let's take this sample:

class Base<T> { field: T; }
class Sub<T> extends Base<T> { constructor(public field: T) { super(); } }
function test<T1, A extends Base<T1>, T2>(arr: A, t2: T2) {
	const data: Array<T1 | T2> = [];
	data.push(arr.field);
	data.push(t2);
	return { input: arr, data };
}

const res = test(new Sub(1), "a"); // T1 = {} , A = Sub<number>, T2 = string
const first: number|string = res.data[0]; // error

Nothing is wrong here and type inference is according to the rules.
The only problem is that T1 inferred as {}/
For my understanding it happens since there is no type specified for T1 from parameters and TSC can choose anything it wants, so {} is good enough.

I want to have such functions to not compile and require writing explicit T1..

I don't want to talk about high-order kinds which solves the problem.
Maybe both samples are different issues.
The main part is that I don't want the implicitly deduced type {} just because there is no other option.

@mhegazy mhegazy added Suggestion An idea for TypeScript In Discussion Not yet reached consensus and removed Working as Intended The behavior described is the intended behavior; this is not a bug labels May 22, 2017
@masaeedu
Copy link
Contributor

Related to #14829

@mattmccutchen
Copy link
Contributor

mattmccutchen commented Jul 15, 2018

Here's a workaround, perhaps not to be taken too seriously (edited to work if T1 is set to a type variable in scope at the call site):

const DUMMY = Symbol();
interface Do_not_mess_with_this_type_parameter {
    [DUMMY]: never;
}
type IfDefinitelyNever<X, A, B, G extends Do_not_mess_with_this_type_parameter> =
    ("good" | G) extends {[P in keyof X]: "good"}[keyof X] ? B : ([X] extends [never] ? A : B);

class Base<T> { field: T; }
class Sub<T> extends Base<T> { constructor(public field: T) { super(); } }
interface T1_was_not_inferred_please_specify_it {
    [DUMMY]: never;
}
function test<T1 = never, A extends Base<T1> = Base<T1>, T2 = never,
    G extends Do_not_mess_with_this_type_parameter = never>(
    arr: A & IfDefinitelyNever<T1, T1_was_not_inferred_please_specify_it, {}, G>, t2: T2) {
    let arr_: A = arr;
    const data: Array<T1 | T2> = [];
    data.push(arr_.field);
    data.push(t2);
    return { input: arr_, data };
}

// error: Argument of type 'Sub<number>' is not assignable to parameter of type 'Sub<number> & T1_was_not_inferred_please_specify_it'.
const res = test(new Sub(1), "a");
const first: number|string = res.data[0];

I support the suggestion. I have another use case for it that I'll write up if I can think of a simple way to explain it.

@trusktr
Copy link
Contributor

trusktr commented Nov 2, 2018

Here's another example (thanks @mattmccutchen for the answer there!).

@RyanCavanaugh RyanCavanaugh added Declined The issue was declined as something which matches the TypeScript vision and removed In Discussion Not yet reached consensus labels Aug 19, 2019
@RyanCavanaugh
Copy link
Member

Setting the default to never or unknown is pretty much guaranteed to produce a downstream error. Uninferrable type parameters are a strong anti-pattern regardless; the example code in the OP simply should not be written.

@SephReed
Copy link

SephReed commented Jul 9, 2021

It's not that it doesn't cause an error, it's that the user facing error is not obvious.

This is extremely debuggable:

function X requires 1-2 Generics

This is much less so:

type something something unknown something something doesn't match type something something something something

5k views on this question btw: https://stackoverflow.com/questions/53109837/how-to-make-a-generic-type-argument-required-in-typescript

@jbreckmckye
Copy link

It is possible to use never to enforce mandatory type parameters.

Consider this function:

function myFunction<T = never, Input extends T = T>(input: Input): T { return input }

This function requires

  • one parameter
  • one type parameter
  • the parameter and type parameter to match

Demonstration:

 // TS-2345: Argument of type 'string' is not assignable to parameter of type 'never'
const value1 = myFunction('hello')

// TS-2345: Argument of type 'number' is not assignable to parameter of type 'string'.(2345)
const value2 = myFunction<string>(123)

// Passes fine!
const value3 = myFunction<string>('hello')

This works because without a T, Input must extend never, which no non-never value can do.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Declined The issue was declined as something which matches the TypeScript vision Suggestion An idea for TypeScript
Projects
None yet
Development

No branches or pull requests

9 participants