-
Notifications
You must be signed in to change notification settings - Fork 10.5k
[CodeCompletion] Update member completion to handle ambiguous and invalid base expressions #33749
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
Conversation
…e expression to the DotExpr completion callback.
…mpletion. This hooks up member completion to the new typeCheckForCodeCompletion API to generate completions from all solutions the constraint solver produces (include ones requiring fixes) rather than relying purely the single solution being applied to the AST (if any). This lets us still give completion results in ambiguous and invalid code.
…g for code completion
…expression and use it If there was an invalid reference which was caught by pre-check, let's remove all context besides code completion itself and use it to produce code completion results.
…or code completion To aid code completion, we need to attempt to convert type holes back into underlying generic parameters if possible, since type of the code completion expression is used as "expected" (or contextual) type so it's helpful to know what requirements it has to filter the list of possible member candidates e.g. ```swift func test<T: P>(_: [T]) {} test(42.#^MEMBERS^#) ``` It's impossible to resolve `T` in this case but code completion expression should still have a type of `[T]` instead of `[<<hole>>]` because it helps to produce correct contextual member list based on a conformance requirement associated with generic parameter `T`.
…ion in cases where typeCheckExpression is never called. This happens when, e.g. an expression being switched on is invalid so expression patterns in the switch cases (which may contain the completion expression) are not checked. Also setup the Lookup object to handle member completion in ObjC selector expressions correctly, and fix passing the wrong expression when computing isStaticallyDerivedMetatype().
…etion` If an expression fails pre-check or constraint generation, code completion should be performed directly on the `CodeCompletionExpr` as a fallback. Let's factor that logic out from `solveForCodeCompletion` and put it directly into `typeCheckForCodeCompletion` because it's easier to establish fault conditions there.
…on` to operate on target instead of expression Using `SolutionApplicationTarget` make it easier to propage contextual information and avoid mistakes of using incorrect accessors for constraint generation.
…f `typeCheckForCodeCompletion` There is no way to separate viability/applicability checking from `TypeChecker::typeCheckForCodeCompletion` because multi-statement closures could be either type-checked together with enclosing context (e.g. when closure represents a function builder body), or separately - when it's just a regular closure. Due to this "duality" we need to attempt to run code completion code to determine whether body of the closure participated in type-check and if it didn't, fallback to a `typeCheckExpression`.
…n to `typeCheckExpression` This fallback to `typeCheckExpression` is triggered when it's determined that code completion expression is located inside of a multi-statement closure and its body is going going to participate in type-check.
…ts test. We were reporting methods that return function types that return void (rather than returning void directly) as being invalid in contexts that expect non-void expressions and testing for that incorrect behavior.
…sion Having all information possible makes it much easier to determine when it's appropriate to fallback to type-check code completion expression and whether it's possible to add some more context to it.
…ayed parsing as well We were previously only doing it when parsing up front.
…improved type relation.
…dds to single expression bodies for void-returning functions This makes TypeCheckNodeAtLoc match the regular function body typechecking behavior and fixes a crash in test/IDE/complete_value_expr.swift
…into `sawSolution` Instead of exposing archetypes during solution generation, let's do that based on solution data when a type of code completion expression is requested by code completion callback.
Code completion expression type could be a hole if and only if there is obosolutely no contextual information available e.g. `let _ = .#^MEMBER^#`
…ined If base type couldn't be determined (e.g. because base expression is an invalid reference), let's not attempt to do a lookup since it wouldn't produce any useful results anyway.
…tion when there's no expected type. The tests were matching the previous implementation's output, which sometimes produced 'unknown' and sometimes 'unrelated' in cases where there was no expected type from the context.
… in the new member completion implementation.
@swift-ci please test |
@swift-ci Please Build Toolchain macOS Platform |
Build failed |
Build failed |
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.
Looks great in general 👍
lib/Sema/TypeCheckCodeCompletion.cpp
Outdated
ReferencedDecl = SelectedOverload->choice.getDeclOrNull(); | ||
|
||
auto Key = std::make_pair(BaseTy, ReferencedDecl); | ||
auto Ret = BaseToSolutionIdx.insert({Key, Solutions.size()}); |
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.
Could you explain why Solutions.size()
is a part of the key?
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.
It's the value here, rather than the key. If the key is not already in the BaseToSolutionIdx map we will insert a new entry into the Solutions vector, and Solutions.size() will be its 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.
Ah, sorry. I misread the code 😓
include/swift/Sema/IDETypeChecking.h
Outdated
/// Called for each solution produced while type-checking an expression containing a code | ||
/// completion expression. | ||
void sawSolution(const constraints::Solution &solution) override; | ||
}; |
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 feel like we should have dedicated header for code completion specific type checking. e.g. CompletionTypeChecking.h
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.
Makes sense to me, particularly as we'll hopefully be adding a bunch more of these.
@@ -1334,9 +1335,12 @@ CodeCompletionResult *CodeCompletionResultBuilder::takeResult() { | |||
} | |||
|
|||
auto typeRelation = ExpectedTypeRelation; | |||
// FIXME: we don't actually have enough info to compute | |||
// IsImplicitlyCurriedInstanceMethod 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.
(Not in this PR) I think we should calculate it in each result building logic which knows the actual result type.
E.g.
Builder.addTypeAnnotation(resultTy, PrintOptions());
Builder.setExpectedTypeRelation(calculateMaxTypeRelation(resultTy, expectedTypes, DC));
include/swift/Sema/IDETypeChecking.h
Outdated
SourceLoc DotLoc; | ||
DeclContext *DC; | ||
CodeCompletionExpr *CompletionExpr; | ||
SmallVector<SolutionInfo, 4> Solutions; |
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.
Naming Solution
can be confusable with constraints::Solution
. But I'm not able to come up with better name 🤔
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.
Yeah, I'm not super happy with most of the names. I was hoping to come up with something better for "CompletionCollector" and "DotExprLookup" too.
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.
How about
CompletionCollector
-> TypeCheckCompletionCallback
DotExprLookup
-> DotExprTypeCheckCompletionCallback
Solution
-> Result
lib/IDE/CodeCompletion.cpp
Outdated
@@ -1526,6 +1530,8 @@ void CodeCompletionContext::sortCompletionResults( | |||
} | |||
|
|||
namespace { | |||
class CompletionLookup; | |||
|
|||
class CodeCompletionCallbacksImpl : public CodeCompletionCallbacks { | |||
CodeCompletionContext &CompletionContext; | |||
std::vector<RequestedCachedModule> RequestedModules; |
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.
Is this still needed in CodeCompletionCallbacksImpl
?
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.
Oh maybe not - thanks!
DotExprLookup Lookup(DotLoc, CurDeclContext, CodeCompleteTokenExpr); | ||
llvm::SaveAndRestore<CompletionCollector*> | ||
CompletionCollector(Context.CompletionCallback, &Lookup); | ||
typeCheckContextAt(CurDeclContext, CompletionLoc); |
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.
(Not in this PR) When we migrate everything, we should rename this to something else :)
e.g.
performCompletionTypeChecking(CurDeclContext, ComletionLoc, Lookup);
lib/IDE/CodeCompletion.cpp
Outdated
|
||
// This (hopefully) only happens in cases where the expression isn't | ||
// typechecked during normal compilation either (e.g. member completion in a | ||
// switch case where there switched value is invalid). Having normal |
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.
According the the language reference, it's called "control expression"
https://docs.swift.org/swift-book/ReferenceManual/Statements.html#ID436
lib/Sema/TypeCheckCodeCompletion.cpp
Outdated
// If there was no completion expr (e.g. if the code completion location is | ||
// within an ErrorExpr without an valid subexpr due to parser error recovery) | ||
// bail. | ||
if (!contextAnalyzer.hasCompletionExpr()) | ||
return false; |
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.
The original expression in ErrorExpr
is now walked into. If you know other cases, please update the comment.
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.
Right, I think this branch is impossible to get into at the moment since we're only doing this for member completion. Do you know if once we change the other completion kinds to use this approach there will ever be a case where no CodeCompletionExpr is formed within an ErrorExpr, or any other kind of expression containing the completion loc? I assumed this was possible, e.g. if the completion location is in the middle of some nonsensical tokens between statement starting tokens that are skipped over during parser recovery, but if we're always guaranteed to have a CodeCompletionExpr regardless of the completion kind this should probably be an assertion.
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 don’t know off the top of my head. But I think we should keep this return false
path, just to be on the safe side.
// First, pre-check the expression, validating any types that occur in the | ||
// expression and folding sequence expressions. | ||
auto failedPreCheck = ConstraintSystem::preCheckExpression( | ||
expr, DC, | ||
/*replaceInvalidRefsWithErrors=*/true); | ||
|
||
target.setExpr(expr); | ||
|
||
if (failedPreCheck) | ||
return false; |
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.
If preCheckExpression()
, it falls back to normal type checking which calls preCheckExpression()
again. @xedin Is it safe to call preCheckExpression()
on already pre-checked expressions?
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.
Yeah, it's effectively a noop.
lib/Sema/TypeCheckCodeCompletion.cpp
Outdated
/*contextualType=*/Type(), | ||
/*isDiscarded=*/true); | ||
|
||
(void)solveForCodeCompletion(completionTarget); |
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.
Assert the result is not CompletionResult::NotApplicable
?
@@ -133,7 +133,7 @@ enum FooEnum: CaseIterable { | |||
// FOO_ENUM_DOT_INVALID-NEXT: Decl[EnumElement]/CurrNominal: Foo1[#FooEnum#]{{; name=.+$}} | |||
// FOO_ENUM_DOT_INVALID-NEXT: Decl[EnumElement]/CurrNominal: Foo2[#FooEnum#]{{; name=.+$}} | |||
// FOO_ENUM_DOT_INVALID-NEXT: Decl[StaticVar]/CurrNominal: alias1[#FooEnum#]{{; name=.+$}} | |||
// FOO_ENUM_DOT_INVALID-NEXT: Decl[InstanceMethod]/CurrNominal/TypeRelation[Invalid]: hash({#(self): FooEnum#})[#(into: inout Hasher) -> Void#]{{; name=.+$}} | |||
// FOO_ENUM_DOT_INVALID-NEXT: Decl[InstanceMethod]/CurrNominal: hash({#(self): FooEnum#})[#(into: inout Hasher) -> Void#]{{; name=.+$}} |
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 makes FOO_ENUM_DOT_INVALID
the same as FOO_ENUM_DOT
. Just remove FOO_ENUM_DOT_INVALID
and modify the FileCheck
argument to use FOO_ENUM_DOT
lib/IDE/CodeCompletion.cpp
Outdated
void DotExprLookup::performLookup(ide::CodeCompletionContext &CompletionCtx, | ||
CodeCompletionConsumer &Consumer, | ||
bool IsInSelector) const { |
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.
It's not clear to me why the logic in this function should be in DotExprLookup
especially considering this is declared in Sema
. How about DotExprLookup
just have an accessor for Solutions
and inline this logic in trySolverCompletion
?
…all refactorings (NFC) Also put subclasses of TypeCheckCompletionCallback into their own header.
…of-date comment (NFC)
@swift-ci clean test |
@swift-ci please test |
Build failed |
Build failed |
lib/Parse/ParseExpr.cpp
Outdated
static bool isMemberCompletion(ASTNode Node) { | ||
struct HasMemberCompletion: public ASTWalker { | ||
bool Value = false; | ||
std::pair<bool, Expr *> walkToExprPre(Expr *E) override { | ||
if (auto *CCE = dyn_cast<CodeCompletionExpr>(E)) { | ||
Value = CCE->getBase(); | ||
return {false, nullptr}; | ||
} | ||
return {true, E}; | ||
} | ||
}; | ||
HasMemberCompletion Check; | ||
Node.walk(Check); | ||
return Check.Value; | ||
} |
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.
Could you consolidate this with the one in ParseDecl
?
@@ -0,0 +1,82 @@ | |||
// |
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.
Missing the first line?
//===--- CodeCompletionTypeChecking.h --------------------------*- C++ -*-===//
…ParseExpr.cpp and ParseDecl.cpp Also: - propagate the Solution -> Result rename to Solution parameter of deliverDotExprResults - fixup header comment in CodeCompletionTypeChecking.h
@swift-ci please test |
Build failed |
Build failed |
@swift-ci please test Linux |
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
This hooks up member completion to the new
typeCheckForCodeCompletion
API to generate completions from all solutions the constraint solver produces – including ones requiring fixes – rather than relying on a single solution being applied to the AST (if any). This lets us still produce member completion results when the base expression is ambiguous or invalid.Whenever typeCheckExpression is called on an expression containing a code completion expression and a CompletionCallback has been set, each solution formed is passed to the callback so the type and decl of the base expression can be extracted along with any other info needed to generate member completions.