TypeScript Version: nightly (on playground).
Search Terms:
generic, return, assertion, inference, checker api.
Code
declare function genericGet<TValue>(object: any, path: string): TValue
const object = { foo: { bar: 123 } };
const result1 = genericGet(object, 'foo.bar');
const result2 = genericGet(object, 'foo.bar') as number;
Expected behavior:
In both cases, the return type of genericGet(object, 'foo.bar') reported by the type checker API (checker.getTypeAtLocation) should be unknown.
Actual behavior:
In the first case, the return type of genericGet(object, 'foo.bar') reported by the type checker API is unknown.

In the second case, the return type is number.

(best shown via ts-ast-viewer, link below).
Related Issues:
https://github.com/microsoft/TypeScript/issues/31435
Loosely related to: https://github.com/microsoft/TypeScript/issues/35431
Related issue in typescript-eslint:
https://github.com/typescript-eslint/typescript-eslint/issues/1410
Related rule logic:
https://github.com/typescript-eslint/typescript-eslint/blob/aee723813ec47ccac0a165cf1bc9674f6257b609/packages/eslint-plugin/src/rules/no-unnecessary-type-assertion.ts#L244-L292
I found https://github.com/microsoft/TypeScript/issues/31435, which seems to suggest that this is entirely intentional.
However I would like to question this from the context of the type checker API.
This makes the type API pretty hard to use for certain use cases. Inspecting the type of the assertion expression (via checker.getTypeAtLocation) says it's of type number, and similarly inspecting the type of the call expression via the same API says it's of type number.
Is there any way to get the type that hasn't been inferred from a return type assertion?
In the context of our no-unnecessary-type-assertion rule, the rule warns about assertions that don't do anything, so that there is less useless code in a codebase..
(i.e. genericGet(object, 'foo.bar') as number is necessary, because it asserts unknown to number, but genericGet<number>(object, 'foo.bar') as number is unnecessary, because the return type is already number).
There isn't a way to do this; the type of the expression really truly actually for real is number, and an unknown is never produced during the inference process for TValue. We don't have any concept of "what would the type have been were it not for the surrounding context of the expression", which seems to be what you'd really need to assess the underlying intent of the rule.
Hopefully this has the end effect of dissuading people from writing functions like genericGet in the first place?
A real world example of this is lodash.get, unfortunately:
(edit: older versions of lodash typings, that is)
interface lodashGet<TObject, TResult> {
(object: TObject, path: string | string[], defaultValue?: TResult): TResult;
}
const result = lodashGet(object, 'foo.bar') as number;
Overrides are one way to work around that specific one:
function lodashGet<TObject, TResult>(object: TObject, path: string | string[]): unknown;
function lodashGet<TObject, TResult>(object: TObject, path: string | string[], defaultValue: TResult): TResult;
function lodashGet<TObject, TResult>(object: TObject, path: string | string[], defaultValue?: TResult): TResult {
// pretend this works
}
…but I'm wary of having to patch common libraries when they have generic return types that are optional like that
I was trying to find that definition, but it looks like the types for lodash's get method have been somewhat refined so that it doesn't use the "generic that is only used in the return position" anti-pattern.
Oh, nice find! I can upgrade my type declarations to mitigate that instance then 👍
Seems likely to come up in other contexts though?
This issue has been marked as 'Question' and has seen no recent activity. It has been automatically closed for house-keeping purposes. If you're still waiting on a response, questions are usually better suited to stackoverflow.
Most helpful comment
This issue has been marked as 'Question' and has seen no recent activity. It has been automatically closed for house-keeping purposes. If you're still waiting on a response, questions are usually better suited to stackoverflow.