[CodeCompletion][Sema] Allow missing args when solving if the completion location indicates the user may intend to write them later.

func foo(a: Int, b: Int) {}
func foo(a: String) {}

// Int and String should both be valid, despite the missing argument for the
// first overload since the second arg may just have not been written yet.
foo(a: <complete here>

func bar(a: (Int) -> ()) {}
func bar(a: (String, Int) -> ()) {}

// $0 being of type String should be valid, rather than just Int, since $1 may
// just have not been written yet.
bar { $0.<complete here> }
This commit is contained in:
Nathan Hawes
2020-10-14 11:05:28 -07:00
parent abd460817c
commit 15f5222bbd
9 changed files with 306 additions and 44 deletions

View File

@@ -40,7 +40,8 @@ MatchCallArgumentListener::~MatchCallArgumentListener() { }
bool MatchCallArgumentListener::extraArgument(unsigned argIdx) { return true; }
Optional<unsigned>
MatchCallArgumentListener::missingArgument(unsigned paramIdx) {
MatchCallArgumentListener::missingArgument(unsigned paramIdx,
unsigned argInsertIdx) {
return None;
}
@@ -701,11 +702,14 @@ static bool matchCallArgumentsImpl(
}
// If we have any unfulfilled parameters, check them now.
Optional<unsigned> prevArgIdx;
if (haveUnfulfilledParams) {
for (auto paramIdx : indices(params)) {
// If we have a binding for this parameter, we're done.
if (!parameterBindings[paramIdx].empty())
if (!parameterBindings[paramIdx].empty()) {
prevArgIdx = parameterBindings[paramIdx].back();
continue;
}
const auto &param = params[paramIdx];
@@ -717,7 +721,8 @@ static bool matchCallArgumentsImpl(
if (paramInfo.hasDefaultArgument(paramIdx))
continue;
if (auto newArgIdx = listener.missingArgument(paramIdx)) {
unsigned argInsertIdx = prevArgIdx ? *prevArgIdx + 1 : 0;
if (auto newArgIdx = listener.missingArgument(paramIdx, argInsertIdx)) {
parameterBindings[paramIdx].push_back(*newArgIdx);
continue;
}
@@ -980,14 +985,47 @@ constraints::matchCallArguments(
};
}
static Optional<unsigned>
getCompletionArgIndex(ASTNode anchor, SourceManager &SM) {
Expr *arg = nullptr;
if (auto *CE = getAsExpr<CallExpr>(anchor))
arg = CE->getArg();
if (auto *SE = getAsExpr<SubscriptExpr>(anchor))
arg = SE->getIndex();
if (auto *OLE = getAsExpr<ObjectLiteralExpr>(anchor))
arg = OLE->getArg();
if (!arg)
return None;
auto containsCompletion = [&](Expr *elem) {
if (!elem)
return false;
SourceRange range = elem->getSourceRange();
return range.isValid() && SM.rangeContainsCodeCompletionLoc(range);
};
if (auto *TE = dyn_cast<TupleExpr>(arg)) {
auto elems = TE->getElements();
auto idx = llvm::find_if(elems, containsCompletion);
if (idx != elems.end())
return std::distance(elems.begin(), idx);
} else if (auto *PE = dyn_cast<ParenExpr>(arg)) {
if (containsCompletion(PE->getSubExpr()))
return 0;
}
return None;
}
class ArgumentFailureTracker : public MatchCallArgumentListener {
ConstraintSystem &CS;
SmallVectorImpl<AnyFunctionType::Param> &Arguments;
ArrayRef<AnyFunctionType::Param> Parameters;
ConstraintLocatorBuilder Locator;
SmallVector<std::pair<unsigned, AnyFunctionType::Param>, 4> MissingArguments;
SmallVector<SynthesizedArg, 4> MissingArguments;
SmallVector<std::pair<unsigned, AnyFunctionType::Param>, 4> ExtraArguments;
Optional<unsigned> CompletionArgIdx;
public:
ArgumentFailureTracker(ConstraintSystem &cs,
@@ -1006,7 +1044,8 @@ public:
}
}
Optional<unsigned> missingArgument(unsigned paramIdx) override {
Optional<unsigned> missingArgument(unsigned paramIdx,
unsigned argInsertIdx) override {
if (!CS.shouldAttemptFixes())
return None;
@@ -1023,10 +1062,22 @@ public:
TVO_CanBindToNoEscape | TVO_CanBindToHole);
auto synthesizedArg = param.withType(argType);
MissingArguments.push_back(std::make_pair(paramIdx, synthesizedArg));
Arguments.push_back(synthesizedArg);
if (CS.isForCodeCompletion()) {
// When solving for code completion, if any argument contains the
// completion location, later arguments shouldn't be considered missing
// (causing the solution to have a worse score) as the user just hasn't
// written them yet. Early exit to avoid recording them in this case.
SourceManager &SM = CS.getASTContext().SourceMgr;
if (!CompletionArgIdx)
CompletionArgIdx = getCompletionArgIndex(Locator.getAnchor(), SM);
if (CompletionArgIdx && *CompletionArgIdx < argInsertIdx)
return newArgIdx;
}
MissingArguments.push_back(SynthesizedArg{paramIdx, synthesizedArg});
return newArgIdx;
}
@@ -1190,12 +1241,11 @@ ConstraintSystem::TypeMatchResult constraints::matchCallArguments(
argsWithLabels.pop_back();
// Let's make sure that labels associated with tuple elements
// line up with what is expected by argument list.
SmallVector<std::pair<unsigned, AnyFunctionType::Param>, 4>
synthesizedArgs;
SmallVector<SynthesizedArg, 4> synthesizedArgs;
for (unsigned i = 0, n = argTuple->getNumElements(); i != n; ++i) {
const auto &elt = argTuple->getElement(i);
AnyFunctionType::Param argument(elt.getType(), elt.getName());
synthesizedArgs.push_back(std::make_pair(i, argument));
synthesizedArgs.push_back(SynthesizedArg{i, argument});
argsWithLabels.push_back(argument);
}
@@ -1771,15 +1821,27 @@ static bool fixMissingArguments(ConstraintSystem &cs, ASTNode anchor,
cs.createTypeVariable(argLoc, TVO_CanBindToNoEscape)));
}
SmallVector<std::pair<unsigned, AnyFunctionType::Param>, 4> synthesizedArgs;
SmallVector<SynthesizedArg, 4> synthesizedArgs;
synthesizedArgs.reserve(numMissing);
for (unsigned i = args.size() - numMissing, n = args.size(); i != n; ++i) {
synthesizedArgs.push_back(std::make_pair(i, args[i]));
synthesizedArgs.push_back(SynthesizedArg{i, args[i]});
}
// Treat missing anonymous arguments as valid in closures containing the
// code completion location, since they may have just not been written yet.
if (cs.isForCodeCompletion()) {
if (auto *closure = getAsExpr<ClosureExpr>(anchor)) {
SourceManager &SM = closure->getASTContext().SourceMgr;
SourceRange range = closure->getSourceRange();
if (range.isValid() && SM.rangeContainsCodeCompletionLoc(range) &&
(closure->hasAnonymousClosureVars() ||
(args.empty() && closure->getInLoc().isInvalid())))
return false;
}
}
auto *fix = AddMissingArguments::create(cs, synthesizedArgs,
cs.getConstraintLocator(locator));
if (cs.recordFix(fix))
return true;
@@ -3966,7 +4028,7 @@ bool ConstraintSystem::repairFailures(
// to diagnose this as a missing argument which can't be ignored.
if (arg != getTypeVariables().end()) {
conversionsOrFixes.push_back(AddMissingArguments::create(
*this, {std::make_pair(0, AnyFunctionType::Param(*arg))},
*this, {SynthesizedArg{0, AnyFunctionType::Param(*arg)}},
getConstraintLocator(anchor, path)));
break;
}