Skip to content

[5.1][CodeCompletion] Call argument completion for implicit member expression #24824

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
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion include/swift/Parse/CodeCompletionCallbacks.h
Original file line number Diff line number Diff line change
Expand Up @@ -209,7 +209,7 @@ class CodeCompletionCallbacks {

virtual void completeAssignmentRHS(AssignExpr *E) {};

virtual void completeCallArg(CodeCompletionExpr *E) {};
virtual void completeCallArg(CodeCompletionExpr *E, bool isFirst) {};

virtual void completeReturnStmt(CodeCompletionExpr *E) {};

Expand Down
120 changes: 44 additions & 76 deletions lib/IDE/CodeCompletion.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1381,7 +1381,7 @@ class CodeCompletionCallbacksImpl : public CodeCompletionCallbacks {
void completeUnresolvedMember(CodeCompletionExpr *E,
SourceLoc DotLoc) override;
void completeAssignmentRHS(AssignExpr *E) override;
void completeCallArg(CodeCompletionExpr *E) override;
void completeCallArg(CodeCompletionExpr *E, bool isFirst) override;
void completeReturnStmt(CodeCompletionExpr *E) override;
void completeYieldStmt(CodeCompletionExpr *E,
Optional<unsigned> yieldIndex) override;
Expand Down Expand Up @@ -3713,81 +3713,14 @@ class CompletionLookup final : public swift::VisibleDeclConsumer {
return;

ModuleDecl *CurrModule = CurrDeclContext->getParentModule();
DeclContext *DC = const_cast<DeclContext *>(CurrDeclContext);

// We can only say .foo where foo is a static member of the contextual
// type and has the same type (or if the member is a function, then the
// same result type) as the contextual type.
FilteredDeclConsumer consumer(*this, [=](ValueDecl *VD,
DeclVisibilityKind Reason) {

if (VD->isOperator())
return false;

if (!VD->hasInterfaceType()) {
TypeResolver->resolveDeclSignature(VD);
if (!VD->hasInterfaceType())
return false;
}

if (T->getOptionalObjectType() &&
VD->getModuleContext()->isStdlibModule()) {
// In optional context, ignore '.init(<some>)', 'init(nilLiteral:)',
if (isa<ConstructorDecl>(VD))
return false;
// TODO: Ignore '.some(<Wrapped>)' and '.none' too *in expression
// context*. They are useful in pattern context though.
}

// Enum element decls can always be referenced by implicit member
// expression.
if (isa<EnumElementDecl>(VD))
return true;

// Only non-failable constructors are implicitly referenceable.
if (auto CD = dyn_cast<ConstructorDecl>(VD)) {
switch (CD->getFailability()) {
case OTK_None:
case OTK_ImplicitlyUnwrappedOptional:
return true;
case OTK_Optional:
return false;
}
}

// Otherwise, check the result type matches the contextual type.
auto declTy = T->getTypeOfMember(CurrModule, VD);
if (declTy->is<ErrorType>())
return false;

DeclContext *DC = const_cast<DeclContext *>(CurrDeclContext);

// Member types can also be implicitly referenceable as long as it's
// convertible to the contextual type.
if (auto CD = dyn_cast<TypeDecl>(VD)) {
declTy = declTy->getMetatypeInstanceType();

// Emit construction for the same type via typealias doesn't make sense
// because we are emitting all `.init()`s.
if (declTy->isEqual(T))
return false;
return swift::isConvertibleTo(declTy, T, *DC);
}

// Only static member can be referenced.
if (!VD->isStatic())
return false;

if (isa<FuncDecl>(VD)) {
// Strip '(Self.Type) ->' and parameters.
declTy = declTy->castTo<AnyFunctionType>()->getResult();
declTy = declTy->castTo<AnyFunctionType>()->getResult();
} else if (auto FT = declTy->getAs<AnyFunctionType>()) {
// The compiler accepts 'static var factory: () -> T' for implicit
// member expression.
// FIXME: This emits just 'factory'. We should emit 'factory()' instead.
declTy = FT->getResult();
}
return declTy->isEqual(T) || swift::isConvertibleTo(declTy, T, *DC);
return isReferenceableByImplicitMemberExpr(CurrModule, DC, T, VD);
});

auto baseType = MetatypeType::get(T);
Expand Down Expand Up @@ -4703,10 +4636,26 @@ void CodeCompletionCallbacksImpl::completeAssignmentRHS(AssignExpr *E) {
Kind = CompletionKind::AssignmentRHS;
}

void CodeCompletionCallbacksImpl::completeCallArg(CodeCompletionExpr *E) {
void CodeCompletionCallbacksImpl::completeCallArg(CodeCompletionExpr *E,
bool isFirst) {
CurDeclContext = P.CurDeclContext;
CodeCompleteTokenExpr = E;
Kind = CompletionKind::CallArg;

ShouldCompleteCallPatternAfterParen = false;
if (isFirst) {
ShouldCompleteCallPatternAfterParen = true;
if (Context.LangOpts.CodeCompleteCallPatternHeuristics) {
// Lookahead one token to decide what kind of call completions to provide.
// When it appears that there is already code for the call present, just
// complete values and/or argument labels. Otherwise give the entire call
// pattern.
Token next = P.peekToken();
if (!next.isAtStartOfLine() && !next.is(tok::eof) && !next.is(tok::r_paren)) {
ShouldCompleteCallPatternAfterParen = false;
}
}
}
}

void CodeCompletionCallbacksImpl::completeReturnStmt(CodeCompletionExpr *E) {
Expand Down Expand Up @@ -5411,13 +5360,32 @@ void CodeCompletionCallbacksImpl::doneParsing() {
}
case CompletionKind::CallArg : {
ExprContextInfo ContextInfo(CurDeclContext, CodeCompleteTokenExpr);
if (!ContextInfo.getPossibleNames().empty()) {

bool shouldPerformGlobalCompletion = true;

if (ShouldCompleteCallPatternAfterParen &&
!ContextInfo.getPossibleCallees().empty()) {
Lookup.setHaveLParen(true);
for (auto &typeAndDecl : ContextInfo.getPossibleCallees())
Lookup.tryFunctionCallCompletions(typeAndDecl.first,
typeAndDecl.second);
Lookup.setHaveLParen(false);

shouldPerformGlobalCompletion =
!Lookup.FoundFunctionCalls ||
(Lookup.FoundFunctionCalls &&
Lookup.FoundFunctionsWithoutFirstKeyword);
} else if (!ContextInfo.getPossibleNames().empty()) {
Lookup.addArgNameCompletionResults(ContextInfo.getPossibleNames());
break;

shouldPerformGlobalCompletion = !ContextInfo.getPossibleTypes().empty();
}

if (shouldPerformGlobalCompletion) {
Lookup.setExpectedTypes(ContextInfo.getPossibleTypes(),
ContextInfo.isSingleExpressionBody());
DoPostfixExprBeginning();
}
Lookup.setExpectedTypes(ContextInfo.getPossibleTypes(),
ContextInfo.isSingleExpressionBody());
DoPostfixExprBeginning();
break;
}

Expand Down
161 changes: 144 additions & 17 deletions lib/IDE/ExprContextAnalysis.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
#include "swift/AST/Initializer.h"
#include "swift/AST/LazyResolver.h"
#include "swift/AST/Module.h"
#include "swift/AST/ParameterList.h"
#include "swift/AST/Pattern.h"
#include "swift/AST/Stmt.h"
#include "swift/AST/Type.h"
Expand Down Expand Up @@ -336,7 +337,9 @@ void collectPossibleCalleesByQualifiedLookup(
Type declaredMemberType = VD->getInterfaceType();
if (VD->getDeclContext()->isTypeContext()) {
if (isa<FuncDecl>(VD)) {
if (!isOnMetaType)
if (!isOnMetaType && VD->isStatic())
continue;
if (isOnMetaType == VD->isStatic())
declaredMemberType =
declaredMemberType->castTo<AnyFunctionType>()->getResult();
} else if (isa<ConstructorDecl>(VD)) {
Expand Down Expand Up @@ -396,8 +399,13 @@ bool collectPossibleCalleesForApply(
auto *fnExpr = callExpr->getFn();

if (auto type = fnExpr->getType()) {
if (auto *funcType = type->getAs<AnyFunctionType>())
candidates.emplace_back(funcType, fnExpr->getReferencedDecl().getDecl());
if (auto *funcType = type->getAs<AnyFunctionType>()) {
auto refDecl = fnExpr->getReferencedDecl();
if (!refDecl)
if (auto apply = dyn_cast<ApplyExpr>(fnExpr))
refDecl = apply->getFn()->getReferencedDecl();
candidates.emplace_back(funcType, refDecl.getDecl());
}
} else if (auto *DRE = dyn_cast<DeclRefExpr>(fnExpr)) {
if (auto *decl = DRE->getDecl()) {
auto declType = decl->getInterfaceType();
Expand Down Expand Up @@ -454,6 +462,31 @@ bool collectPossibleCalleesForSubscript(
return !candidates.empty();
}

/// For the given \p unresolvedMemberExpr, collect possible callee types and
/// declarations.
static bool collectPossibleCalleesForUnresolvedMember(
DeclContext &DC, UnresolvedMemberExpr *unresolvedMemberExpr,
SmallVectorImpl<FunctionTypeAndDecl> &candidates) {
auto currModule = DC.getParentModule();
auto baseName = unresolvedMemberExpr->getName().getBaseName();

// Get the context of the expression itself.
ExprContextInfo contextInfo(&DC, unresolvedMemberExpr);
for (auto expectedTy : contextInfo.getPossibleTypes()) {
if (!expectedTy->mayHaveMembers())
continue;
SmallVector<FunctionTypeAndDecl, 2> members;
collectPossibleCalleesByQualifiedLookup(DC, MetatypeType::get(expectedTy),
baseName, members);
for (auto member : members) {
if (isReferenceableByImplicitMemberExpr(currModule, &DC, expectedTy,
member.second))
candidates.push_back(member);
}
}
return !candidates.empty();
}

/// Get index of \p CCExpr in \p Args. \p Args is usually a \c TupleExpr,
/// \c ParenExpr, or a \c ArgumentShuffleExpr.
/// \returns \c true if success, \c false if \p CCExpr is not a part of \p Args.
Expand Down Expand Up @@ -552,6 +585,11 @@ class ExprContextAnalyzer {
if (!collectPossibleCalleesForSubscript(*DC, subscriptExpr, Candidates))
return false;
Arg = subscriptExpr->getIndex();
} else if (auto *unresolvedMemberExpr = dyn_cast<UnresolvedMemberExpr>(E)) {
if (!collectPossibleCalleesForUnresolvedMember(*DC, unresolvedMemberExpr,
Candidates))
return false;
Arg = unresolvedMemberExpr->getArgument();
} else {
llvm_unreachable("unexpected expression kind");
}
Expand All @@ -568,7 +606,8 @@ class ExprContextAnalyzer {
// Collect possible types (or labels) at the position.
{
bool MayNeedName = !HasName && !E->isImplicit() &&
(isa<CallExpr>(E) | isa<SubscriptExpr>(E));
(isa<CallExpr>(E) | isa<SubscriptExpr>(E) ||
isa<UnresolvedMemberExpr>(E));
SmallPtrSet<TypeBase *, 4> seenTypes;
SmallPtrSet<Identifier, 4> seenNames;
for (auto &typeAndDecl : Candidates) {
Expand All @@ -577,18 +616,30 @@ class ExprContextAnalyzer {
memberDC = typeAndDecl.second->getInnermostDeclContext();

auto Params = typeAndDecl.first->getParams();
if (Position >= Params.size())
continue;
const auto &Param = Params[Position];
if (Param.hasLabel() && MayNeedName) {
if (seenNames.insert(Param.getLabel()).second)
recordPossibleName(Param.getLabel().str());
} else {
Type ty = Param.getOldType();
if (memberDC && ty->hasTypeParameter())
ty = memberDC->mapTypeIntoContext(ty);
if (seenTypes.insert(ty.getPointer()).second)
recordPossibleType(ty);
ParameterList *paramList = nullptr;
if (auto VD = typeAndDecl.second) {
if (auto FD = dyn_cast<AbstractFunctionDecl>(VD))
paramList = FD->getParameters();
else if (auto SD = dyn_cast<SubscriptDecl>(VD))
paramList = SD->getIndices();
if (paramList && paramList->size() != Params.size())
paramList = nullptr;
}
for (auto Pos = Position; Pos < Params.size(); ++Pos) {
const auto &Param = Params[Pos];
if (Param.hasLabel() && MayNeedName) {
if (seenNames.insert(Param.getLabel()).second)
recordPossibleName(Param.getLabel().str());
if (paramList && paramList->get(Position)->isDefaultArgument())
continue;
} else {
Type ty = Param.getOldType();
if (memberDC && ty->hasTypeParameter())
ty = memberDC->mapTypeIntoContext(ty);
if (seenTypes.insert(ty.getPointer()).second)
recordPossibleType(ty);
}
break;
}
}
}
Expand All @@ -599,6 +650,7 @@ class ExprContextAnalyzer {
switch (Parent->getKind()) {
case ExprKind::Call:
case ExprKind::Subscript:
case ExprKind::UnresolvedMember:
case ExprKind::Binary:
case ExprKind::PrefixUnary: {
analyzeApplyExpr(Parent);
Expand Down Expand Up @@ -783,11 +835,14 @@ class ExprContextAnalyzer {
case ExprKind::PrefixUnary:
case ExprKind::Assign:
return true;
case ExprKind::UnresolvedMember:
return true;
case ExprKind::Tuple: {
auto ParentE = Parent.getAsExpr();
return !ParentE ||
(!isa<CallExpr>(ParentE) && !isa<SubscriptExpr>(ParentE) &&
!isa<BinaryExpr>(ParentE) && !isa<ArgumentShuffleExpr>(ParentE));
!isa<BinaryExpr>(ParentE) && !isa<ArgumentShuffleExpr>(ParentE) &&
!isa<UnresolvedMemberExpr>(ParentE));
}
case ExprKind::Closure:
return isSingleExpressionBodyForCodeCompletion(
Expand Down Expand Up @@ -856,3 +911,75 @@ ExprContextInfo::ExprContextInfo(DeclContext *DC, Expr *TargetExpr) {
PossibleCallees, singleExpressionBody);
Analyzer.Analyze();
}

//===----------------------------------------------------------------------===//
// isReferenceableByImplicitMemberExpr(ModuleD, DeclContext, Type, ValueDecl)
//===----------------------------------------------------------------------===//

bool swift::ide::isReferenceableByImplicitMemberExpr(
ModuleDecl *CurrModule, DeclContext *DC, Type T, ValueDecl *VD) {

if (VD->isOperator())
return false;

if (!VD->hasInterfaceType())
return false;

if (T->getOptionalObjectType() &&
VD->getModuleContext()->isStdlibModule()) {
// In optional context, ignore '.init(<some>)', 'init(nilLiteral:)',
if (isa<ConstructorDecl>(VD))
return false;
// TODO: Ignore '.some(<Wrapped>)' and '.none' too *in expression
// context*. They are useful in pattern context though.
}

// Enum element decls can always be referenced by implicit member
// expression.
if (isa<EnumElementDecl>(VD))
return true;

// Only non-failable constructors are implicitly referenceable.
if (auto CD = dyn_cast<ConstructorDecl>(VD)) {
switch (CD->getFailability()) {
case OTK_None:
case OTK_ImplicitlyUnwrappedOptional:
return true;
case OTK_Optional:
return false;
}
}

// Otherwise, check the result type matches the contextual type.
auto declTy = T->getTypeOfMember(CurrModule, VD);
if (declTy->is<ErrorType>())
return false;

// Member types can also be implicitly referenceable as long as it's
// convertible to the contextual type.
if (auto CD = dyn_cast<TypeDecl>(VD)) {
declTy = declTy->getMetatypeInstanceType();

// Emit construction for the same type via typealias doesn't make sense
// because we are emitting all `.init()`s.
if (declTy->isEqual(T))
return false;
return swift::isConvertibleTo(declTy, T, *DC);
}

// Only static member can be referenced.
if (!VD->isStatic())
return false;

if (isa<FuncDecl>(VD)) {
// Strip '(Self.Type) ->' and parameters.
declTy = declTy->castTo<AnyFunctionType>()->getResult();
declTy = declTy->castTo<AnyFunctionType>()->getResult();
} else if (auto FT = declTy->getAs<AnyFunctionType>()) {
// The compiler accepts 'static var factory: () -> T' for implicit
// member expression.
// FIXME: This emits just 'factory'. We should emit 'factory()' instead.
declTy = FT->getResult();
}
return declTy->isEqual(T) || swift::isConvertibleTo(declTy, T, *DC);
}
Loading