Skip to content

[CodeCompletion] Migrate argument position completion to the solver-based implementation #39373

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

Merged
merged 2 commits into from
Mar 17, 2022

Conversation

ahoppen
Copy link
Member

@ahoppen ahoppen commented Sep 20, 2021

Revised version of #38266 with most stress tester failures fixed.

CC @nathawes

rdar://83435550

@ahoppen ahoppen requested a review from nathawes September 20, 2021 12:14
Copy link
Contributor

@hamishknight hamishknight left a comment

Choose a reason for hiding this comment

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

Cool! Hope you don't mind some drive-by nits :)

@ahoppen
Copy link
Member Author

ahoppen commented Sep 22, 2021

Thanks @hamishknight for the suggestions for the new ArgumentList API. I just rebased it so it worked and didn’t look if the code could be improved.

For now, I’m looking to merge the changes to solution scoring (which are needed to fix the stress tester issues) here: #39392

@ahoppen ahoppen force-pushed the pr/solver-based-arg-completion branch 3 times, most recently from a925c49 to 556334b Compare February 11, 2022 11:37
@ahoppen ahoppen force-pushed the pr/solver-based-arg-completion branch 5 times, most recently from ea7ed3d to 54a9ce4 Compare March 2, 2022 12:07
@ahoppen ahoppen changed the title [WIP][CodeCompletion] Migrate argument position completion to the solver-based implementation [CodeCompletion] Migrate argument position completion to the solver-based implementation Mar 2, 2022
@ahoppen ahoppen force-pushed the pr/solver-based-arg-completion branch 2 times, most recently from 952284f to c498b53 Compare March 3, 2022 22:15
@ahoppen ahoppen marked this pull request as ready for review March 3, 2022 22:52
// We don't care how much source code was ignored after the code
// completion token, just whether if there was any.
if (S.getFixedScore().Data[SK_IgnoreAfterCompletionToken] > 0 &&
minScore.Data[SK_IgnoreAfterCompletionToken] == 0) {
Copy link
Member Author

Choose a reason for hiding this comment

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

I am not really happy with this part yet. What I am really trying to achieve is to have a boolean flag in the score that indicates whether code after the code completion token was ignored – I don’t care how much. At the moment we might end up with solutions that have a SK_IgnoreAfterCompletionToken score of >1 (sometimes also created by combining two partial solutions, where each partial solution ignored arguments).

In the current implementation, we might filter out solutions with a SK_IgnoreAfterCompletionToken of 2 when comparing against a solution that has a SK_IgnoreAfterCompletionToken of 1 in the constraint system, even though we don’t really consider it worse.

Options that I have considered:

  • Only increase score in recordFix if SK_IgnoreAfterCompletionToken is 0 — doesn’t work because we might combine two partial solutions that both have SK_IgnoreAfterCompletionToken = 1
  • Hacking the operator< of Score to consider all SK_IgnoreAfterCompletionToken > 0 equally bad — this seems very surprising to me
  • Hack the operator+ of Score such that 1 + 1 = 1 for SK_IgnoreAfterCompletionToken — this makes Score addition non-linear, not sure what effects it might have
  • Leave it as-is for now because we don’t have any counter-examples that break yet

var bar: Binding<MyStruct>

func test(index: Int) {
_ = bar[#^DYNAMIC_MEMBER_SUBSCRIPT_LOOKUP?xfail=FIXME^#index]
Copy link
Member Author

Choose a reason for hiding this comment

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

This is the test case for the

// FIXME: We currently dan't look through @dynamicMemberLookup applications
// for subscripts

comment in ArgumentCompletion.cpp. I’ll file a bug report for this one and fix it in a follow-up PR.

@@ -126,6 +126,9 @@ class SavedTypeVariableBinding {
/// The parent or fixed type.
llvm::PointerUnion<TypeVariableType *, TypeBase *> ParentOrFixed;

/// The locator associated with bound type
ConstraintLocator *BoundLocator;
Copy link
Contributor

Choose a reason for hiding this comment

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

Probably Binding in this case.

@@ -486,14 +503,15 @@ class TypeVariableType::Implementation {
}

/// Assign a fixed type to this equivalence class.
void assignFixedType(Type type,
void assignFixedType(Type type, constraints::ConstraintLocator *loc,
Copy link
Contributor

Choose a reason for hiding this comment

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

Please change a name here to bindingLocation or something like that and add a comment about it.

/// the types we do know instead of bailing out completely because \p type
/// contains an error type.
static Type replaceParamErrorTypeByPlaceholder(Type type, ValueDecl *value) {
if (auto funcType = type->getAs<AnyFunctionType>()) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Minor stylistic comment - it's preferred not to nest code like this, if you could make an early return - prefer it.

assert(declParams->size() == typeParams.size());
SmallVector<AnyFunctionType::Param, 4> newParams;
newParams.reserve(declParams->size());
for (size_t i = 0; i < declParams->size(); i++) {
Copy link
Contributor

Choose a reason for hiding this comment

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

I know that there is an assert that declParams have the same size as typeParams but it's still kind of weird to use declParams here. This could be simplified down to for (auto i : indices(typeParams))

@@ -260,6 +263,10 @@ class TypeVariableType::Implementation {
/// type is bound.
llvm::PointerUnion<TypeVariableType *, TypeBase *> ParentOrFixed;

/// The locator describing where the type bound to this type variable
/// originated.
constraints::ConstraintLocator *BoundLocator = nullptr;
Copy link
Contributor

Choose a reason for hiding this comment

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

Same here.

// Don't allow arguments after the completion position to be claimed
// during recovery. We ignore them if they are invalid, as the user may
// may be re-writing the callsite rather than updating an argument.
if (completionInfo && completionInfo->isBefore(nextArgIdx))
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 a comment about this particular place but a general pattern here and other places in CSSimply.

Reading through the code in CSSimplify here makes me wonder - why don't we just add a new matchCallArgumentForCodeCompletion method instead? It seems like the rules here are pretty straight-forward - positional matches, with ignoring failures after the code completion token, out-of-order matching is disabled for the "after code completion token" (or should it be even disabled outright?), some fixes are ignored...

Another solution to consider - while generating constraints for a call we could produce a special applicable fn with code completion constraint if one of the arguments has a code completion token, this way we can control the "argument function", and potentially generate type variables on demand after matching parameters to arguments based on code completion rules.

I'm hoping that this would allow us to avoid IgnoreFailureAfterCompletionArg and new score kind.

Copy link
Member Author

Choose a reason for hiding this comment

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

I was able to migrate a lot of this logic into a new argument match listener for code completion and was able to use SK_Fix instead of the new score kind. I don’t think we can get rid of IgnoreFailureAfterCompletionArg though, because we need to have some way to detect whether we already ignored arguments for a given call.

Copy link
Contributor

Choose a reason for hiding this comment

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

I think we shouldn't use fixes for this purpose, instead we should add new storage to a constraint system which is going to keep track of that.

@@ -7631,14 +7783,14 @@ static bool isForKeyPathSubscript(ConstraintSystem &cs,
return false;
}

static bool isForKeyPathSubscriptWithoutLabel(ConstraintSystem &cs,
ConstraintLocator *locator) {
static bool mayBeForKeyPathSubscriptWithoutLabel(ConstraintSystem &cs,
Copy link
Contributor

Choose a reason for hiding this comment

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

Nit: lower-case b here.

Copy link
Member Author

Choose a reason for hiding this comment

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

I think the capital b is correct here as it’s “may be for key path subscript without label” where “be” is a verb.

Copy link
Contributor

Choose a reason for hiding this comment

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

Okay, makes sense, <locator> may be for ... definitely makes sense.

@ahoppen ahoppen force-pushed the pr/solver-based-arg-completion branch 3 times, most recently from 5359ac0 to 42cf5b2 Compare March 16, 2022 11:44
@ahoppen
Copy link
Member Author

ahoppen commented Mar 16, 2022

After some discussion with @xedin, we have decided to always ignore arguments after the code completion token because it defines away an entire family of ambiguities where it’s unclear whether these arguments should influence what’s suggested for the code completion token because we can’t know whether the user wants to rewrite the entire call site or only update one parameter. We might be able to revisit this decision in a follow-up PR. As a side-effect, it also simplifies this PR.

@ahoppen ahoppen force-pushed the pr/solver-based-arg-completion branch from 42cf5b2 to ca504b5 Compare March 16, 2022 22:18
@ahoppen
Copy link
Member Author

ahoppen commented Mar 16, 2022

@swift-ci Please smoke test

@ahoppen
Copy link
Member Author

ahoppen commented Mar 16, 2022

@swift-ci Please SourceKit stress test

Copy link
Contributor

@xedin xedin left a comment

Choose a reason for hiding this comment

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

Solver related changes look good!

@@ -7631,14 +7783,14 @@ static bool isForKeyPathSubscript(ConstraintSystem &cs,
return false;
}

static bool isForKeyPathSubscriptWithoutLabel(ConstraintSystem &cs,
ConstraintLocator *locator) {
static bool mayBeForKeyPathSubscriptWithoutLabel(ConstraintSystem &cs,
Copy link
Contributor

Choose a reason for hiding this comment

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

Okay, makes sense, <locator> may be for ... definitely makes sense.

…he solver-based implementation

This hooks up call argument position completion to the typeCheckForCodeCompletion API to generate completions from all the solutions the constraint solver produces (even those requiring fixes), rather than relying on a single solution being applied to the AST (if any).

Co-authored-by: Nathan Hawes <[email protected]>
@ahoppen ahoppen force-pushed the pr/solver-based-arg-completion branch from ca504b5 to f538d33 Compare March 17, 2022 14:16
@ahoppen
Copy link
Member Author

ahoppen commented Mar 17, 2022

@swift-ci Please smoke test

@ahoppen
Copy link
Member Author

ahoppen commented Mar 17, 2022

@swift-ci Please SourceKit stress test

@ahoppen
Copy link
Member Author

ahoppen commented Mar 17, 2022

I addressed your review comments @xedin and made the following changes:

  • Don’t optimize constraints of arguments that are being ignored: CSGen.cpp:791
  • Extracted logic whether a argument should be claimed despite label mismatch (because it is the completion expression) to CompletionArgumentTracker (CSSimplify.cpp:1295). This makes sure that we only claim arguments despite label mismatches when we are performing solver-based completion. It broke a couple of test cases in the legacy code completion.

Copy link
Contributor

@xedin xedin left a comment

Choose a reason for hiding this comment

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

LGTM! We have discussed this offline but I'm just going to leave it here - the storage for ignored arguments would be switched to use Expr * and check in ConstraintWalker @ CSGen.cpp:791 would have to get wrapped into if (cs.isForCodeCompletion()) in a follow-up.

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