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鈥檒l occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve autocomplete for union of tuples #58571

Open
wants to merge 20 commits into
base: main
Choose a base branch
from

Conversation

rynobax
Copy link

@rynobax rynobax commented May 19, 2024

Fixes #46457

Another attempt at this pr from two years ago 馃槄

This feels fairly wrong (not sure the proper way to narrow these types, I don't really understand what getTypeArguments does), but once again since the tests pass I feel like it might be close? If anyone could give pointers I can make changes to clean this up, or feel free to modify if you know what you are doing. Thanks!

The changes to src/services/stringCompletions.ts was just me making changes while finding my way around the code, can revert them if you prefer the other formatting.

@typescript-bot typescript-bot added the For Backlog Bug PRs that fix a backlog bug label May 19, 2024
src/services/utilities.ts Outdated Show resolved Hide resolved
// Create a discriminator for each element in the list
const discriminators = map(
node.elements,
(e, ndx) => [() => getContextFreeTypeOfExpression(e), "" + ndx as __String] as const,
Copy link
Author

Choose a reason for hiding this comment

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

In the object version of this function there is a check for isPossiblyDiscriminantValue. Is that necessary here?

Copy link
Contributor

Choose a reason for hiding this comment

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

I'd expect both functions to be very similar so I'd say - yes. if isPossiblyDiscriminantValue is needed there - it's also needed here.

function getMatchingUnionConstituentForArrayLiteral(unionType: UnionType, node: ArrayLiteralExpression) {
const resolvedElements = node.elements.map(el => getContextFreeTypeOfExpression(el));
for (const type of unionType.types) {
if (!isTupleType(type)) continue;
Copy link
Contributor

Choose a reason for hiding this comment

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

q: shouldn't this exit the whole process? Or at least when !isTupleLikeType(type)?

Copy link
Author

Choose a reason for hiding this comment

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

not sure I understand this question. if the union constituent is not a tuple, it's not valid, but I don't think that means other union constituents couldn't be, so i think we should continue looping?

Copy link
Contributor

Choose a reason for hiding this comment

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

TS is structural and you can create some weird (acceptable) types like [number, "foo"] | { "1": "bar" }. This could lead to rejecting some types that should still be considered here. So the way to ignore the problem would be to just (for now?) exit this process.

isTupleLikeType point stands, I think. Types like { "0": "foo" } should contribute to autocompletes

Copy link
Author

Choose a reason for hiding this comment

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

I was confused when I left my last comment, was thinking this function was something else. I'm realizing I don't even understand the purpose of getMatchingUnionConstituentForObjectLiteral or getMatchingUnionConstituentForArrayLiteral. Do you know what this function should be doing? I'm just going to remove it from the PR for now, can add something back if it makes sense.

@@ -31361,6 +31375,28 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
);
}

function discriminateContextualTypeByElements(node: ArrayLiteralExpression, contextualType: UnionType) {
Copy link
Contributor

Choose a reason for hiding this comment

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

food for thought: length should also be treated as a potential discriminant but it probably should be ignored when requesting the contextual type for completions. You could try to use ContextFlags.Completions for that or you might have to introduce a new context flag for this purpose, smth like ContextFlags.NoTupleBounds.

Copy link
Author

Choose a reason for hiding this comment

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

removed the length check in getMatchingUnionConstituentForArrayLiteral so not sure if this is still relevant

Copy link
Contributor

Choose a reason for hiding this comment

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

it's relevant because this arg could contextually be typed as number

const tup: [(arg: number) => void] | [(arg: string) => void, number] = [
  (arg) => {},
];

Comment on lines 26907 to 26908
const unionElements = getElementTypes(type);
if (unionElements.length !== resolvedElements.length) continue;
Copy link
Contributor

Choose a reason for hiding this comment

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

This is not quite right. You need to experiment more with this and try to create multiple test cases mixing tuples with different fixed sizes and also the ones with variadic sizes. It would probably be the easiest to only reason about the leading fixed elements.

Copy link
Author

Choose a reason for hiding this comment

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

79a4a79

i didn't see an obvious way to determine the number of leading elements so i removed that early exit

const targetType = getTypeOfPropertyOrIndexSignatureOfType(types[i], propertyName);
const targetType = typeof propertyName === "number"
? getContextualTypeForElementExpression(types[i], propertyName)
: getTypeOfPropertyOrIndexSignatureOfType(types[i], propertyName);
Copy link
Author

Choose a reason for hiding this comment

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

found that this was not working like I wanted with variadic tuple types, so for tuples im using getContextualTypeForElementExpression. Not sure if there's a better way to do this, also considered making this function an argument

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
For Backlog Bug PRs that fix a backlog bug
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Code hint not narrowed properly on union of literal tuple
3 participants