Skip to content

Unrelated overload signature affects inference #25917

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

Closed
cartant opened this issue Jul 25, 2018 · 6 comments
Closed

Unrelated overload signature affects inference #25917

cartant opened this issue Jul 25, 2018 · 6 comments
Labels
Design Limitation Constraints of the existing architecture prevent this from being fixed

Comments

@cartant
Copy link

cartant commented Jul 25, 2018

In fixing a problem with the typings for the RxJS first and last operators, I've stumbled across some weird behaviour with overload signatures.

TypeScript Version: next (3.1.0-dev.20180725)

Search Terms:

overload signature inference unrelated incorrect

Code

interface Foo<T> {}
interface Bar<T, S> {}

export function foo<T, S extends T>(predicate: (value: T) => value is S, defaultValue?: S): Bar<T, S>;
export function foo<T>(predicate: (value: T) => boolean, defaultValue?: T): Foo<T>;
export function foo<T>(predicate: (value: T) => boolean, defaultValue?: any): Bar<T, any> | Foo<T> {
  throw new Error('Unimplemented');
}

For the behaviour to be effected, strictFunctionTypes must be true and strictNullChecks must be false:

{
  "compilerOptions": {
    "strictFunctionTypes": true,
    "strictNullChecks": false
  },
  "files": ["index.ts"]
}

Expected behavior:

When passing a function that is not a user-defined type guard, I'd expect the inferred type to be independent of whether or not the type-guard-accepting overload signature is available or is commented out.

Actual behavior:

However, if the signature that accepts a type guard is available, the inferred type - for a call that does not involve a user-defined type guard - will be:

const predicated = foo(x => x === 's', 's'); // Foo<"s">

And if the signature that accepts a type guard is commented out, the inferred type - for a call that does not involve a user-defined type guard - will be:

const predicated = foo(x => x === 's', 's'); // Foo<string>

In RxJS, this causes a problem when the operators are used with pipe. If a return type with a string literal is inferred - e.g. MonoTypeOperatorFunction<"s"> - an error will be effected if the operators are used with a string source, as string won't be assignable to "s".

Also, why is the behaviour dependent upon the above-mentioned compiler options? In particular, why does it depend upon the strictNullChecks option?

Related Issues: None found.

@ghost
Copy link

ghost commented Jul 25, 2018

I'm actually seeing Foo<{}> when the predicate overload is available.

@ghost ghost added the Bug A bug in TypeScript label Jul 25, 2018
@mhegazy mhegazy added Design Limitation Constraints of the existing architecture prevent this from being fixed and removed Bug A bug in TypeScript labels Jul 25, 2018
@mhegazy
Copy link
Contributor

mhegazy commented Jul 25, 2018

This is a design limitation of how overload resolution is implemented. when the compiler is trying to resolve overloads it goes through them in order one at a time trying to see which is a fit. The arguments to the function can be "contextually sensitive", i.e. their type depends on the containing context, for instance function expressions or object literals. in this case the compiler will try to infer a contextual type to go with the function expression. the type is then used to contextually type the function expression, and then check the body of the function to infer the return type. if that fits, then good, the signature is picked, if that does not the compiler moves on to the next overload. the issue here is once a function expression gets a contextual type, there is no going back. the compiler can not just remove the type and recheck it (that is not how it is implemented). so what happens is that the next overload will have the same contextual type from the previous overload, ({} in the OP). So though the first overload was not picked, it did contribute to the type of your function expression.

A better implementation of overload resolution would be one that can erase contextual types, and try others without side effects. this, however, is a tantamount to a complete rewrite to the overload resolution logic, and has some drastic impacts on performance.

@cartant
Copy link
Author

cartant commented Jul 25, 2018

@Andy-MS The behaviour is sensitive to the compiler options mentioned in the issue:

"strictFunctionTypes": true,
"strictNullChecks": false

For example, if strictNullChecks is true, the inferred type will be Foo<{}>.

@cartant
Copy link
Author

cartant commented Jul 25, 2018

@mhegazy Why is the behaviour different - with Foo<{}> inferred instead of Foo<"s"> - if the seemingly unrelated strictNullChecks compiler option is true instead of false?

@mhegazy
Copy link
Contributor

mhegazy commented Jul 25, 2018

I am not seeing that on the latest version of the compiler.

@cartant
Copy link
Author

cartant commented Jul 26, 2018

Apologies. I thought that VS Code used the local version of TypeScript - as that's what the linting extension does. I can see now that I was wrong. (In fact, the TS version used by VS Code is indicated in the toolbar.)

I created a small compiler API harness and the behaviour's dependency on the compiler-options appears to have been addressed in version 3.0, as the 3.0.0-rc release infers Foo<{}> regardless of the compiler options.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Design Limitation Constraints of the existing architecture prevent this from being fixed
Projects
None yet
Development

No branches or pull requests

2 participants