diff --git a/include/swift/AST/Attr.def b/include/swift/AST/Attr.def index fb1085509c8f3..9b06e9f2c2229 100644 --- a/include/swift/AST/Attr.def +++ b/include/swift/AST/Attr.def @@ -560,6 +560,11 @@ SIMPLE_DECL_ATTR(noDerivative, NoDerivative, ABIBreakingToAdd | ABIBreakingToRemove | APIBreakingToAdd | APIBreakingToRemove, 100) +DECL_ATTR(_trailingClosureMatching, TrailingClosureMatching, + OnAbstractFunction | OnSubscript | OnEnumElement | UserInaccessible | + ABIStableToAdd | ABIStableToRemove | APIBreakingToAdd | APIStableToRemove, + 101) + #undef TYPE_ATTR #undef DECL_ATTR_ALIAS #undef CONTEXTUAL_DECL_ATTR_ALIAS diff --git a/include/swift/AST/Attr.h b/include/swift/AST/Attr.h index d6482297d272f..a433ee52b5fa5 100644 --- a/include/swift/AST/Attr.h +++ b/include/swift/AST/Attr.h @@ -54,6 +54,7 @@ class GenericFunctionType; class LazyConformanceLoader; class LazyMemberLoader; class PatternBindingInitializer; +enum class TrailingClosureMatching: uint8_t; class TrailingWhereClause; class TypeExpr; @@ -2046,6 +2047,26 @@ class TransposeAttr final } }; +/// Attribute that controls the matching of trailing closures. +class TrailingClosureMatchingAttr final : public DeclAttribute { + TrailingClosureMatching matchingRule; + +public: + TrailingClosureMatchingAttr(SourceLoc atLoc, SourceRange range, + TrailingClosureMatching matchingRule, + bool implicit) + : DeclAttribute(DAK_TrailingClosureMatching, atLoc, range, implicit), + matchingRule(matchingRule) {} + + TrailingClosureMatching getMatchingRule() const { + return matchingRule; + } + + static bool classof(const DeclAttribute *DA) { + return DA->getKind() == DAK_TrailingClosureMatching; + } +}; + /// Attributes that may be applied to declarations. class DeclAttributes { /// Linked list of declaration attributes. diff --git a/include/swift/AST/DiagnosticsParse.def b/include/swift/AST/DiagnosticsParse.def index 1a79bcc0da34d..e5bd612a2a0bf 100644 --- a/include/swift/AST/DiagnosticsParse.def +++ b/include/swift/AST/DiagnosticsParse.def @@ -1372,6 +1372,10 @@ ERROR(attr_only_at_non_local_scope, none, ERROR(projection_value_property_not_identifier,none, "@_projectedValueProperty name must be an identifier", ()) +ERROR(trailing_closure_matching_missing_dot_identifier,none, + "@_trailingClosureMatching parameter must be '.forward' or '.backward'", + ()) + // Access control ERROR(attr_access_expected_set,none, "expected 'set' as subject of '%0' modifier", (StringRef)) diff --git a/include/swift/AST/Types.h b/include/swift/AST/Types.h index 804cc7960ffdf..98e6fa8452fa0 100644 --- a/include/swift/AST/Types.h +++ b/include/swift/AST/Types.h @@ -3492,10 +3492,23 @@ BEGIN_CAN_TYPE_WRAPPER(FunctionType, AnyFunctionType) } END_CAN_TYPE_WRAPPER(FunctionType, AnyFunctionType) +/// Describes the matching rules for trailing closures. +enum class TrailingClosureMatching: uint8_t { + /// Match trailing closures from the end of the parameter list, which is + /// the default rule for Swift < 6. + Backward, + /// Match trailing closures by scanning "forward" from the previously- + /// matched parameter and skipping over parameters that aren't structurally + /// function types. + Forward, +}; + /// Provides information about the parameter list of a given declaration, including whether each parameter /// has a default argument. struct ParameterListInfo { SmallBitVector defaultArguments; + SmallBitVector acceptsUnlabeledTrailingClosures; + Optional trailingClosureMatching; public: ParameterListInfo() { } @@ -3503,9 +3516,19 @@ struct ParameterListInfo { ParameterListInfo(ArrayRef params, const ValueDecl *paramOwner, bool skipCurriedSelf); + /// Retrieve the trailing closure matching rule, if one was explicitly + /// specified. + Optional getTrailingClosureMatching() const { + return trailingClosureMatching; + } + /// Whether the parameter at the given index has a default argument. bool hasDefaultArgument(unsigned paramIdx) const; + /// Whether the parameter accepts an unlabeled trailing closure argument + /// according to the "forward-scan" rule. + bool acceptsUnlabeledTrailingClosureArgument(unsigned paramIdx) const; + /// Retrieve the number of non-defaulted parameters. unsigned numNonDefaultedParameters() const { return defaultArguments.count(); diff --git a/include/swift/Basic/LangOptions.h b/include/swift/Basic/LangOptions.h index 9456b729871b3..d370196457797 100644 --- a/include/swift/Basic/LangOptions.h +++ b/include/swift/Basic/LangOptions.h @@ -271,6 +271,9 @@ namespace swift { /// behavior. This is a staging flag, and will be removed in the future. bool EnableNewOperatorLookup = false; + /// Whether to default to the "forward" scan for trailing closure matching. + bool EnableForwardTrailingClosureMatching = false; + /// Use Clang function types for computing canonical types. /// If this option is false, the clang function types will still be computed /// but will not be used for checking type equality. diff --git a/include/swift/Option/Options.td b/include/swift/Option/Options.td index 72ef695375441..5dc210b02cdd1 100644 --- a/include/swift/Option/Options.td +++ b/include/swift/Option/Options.td @@ -534,6 +534,11 @@ def enable_experimental_concise_pound_file : Flag<["-"], Flags<[FrontendOption]>, HelpText<"Enable experimental concise '#file' identifier">; +def enable_experimental_trailing_closure_matching : Flag<["-"], + "enable-experimental-trailing-closure-matching">, + Flags<[FrontendOption]>, + HelpText<"Enable the experimental 'forward' matching rule for trailing closures">; + def enable_direct_intramodule_dependencies : Flag<["-"], "enable-direct-intramodule-dependencies">, Flags<[FrontendOption, HelpHidden]>, diff --git a/lib/AST/Attr.cpp b/lib/AST/Attr.cpp index a9de9a3d36ea3..c76702524e469 100644 --- a/lib/AST/Attr.cpp +++ b/lib/AST/Attr.cpp @@ -1075,6 +1075,22 @@ bool DeclAttribute::printImpl(ASTPrinter &Printer, const PrintOptions &Options, break; } + case DAK_TrailingClosureMatching: { + Printer.printAttrName("@_trailingClosureMatching"); + Printer << '('; + switch (cast(this)->getMatchingRule()) { + case TrailingClosureMatching::Backward: + Printer << ".backward"; + break; + + case TrailingClosureMatching::Forward: + Printer << ".forward"; + break; + } + Printer << ')'; + break; + } + case DAK_Count: llvm_unreachable("exceed declaration attribute kinds"); @@ -1217,6 +1233,8 @@ StringRef DeclAttribute::getAttrName() const { return "derivative"; case DAK_Transpose: return "transpose"; + case DAK_TrailingClosureMatching: + return "_trailingClosureMatching"; } llvm_unreachable("bad DeclAttrKind"); } diff --git a/lib/AST/Type.cpp b/lib/AST/Type.cpp index 7b1e88695cd5b..d247890c20d53 100644 --- a/lib/AST/Type.cpp +++ b/lib/AST/Type.cpp @@ -821,16 +821,46 @@ Type TypeBase::replaceCovariantResultType(Type newResultType, return FunctionType::get(inputType, resultType, fnType->getExtInfo()); } +/// Whether this parameter accepts an unlabeled trailing closure argument +/// using the more-restrictive forward-scan rule. +static bool allowsUnlabeledTrailingClosureParameter(const ParamDecl *param) { + // Variadic parameters never allow an unlabeled trailing closure. + if (param->isVariadic()) + return false; + + // inout parameters never allow an unlabeled trailing closure. + if (param->isInOut()) + return false; + + Type paramType = + param->getInterfaceType()->getRValueType()->lookThroughAllOptionalTypes(); + + // For autoclosure parameters, look through the autoclosure result type + // to get the actual argument type. + if (param->isAutoClosure()) { + auto fnType = paramType->getAs(); + if (!fnType) + return false; + + paramType = fnType->getResult()->lookThroughAllOptionalTypes(); + } + + // After lookup through all optional types, this parameter allows an + // unlabeled trailing closure if it is (structurally) a function type. + return paramType->is(); +} + ParameterListInfo::ParameterListInfo( ArrayRef params, const ValueDecl *paramOwner, bool skipCurriedSelf) { defaultArguments.resize(params.size()); + acceptsUnlabeledTrailingClosures.resize(params.size()); // No parameter owner means no parameter list means no default arguments // - hand back the zeroed bitvector. // - // FIXME: We ought to not request default argument info in this case. + // FIXME: We ought to not request paramer list info in this case. if (!paramOwner) return; @@ -848,6 +878,12 @@ ParameterListInfo::ParameterListInfo( paramList = enumElement->getParameterList(); } + // Retrieve the explicitly-specified trailing closure matching rule. + if (auto attr = + paramOwner->getAttrs().getAttribute()) { + trailingClosureMatching = attr->getMatchingRule(); + } + // No parameter list means no default arguments - hand back the zeroed // bitvector. if (!paramList) { @@ -864,12 +900,17 @@ ParameterListInfo::ParameterListInfo( if (params.size() != paramList->size()) return; - // Note which parameters have default arguments and/or function builders. + // Note which parameters have default arguments and/or accept unlabeled + // trailing closure arguments with the forward-scan rule. for (auto i : range(0, params.size())) { auto param = paramList->get(i); if (param->isDefaultArgument()) { defaultArguments.set(i); } + + if (allowsUnlabeledTrailingClosureParameter(param)) { + acceptsUnlabeledTrailingClosures.set(i); + } } } @@ -878,6 +919,12 @@ bool ParameterListInfo::hasDefaultArgument(unsigned paramIdx) const { : false; } +bool ParameterListInfo::acceptsUnlabeledTrailingClosureArgument( + unsigned paramIdx) const { + return paramIdx < acceptsUnlabeledTrailingClosures.size() && + acceptsUnlabeledTrailingClosures[paramIdx]; +} + /// Turn a param list into a symbolic and printable representation that does not /// include the types, something like (_:, b:, c:) std::string swift::getParamListAsString(ArrayRef params) { diff --git a/lib/Driver/ToolChains.cpp b/lib/Driver/ToolChains.cpp index 5e2d1fe29309d..b33dc7d35d228 100644 --- a/lib/Driver/ToolChains.cpp +++ b/lib/Driver/ToolChains.cpp @@ -251,6 +251,9 @@ void ToolChain::addCommonFrontendArgs(const OutputInfo &OI, inputArgs.AddLastArg(arguments, options::OPT_disable_parser_lookup); inputArgs.AddLastArg(arguments, options::OPT_enable_experimental_concise_pound_file); + inputArgs.AddLastArg( + arguments, + options::OPT_enable_experimental_trailing_closure_matching); inputArgs.AddLastArg(arguments, options::OPT_verify_incremental_dependencies); inputArgs.AddLastArg(arguments, diff --git a/lib/Frontend/CompilerInvocation.cpp b/lib/Frontend/CompilerInvocation.cpp index eaced3e3bad99..09ad7c27cd92c 100644 --- a/lib/Frontend/CompilerInvocation.cpp +++ b/lib/Frontend/CompilerInvocation.cpp @@ -546,6 +546,8 @@ static bool ParseLangArgs(LangOptions &Opts, ArgList &Args, Opts.EnableConcisePoundFile = Args.hasArg(OPT_enable_experimental_concise_pound_file); + Opts.EnableForwardTrailingClosureMatching = + Args.hasArg(OPT_enable_experimental_trailing_closure_matching); Opts.EnableCrossImportOverlays = Args.hasFlag(OPT_enable_cross_import_overlays, diff --git a/lib/Parse/ParseDecl.cpp b/lib/Parse/ParseDecl.cpp index b69991bafd180..9a8407e73bbe4 100644 --- a/lib/Parse/ParseDecl.cpp +++ b/lib/Parse/ParseDecl.cpp @@ -2301,6 +2301,49 @@ bool Parser::parseNewDeclAttribute(DeclAttributes &Attributes, SourceLoc AtLoc, name, AtLoc, range, /*implicit*/ false)); break; } + + case DAK_TrailingClosureMatching: { + if (!consumeIf(tok::l_paren)) { + diagnose(Loc, diag::attr_expected_lparen, AttrName, + DeclAttribute::isDeclModifier(DK)); + return false; + } + + if (!consumeIf(tok::period_prefix)) { + diagnose(Loc, diag::trailing_closure_matching_missing_dot_identifier); + return false; + } + + if (Tok.isNot(tok::identifier)) { + diagnose(Loc, diag::trailing_closure_matching_missing_dot_identifier); + return false; + } + + Identifier name; + consumeIdentifier(&name, /*allowDollarIdentifier=*/true); + + TrailingClosureMatching matchingRule; + if (name.str() == "forward") + matchingRule = TrailingClosureMatching::Forward; + else if (name.str() == "backward") + matchingRule = TrailingClosureMatching::Backward; + else { + diagnose(Loc, diag::trailing_closure_matching_missing_dot_identifier); + return false; + } + + auto range = SourceRange(Loc, Tok.getRange().getStart()); + + if (!consumeIf(tok::r_paren)) { + diagnose(Loc, diag::attr_expected_rparen, AttrName, + DeclAttribute::isDeclModifier(DK)); + return false; + } + + Attributes.add(new (Context) TrailingClosureMatchingAttr( + AtLoc, range, matchingRule, /*implicit=*/false)); + break; + } } if (DuplicateAttribute) { diff --git a/lib/Sema/CSApply.cpp b/lib/Sema/CSApply.cpp index 43bac6311dfda..836a4acd2f003 100644 --- a/lib/Sema/CSApply.cpp +++ b/lib/Sema/CSApply.cpp @@ -1529,8 +1529,6 @@ namespace { /// \param callee The callee for the function being applied. /// \param apply The ApplyExpr that forms the call. /// \param argLabels The argument labels provided for the call. - /// \param hasTrailingClosure Whether the last argument is a trailing - /// closure. /// \param locator Locator used to describe where in this expression we are. /// /// \returns the coerced expression, which will have type \c ToType. @@ -1538,7 +1536,6 @@ namespace { coerceCallArguments(Expr *arg, AnyFunctionType *funcType, ConcreteDeclRef callee, ApplyExpr *apply, ArrayRef argLabels, - bool hasTrailingClosure, ConstraintLocatorBuilder locator); /// Coerce the given object argument (e.g., for the base of a @@ -1749,7 +1746,7 @@ namespace { auto fullSubscriptTy = openedFullFnType->getResult() ->castTo(); index = coerceCallArguments(index, fullSubscriptTy, subscriptRef, nullptr, - argLabels, hasTrailingClosure, + argLabels, locator.withPathElement( ConstraintLocator::ApplyArgument)); if (!index) @@ -2574,7 +2571,6 @@ namespace { auto newArg = coerceCallArguments( expr->getArg(), fnType, witness, /*applyExpr=*/nullptr, expr->getArgumentLabels(), - expr->hasTrailingClosure(), cs.getConstraintLocator(expr, ConstraintLocator::ApplyArgument)); expr->setInitializer(witness); @@ -4919,7 +4915,7 @@ namespace { auto *newIndexExpr = coerceCallArguments(indexExpr, subscriptType, ref, /*applyExpr*/ nullptr, labels, - /*hasTrailingClosure*/ false, locator); + locator); // We need to be able to hash the captured index values in order for // KeyPath itself to be hashable, so check that all of the subscript @@ -5452,12 +5448,12 @@ static bool hasCurriedSelf(ConstraintSystem &cs, ConcreteDeclRef callee, return false; } -Expr *ExprRewriter::coerceCallArguments(Expr *arg, AnyFunctionType *funcType, - ConcreteDeclRef callee, - ApplyExpr *apply, - ArrayRef argLabels, - bool hasTrailingClosure, - ConstraintLocatorBuilder locator) { +Expr *ExprRewriter::coerceCallArguments( + Expr *arg, AnyFunctionType *funcType, + ConcreteDeclRef callee, + ApplyExpr *apply, + ArrayRef argLabels, + ConstraintLocatorBuilder locator) { auto &ctx = getConstraintSystem().getASTContext(); auto params = funcType->getParams(); @@ -5507,9 +5503,11 @@ Expr *ExprRewriter::coerceCallArguments(Expr *arg, AnyFunctionType *funcType, MatchCallArgumentListener listener; SmallVector parameterBindings; + auto unlabeledTrailingClosureIndex = + arg->getUnlabeledTrailingClosureIndexOfPackedArgument(); bool failed = constraints::matchCallArguments(args, params, paramInfo, - arg->getUnlabeledTrailingClosureIndexOfPackedArgument(), + unlabeledTrailingClosureIndex, /*allowFixes=*/false, listener, parameterBindings); @@ -5739,7 +5737,7 @@ Expr *ExprRewriter::coerceCallArguments(Expr *arg, AnyFunctionType *funcType, (params[0].getValueOwnership() == ValueOwnership::Default || params[0].getValueOwnership() == ValueOwnership::InOut)) { assert(newArgs.size() == 1); - assert(!hasTrailingClosure); + assert(!unlabeledTrailingClosureIndex); return newArgs[0]; } @@ -5752,8 +5750,9 @@ Expr *ExprRewriter::coerceCallArguments(Expr *arg, AnyFunctionType *funcType, argParen->setSubExpr(newArgs[0]); } else { bool isImplicit = arg->isImplicit(); - arg = new (ctx) - ParenExpr(lParenLoc, newArgs[0], rParenLoc, hasTrailingClosure); + arg = new (ctx) ParenExpr( + lParenLoc, newArgs[0], rParenLoc, + static_cast(unlabeledTrailingClosureIndex)); arg->setImplicit(isImplicit); } } else { @@ -5767,8 +5766,8 @@ Expr *ExprRewriter::coerceCallArguments(Expr *arg, AnyFunctionType *funcType, } } else { // Build a new TupleExpr, re-using source location information. - arg = TupleExpr::create(ctx, lParenLoc, newArgs, newLabels, newLabelLocs, - rParenLoc, hasTrailingClosure, + arg = TupleExpr::create(ctx, lParenLoc, rParenLoc, newArgs, newLabels, + newLabelLocs, unlabeledTrailingClosureIndex, /*implicit=*/arg->isImplicit()); } } @@ -7119,9 +7118,6 @@ Expr *ExprRewriter::finishApply(ApplyExpr *apply, Type openedType, auto *arg = apply->getArg(); auto *fn = apply->getFn(); - bool hasTrailingClosure = - isa(apply) && cast(apply)->hasTrailingClosure(); - auto finishApplyOfDeclWithSpecialTypeCheckingSemantics = [&](ApplyExpr *apply, ConcreteDeclRef declRef, @@ -7137,7 +7133,6 @@ Expr *ExprRewriter::finishApply(ApplyExpr *apply, Type openedType, arg = coerceCallArguments(arg, fnType, declRef, apply, apply->getArgumentLabels(argLabelsScratch), - hasTrailingClosure, locator.withPathElement( ConstraintLocator::ApplyArgument)); if (!arg) { @@ -7341,7 +7336,6 @@ Expr *ExprRewriter::finishApply(ApplyExpr *apply, Type openedType, if (auto fnType = cs.getType(fn)->getAs()) { arg = coerceCallArguments(arg, fnType, callee, apply, apply->getArgumentLabels(argLabelsScratch), - hasTrailingClosure, locator.withPathElement( ConstraintLocator::ApplyArgument)); if (!arg) { diff --git a/lib/Sema/CSSimplify.cpp b/lib/Sema/CSSimplify.cpp index 6341a41ea7237..df7f45d811f5b 100644 --- a/lib/Sema/CSSimplify.cpp +++ b/lib/Sema/CSSimplify.cpp @@ -215,6 +215,23 @@ static bool acceptsTrailingClosure(const AnyFunctionType::Param ¶m) { paramTy->isAny(); } +/// Determine the trailing closure matching rule to apply. +static TrailingClosureMatching getTrailingClosureMatching( + const ParameterListInfo ¶mInfo, const ASTContext *ctx) { + if (auto explicitRule = paramInfo.getTrailingClosureMatching()) + return *explicitRule; + + if (ctx) { + return ctx->LangOpts.EnableForwardTrailingClosureMatching + ? TrailingClosureMatching::Forward + : TrailingClosureMatching::Backward; + } + + // No context only happens when there are no parameters, so it doesn't + // matter what we choose. + return TrailingClosureMatching::Forward; +} + // FIXME: This should return ConstraintSystem::TypeMatchResult instead // to give more information to the solver about the failure. bool constraints:: @@ -229,6 +246,16 @@ matchCallArguments(SmallVectorImpl &args, assert(!unlabeledTrailingClosureArgIndex || *unlabeledTrailingClosureArgIndex < args.size()); + // Determine which rule we will use for matching trailing closures. + TrailingClosureMatching trailingClosureMatching; + { + const ASTContext *ctx = nullptr; + if (!params.empty()) + ctx = ¶ms.front().getPlainType()->getASTContext(); + + trailingClosureMatching = getTrailingClosureMatching(paramInfo, ctx); + } + // Keep track of the parameter we're matching and what argument indices // got bound to each parameter. unsigned numParams = params.size(); @@ -285,6 +312,7 @@ matchCallArguments(SmallVectorImpl &args, auto skipClaimedArgs = [&](unsigned &nextArgIdx) { while (nextArgIdx != numArgs && claimedArgs[nextArgIdx]) ++nextArgIdx; + return nextArgIdx; }; // Local function that retrieves the next unclaimed argument with the given @@ -299,6 +327,14 @@ matchCallArguments(SmallVectorImpl &args, if (numClaimedArgs == numArgs) return None; + // If we're claiming variadic arguments, do not claim an unlabeled trailing + // closure argument during a forward scan. + if (forVariadic && + trailingClosureMatching == TrailingClosureMatching::Forward && + unlabeledTrailingClosureArgIndex && + nextArgIdx == *unlabeledTrailingClosureArgIndex) + return None; + // Go hunting for an unclaimed argument whose name does match. Optional claimedWithSameName; for (unsigned i = nextArgIdx; i != numArgs; ++i) { @@ -387,12 +423,31 @@ matchCallArguments(SmallVectorImpl &args, auto bindNextParameter = [&](unsigned paramIdx, unsigned &nextArgIdx, bool ignoreNameMismatch) { const auto ¶m = params[paramIdx]; + Identifier paramLabel = param.getLabel(); + + // If we have the trailing closure argument and are performing a forward + // match, look for the matching parameter. + if (trailingClosureMatching == TrailingClosureMatching::Forward && + unlabeledTrailingClosureArgIndex && + skipClaimedArgs(nextArgIdx) == *unlabeledTrailingClosureArgIndex) { + + // If the parameter we are looking at does not supports the (unlabeled) + // trailing closure argument, this parameter is unfulfilled. + if (!paramInfo.acceptsUnlabeledTrailingClosureArgument(paramIdx)) { + haveUnfulfilledParams = true; + return; + } + + // The argument is unlabeled, so mark the parameter as unlabeled as + // well. + paramLabel = Identifier(); + } // Handle variadic parameters. if (param.isVariadic()) { // Claim the next argument with the name of this parameter. auto claimed = - claimNextNamed(nextArgIdx, param.getLabel(), ignoreNameMismatch); + claimNextNamed(nextArgIdx, paramLabel, ignoreNameMismatch); // If there was no such argument, leave the parameter unfulfilled. if (!claimed) { @@ -425,7 +480,7 @@ matchCallArguments(SmallVectorImpl &args, // Try to claim an argument for this parameter. if (auto claimed = - claimNextNamed(nextArgIdx, param.getLabel(), ignoreNameMismatch)) { + claimNextNamed(nextArgIdx, paramLabel, ignoreNameMismatch)) { parameterBindings[paramIdx].push_back(*claimed); return; } @@ -436,7 +491,8 @@ matchCallArguments(SmallVectorImpl &args, // If we have an unlabeled trailing closure, we match trailing closure // labels from the end, then match the trailing closure arg. - if (unlabeledTrailingClosureArgIndex) { + if (unlabeledTrailingClosureArgIndex && + trailingClosureMatching == TrailingClosureMatching::Backward) { unsigned unlabeledArgIdx = *unlabeledTrailingClosureArgIndex; // One past the next parameter index to look at. diff --git a/lib/Sema/MiscDiagnostics.cpp b/lib/Sema/MiscDiagnostics.cpp index 28c1b5b03e12c..2e14d97235f44 100644 --- a/lib/Sema/MiscDiagnostics.cpp +++ b/lib/Sema/MiscDiagnostics.cpp @@ -1760,7 +1760,8 @@ bool swift::diagnoseArgumentLabelError(ASTContext &ctx, newName = newNames[i]; if (oldName == newName || - (argList.hasTrailingClosure && i == argList.args.size()-1)) + (argList.hasTrailingClosure && i == argList.args.size()-1 && + (numMissing > 0 || numExtra > 0 || numWrong > 0))) continue; if (oldName.empty()) { diff --git a/lib/Sema/TypeCheckAttr.cpp b/lib/Sema/TypeCheckAttr.cpp index bb54abd9ffa01..4f9d90fb3a0de 100644 --- a/lib/Sema/TypeCheckAttr.cpp +++ b/lib/Sema/TypeCheckAttr.cpp @@ -117,6 +117,7 @@ class AttributeChecker : public AttributeVisitor { IGNORED_ATTR(ReferenceOwnership) IGNORED_ATTR(OriginallyDefinedIn) IGNORED_ATTR(NoDerivative) + IGNORED_ATTR(TrailingClosureMatching) #undef IGNORED_ATTR void visitAlignmentAttr(AlignmentAttr *attr) { diff --git a/lib/Sema/TypeCheckDeclOverride.cpp b/lib/Sema/TypeCheckDeclOverride.cpp index 6a82bcd6630dd..99e3da5fbc239 100644 --- a/lib/Sema/TypeCheckDeclOverride.cpp +++ b/lib/Sema/TypeCheckDeclOverride.cpp @@ -1436,6 +1436,7 @@ namespace { UNINTERESTING_ATTR(Convenience) UNINTERESTING_ATTR(Semantics) UNINTERESTING_ATTR(SetterAccess) + UNINTERESTING_ATTR(TrailingClosureMatching) UNINTERESTING_ATTR(TypeEraser) UNINTERESTING_ATTR(SPIAccessControl) UNINTERESTING_ATTR(HasStorage) diff --git a/lib/Serialization/Deserialization.cpp b/lib/Serialization/Deserialization.cpp index 67b5d043a4242..d87c3d43618b4 100644 --- a/lib/Serialization/Deserialization.cpp +++ b/lib/Serialization/Deserialization.cpp @@ -4446,6 +4446,18 @@ llvm::Error DeclDeserializer::deserializeDeclAttributes() { break; } + case decls_block::TrailingClosureMatching_DECL_ATTR: { + bool isImplicit; + uint8_t matchingRule; + serialization::decls_block::TrailingClosureMatchingDeclAttrLayout + ::readRecord(scratch, isImplicit, matchingRule); + + Attr = new (ctx) TrailingClosureMatchingAttr( + SourceLoc(), SourceRange(), + static_cast(matchingRule), isImplicit); + break; + } + #define SIMPLE_DECL_ATTR(NAME, CLASS, ...) \ case decls_block::CLASS##_DECL_ATTR: { \ bool isImplicit; \ diff --git a/lib/Serialization/ModuleFormat.h b/lib/Serialization/ModuleFormat.h index 42d5001304a2c..608f082347fba 100644 --- a/lib/Serialization/ModuleFormat.h +++ b/lib/Serialization/ModuleFormat.h @@ -1705,6 +1705,12 @@ namespace decls_block { BCArray // SPI names >; + using TrailingClosureMatchingDeclAttrLayout = BCRecordLayout< + TrailingClosureMatching_DECL_ATTR, + BCFixed<1>, // implicit flag + BCFixed<1> // matching rule + >; + using AlignmentDeclAttrLayout = BCRecordLayout< Alignment_DECL_ATTR, BCFixed<1>, // implicit flag diff --git a/lib/Serialization/Serialization.cpp b/lib/Serialization/Serialization.cpp index fe89ccb7d3542..fa8a80673074c 100644 --- a/lib/Serialization/Serialization.cpp +++ b/lib/Serialization/Serialization.cpp @@ -2230,6 +2230,17 @@ class Serializer::DeclSerializer : public DeclVisitor { return; } + case DAK_TrailingClosureMatching: { + auto theAttr = cast(DA); + auto abbrCode = + S.DeclTypeAbbrCodes[TrailingClosureMatchingDeclAttrLayout::Code]; + + TrailingClosureMatchingDeclAttrLayout::emitRecord( + S.Out, S.ScratchRecord, abbrCode, theAttr->isImplicit(), + static_cast(theAttr->getMatchingRule())); + return; + } + case DAK_Alignment: { auto *theAlignment = cast(DA); auto abbrCode = S.DeclTypeAbbrCodes[AlignmentDeclAttrLayout::Code]; diff --git a/test/expr/postfix/call/forward_trailing_closure.swift b/test/expr/postfix/call/forward_trailing_closure.swift new file mode 100644 index 0000000000000..90a0cec340ce8 --- /dev/null +++ b/test/expr/postfix/call/forward_trailing_closure.swift @@ -0,0 +1,86 @@ +// RUN: %target-typecheck-verify-swift + +@_trailingClosureMatching(.forward) +func forwardMatch1( + a: Int = 0, b: Int = 17, closure1: (Int) -> Int = { $0 }, c: Int = 42, + numbers: Int..., closure2: ((Double) -> Double)? = nil, + closure3: (String) -> String = { $0 + "!" } +) { +} + +func testForwardMatch1(i: Int, d: Double, s: String) { + forwardMatch1() + + // Matching closure1 + forwardMatch1 { _ in i } + forwardMatch1 { _ in i } closure2: { d + $0 } + forwardMatch1 { _ in i } closure2: { d + $0 } closure3: { $0 + ", " + s + "!" } + forwardMatch1 { _ in i } closure3: { $0 + ", " + s + "!" } + + forwardMatch1(a: 5) { $0 + i } + forwardMatch1(a: 5) { $0 + i } closure2: { d + $0 } + forwardMatch1(a: 5) { $0 + i } closure2: { d + $0 } closure3: { $0 + ", " + s + "!" } + forwardMatch1(a: 5) { $0 + i } closure3: { $0 + ", " + s + "!" } + + forwardMatch1(b: 1) { $0 + i } + forwardMatch1(b: 1) { $0 + i } closure2: { d + $0 } + forwardMatch1(b: 1) { $0 + i } closure2: { d + $0 } closure3: { $0 + ", " + s + "!" } + forwardMatch1(b: 1) { $0 + i } closure3: { $0 + ", " + s + "!" } + + // Matching closure2 + forwardMatch1(closure1: { $0 + i }) { d + $0 } + forwardMatch1(closure1: { $0 + i }, numbers: 1, 2, 3) { d + $0 } + forwardMatch1(closure1: { $0 + i }, numbers: 1, 2, 3) { d + $0 } closure3: { $0 + ", " + s + "!" } + + // Matching closure3 + forwardMatch1(closure2: nil) { $0 + ", " + s + "!" } +} + +@_trailingClosureMatching(.forward) +func forwardMatchWithAutoclosure1( + a: String = "hello", + b: @autoclosure () -> Int = 17, + closure1: () -> String +) { } + +func testForwardMatchWithAutoclosure1(i: Int, s: String) { + forwardMatchWithAutoclosure1 { + print(s) + return s + } + forwardMatchWithAutoclosure1(a: s) { + print(s) + return s + } + forwardMatchWithAutoclosure1(b: i) { + print(s) + return s + } + forwardMatchWithAutoclosure1(a: s, b: i) { + print(s) + return s + } +} + +@_trailingClosureMatching(.forward) +func forwardMatchWithAutoclosure2( + a: String = "hello", + closure1: @autoclosure () -> (() -> String), + closure2: () -> Int = { 5 } +) { } + +func testForwardMatchWithAutoclosure2(i: Int, s: String) { + forwardMatchWithAutoclosure2 { + print(s) + return s + } + forwardMatchWithAutoclosure2(a: s) { + print(s) + return s + } + + forwardMatchWithAutoclosure2(a: s, closure1: { s }) { + print(i) + return i + } +} diff --git a/test/expr/postfix/call/forward_trailing_closure_errors.swift b/test/expr/postfix/call/forward_trailing_closure_errors.swift new file mode 100644 index 0000000000000..ac74237892c04 --- /dev/null +++ b/test/expr/postfix/call/forward_trailing_closure_errors.swift @@ -0,0 +1,19 @@ +// RUN: %target-typecheck-verify-swift -enable-experimental-trailing-closure-matching + +func forwardMatchWithGeneric( // expected-note{{'forwardMatchWithGeneric(closure1:closure2:)' declared here}} + closure1: T, + closure2: () -> Int = { 5 } +) { } + +func testKnownSourceBreaks(i: Int) { + forwardMatchWithGeneric { i } // expected-error{{missing argument for parameter 'closure1' in call}} + let _: (() -> ()).Type = type { } // expected-error{{missing argument label 'of:' in call}} +} + +func testUnlabeledParamMatching(i: Int, fn: ((Int) -> Int) -> Void) { + var arrayOfFuncs = [() -> Void]() + arrayOfFuncs.append { print("Hi") } // okay because the parameter is unlabeled? + + fn { $0 + i} // okay because the parameter label is empty + +}