mirror of
https://github.com/apple/swift.git
synced 2025-12-21 12:14:44 +01:00
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:
@@ -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 ¶m = 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);
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user