Skip to content

Commit 74b5eb7

Browse files
authored
Merge pull request #36538 from ahoppen/pr/complete-defaulted-labels
[CodeComplete] Match argument labels when completing function arguments
2 parents 2c0b8ac + 433d756 commit 74b5eb7

File tree

3 files changed

+116
-20
lines changed

3 files changed

+116
-20
lines changed

lib/IDE/ExprContextAnalysis.cpp

Lines changed: 85 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -745,6 +745,79 @@ static bool getPositionInArgs(DeclContext &DC, Expr *Args, Expr *CCExpr,
745745
return false;
746746
}
747747

748+
/// Get index of \p CCExpr in \p Params. Note that the position in \p Params may
749+
/// be different than the position in \p Args if there are defaulted arguments
750+
/// in \p Params which don't occur in \p Args.
751+
///
752+
/// \returns \c true if success, \c false if \p CCExpr is not a part of \p Args.
753+
static bool getPositionInParams(DeclContext &DC, Expr *Args, Expr *CCExpr,
754+
ArrayRef<AnyFunctionType::Param> Params,
755+
unsigned &PosInParams) {
756+
if (isa<ParenExpr>(Args)) {
757+
PosInParams = 0;
758+
return true;
759+
}
760+
761+
auto *tuple = dyn_cast<TupleExpr>(Args);
762+
if (!tuple) {
763+
return false;
764+
}
765+
766+
auto &SM = DC.getASTContext().SourceMgr;
767+
PosInParams = 0;
768+
unsigned PosInArgs = 0;
769+
bool LastParamWasVariadic = false;
770+
// We advance PosInArgs until we find argument that is after the code
771+
// completion token, which is when we stop.
772+
// For each argument, we try to find a matching parameter either by matching
773+
// argument labels, in which case PosInParams may be advanced by more than 1,
774+
// or by advancing PosInParams and PosInArgs both by 1.
775+
for (; PosInArgs < tuple->getNumElements(); ++PosInArgs) {
776+
if (!SM.isBeforeInBuffer(tuple->getElement(PosInArgs)->getEndLoc(),
777+
CCExpr->getStartLoc())) {
778+
// The arg is after the code completion position. Stop.
779+
break;
780+
}
781+
782+
auto ArgName = tuple->getElementName(PosInArgs);
783+
// If the last parameter we matched was variadic, we claim all following
784+
// unlabeled arguments for that variadic parameter -> advance PosInArgs but
785+
// not PosInParams.
786+
if (LastParamWasVariadic && ArgName.empty()) {
787+
continue;
788+
} else {
789+
LastParamWasVariadic = false;
790+
}
791+
792+
// Look for a matching parameter label.
793+
bool FoundLabelMatch = false;
794+
for (unsigned i = PosInParams; i < Params.size(); ++i) {
795+
if (Params[i].getLabel() == ArgName) {
796+
// We have found a label match. Advance the position in the params
797+
// to point to the param after the one with this label.
798+
PosInParams = i + 1;
799+
FoundLabelMatch = true;
800+
if (Params[i].isVariadic()) {
801+
LastParamWasVariadic = true;
802+
}
803+
break;
804+
}
805+
}
806+
807+
if (!FoundLabelMatch) {
808+
// We haven't found a matching argument label. Assume the current one is
809+
// named incorrectly and advance by one.
810+
++PosInParams;
811+
}
812+
}
813+
if (PosInArgs < tuple->getNumElements() && PosInParams < Params.size()) {
814+
// We didn't search until the end, so we found a position in Params. Success
815+
return true;
816+
} else {
817+
return false;
818+
}
819+
}
820+
748821
/// Given an expression and its context, the analyzer tries to figure out the
749822
/// expected type of the expression by analyzing its context.
750823
class ExprContextAnalyzer {
@@ -791,14 +864,12 @@ class ExprContextAnalyzer {
791864
PossibleCallees.assign(Candidates.begin(), Candidates.end());
792865

793866
// Determine the position of code completion token in call argument.
794-
unsigned Position;
867+
unsigned PositionInArgs;
795868
bool HasName;
796-
if (!getPositionInArgs(*DC, Arg, ParsedExpr, Position, HasName))
869+
if (!getPositionInArgs(*DC, Arg, ParsedExpr, PositionInArgs, HasName))
797870
return false;
798871

799872
// Collect possible types (or labels) at the position.
800-
// FIXME: Take variadic and optional parameters into account. We need to do
801-
// something equivalent to 'constraints::matchCallArguments'
802873
{
803874
bool MayNeedName = !HasName && !E->isImplicit() &&
804875
(isa<CallExpr>(E) | isa<SubscriptExpr>(E) ||
@@ -811,13 +882,22 @@ class ExprContextAnalyzer {
811882
memberDC = typeAndDecl.Decl->getInnermostDeclContext();
812883

813884
auto Params = typeAndDecl.Type->getParams();
885+
unsigned PositionInParams;
886+
if (!getPositionInParams(*DC, Arg, ParsedExpr, Params,
887+
PositionInParams)) {
888+
// If the argument doesn't have a matching position in the parameters,
889+
// indicate that with optional nullptr param.
890+
if (seenArgs.insert({Identifier(), CanType()}).second)
891+
recordPossibleParam(nullptr, /*isRequired=*/false);
892+
continue;
893+
}
814894
ParameterList *paramList = nullptr;
815895
if (auto VD = typeAndDecl.Decl) {
816896
paramList = getParameterList(VD);
817897
if (paramList && paramList->size() != Params.size())
818898
paramList = nullptr;
819899
}
820-
for (auto Pos = Position; Pos < Params.size(); ++Pos) {
900+
for (auto Pos = PositionInParams; Pos < Params.size(); ++Pos) {
821901
const auto &paramType = Params[Pos];
822902
Type ty = paramType.getPlainType();
823903
if (memberDC && ty->hasTypeParameter())
@@ -843,12 +923,6 @@ class ExprContextAnalyzer {
843923
if (!canSkip)
844924
break;
845925
}
846-
// If the argument position is out of expeceted number, indicate that
847-
// with optional nullptr param.
848-
if (Position >= Params.size()) {
849-
if (seenArgs.insert({Identifier(), CanType()}).second)
850-
recordPossibleParam(nullptr, /*isRequired=*/false);
851-
}
852926
}
853927
}
854928
return !PossibleTypes.empty() || !PossibleParams.empty();

test/IDE/complete_call_arg.swift

Lines changed: 30 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,10 @@
116116

117117
// RUN: %target-swift-ide-test -code-completion -source-filename %s -code-completion-token=LABEL_IN_SELF_DOT_INIT | %FileCheck %s -check-prefix=LABEL_IN_SELF_DOT_INIT
118118

119+
// RUN: %target-swift-ide-test -code-completion -source-filename %s -code-completion-token=MISSING_REQUIRED_PARAM | %FileCheck %s -check-prefix=MISSING_REQUIRED_PARAM
120+
121+
// RUN: %target-swift-ide-test -code-completion -source-filename %s -code-completion-token=NAMED_PARAMETER_WITH_LEADING_VARIADIC | %FileCheck %s -check-prefix=NAMED_PARAMETER_WITH_LEADING_VARIADIC
122+
119123
var i1 = 1
120124
var i2 = 2
121125
var oi1 : Int?
@@ -700,9 +704,7 @@ func testStaticMemberCall() {
700704
// STATIC_METHOD_SECOND: End completions
701705

702706
let _ = TestStaticMemberCall.create2(1, arg3: 2, #^STATIC_METHOD_SKIPPED^#)
703-
// STATIC_METHOD_SKIPPED: Begin completions, 2 items
704-
// FIXME: 'arg3' shouldn't be suggested.
705-
// STATIC_METHOD_SKIPPED: Pattern/ExprSpecific: {#arg3: Int#}[#Int#];
707+
// STATIC_METHOD_SKIPPED: Begin completions, 1 item
706708
// STATIC_METHOD_SKIPPED: Pattern/ExprSpecific: {#arg4: Int#}[#Int#];
707709
// STATIC_METHOD_SKIPPED: End completions
708710

@@ -739,9 +741,7 @@ func testImplicitMember() {
739741
// IMPLICIT_MEMBER_SECOND: End completions
740742

741743
let _: TestStaticMemberCall = .create2(1, arg3: 2, #^IMPLICIT_MEMBER_SKIPPED^#)
742-
// IMPLICIT_MEMBER_SKIPPED: Begin completions, 2 items
743-
// FIXME: 'arg3' shouldn't be suggested.
744-
// IMPLICIT_MEMBER_SKIPPED: Pattern/ExprSpecific: {#arg3: Int#}[#Int#];
744+
// IMPLICIT_MEMBER_SKIPPED: Begin completions, 1 item
745745
// IMPLICIT_MEMBER_SKIPPED: Pattern/ExprSpecific: {#arg4: Int#}[#Int#];
746746
// IMPLICIT_MEMBER_SKIPPED: End completions
747747

@@ -923,3 +923,27 @@ func testLabelsInSelfDotInit() {
923923
}
924924
}
925925
}
926+
927+
func testMissingRequiredParameter() {
928+
class C {
929+
func foo(x: Int, y: Int, z: Int) {}
930+
}
931+
func test(c: C) {
932+
c.foo(y: 1, #^MISSING_REQUIRED_PARAM^#)
933+
// MISSING_REQUIRED_PARAM: Begin completions, 1 item
934+
// MISSING_REQUIRED_PARAM-DAG: Pattern/ExprSpecific: {#z: Int#}[#Int#]
935+
// MISSING_REQUIRED_PARAM: End completions
936+
}
937+
}
938+
939+
func testAfterVariadic() {
940+
class C {
941+
func foo(x: Int..., y: Int, z: Int) {}
942+
}
943+
func test(c: C) {
944+
c.foo(x: 10, 20, 30, y: 40, #^NAMED_PARAMETER_WITH_LEADING_VARIADIC^#)
945+
// NAMED_PARAMETER_WITH_LEADING_VARIADIC: Begin completions, 1 item
946+
// NAMED_PARAMETER_WITH_LEADING_VARIADIC-DAG: Pattern/ExprSpecific: {#z: Int#}[#Int#]
947+
// NAMED_PARAMETER_WITH_LEADING_VARIADIC: End completions
948+
}
949+
}

test/IDE/complete_call_as_function.swift

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -103,10 +103,8 @@ func testCallAsFunctionOverloaded(fn: Functor) {
103103
//OVERLOADED_PAREN: End completions
104104

105105
fn(h: .left, #^OVERLOADED_ARG2_LABEL^#)
106-
// FIXME: Should only suggest 'v:' (rdar://problem/60346573).
107-
//OVERLOADED_ARG2_LABEL: Begin completions, 2 items
106+
//OVERLOADED_ARG2_LABEL: Begin completions, 1 item
108107
//OVERLOADED_ARG2_LABEL-DAG: Pattern/ExprSpecific: {#v: Functor.Vertical#}[#Functor.Vertical#];
109-
//OVERLOADED_ARG2_LABEL-DAG: Pattern/ExprSpecific: {#h: Functor.Horizontal#}[#Functor.Horizontal#];
110108
//OVERLOADED_ARG2_LABEL: End completions
111109

112110
fn(h: .left, v: .#^OVERLOADED_ARG2_VALUE^#)

0 commit comments

Comments
 (0)