-
Notifications
You must be signed in to change notification settings - Fork 12.6k
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
Suggestion: Noninferential type parameter usage #14829
Comments
Is using multiple type parameters not an option? function doSomething<T, U extends T>(value: T, getDefault: () => U) ;
function assertEqual<T, U extends T>(actual: T, expected: U): boolean; |
@mhegazy, speaking from my experience, I never ever would have guessed that
could be amended to do what I want by changing it to
Multiple type parameters are an option! It's just that right now they are a very surprising and unintuitive one. |
I would not have guessed that NoInfer was the solution :) |
Unfortunately, using the declare function invoke<T, U extends T, R>(func: (value: T) => R, value: T): R;
declare function test(value: { x: number; }): number;
invoke(test, { x: 1, y: 2 }); // Works
test({ x: 1, y: 2 }); // Compiler error |
Type of `options` should not be used to infer type parameter `T`. Refs: microsoft/TypeScript#14829
Need to collect some use concrete cases before attempting to move forward with this |
I also ran into the problem that I found a rather simple work around: declare function invoke<T, R>(func: (value: T) => R, value: {} & T): R;
declare function test(value: { x: number; }): number;
invoke(test, { x: 1, y: 2 }); // Compiler error
test({ x: 1, y: 2 }); // Compiler error |
@ajafff 😲 ! I would expect |
@RyanCavanaugh can I expect an official statement whether this is supported / intended behavior or not? I don't want to rely on a bug that - when fixed - will break the build. There may already be some programmers relying on it. I found that pattern while reading some issues in this bug tracker. |
Ran into this when writing some tests. interface User {
name: string;
age: number;
}
type ApiCall<T> = (...args: any[]) => Promise<T>;
declare const fetchUser: ApiCall<User>;
declare function mock<T>(call: ApiCall<T>, result: T): void;
//no error, expected missing property (and property completions ideally)
mock(fetchUser, { name: '' }); Edit: making the undesired inference location an intersection per above fixes things up here, too! Results in the expected missing property error. declare function mock<T>(call: ApiCall<T>, result: {} & T): void; |
Just an update - |
Maybe give it a nice wrapper, so it is very similar to the proposal. type InferLast<T> = T & {} Example: type InferLast<T> = T & {}
class Animal { move }
class Dog extends Animal { woof }
function doSomething<T>(value: T, getDefault: () => InferLast<T>) { }
doSomething(new Dog(), () => new Animal()); // error on 2nd parameter |
I found that mapped types also defer inference, so i tried My final solution is a mapped type intersected with the type itself: export type NoInfer<T> = T & {[K in keyof T]: T[K]}; |
@ajafff FYI - I added your |
Unfortunately, these two types are not equivalent: class ExampleClass<T> {}
type NoInferOk<T> = [T][T extends any ? 0 : never];
type NoInferBad<T> = T extends infer S ? S : never;
export class OkClass<T> {
constructor(private clazz: ExampleClass<T>, private _value: NoInferOk<T>) {
}
get value(): T {
return this._value; // OK. No TS error.
}
}
export class BadClass<T> {
constructor(private clazz: ExampleClass<T>, private _value: NoInferBad<T>) {
}
get value(): T {
return this._value; // TS Error: Type 'NoInferBad<T>' is not assignable to type 'T'. 'T' could be instantiated with an arbitrary type which could be unrelated to 'NoInferBad<T>'.(2322)
}
} |
@RyanCavanaugh Is it possible to document this behaviour ? |
Just linking to this issue I raised about rest parameter inference, as |
I've seen cases when |
@RyanCavanaugh I wonder if, with the recent additions of type parameter modifiers, it wouldn't be possible to use "usage modifiers" over intrinsic types. What I mean is that this could be implemented using a custom syntax/keyword, like this: declare function assertEqual<T>(actual: T, expected: noinfer T): boolean;
const g = { x: 3, y: 2 };
assertEqual(g, { x: 3 }); // Error I also wonder what's the current "status" of this proposal. I understand that the TS team is currently not working on this - but what if I would attempt to implement this? I totally understand that any PR is just a PR and nothing is set in stone until merged (and not even that makes anything set in stone 🤣 ) - but I'm hesitant to implement features that are likely to be rejected. |
I don't see why a new keyword would be needed ( declare function invoke<T, R>(func: (value: infer T) => R, value: T): R;
|
I started working on this here. Feedback is welcome :) |
@sandersn has asked me if there are any learnings that I could share based on my PoC implementation of this feature, so here we go:
TLDR: TS is insanely expressive already, this would add a new tool to author things that are not always possible today without "hacks" or unintuitive workarounds + the cost of the feature seems to be pretty low to me. What not to like about it? 😉 |
What happens when function append<T>(dest: T[], src: NoInfer<T[]>) {
// TODO
} Where the author intended to do: function append<T>(dest: T[], src: NoInfer<T>[]) {
// TODO
} |
My PR doesn't assume that this "does nothing". It blocks the whole type from being used as an inference source. You could intentionally wrap a type that references multiple type parameters with a single |
I'm not 100% sure about this yet but I also think that the builtin |
Just discovered this thread via https://stackoverflow.com/questions/75909231/how-can-this-advanced-function-type-be-achieved/75909852#75909852 where @jcalz enlightened me and pointed out this issue. Thanks for your work @Andarist. |
FYI, the following code seems to do the trick without messing up with the declared types: the drawback is that it has a second type parameter, and, though that parameter has a default so that it needn't be specified, it is quite "inelegant" and, IMO, hardly acceptable (maybe because I can't think of a significant use case for it: that we like it or not, the type function doSomething<T = never, U extends T = T>(t: U) {
// do something with `t`
}
type Num = { x: number };
doSomething<Num>({ x: 1 }); // doSomething<Num, Num> => OK
doSomething({ x: 1 }) // doSomething<never, never> => ERROR! OTOH, a single type parameter |
P.S. Sorry, what I have said above, about a single type parameter // for example, just notice that `T` does not appear in the type of the arguments:
function coercion<T = never>(t: unknown): T { return t as T; }
type Num = { x: number };
coercion<Num>({ x: 1 }); // coercion<Num> => OK
coercion({ x: 1 }) // coercion<never> => OK, returns `never`! I do have a use case where a plain |
In this formula it is completely feed forward, no inference between variables.
|
I have just updated a big code base to use generic react components thanks to Type declarations are complicated enough already, and |
I just wanted to chime in and say that |
We keep getting bugs like this and I keep not finding the original so I'm making a new one so we can find it.
Keywords: ignore type parameter optional inference
Problem
Often with generics, there will be some locations where a type parameter should be inferrable from usage, and other places where the type parameter should only be used to enforce typechecking. This comes up in a variety of contexts
Proposal Sketch
We should be able to mark type parameter consumption sites as being "not eligible for inference". For example, let's say we had a special global type that the compiler knew not to unwrap during inference:
Then we can annotate usage sites
The text was updated successfully, but these errors were encountered: