Implement the conservative option for typechecking multiple trailing closures.

The current approach for type-checking single trailing closures
is to scan backwards from the end of the parameter list, looking
for something that can be passed a closure.  This generalizes that
to perform label-matching in reverse on any later trailing closures,
then pick up from that point when trying to place the unlabeled
trailing closure.

The current approach is really not a good language rule, but changing
it as much as we'd really like will require evolution approval and a
source break, so we have to be cautious.
This commit is contained in:
John McCall
2020-04-09 17:53:31 -04:00
parent a518e759d9
commit 882035f003
2 changed files with 145 additions and 127 deletions

View File

@@ -432,59 +432,114 @@ matchCallArguments(SmallVectorImpl<AnyFunctionType::Param> &args,
haveUnfulfilledParams = true;
};
// If we have a trailing closure, it maps to the last parameter.
// TODO: generalize this correctly for the closure arg index.
if (unlabeledTrailingClosureArgIndex && numParams > 0) {
unsigned lastParamIdx = numParams - 1;
bool lastAcceptsTrailingClosure =
acceptsTrailingClosure(params[lastParamIdx]);
// If we have an unlabeled trailing closure, we match trailing closure
// labels from the end, then match the trailing closure arg.
if (unlabeledTrailingClosureArgIndex) {
unsigned unlabeledArgIdx = *unlabeledTrailingClosureArgIndex;
// If the last parameter is defaulted, this might be
// an attempt to use a trailing closure with previous
// parameter that accepts a function type e.g.
//
// func foo(_: () -> Int, _ x: Int = 0) {}
// foo { 42 }
if (!lastAcceptsTrailingClosure && numParams > 1 &&
paramInfo.hasDefaultArgument(lastParamIdx)) {
auto paramType = params[lastParamIdx - 1].getPlainType();
// If the parameter before defaulted last accepts.
if (paramType->is<AnyFunctionType>()) {
lastAcceptsTrailingClosure = true;
lastParamIdx -= 1;
// One past the next parameter index to look at.
unsigned prevParamIdx = numParams;
// Scan backwards to match any labeled trailing closures.
for (unsigned argIdx = numArgs - 1; argIdx != unlabeledArgIdx; --argIdx) {
bool claimed = false;
// We do this scan on a copy of prevParamIdx so that, if it fails,
// we'll restart at this same point for the next trailing closure.
unsigned prevParamIdxForArg = prevParamIdx;
while (prevParamIdxForArg > 0) {
prevParamIdxForArg -= 1;
unsigned paramIdx = prevParamIdxForArg;
// Check for an exact label match.
const auto &param = params[paramIdx];
if (param.getLabel() == args[argIdx].getLabel()) {
parameterBindings[paramIdx].push_back(argIdx);
claim(param.getLabel(), argIdx);
// Future arguments should search prior to this parameter.
prevParamIdx = prevParamIdxForArg;
claimed = true;
break;
}
}
// If we ran out of parameters, there's no match for this argument.
// TODO: somehow fall through to report out-of-order arguments?
if (!claimed) {
if (listener.extraArgument(argIdx))
return true;
}
}
bool isExtraClosure = false;
// If there is no suitable last parameter to accept the trailing closure,
// Okay, we've matched all the labeled closures; scan backwards from
// there to match the unlabeled trailing closure.
Optional<unsigned> unlabeledParamIdx;
if (prevParamIdx > 0) {
unsigned paramIdx = prevParamIdx - 1;
bool lastAcceptsTrailingClosure =
acceptsTrailingClosure(params[paramIdx]);
// If the last parameter is defaulted, this might be
// an attempt to use a trailing closure with previous
// parameter that accepts a function type e.g.
//
// func foo(_: () -> Int, _ x: Int = 0) {}
// foo { 42 }
//
// FIXME: shouldn't this skip multiple arguments and look for
// something that acceptsTrailingClosure rather than just something
// with a function type?
if (!lastAcceptsTrailingClosure && paramIdx > 0 &&
paramInfo.hasDefaultArgument(paramIdx)) {
auto paramType = params[paramIdx - 1].getPlainType();
// If the parameter before defaulted last accepts.
if (paramType->is<AnyFunctionType>()) {
lastAcceptsTrailingClosure = true;
paramIdx -= 1;
}
}
if (lastAcceptsTrailingClosure)
unlabeledParamIdx = paramIdx;
}
// If there's no suitable last parameter to accept the trailing closure,
// notify the listener and bail if we need to.
if (!lastAcceptsTrailingClosure) {
if (numArgs > numParams) {
if (!unlabeledParamIdx) {
bool isExtraClosure = false;
if (prevParamIdx == 0) {
isExtraClosure = true;
} else if (unlabeledArgIdx > 0) {
// Argument before the trailing closure.
unsigned prevArg = numArgs - 2;
unsigned prevArg = unlabeledArgIdx - 1;
auto &arg = args[prevArg];
// If the argument before trailing closure matches
// last parameter, this is just a special case of
// an extraneous argument.
const auto param = params[numParams - 1];
const auto param = params[prevParamIdx - 1];
if (param.hasLabel() && param.getLabel() == arg.getLabel()) {
isExtraClosure = true;
if (listener.extraArgument(numArgs - 1))
return true;
}
}
if (!isExtraClosure &&
listener.trailingClosureMismatch(lastParamIdx, numArgs - 1))
return true;
}
if (isExtraClosure) {
if (listener.extraArgument(unlabeledArgIdx))
return true;
} else {
if (listener.trailingClosureMismatch(prevParamIdx - 1,
unlabeledArgIdx))
return true;
}
// Claim the parameter/argument pair.
claim(params[lastParamIdx].getLabel(), numArgs - 1,
/*ignoreNameClash=*/true);
// Let's claim the trailing closure unless it's an extra argument.
if (!isExtraClosure)
parameterBindings[lastParamIdx].push_back(numArgs - 1);
} else {
// Claim the parameter/argument pair.
claim(params[*unlabeledParamIdx].getLabel(), unlabeledArgIdx,
/*ignoreNameClash=*/true);
parameterBindings[*unlabeledParamIdx].push_back(unlabeledArgIdx);
}
}
{