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

In services, when overload resolution fails, create a union signature #19620

Closed
wants to merge 8 commits into from

Conversation

ghost
Copy link

@ghost ghost commented Oct 31, 2017

Fixes #19474
Edit: Fixes #19854 too

(getUnionSignatures seems like it would be useful but it returns a Signature[] and I just want a Signature.)

@ghost ghost force-pushed the completionsCombineOverloads branch from 04c7c91 to 8463d9e Compare November 1, 2017 17:25
@ghost ghost requested a review from sandersn November 1, 2017 17:25
@ghost
Copy link
Author

ghost commented Nov 4, 2017

@sandersn Could you review?
Note we should try to avoid making the problem in #19686 any worse than it already is.

Copy link
Member

@sandersn sandersn left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@weswigham has spent considerable time thinking about unions of signatures.

  1. How close is this to what he's worked on?
  2. How close is his work to being merged into the main path code, instead of the error path?

@weswigham, care to comment on the previous two questions?

// Normally we will combine overloads. Skip this if they have type parameters since that's hard to combine.
// Don't do this if there is a `candidatesOutArray`,
// because then we want the chosen best candidate to be one of the overloads, not a combination.
if (!hasCandidatesOutArray && candidates.length > 1 && !candidates.some(c => !!c.typeParameters)) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. I think the pick-longest-signature code would benefit from being in a separate function as well.
  2. I think this condition would read better inverted in that case.
  3. If you don't do (1) and (2), I would recommend pushing the negation inside some and inverting it to candidate.every(c => !c.typeParameters). ("every not" is easier to read as "none" than "not some")

Copy link
Member

@sandersn sandersn left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The idea seems OK, but I'd like @weswigham to review as well, since he's thought about this a lot more.

i < parameters.length ? parameters[i] : undefined);
Debug.assert(symbols.length !== 0);
const types = mapDefined(candidates, candidate => tryGetTypeAtPosition(candidate, i));
parameters.push(createCombinedSymbolWithType(symbols, getUnionType(types, /*subtypeReduction*/ true)));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

instead of creating a small-but-already-leaky abstraction, what about creating a single-use createCombinedSymbol that accounts for other common code, like the calls to [try]GetTypeOf[Parameter/Rest], mapDefined and getUnionType.

That function would not misrepresent itself in its name, and would make this function considerably more readable.

thisParameter = createCombinedSymbolWithType(thisParameters, getUnionType(thisParameters.map(getTypeOfParameter), /*subtypeReduction*/ true));
}

const { min: minArgumentCount, max: maxNonRestParam } = minAndMax(candidates, getNumNonRestParameters);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

linter should catch the double space? I think?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's no such rule currently. Note that we do equals-sign alignment in a few places so we'd have to change our style to conform to such a rule -- it might be worth it though.

@weswigham
Copy link
Member

weswigham commented Nov 6, 2017

@sandersn #17819 Works well (if I remember, the only change it had were visible in the .type baselines when we had them and they were always improvements); but that's just for contextual types, not actual overload resolution. I believe we've been hesitant to go so far as to change overload resolution, since in the past we've found people who depend on its current behavior. It's also worth noting that I think this, as written, doesn't just affect services; this could also affect inference and resolution in JS (without checkJS, which I believe also has diagnostic reporting off).

@andy-ms Would you be willing to check that the merged signature creation going on here functions the same as in #17819? It looks like you don't bother with setting the return type, and your rest parameter handling is a bit different, and you don't combine type parameters. If this is just a best-effort thing intended for the LS's quickinfo and such, why not set a flag on the signature we return today and generate the combined signature in the language service? createSignature is (internally) exposed, after all. I think the bar would be much lower for that.

@ghost
Copy link
Author

ghost commented Dec 6, 2017

@weswigham It looks like #17819 is doing a different thing than this -- that only modifies getContextualSignature. Meaning it will affect you if you write (x, y) => ... and need a type for x, but won't affect you if you call a function and need a contextual type for the argument.

Since this is for a different use case I'm not actually sure what the correct behavior for using type parameters would be -- type inference failed the first time or we wouldn't get here, so if we synthesized type parameters for the union signature we would have to do type inference again, but that might fail again... I think it's best to avoid that case and just leave it as an error. We can't expect to get a good contextual type from a call to a generic function that fails anyway.
As for the return type I think it would make sense to use an intersection type as in your PR, that way if you have:

declare function f(x: number): number;
declare function f(x: string): string;
f().toUpperCase();

You won't get an "extra" error at toUpperCase() since it's number & string. But you will get an error if you write toUperCase.

We can't accomplish this PR's goal by trying to create a new signature after the fact, because outside of the checker we don't know where a contextual type is coming from.

@ghost ghost force-pushed the completionsCombineOverloads branch from 811ae82 to 808b18b Compare December 6, 2017 17:35
@ghost ghost force-pushed the completionsCombineOverloads branch from 808b18b to c8f70c4 Compare December 6, 2017 17:35
@ghost ghost mentioned this pull request Dec 6, 2017
@ghost ghost force-pushed the completionsCombineOverloads branch from 43e67b9 to e87fc88 Compare December 14, 2017 22:35
@typescript-bot
Copy link
Collaborator

Thanks for your contribution. This PR has not been updated in a while and cannot be automatically merged at the time being. For housekeeping purposes we are closing stale PRs. If you'd still like to continue working on this PR, please leave a message and one of the maintainers can reopen it.

@ghost ghost deleted the completionsCombineOverloads branch June 20, 2018 18:55
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants