Skip to content

Continue inferring single generic signature's type parameters in presence of errors #19159

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

Conversation

sandersn
Copy link
Member

Fixes #18715

Type inference initially skips context-sensitive arguments when inferring type parameters. After it has inferred all it can from the non-context-sensitive arguments, it enables the context-sensitive arguments one at a time and retries inference each time.

Previously, this process would stop as soon as inferences resulted in an erroneous signature -- one that was not assignable to the original signature. But stopping early can miss inferences from context-sensitive arguments that might not result in errors. This changes chooseOverload to continue inferring from context-sensitive arguments, even when a previous set of inferences resulted in an erroneous signature. chooseOverload only gives up if inferring from all the arguments results in an error. This improves inference in tricky cases like this:

declare function inference<T>(target: T, name: keyof T): void;
inference({ a: 1, b: 2, c: 3, d(n) { return n } }, "d");

Here, the only inferences come from the context-sensitive object literal -- inferring from "d" to keyof T gives nothing. But those inferences come in the second round, even though the first round is an error after inferring T={}, because "d" is not in keyof {}.

Notably, however, this improvement only applies to generic signatures without overloads. The resolution process interleaves overload checking with the progressive checking of context-sensitive arguments. Because inference can fix the types of context-sensitive arguments, checking more context-sensitive arguments after encountering an error can leave those arguments fixed to types that are known to be incorrect.

For example:

function forEachKey<T>(map: Map<__String>, callback: (key: __String) => T | undefined): T | undefined;
function forEachKey<T>(map: Map<string>, callback: (key: string) => T | undefined): T | undefined;
forEachKey(createMap<string>(), key => 101);

If this improvement were applied to multiple overloads, then, when createMap<string>() failed to match the first overload, inference would continue on to inference from key => 101. It would infer T=number, but it would also fix the type of key to __String. Then it would fail to match the first overload again (createMap<string>() is still of type Map<string> not Map<__String>). When checking the second overload, though, key would still be fixed to __String, so even though createMap<string>() would match, key would not.

Type inference initially skips context-sensitive arguments when
inferring type parameters. After it has inferred all it can from the
non-context-sensitive arguments, it enables the context-sensitive
arguments one at a time and retries inference.

Previously, this process would stop as soon as inferences resulted in an
erroneous signature -- one that was not assignable to the original
signature. But stopping early can miss inferences from context-sensitive
arguments that might not result in errors. This changes `chooseOverload`
to continue inferring from context-sensitive arguments, even when a
previous set of inferences resulted in an erroneous signature.
`chooseOverload` doesn't give up until it has collected inferences from
all arguments. This improves inference in tricky cases like this:

```ts
declare function inference<T>(target: T, name: keyof T): void;
inference({ a: 1, b: 2, c: 3, d(n) { return n } }, "d");
```

Here, the only inferences come from the context-sensitive object
literal -- inferring from `"d"` to `keyof T` gives nothing. But those
inferences come in the second round, even though the first round is an
error after inferring `T={}` and noticing that `"d"` is not in `keyof
{}`.

Notably, however, this improvement only applies to generic signatures without
overloads. The resolution process interleaves overload checking with the
progressive checking of context-sensitive arguments. Because of the way
that inference can fix the types of context-sensitive arguments,
checking more context-sensitive arguments after encountering an error
can leave those arguments fixed to types that are known to be incorrect.

For example:

```ts
function forEachKey<T>(map: Map<__String>, callback: (key: __String) => T | undefined): T | undefined;
function forEachKey<T>(map: Map<string>, callback: (key: string) => T | undefined): T | undefined;
forEachKey(createMap<string>(), key => 101);
```

If this improvement were applied to multiple overloads, then, when
`createMap<string>()` failed to match the first overload, inference
would continue on to inference from `key` => 101`. It would infer
`T=number`, but it would also fix the type of `key` to `__String`. Then
it would fail to match the first overload again (`createMap<string>()`
is still of type `Map<string>` not `Map<__String>`). When checking the
second overload, though, `key` would *still* be fixe to `__String`, so
even though `createMap<string>()` would match, `key` would not.
@sandersn
Copy link
Member Author

Obsoleted by #19227

@sandersn sandersn closed this Oct 31, 2017
@mhegazy mhegazy deleted the sandersn/try-harder-to-infer-single-generic-type-parameters branch October 31, 2017 18:21
@microsoft microsoft locked and limited conversation to collaborators Jun 14, 2018
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

keyof operator corrupted if object which contains call signature passed to generic function.
1 participant