-
Notifications
You must be signed in to change notification settings - Fork 10.5k
[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
[CodeCompletion] Migrate argument position completion to the solver-based implementation #39373
Conversation
There was a problem hiding this 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 :)
Thanks @hamishknight for the suggestions for the new For now, I’m looking to merge the changes to solution scoring (which are needed to fix the stress tester issues) here: #39392 |
a925c49
to
556334b
Compare
ea7ed3d
to
54a9ce4
Compare
952284f
to
c498b53
Compare
lib/Sema/TypeCheckCodeCompletion.cpp
Outdated
// 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) { |
There was a problem hiding this comment.
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
ifSK_IgnoreAfterCompletionToken
is 0 — doesn’t work because we might combine two partial solutions that both haveSK_IgnoreAfterCompletionToken = 1
- Hacking the
operator<
ofScore
to consider allSK_IgnoreAfterCompletionToken > 0
equally bad — this seems very surprising to me - Hack the
operator+
ofScore
such that1 + 1 = 1
forSK_IgnoreAfterCompletionToken
— this makesScore
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
test/IDE/complete_call_arg.swift
Outdated
var bar: Binding<MyStruct> | ||
|
||
func test(index: Int) { | ||
_ = bar[#^DYNAMIC_MEMBER_SUBSCRIPT_LOOKUP?xfail=FIXME^#index] |
There was a problem hiding this comment.
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; |
There was a problem hiding this comment.
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, |
There was a problem hiding this comment.
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.
lib/Sema/ConstraintSystem.cpp
Outdated
/// 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>()) { |
There was a problem hiding this comment.
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.
lib/Sema/ConstraintSystem.cpp
Outdated
assert(declParams->size() == typeParams.size()); | ||
SmallVector<AnyFunctionType::Param, 4> newParams; | ||
newParams.reserve(declParams->size()); | ||
for (size_t i = 0; i < declParams->size(); i++) { |
There was a problem hiding this comment.
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; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same here.
lib/Sema/CSSimplify.cpp
Outdated
// 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)) |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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, |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
5359ac0
to
42cf5b2
Compare
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. |
42cf5b2
to
ca504b5
Compare
@swift-ci Please smoke test |
@swift-ci Please SourceKit stress test |
There was a problem hiding this 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, |
There was a problem hiding this comment.
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.
…a code completion token
…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]>
ca504b5
to
f538d33
Compare
@swift-ci Please smoke test |
@swift-ci Please SourceKit stress test |
I addressed your review comments @xedin and made the following changes:
|
There was a problem hiding this 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.
Revised version of #38266 with most stress tester failures fixed.
CC @nathawes
rdar://83435550