diff --git a/include/swift/AST/ASTTypeIDs.h b/include/swift/AST/ASTTypeIDs.h index fb121e0ea89d7..1a7b06b6ee775 100644 --- a/include/swift/AST/ASTTypeIDs.h +++ b/include/swift/AST/ASTTypeIDs.h @@ -27,6 +27,7 @@ struct PropertyWrapperTypeInfo; class Type; class VarDecl; class TypeAliasDecl; +class Type; #define SWIFT_AST_TYPEID_ZONE 1 diff --git a/include/swift/AST/Attr.def b/include/swift/AST/Attr.def index de90e54a3bc66..597b5a6718983 100644 --- a/include/swift/AST/Attr.def +++ b/include/swift/AST/Attr.def @@ -407,6 +407,9 @@ SIMPLE_DECL_ATTR(_propertyWrapper, PropertyWrapper, SIMPLE_DECL_ATTR(_disfavoredOverload, DisfavoredOverload, OnAbstractFunction | OnVar | OnSubscript | UserInaccessible, 87) +SIMPLE_DECL_ATTR(_functionBuilder, FunctionBuilder, + OnNominalType, + 88) SIMPLE_DECL_ATTR(IBSegueAction, IBSegueAction, OnFunc, diff --git a/include/swift/AST/Decl.h b/include/swift/AST/Decl.h index 5c0b32b80843b..7e6706056cace 100644 --- a/include/swift/AST/Decl.h +++ b/include/swift/AST/Decl.h @@ -2709,6 +2709,14 @@ class ValueDecl : public Decl { /// `this` must be of a decl type that supports opaque return types, and /// must not have previously had an opaque result type set. void setOpaqueResultTypeDecl(OpaqueTypeDecl *D); + + /// Retrieve the attribute associating this declaration with a + /// function builder, if there is one. + CustomAttr *getAttachedFunctionBuilder() const; + + /// Retrieve the @functionBuilder type attached to this declaration, + /// if there is one. + Type getFunctionBuilderType() const; }; /// This is a common base class for declarations which declare a type. @@ -5334,7 +5342,7 @@ class ParamDecl : public VarDecl { assert(isVariadic()); return getVarargBaseTy(getInterfaceType()); } - + SourceRange getSourceRange() const; AnyFunctionType::Param toFunctionParam(Type type = Type()) const; diff --git a/include/swift/AST/DiagnosticsSema.def b/include/swift/AST/DiagnosticsSema.def index 5760336414ccf..1e2d4489c2f3b 100644 --- a/include/swift/AST/DiagnosticsSema.def +++ b/include/swift/AST/DiagnosticsSema.def @@ -4463,6 +4463,44 @@ ERROR(property_wrapper_type_not_usable_from_inline,none, "must be '@usableFromInline' or public", (bool, bool)) +//------------------------------------------------------------------------------ +// MARK: function builder diagnostics +//------------------------------------------------------------------------------ +ERROR(function_builder_decl, none, + "closure containing a declaration cannot be used with function " + "builder %0", (DeclName)) +NOTE(note_function_builder_decl, none, + "closure containing a declaration cannot be used with function " + "builder %0", (DeclName)) +ERROR(function_builder_control_flow, none, + "closure containing control flow statement cannot be used with function " + "builder %0", (DeclName)) +NOTE(note_function_builder_control_flow, none, + "closure containing control flow statement cannot be used with function " + "builder %0", (DeclName)) +ERROR(function_builder_attribute_not_allowed_here, none, + "function builder attribute %0 can only be applied to a parameter, " + "function, or computed property", (DeclName)) +ERROR(function_builder_attribute_on_storage_without_getter, none, + "function builder attribute %0 can only be applied to a " + "%select{subscript|property|constant|variable}1 if it defines a getter", + (DeclName, unsigned)) +ERROR(function_builder_parameter_not_of_function_type, none, + "function builder attribute %0 can only be applied to a parameter of " + "function type", + (DeclName)) +ERROR(function_builder_parameter_autoclosure, none, + "function builder attribute %0 cannot be applied to an autoclosure " + "parameter", + (DeclName)) +ERROR(function_builder_multiple, none, + "only one function builder attribute can be attached to a " + "%select{declaration|parameter}0", (bool)) +NOTE(previous_function_builder_here, none, + "previous function builder specified here", ()) +ERROR(function_builder_arguments, none, + "function builder attributes cannot have arguments", ()) + #ifndef DIAG_NO_UNDEF # if defined(DIAG) # undef DIAG diff --git a/include/swift/AST/KnownIdentifiers.def b/include/swift/AST/KnownIdentifiers.def index 6288b8d7de860..8f01af90a8e81 100644 --- a/include/swift/AST/KnownIdentifiers.def +++ b/include/swift/AST/KnownIdentifiers.def @@ -31,6 +31,10 @@ IDENTIFIER(Any) IDENTIFIER(ArrayLiteralElement) IDENTIFIER(atIndexedSubscript) IDENTIFIER_(bridgeToObjectiveC) +IDENTIFIER(buildBlock) +IDENTIFIER(buildDo) +IDENTIFIER(buildEither) +IDENTIFIER(buildIf) IDENTIFIER(Change) IDENTIFIER_WITH_NAME(code_, "_code") IDENTIFIER(CodingKeys) @@ -59,6 +63,7 @@ IDENTIFIER(Encoder) IDENTIFIER(encoder) IDENTIFIER(error) IDENTIFIER(errorDomain) +IDENTIFIER(first) IDENTIFIER(forKeyedSubscript) IDENTIFIER(Foundation) IDENTIFIER(for) @@ -93,6 +98,7 @@ IDENTIFIER(parameter) IDENTIFIER(Protocol) IDENTIFIER(rawValue) IDENTIFIER(RawValue) +IDENTIFIER(second) IDENTIFIER(Selector) IDENTIFIER(self) IDENTIFIER(Self) diff --git a/include/swift/AST/TypeCheckRequests.h b/include/swift/AST/TypeCheckRequests.h index f6233a9184c0f..1381965d9e062 100644 --- a/include/swift/AST/TypeCheckRequests.h +++ b/include/swift/AST/TypeCheckRequests.h @@ -35,6 +35,7 @@ class RequirementRepr; class SpecializeAttr; class TypeAliasDecl; struct TypeLoc; +class ValueDecl; /// Display a nominal type or extension thereof. void simple_display( @@ -622,6 +623,57 @@ class StructuralTypeRequest : bool isCached() const { return true; } }; +/// Request the custom attribute which attaches a function builder to the +/// given declaration. +class AttachedFunctionBuilderRequest : + public SimpleRequest { +public: + using SimpleRequest::SimpleRequest; + +private: + friend SimpleRequest; + + // Evaluation. + llvm::Expected + evaluate(Evaluator &evaluator, ValueDecl *decl) const; + +public: + // Caching + bool isCached() const; + + // Cycle handling + void diagnoseCycle(DiagnosticEngine &diags) const; + void noteCycleStep(DiagnosticEngine &diags) const; +}; + +/// Request the function builder type attached to the given declaration, +/// if any. +class FunctionBuilderTypeRequest : + public SimpleRequest { +public: + using SimpleRequest::SimpleRequest; + +private: + friend SimpleRequest; + + llvm::Expected + evaluate(Evaluator &evaluator, ValueDecl *decl) const; + +public: + // Caching + bool isCached() const { return true; } + + // Cycle handling + void diagnoseCycle(DiagnosticEngine &diags) const; + void noteCycleStep(DiagnosticEngine &diags) const; +}; + // Allow AnyValue to compare two Type values, even though Type doesn't // support ==. template<> @@ -631,7 +683,7 @@ inline bool AnyValue::Holder::equals(const HolderBase &other) const { static_cast &>(other).value.getPointer(); } -void simple_display(llvm::raw_ostream &out, const Type &type); +void simple_display(llvm::raw_ostream &out, Type value); /// The zone number for the type checker. #define SWIFT_TYPE_CHECKER_REQUESTS_TYPEID_ZONE 10 diff --git a/include/swift/AST/TypeCheckerTypeIDZone.def b/include/swift/AST/TypeCheckerTypeIDZone.def index 15c079a7dd56e..b308674200417 100644 --- a/include/swift/AST/TypeCheckerTypeIDZone.def +++ b/include/swift/AST/TypeCheckerTypeIDZone.def @@ -33,3 +33,5 @@ SWIFT_TYPEID(AttachedPropertyWrapperRequest) SWIFT_TYPEID(AttachedPropertyWrapperTypeRequest) SWIFT_TYPEID(PropertyWrapperBackingPropertyTypeRequest) SWIFT_TYPEID(PropertyWrapperBackingPropertyInfoRequest) +SWIFT_TYPEID(AttachedFunctionBuilderRequest) +SWIFT_TYPEID(FunctionBuilderTypeRequest) diff --git a/include/swift/AST/Types.h b/include/swift/AST/Types.h index 012d5c59c3f8f..0cd3f9d573ab3 100644 --- a/include/swift/AST/Types.h +++ b/include/swift/AST/Types.h @@ -3113,12 +3113,32 @@ BEGIN_CAN_TYPE_WRAPPER(FunctionType, AnyFunctionType) } END_CAN_TYPE_WRAPPER(FunctionType, AnyFunctionType) -/// Map the given parameter list onto a bitvector describing whether -/// the argument type at each index has a default argument associated with -/// it. -SmallBitVector -computeDefaultMap(ArrayRef params, - const ValueDecl *paramOwner, bool skipCurriedSelf); +/// Provides information about the parameter list of a given declaration, including whether each parameter +/// has a default argument. +struct ParameterListInfo { + SmallBitVector defaultArguments; + std::vector functionBuilderTypes; + +public: + ParameterListInfo() { } + + ParameterListInfo(ArrayRef params, + const ValueDecl *paramOwner, bool skipCurriedSelf); + + /// Whether the parameter at the given index has a default argument. + bool hasDefaultArgument(unsigned paramIdx) const; + + /// Retrieve the number of non-defaulted parameters. + unsigned numNonDefaultedParameters() const { + return defaultArguments.count(); + } + + /// Retrieve the number of parameters for which we have information. + unsigned size() const { return defaultArguments.size(); } + + /// Retrieve the function builder type for the given parameter. + Type getFunctionBuilderType(unsigned paramIdx) const; +}; /// Turn a param list into a symbolic and printable representation that does not /// include the types, something like (: , b:, c:) diff --git a/include/swift/Parse/CodeCompletionCallbacks.h b/include/swift/Parse/CodeCompletionCallbacks.h index d897055c43b6b..bca5ad49e786f 100644 --- a/include/swift/Parse/CodeCompletionCallbacks.h +++ b/include/swift/Parse/CodeCompletionCallbacks.h @@ -116,7 +116,7 @@ class CodeCompletionCallbacks { }; /// Set target decl for attribute if the CC token is in attribute of the decl. - virtual void setAttrTargetDecl(Decl *D) {} + virtual void setAttrTargetDeclKind(Optional DK) {} /// Complete the whole expression. This is a fallback that should /// produce results when more specific completion methods failed. diff --git a/lib/AST/ASTPrinter.cpp b/lib/AST/ASTPrinter.cpp index a6684892cb400..5244c57279d70 100644 --- a/lib/AST/ASTPrinter.cpp +++ b/lib/AST/ASTPrinter.cpp @@ -2590,6 +2590,7 @@ void PrintAST::printOneParameter(const ParamDecl *param, auto TheTypeLoc = param->getTypeLoc(); + printAttributes(param); printArgName(); if (!TheTypeLoc.getTypeRepr() && param->hasInterfaceType()) diff --git a/lib/AST/ASTWalker.cpp b/lib/AST/ASTWalker.cpp index 140358f0e154a..b65ce75c63d64 100644 --- a/lib/AST/ASTWalker.cpp +++ b/lib/AST/ASTWalker.cpp @@ -1154,13 +1154,16 @@ class Traversal : public ASTVisitorisImplicit() && !P->isTypeLocImplicit() && doIt(P->getTypeLoc())) return true; - + if (auto *E = P->getDefaultValue()) { auto res = doIt(E); if (!res) return true; diff --git a/lib/AST/Decl.cpp b/lib/AST/Decl.cpp index 057f23df1b966..a616a6f22a0fb 100644 --- a/lib/AST/Decl.cpp +++ b/lib/AST/Decl.cpp @@ -5740,6 +5740,30 @@ void ParamDecl::setStoredProperty(VarDecl *var) { DefaultValueAndFlags.getPointer()->DefaultArg = var; } +Type ValueDecl::getFunctionBuilderType() const { + // Fast path: most declarations (especially parameters, which is where + // this is hottest) do not have any custom attributes at all. + if (!getAttrs().hasAttribute()) return Type(); + + auto &ctx = getASTContext(); + auto mutableThis = const_cast(this); + return evaluateOrDefault(ctx.evaluator, + FunctionBuilderTypeRequest{mutableThis}, + Type()); +} + +CustomAttr *ValueDecl::getAttachedFunctionBuilder() const { + // Fast path: most declarations (especially parameters, which is where + // this is hottest) do not have any custom attributes at all. + if (!getAttrs().hasAttribute()) return nullptr; + + auto &ctx = getASTContext(); + auto mutableThis = const_cast(this); + return evaluateOrDefault(ctx.evaluator, + AttachedFunctionBuilderRequest{mutableThis}, + nullptr); +} + void ParamDecl::setDefaultArgumentInitContext(Initializer *initContext) { assert(DefaultValueAndFlags.getPointer()); DefaultValueAndFlags.getPointer()->InitContext = initContext; diff --git a/lib/AST/Expr.cpp b/lib/AST/Expr.cpp index 7dce3aace587f..df2c06911dcaf 100644 --- a/lib/AST/Expr.cpp +++ b/lib/AST/Expr.cpp @@ -117,6 +117,8 @@ namespace { return getStartLocImpl(E); } template static SourceRange getSourceRange(const T *E) { + if (E->getStartLoc().isInvalid() != E->getEndLoc().isInvalid()) + return SourceRange(); return { E->getStartLoc(), E->getEndLoc() }; } }; diff --git a/lib/AST/Type.cpp b/lib/AST/Type.cpp index b9af83796638d..661d3bc929c05 100644 --- a/lib/AST/Type.cpp +++ b/lib/AST/Type.cpp @@ -729,16 +729,19 @@ Type TypeBase::replaceCovariantResultType(Type newResultType, return FunctionType::get(inputType, resultType, fnType->getExtInfo()); } -SmallBitVector -swift::computeDefaultMap(ArrayRef params, - const ValueDecl *paramOwner, bool skipCurriedSelf) { - SmallBitVector resultVector(params.size()); +ParameterListInfo::ParameterListInfo( + ArrayRef params, + const ValueDecl *paramOwner, + bool skipCurriedSelf) { + defaultArguments.resize(params.size()); + functionBuilderTypes.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. if (!paramOwner) - return resultVector; + return; // Find the corresponding parameter list. const ParameterList *paramList = nullptr; @@ -759,27 +762,44 @@ swift::computeDefaultMap(ArrayRef params, // No parameter list means no default arguments - hand back the zeroed // bitvector. if (!paramList) - return resultVector; + return; switch (params.size()) { case 0: - return resultVector; + return; default: // Arguments and parameters are not guaranteed to always line-up // perfectly, e.g. failure diagnostics tries to match argument type // to different "candidate" parameters. if (params.size() != paramList->size()) - return resultVector; + return; - for (auto i : range(0, params.size())) { - if (paramList->get(i)->isDefaultArgument()) { - resultVector.set(i); - } - } break; } - return resultVector; + + // Note which parameters have default arguments and/or function builders. + for (auto i : range(0, params.size())) { + auto param = paramList->get(i); + if (param->isDefaultArgument()) { + defaultArguments.set(i); + } + + if (Type functionBuilderType = param->getFunctionBuilderType()) { + functionBuilderTypes[i] = functionBuilderType; + } + } +} + +bool ParameterListInfo::hasDefaultArgument(unsigned paramIdx) const { + return paramIdx < defaultArguments.size() ? defaultArguments[paramIdx] + : false; +} + +Type ParameterListInfo::getFunctionBuilderType(unsigned paramIdx) const { + return paramIdx < functionBuilderTypes.size() + ? functionBuilderTypes[paramIdx] + : Type(); } /// Turn a param list into a symbolic and printable representation that does not diff --git a/lib/AST/TypeCheckRequests.cpp b/lib/AST/TypeCheckRequests.cpp index 756b897b4d741..3bb7073d64742 100644 --- a/lib/AST/TypeCheckRequests.cpp +++ b/lib/AST/TypeCheckRequests.cpp @@ -59,6 +59,13 @@ void swift::simple_display(llvm::raw_ostream &out, } } +void swift::simple_display(llvm::raw_ostream &out, Type type) { + if (type) + type.print(out); + else + out << "null"; +} + //----------------------------------------------------------------------------// // Inherited type computation. //----------------------------------------------------------------------------// @@ -688,13 +695,6 @@ void swift::simple_display( out << " }"; } -void swift::simple_display(llvm::raw_ostream &out, const Type &type) { - if (type) - type.print(out); - else - out << "null"; -} - //----------------------------------------------------------------------------// // StructuralTypeRequest. //----------------------------------------------------------------------------// @@ -706,3 +706,31 @@ void StructuralTypeRequest::diagnoseCycle(DiagnosticEngine &diags) const { void StructuralTypeRequest::noteCycleStep(DiagnosticEngine &diags) const { diags.diagnose(SourceLoc(), diag::circular_reference_through); } + +//----------------------------------------------------------------------------// +// FunctionBuilder-related requests. +//----------------------------------------------------------------------------// + +bool AttachedFunctionBuilderRequest::isCached() const { + // Only needs to be cached if there are any custom attributes. + auto var = std::get<0>(getStorage()); + return var->getAttrs().hasAttribute(); +} + +void AttachedFunctionBuilderRequest::diagnoseCycle( + DiagnosticEngine &diags) const { + std::get<0>(getStorage())->diagnose(diag::circular_reference); +} + +void AttachedFunctionBuilderRequest::noteCycleStep( + DiagnosticEngine &diags) const { + std::get<0>(getStorage())->diagnose(diag::circular_reference_through); +} + +void FunctionBuilderTypeRequest::diagnoseCycle(DiagnosticEngine &diags) const { + std::get<0>(getStorage())->diagnose(diag::circular_reference); +} + +void FunctionBuilderTypeRequest::noteCycleStep(DiagnosticEngine &diags) const { + std::get<0>(getStorage())->diagnose(diag::circular_reference_through); +} diff --git a/lib/IDE/CodeCompletion.cpp b/lib/IDE/CodeCompletion.cpp index f34d38ac0189b..8e93bc9098bf9 100644 --- a/lib/IDE/CodeCompletion.cpp +++ b/lib/IDE/CodeCompletion.cpp @@ -1348,12 +1348,7 @@ class CodeCompletionCallbacksImpl : public CodeCompletionCallbacks { Consumer(Consumer) { } - void setAttrTargetDecl(Decl *D) override { - if (D == nullptr) { - AttTargetDK = None; - return; - } - auto DK = D->getKind(); + void setAttrTargetDeclKind(Optional DK) override { if (DK == DeclKind::PatternBinding) DK = DeclKind::Var; AttTargetDK = DK; @@ -1468,11 +1463,10 @@ protocolForLiteralKind(CodeCompletionLiteralKind kind) { /// that is of type () -> (). static bool hasTrivialTrailingClosure(const FuncDecl *FD, AnyFunctionType *funcType) { - SmallBitVector defaultMap = - computeDefaultMap(funcType->getParams(), FD, - /*level*/ FD->isInstanceMember() ? 1 : 0); + ParameterListInfo paramInfo(funcType->getParams(), FD, + /*level*/ FD->isInstanceMember() ? 1 : 0); - if (defaultMap.size() - defaultMap.count() == 1) { + if (paramInfo.size() - paramInfo.numNonDefaultedParameters() == 1) { auto param = funcType->getParams().back(); if (!param.isAutoClosure()) { if (auto Fn = param.getOldType()->getAs()) { @@ -5382,7 +5376,8 @@ void CodeCompletionCallbacksImpl::doneParsing() { Lookup.getAttributeDeclCompletions(IsInSil, AttTargetDK); // Provide any type name for property delegate. - if (!AttTargetDK || *AttTargetDK == DeclKind::Var) + if (!AttTargetDK || *AttTargetDK == DeclKind::Var || + *AttTargetDK == DeclKind::Param) Lookup.getTypeCompletionsInDeclContext( P.Context.SourceMgr.getCodeCompletionLoc()); break; diff --git a/lib/Parse/ParseDecl.cpp b/lib/Parse/ParseDecl.cpp index df0605c8beca1..d4092bf9017b4 100644 --- a/lib/Parse/ParseDecl.cpp +++ b/lib/Parse/ParseDecl.cpp @@ -2998,7 +2998,10 @@ Parser::parseDecl(ParseDeclOptions Flags, if (AttrStatus.hasCodeCompletion()) { if (CodeCompletion) { - CodeCompletion->setAttrTargetDecl(DeclResult.getPtrOrNull()); + Optional DK; + if (DeclResult.isNonNull()) + DK = DeclResult.get()->getKind(); + CodeCompletion->setAttrTargetDeclKind(DK); } else { delayParseFromBeginningToHere(BeginParserPosition, Flags); return makeParserError(); diff --git a/lib/Parse/ParsePattern.cpp b/lib/Parse/ParsePattern.cpp index 50075010d3845..35d06f2f2a675 100644 --- a/lib/Parse/ParsePattern.cpp +++ b/lib/Parse/ParsePattern.cpp @@ -221,8 +221,11 @@ Parser::parseParameterClause(SourceLoc &leftParenLoc, // Attributes. if (paramContext != ParameterContextKind::EnumElement) { auto AttrStatus = parseDeclAttributeList(param.Attrs); - if (AttrStatus.hasCodeCompletion()) + if (AttrStatus.hasCodeCompletion()) { + if (CodeCompletion) + CodeCompletion->setAttrTargetDeclKind(DeclKind::Param); status.setHasCodeCompletion(); + } } // ('inout' | '__shared' | '__owned')? diff --git a/lib/Sema/BuilderTransform.cpp b/lib/Sema/BuilderTransform.cpp new file mode 100644 index 0000000000000..5655dd6105771 --- /dev/null +++ b/lib/Sema/BuilderTransform.cpp @@ -0,0 +1,597 @@ +//===--- BuilderTransform.cpp - Function-builder transformation -----------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2014 - 2018 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See https://swift.org/LICENSE.txt for license information +// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors +// +//===----------------------------------------------------------------------===// +// +// This file implements routines associated with the function-builder +// transformation. +// +//===----------------------------------------------------------------------===// + +#include "ConstraintSystem.h" +#include "TypeChecker.h" +#include "swift/AST/ASTVisitor.h" +#include "swift/AST/ASTWalker.h" +#include "swift/AST/NameLookup.h" +#include "swift/AST/NameLookupRequests.h" +#include "swift/AST/ParameterList.h" +#include "llvm/ADT/DenseMap.h" +#include "llvm/ADT/SmallVector.h" +#include +#include +#include +#include +#include + +using namespace swift; +using namespace constraints; + +namespace { + +/// Visitor to classify the contents of the given closure. +class BuilderClosureVisitor + : public StmtVisitor { + ConstraintSystem *cs; + ASTContext &ctx; + bool wantExpr; + Type builderType; + NominalTypeDecl *builder = nullptr; + llvm::SmallDenseMap supportedOps; + +public: + SkipUnhandledConstructInFunctionBuilder::UnhandledNode unhandledNode; + +private: + /// Produce a builder call to the given named function with the given arguments. + CallExpr *buildCallIfWanted(SourceLoc loc, + Identifier fnName, ArrayRef args, + ArrayRef argLabels = {}) { + if (!wantExpr) + return nullptr; + + // FIXME: Setting a TypeLoc on this expression is necessary in order + // to get diagnostics if something about this builder call fails, + // e.g. if there isn't a matching overload for `buildBlock`. + // But we can only do this if there isn't a type variable in the type. + TypeLoc typeLoc(nullptr, + builderType->hasTypeVariable() ? Type() : builderType); + + auto typeExpr = new (ctx) TypeExpr(typeLoc); + if (cs) { + cs->setType(typeExpr, MetatypeType::get(builderType)); + cs->setType(&typeExpr->getTypeLoc(), builderType); + } + + typeExpr->setImplicit(); + auto memberRef = new (ctx) UnresolvedDotExpr( + typeExpr, loc, fnName, DeclNameLoc(loc), /*implicit=*/true); + return CallExpr::createImplicit(ctx, memberRef, args, argLabels); + } + + /// Check whether the builder supports the given operation. + bool builderSupports(Identifier fnName, + ArrayRef argLabels = {}) { + auto known = supportedOps.find(fnName); + if (known != supportedOps.end()) { + return known->second; + } + + bool found = false; + for (auto decl : builder->lookupDirect(fnName)) { + if (auto func = dyn_cast(decl)) { + // Function must be static. + if (!func->isStatic()) + continue; + + // Function must have the right argument labels, if provided. + if (!argLabels.empty()) { + auto funcLabels = func->getFullName().getArgumentNames(); + if (argLabels.size() > funcLabels.size() || + funcLabels.slice(0, argLabels.size()) != argLabels) + continue; + } + + // Okay, it's a good-enough match. + found = true; + break; + } + } + + return supportedOps[fnName] = found; + } + +public: + BuilderClosureVisitor(ASTContext &ctx, ConstraintSystem *cs, + bool wantExpr, Type builderType) + : cs(cs), ctx(ctx), wantExpr(wantExpr), builderType(builderType) { + assert((cs || !builderType->hasTypeVariable()) && + "cannot handle builder type with type variables without " + "constraint system"); + builder = builderType->getAnyNominal(); + } + +#define CONTROL_FLOW_STMT(StmtClass) \ + Expr *visit##StmtClass##Stmt(StmtClass##Stmt *stmt) { \ + if (!unhandledNode) \ + unhandledNode = stmt; \ + \ + return nullptr; \ + } + + Expr *visitBraceStmt(BraceStmt *braceStmt) { + SmallVector expressions; + for (const auto &node : braceStmt->getElements()) { + if (auto stmt = node.dyn_cast()) { + auto expr = visit(stmt); + if (expr) + expressions.push_back(expr); + continue; + } + + if (auto decl = node.dyn_cast()) { + if (!unhandledNode) + unhandledNode = decl; + + continue; + } + + auto expr = node.get(); + expressions.push_back(expr); + } + + // Call Builder.buildBlock(... args ...) + return buildCallIfWanted(braceStmt->getStartLoc(), + ctx.Id_buildBlock, expressions); + } + + Expr *visitReturnStmt(ReturnStmt *stmt) { + // Allow implicit returns due to 'return' elision. + if (!stmt->isImplicit() || !stmt->hasResult()) { + if (!unhandledNode) + unhandledNode = stmt; + return nullptr; + } + + return stmt->getResult(); + } + + Expr *visitDoStmt(DoStmt *doStmt) { + if (!builderSupports(ctx.Id_buildDo)) { + if (!unhandledNode) + unhandledNode = doStmt; + return nullptr; + } + + auto arg = visit(doStmt->getBody()); + if (!arg) + return nullptr; + + return buildCallIfWanted(doStmt->getStartLoc(), ctx.Id_buildDo, arg); + } + + CONTROL_FLOW_STMT(Yield) + CONTROL_FLOW_STMT(Defer) + + static Expr *getTrivialBooleanCondition(StmtCondition condition) { + if (condition.size() != 1) + return nullptr; + + return condition.front().getBooleanOrNull(); + } + + static bool isBuildableIfChainRecursive(IfStmt *ifStmt, + unsigned &numPayloads, + bool &isOptional) { + // The conditional must be trivial. + if (!getTrivialBooleanCondition(ifStmt->getCond())) + return false; + + // The 'then' clause contributes a payload. + numPayloads++; + + // If there's an 'else' clause, it contributes payloads: + if (auto elseStmt = ifStmt->getElseStmt()) { + // If it's 'else if', it contributes payloads recursively. + if (auto elseIfStmt = dyn_cast(elseStmt)) { + return isBuildableIfChainRecursive(elseIfStmt, numPayloads, + isOptional); + // Otherwise it's just the one. + } else { + numPayloads++; + } + + // If not, the chain result is at least optional. + } else { + isOptional = true; + } + + return true; + } + + bool isBuildableIfChain(IfStmt *ifStmt, unsigned &numPayloads, + bool &isOptional) { + if (!isBuildableIfChainRecursive(ifStmt, numPayloads, isOptional)) + return false; + + // If there's a missing 'else', we need 'buildIf' to exist. + if (isOptional && !builderSupports(ctx.Id_buildIf)) + return false; + + // If there are multiple clauses, we need 'buildEither(first:)' and + // 'buildEither(second:)' to both exist. + if (numPayloads > 1) { + if (!builderSupports(ctx.Id_buildEither, {ctx.Id_first}) || + !builderSupports(ctx.Id_buildEither, {ctx.Id_second})) + return false; + } + + return true; + } + + Expr *visitIfStmt(IfStmt *ifStmt) { + // Check whether the chain is buildable and whether it terminates + // without an `else`. + bool isOptional = false; + unsigned numPayloads = 0; + if (!isBuildableIfChain(ifStmt, numPayloads, isOptional)) { + if (!unhandledNode) + unhandledNode = ifStmt; + return nullptr; + } + + // Attempt to build the chain, propagating short-circuits, which + // might arise either do to error or not wanting an expression. + auto chainExpr = + buildIfChainRecursive(ifStmt, 0, numPayloads, isOptional); + if (!chainExpr) + return nullptr; + assert(wantExpr); + + // The operand should have optional type if we had optional results, + // so we just need to call `buildIf` now, since we're at the top level. + if (isOptional) { + chainExpr = buildCallIfWanted(ifStmt->getStartLoc(), + ctx.Id_buildIf, chainExpr); + } + + return chainExpr; + } + + /// Recursively build an if-chain: build an expression which will have + /// a value of the chain result type before any call to `buildIf`. + /// The expression will perform any necessary calls to `buildEither`, + /// and the result will have optional type if `isOptional` is true. + Expr *buildIfChainRecursive(IfStmt *ifStmt, unsigned payloadIndex, + unsigned numPayloads, bool isOptional) { + assert(payloadIndex < numPayloads); + // Make sure we recursively visit both sides even if we're not + // building expressions. + + // Build the then clause. This will have the corresponding payload + // type (i.e. not wrapped in any way). + Expr *thenArg = visit(ifStmt->getThenStmt()); + + // Build the else clause, if present. If this is from an else-if, + // this will be fully wrapped; otherwise it will have the corresponding + // payload type (at index `payloadIndex + 1`). + assert(ifStmt->getElseStmt() || isOptional); + bool isElseIf = false; + Optional elseChain; + if (auto elseStmt = ifStmt->getElseStmt()) { + if (auto elseIfStmt = dyn_cast(elseStmt)) { + isElseIf = true; + elseChain = buildIfChainRecursive(elseIfStmt, payloadIndex + 1, + numPayloads, isOptional); + } else { + elseChain = visit(elseStmt); + } + } + + // Short-circuit if appropriate. + if (!wantExpr || !thenArg || (elseChain && !*elseChain)) + return nullptr; + + // Okay, build the conditional expression. + + // Prepare the `then` operand by wrapping it to produce a chain result. + SourceLoc thenLoc = ifStmt->getThenStmt()->getStartLoc(); + Expr *thenExpr = buildWrappedChainPayload(thenArg, payloadIndex, + numPayloads, isOptional); + + // Prepare the `else operand: + Expr *elseExpr; + SourceLoc elseLoc; + + // - If there's no `else` clause, use `Optional.none`. + if (!elseChain) { + assert(isOptional); + elseLoc = ifStmt->getEndLoc(); + elseExpr = buildNoneExpr(elseLoc); + + // - If there's an `else if`, the chain expression from that + // should already be producing a chain result. + } else if (isElseIf) { + elseExpr = *elseChain; + elseLoc = ifStmt->getElseLoc(); + + // - Otherwise, wrap it to produce a chain result. + } else { + elseLoc = ifStmt->getElseLoc(); + elseExpr = buildWrappedChainPayload(*elseChain, + payloadIndex + 1, numPayloads, + isOptional); + } + + Expr *condition = getTrivialBooleanCondition(ifStmt->getCond()); + assert(condition && "checked by isBuildableIfChain"); + + auto ifExpr = new (ctx) IfExpr(condition, thenLoc, thenExpr, + elseLoc, elseExpr); + ifExpr->setImplicit(); + return ifExpr; + } + + /// Wrap a payload value in an expression which will produce a chain + /// result (without `buildIf`). + Expr *buildWrappedChainPayload(Expr *operand, unsigned payloadIndex, + unsigned numPayloads, bool isOptional) { + assert(payloadIndex < numPayloads); + + // Inject into the appropriate chain position. + // + // We produce a (left-biased) balanced binary tree of Eithers in order + // to prevent requiring a linear number of injections in the worst case. + // That is, if we have 13 clauses, we want to produce: + // + // /------------------Either------------\ + // /-------Either-------\ /--Either--\ + // /--Either--\ /--Either--\ /--Either--\ \ + // /-E-\ /-E-\ /-E-\ /-E-\ /-E-\ /-E-\ \ + // 0000 0001 0010 0011 0100 0101 0110 0111 1000 1001 1010 1011 1100 + // + // Note that a prefix of length D of the payload index acts as a path + // through the tree to the node at depth D. On the rightmost path + // through the tree (when this prefix is equal to the corresponding + // prefix of the maximum payload index), the bits of the index mark + // where Eithers are required. + // + // Since we naturally want to build from the innermost Either out, and + // therefore work with progressively shorter prefixes, we can do it all + // with right-shifts. + for (auto path = payloadIndex, maxPath = numPayloads - 1; + maxPath != 0; path >>= 1, maxPath >>= 1) { + // Skip making Eithers on the rightmost path where they aren't required. + // This isn't just an optimization: adding spurious Eithers could + // leave us with unresolvable type variables if `buildEither` has + // a signature like: + // static func buildEither(first value: T) -> Either + // which relies on unification to work. + if (path == maxPath && !(maxPath & 1)) continue; + + bool isSecond = (path & 1); + operand = buildCallIfWanted(operand->getStartLoc(), + ctx.Id_buildEither, operand, + {isSecond ? ctx.Id_second : ctx.Id_first}); + } + + // Inject into Optional if required. We'll be adding the call to + // `buildIf` after all the recursive calls are complete. + if (isOptional) { + operand = buildSomeExpr(operand); + } + + return operand; + } + + Expr *buildSomeExpr(Expr *arg) { + auto optionalDecl = ctx.getOptionalDecl(); + auto optionalType = optionalDecl->getDeclaredType(); + + auto optionalTypeExpr = TypeExpr::createImplicit(optionalType, ctx); + auto someRef = new (ctx) UnresolvedDotExpr( + optionalTypeExpr, SourceLoc(), ctx.getIdentifier("some"), + DeclNameLoc(), /*implicit=*/true); + return CallExpr::createImplicit(ctx, someRef, arg, { }); + } + + Expr *buildNoneExpr(SourceLoc endLoc) { + auto optionalDecl = ctx.getOptionalDecl(); + auto optionalType = optionalDecl->getDeclaredType(); + + auto optionalTypeExpr = TypeExpr::createImplicit(optionalType, ctx); + return new (ctx) UnresolvedDotExpr( + optionalTypeExpr, endLoc, ctx.getIdentifier("none"), + DeclNameLoc(endLoc), /*implicit=*/true); + } + + CONTROL_FLOW_STMT(Guard) + CONTROL_FLOW_STMT(While) + CONTROL_FLOW_STMT(DoCatch) + CONTROL_FLOW_STMT(RepeatWhile) + CONTROL_FLOW_STMT(ForEach) + CONTROL_FLOW_STMT(Switch) + CONTROL_FLOW_STMT(Case) + CONTROL_FLOW_STMT(Catch) + CONTROL_FLOW_STMT(Break) + CONTROL_FLOW_STMT(Continue) + CONTROL_FLOW_STMT(Fallthrough) + CONTROL_FLOW_STMT(Fail) + CONTROL_FLOW_STMT(Throw) + CONTROL_FLOW_STMT(PoundAssert) + +#undef CONTROL_FLOW_STMT +}; + +} // end anonymous namespace + +/// Determine whether the given statement contains a 'return' statement anywhere. +static bool hasReturnStmt(Stmt *stmt) { + class ReturnStmtFinder : public ASTWalker { + public: + bool hasReturnStmt = false; + + std::pair walkToExprPre(Expr *expr) override { + return { false, expr }; + } + + std::pair walkToStmtPre(Stmt *stmt) override { + // Did we find a 'return' statement? + if (isa(stmt)) { + hasReturnStmt = true; + } + + return { !hasReturnStmt, stmt }; + } + + Stmt *walkToStmtPost(Stmt *stmt) override { + return hasReturnStmt ? nullptr : stmt; + } + + std::pair walkToPatternPre(Pattern *pattern) override { + return { false, pattern }; + } + + bool walkToDeclPre(Decl *D) override { return false; } + + bool walkToTypeLocPre(TypeLoc &TL) override { return false; } + + bool walkToTypeReprPre(TypeRepr *T) override { return false; } + }; + + ReturnStmtFinder finder{}; + stmt->walk(finder); + return finder.hasReturnStmt; +} + +bool TypeChecker::typeCheckFunctionBuilderFuncBody(FuncDecl *FD, + Type builderType) { + // Try to build a single result expression. + BuilderClosureVisitor visitor(Context, nullptr, + /*wantExpr=*/true, builderType); + Expr *returnExpr = visitor.visit(FD->getBody()); + if (!returnExpr) + return true; + + // Make sure we have a usable result type for the body. + Type returnType = AnyFunctionRef(FD).getBodyResultType(); + if (!returnType || returnType->hasError()) + return true; + + // Type-check the single result expression. + Type returnExprType = typeCheckExpression(returnExpr, FD, + TypeLoc::withoutLoc(returnType), + CTP_ReturnStmt); + if (!returnExprType) + return true; + assert(returnExprType->isEqual(returnType)); + + auto returnStmt = new (Context) ReturnStmt(SourceLoc(), returnExpr); + auto origBody = FD->getBody(); + auto fakeBody = BraceStmt::create(Context, origBody->getLBraceLoc(), + { returnStmt }, + origBody->getRBraceLoc()); + FD->setBody(fakeBody); + return false; +} + +ConstraintSystem::TypeMatchResult ConstraintSystem::applyFunctionBuilder( + ClosureExpr *closure, Type builderType, ConstraintLocator *calleeLocator, + ConstraintLocatorBuilder locator) { + auto builder = builderType->getAnyNominal(); + assert(builder && "Bad function builder type"); + assert(builder->getAttrs().hasAttribute()); + + // Check the form of this closure to see if we can apply the function-builder + // translation at all. + { + // FIXME: Right now, single-expression closures suppress the function + // builder translation. + if (closure->hasSingleExpressionBody()) + return getTypeMatchSuccess(); + + // The presence of an explicit return suppresses the function builder + // translation. + if (hasReturnStmt(closure->getBody())) { + return getTypeMatchSuccess(); + } + + // Check whether we can apply this function builder. + BuilderClosureVisitor visitor(getASTContext(), this, + /*wantExpr=*/false, builderType); + (void)visitor.visit(closure->getBody()); + + + // If we saw a control-flow statement or declaration that the builder + // cannot handle, we don't have a well-formed function builder application. + if (visitor.unhandledNode) { + // If we aren't supposed to attempt fixes, fail. + if (!shouldAttemptFixes()) { + return getTypeMatchFailure(locator); + } + + // Record the first unhandled construct as a fix. + if (recordFix( + SkipUnhandledConstructInFunctionBuilder::create( + *this, visitor.unhandledNode, builder, + getConstraintLocator(locator)))) { + return getTypeMatchFailure(locator); + } + } + } + + // If the builder type has a type parameter, substitute in the type + // variables. + if (builderType->hasTypeParameter()) { + // Find the opened type for this callee and substitute in the type + // parametes. + for (const auto &opened : OpenedTypes) { + if (opened.first == calleeLocator) { + OpenedTypeMap replacements(opened.second.begin(), + opened.second.end()); + builderType = openType(builderType, replacements); + break; + } + } + assert(!builderType->hasTypeParameter()); + } + + BuilderClosureVisitor visitor(getASTContext(), this, + /*wantExpr=*/true, builderType); + Expr *singleExpr = visitor.visit(closure->getBody()); + + if (TC.precheckedClosures.insert(closure).second && + TC.preCheckExpression(singleExpr, closure)) + return getTypeMatchFailure(locator); + + singleExpr = generateConstraints(singleExpr, closure); + if (!singleExpr) + return getTypeMatchFailure(locator); + + Type transformedType = getType(singleExpr); + assert(transformedType && "Missing type"); + + // Record the transformation. + assert(std::find_if(builderTransformedClosures.begin(), + builderTransformedClosures.end(), + [&](const std::tuple &elt) { + return std::get<0>(elt) == closure; + }) == builderTransformedClosures.end() && + "already transformed this closure along this path!?!"); + builderTransformedClosures.push_back( + std::make_tuple(closure, builderType, singleExpr)); + + // Bind the result type of the closure to the type of the transformed + // expression. + Type closureType = getType(closure); + auto fnType = closureType->castTo(); + addConstraint(ConstraintKind::Equal, fnType->getResult(), transformedType, + locator); + return getTypeMatchSuccess(); +} diff --git a/lib/Sema/CMakeLists.txt b/lib/Sema/CMakeLists.txt index dfc3898ae7649..6dc488ba4eef1 100644 --- a/lib/Sema/CMakeLists.txt +++ b/lib/Sema/CMakeLists.txt @@ -4,6 +4,7 @@ if (SWIFT_FORCE_OPTIMIZED_TYPECHECKER) endif() add_swift_host_library(swiftSema STATIC + BuilderTransform.cpp CSApply.cpp CSBindings.cpp CSDiag.cpp diff --git a/lib/Sema/CSApply.cpp b/lib/Sema/CSApply.cpp index 6ff140658bd53..990bd8c54091e 100644 --- a/lib/Sema/CSApply.cpp +++ b/lib/Sema/CSApply.cpp @@ -5345,8 +5345,7 @@ Expr *ExprRewriter::coerceCallArguments( // Determine whether this application has curried self. bool skipCurriedSelf = apply ? hasCurriedSelf(cs, callee, apply) : true; // Determine the parameter bindings. - SmallBitVector defaultMap - = computeDefaultMap(params, callee.getDecl(), skipCurriedSelf); + ParameterListInfo paramInfo(params, callee.getDecl(), skipCurriedSelf); SmallVector args; AnyFunctionType::decomposeInput(cs.getType(arg), args); @@ -5361,7 +5360,7 @@ Expr *ExprRewriter::coerceCallArguments( MatchCallArgumentListener listener; SmallVector parameterBindings; bool failed = constraints::matchCallArguments(args, params, - defaultMap, + paramInfo, hasTrailingClosure, /*allowFixes=*/false, listener, parameterBindings); @@ -7343,6 +7342,21 @@ namespace { // metadata types/conformances during IRGen. tc.requestRequiredNominalTypeLayoutForParameters(params); + // If this closure had a function builder applied, rewrite it to a + // closure with a single expression body containing the builder + // invocations. + auto builder = + Rewriter.solution.builderTransformedClosures.find(closure); + if (builder != Rewriter.solution.builderTransformedClosures.end()) { + auto singleExpr = builder->second.second; + auto returnStmt = new (tc.Context) ReturnStmt( + singleExpr->getStartLoc(), singleExpr, /*implicit=*/true); + auto braceStmt = BraceStmt::create( + tc.Context, returnStmt->getStartLoc(), ASTNode(returnStmt), + returnStmt->getEndLoc(), /*implicit=*/true); + closure->setBody(braceStmt, /*isSingleExpression=*/true); + } + // If this is a single-expression closure, convert the expression // in the body to the result type of the closure. if (closure->hasSingleExpressionBody()) { @@ -7473,6 +7487,11 @@ Expr *ConstraintSystem::applySolution(Solution &solution, Expr *expr, Type convertType, bool discardedExpr, bool skipClosures) { + // Add the node types back. + for (auto &nodeType : solution.addedNodeTypes) { + setType(nodeType.first, nodeType.second); + } + // If any fixes needed to be applied to arrive at this solution, resolve // them to specific expressions. if (!solution.Fixes.empty()) { diff --git a/lib/Sema/CSDiag.cpp b/lib/Sema/CSDiag.cpp index ebe79ebcf8a0e..6576bb6a62304 100644 --- a/lib/Sema/CSDiag.cpp +++ b/lib/Sema/CSDiag.cpp @@ -2672,10 +2672,12 @@ typeCheckArgumentChildIndependently(Expr *argExpr, Type argType, // If we have a candidate function around, compute the position of its // default arguments. - SmallBitVector defaultMap(params.size()); + ParameterListInfo paramInfo; if (!candidates.empty()) { - defaultMap = computeDefaultMap(params, candidates[0].getDecl(), - candidates[0].skipCurriedSelf); + paramInfo = ParameterListInfo(params, candidates[0].getDecl(), + candidates[0].skipCurriedSelf); + } else { + paramInfo = ParameterListInfo(params, nullptr, /*skipCurriedSelf=*/false); } // Form a set of call arguments, using a dummy type (Void), because the @@ -2694,7 +2696,7 @@ typeCheckArgumentChildIndependently(Expr *argExpr, Type argType, } listener; SmallVector paramBindings; - if (!matchCallArguments(args, params, defaultMap, + if (!matchCallArguments(args, params, paramInfo, callArgHasTrailingClosure(argExpr), /*allowFixes=*/true, listener, paramBindings)) { @@ -3290,7 +3292,7 @@ class ArgumentMatcher : public MatchCallArgumentListener { Expr *FnExpr; Expr *ArgExpr; ArrayRef &Parameters; - const SmallBitVector &DefaultMap; + const ParameterListInfo &ParamInfo; SmallVectorImpl &Arguments; CalleeCandidateInfo CandidateInfo; @@ -3306,11 +3308,11 @@ class ArgumentMatcher : public MatchCallArgumentListener { public: ArgumentMatcher(Expr *fnExpr, Expr *argExpr, ArrayRef ¶ms, - const SmallBitVector &defaultMap, + const ParameterListInfo ¶mInfo, SmallVectorImpl &args, CalleeCandidateInfo &CCI, bool isSubscript) : TC(CCI.CS.TC), FnExpr(fnExpr), ArgExpr(argExpr), Parameters(params), - DefaultMap(defaultMap), Arguments(args), CandidateInfo(CCI), + ParamInfo(paramInfo), Arguments(args), CandidateInfo(CCI), IsSubscript(isSubscript) {} void extraArgument(unsigned extraArgIdx) override { @@ -3532,7 +3534,7 @@ class ArgumentMatcher : public MatchCallArgumentListener { // Use matchCallArguments to determine how close the argument list is (in // shape) to the specified candidates parameters. This ignores the // concrete types of the arguments, looking only at the argument labels. - matchCallArguments(Arguments, Parameters, DefaultMap, + matchCallArguments(Arguments, Parameters, ParamInfo, CandidateInfo.hasTrailingClosure, /*allowFixes:*/ true, *this, Bindings); @@ -3559,9 +3561,8 @@ diagnoseSingleCandidateFailures(CalleeCandidateInfo &CCI, Expr *fnExpr, return false; auto params = candidate.getParameters(); - - SmallBitVector defaultMap = - computeDefaultMap(params, candidate.getDecl(), candidate.skipCurriedSelf); + ParameterListInfo paramInfo(params, candidate.getDecl(), + candidate.skipCurriedSelf); auto args = decomposeArgType(CCI.CS.getType(argExpr), argLabels); // Check the case where a raw-representable type is constructed from an @@ -3613,7 +3614,7 @@ diagnoseSingleCandidateFailures(CalleeCandidateInfo &CCI, Expr *fnExpr, // If we have a single candidate that failed to match the argument list, // attempt to use matchCallArguments to diagnose the problem. - return ArgumentMatcher(fnExpr, argExpr, params, defaultMap, args, CCI, + return ArgumentMatcher(fnExpr, argExpr, params, paramInfo, args, CCI, isa(fnExpr)) .diagnose(); } @@ -4208,13 +4209,13 @@ bool FailureDiagnosis::diagnoseArgumentGenericRequirements( return false; auto params = candidate.getParameters(); - SmallBitVector defaultMap = - computeDefaultMap(params, candidate.getDecl(), candidate.skipCurriedSelf); + ParameterListInfo paramInfo(params, candidate.getDecl(), + candidate.skipCurriedSelf); auto args = decomposeArgType(CS.getType(argExpr), argLabels); SmallVector bindings; MatchCallArgumentListener listener; - if (matchCallArguments(args, params, defaultMap, + if (matchCallArguments(args, params, paramInfo, candidates.hasTrailingClosure, /*allowFixes=*/false, listener, bindings)) return false; @@ -4682,8 +4683,8 @@ static bool isViableOverloadSet(const CalleeCandidateInfo &CCI, return true; }; - auto defaultMap = computeDefaultMap(params, funcDecl, cand.skipCurriedSelf); - InputMatcher IM(params, defaultMap); + ParameterListInfo paramInfo(params, funcDecl, cand.skipCurriedSelf); + InputMatcher IM(params, paramInfo); auto result = IM.match(numArgs, pairMatcher); if (result == InputMatcher::IM_Succeeded) return true; diff --git a/lib/Sema/CSDiagnostics.cpp b/lib/Sema/CSDiagnostics.cpp index 8ce2e332ea5db..50526bdb55e5a 100644 --- a/lib/Sema/CSDiagnostics.cpp +++ b/lib/Sema/CSDiagnostics.cpp @@ -3157,3 +3157,33 @@ bool MissingGenericArgumentsFailure::findArgumentLocations( typeLoc.getTypeRepr()->walk(associator); return associator.allParamsAssigned(); } + +void SkipUnhandledConstructInFunctionBuilderFailure::diagnosePrimary( + bool asNote) { + if (auto stmt = unhandled.dyn_cast()) { + emitDiagnostic(stmt->getStartLoc(), + asNote? diag::note_function_builder_control_flow + : diag::function_builder_control_flow, + builder->getFullName()); + } else { + auto decl = unhandled.get(); + emitDiagnostic(decl, + asNote ? diag::note_function_builder_decl + : diag::function_builder_decl, + builder->getFullName()); + } +} + +bool SkipUnhandledConstructInFunctionBuilderFailure::diagnoseAsError() { + diagnosePrimary(/*asNote=*/false); + emitDiagnostic(builder, + diag::kind_declname_declared_here, + builder->getDescriptiveKind(), + builder->getFullName()); + return true; +} + +bool SkipUnhandledConstructInFunctionBuilderFailure::diagnoseAsNote() { + diagnosePrimary(/*asNote=*/true); + return true; +} diff --git a/lib/Sema/CSDiagnostics.h b/lib/Sema/CSDiagnostics.h index 18fcb296cd40d..784907ce3e3ad 100644 --- a/lib/Sema/CSDiagnostics.h +++ b/lib/Sema/CSDiagnostics.h @@ -1308,6 +1308,30 @@ class MissingGenericArgumentsFailure final : public FailureDiagnostic { llvm::function_ref callback); }; +class SkipUnhandledConstructInFunctionBuilderFailure final + : public FailureDiagnostic { +public: + using UnhandledNode = llvm::PointerUnion; + + UnhandledNode unhandled; + NominalTypeDecl *builder; + + void diagnosePrimary(bool asNote); + +public: + SkipUnhandledConstructInFunctionBuilderFailure(Expr *root, + ConstraintSystem &cs, + UnhandledNode unhandled, + NominalTypeDecl *builder, + ConstraintLocator *locator) + : FailureDiagnostic(root, cs, locator), + unhandled(unhandled), + builder(builder) { } + + bool diagnoseAsError() override; + bool diagnoseAsNote() override; +}; + } // end namespace constraints } // end namespace swift diff --git a/lib/Sema/CSFix.cpp b/lib/Sema/CSFix.cpp index 6f765fe75da5c..942204025f926 100644 --- a/lib/Sema/CSFix.cpp +++ b/lib/Sema/CSFix.cpp @@ -573,3 +573,19 @@ ExplicitlySpecifyGenericArguments *ExplicitlySpecifyGenericArguments::create( size, alignof(ExplicitlySpecifyGenericArguments)); return new (mem) ExplicitlySpecifyGenericArguments(cs, params, locator); } + +SkipUnhandledConstructInFunctionBuilder * +SkipUnhandledConstructInFunctionBuilder::create(ConstraintSystem &cs, + UnhandledNode unhandled, + NominalTypeDecl *builder, + ConstraintLocator *locator) { + return new (cs.getAllocator()) + SkipUnhandledConstructInFunctionBuilder(cs, unhandled, builder, locator); +} + +bool SkipUnhandledConstructInFunctionBuilder::diagnose(Expr *root, + bool asNote) const { + SkipUnhandledConstructInFunctionBuilderFailure failure( + root, getConstraintSystem(), unhandled, builder, getLocator()); + return failure.diagnose(asNote); +} diff --git a/lib/Sema/CSFix.h b/lib/Sema/CSFix.h index 429cab419ac18..0e52d4a63f216 100644 --- a/lib/Sema/CSFix.h +++ b/lib/Sema/CSFix.h @@ -163,6 +163,10 @@ enum class FixKind : uint8_t { /// specified in the source. This fix groups all of the missing arguments /// associated with single declaration. ExplicitlySpecifyGenericArguments, + + /// Skip any unhandled constructs that occur within a closure argument that matches up with a + /// parameter that has a function builder. + SkipUnhandledConstructInFunctionBuilder, }; class ConstraintFix { @@ -993,6 +997,34 @@ class ExplicitlySpecifyGenericArguments final } }; +class SkipUnhandledConstructInFunctionBuilder final : public ConstraintFix { +public: + using UnhandledNode = llvm::PointerUnion; + +private: + UnhandledNode unhandled; + NominalTypeDecl *builder; + + SkipUnhandledConstructInFunctionBuilder(ConstraintSystem &cs, + UnhandledNode unhandled, + NominalTypeDecl *builder, + ConstraintLocator *locator) + : ConstraintFix(cs, FixKind::SkipUnhandledConstructInFunctionBuilder, + locator), + unhandled(unhandled), builder(builder) { } + +public: + std::string getName() const override { + return "skip unhandled constructs when applying a function builder"; + } + + bool diagnose(Expr *root, bool asNote = false) const override; + + static SkipUnhandledConstructInFunctionBuilder * + create(ConstraintSystem &cs, UnhandledNode unhandledNode, + NominalTypeDecl *builder, ConstraintLocator *locator); +}; + } // end namespace constraints } // end namespace swift diff --git a/lib/Sema/CSGen.cpp b/lib/Sema/CSGen.cpp index 32b15bfa4afab..80d7411a0dc5a 100644 --- a/lib/Sema/CSGen.cpp +++ b/lib/Sema/CSGen.cpp @@ -1121,7 +1121,9 @@ namespace { } public: - ConstraintGenerator(ConstraintSystem &CS) : CS(CS), CurDC(CS.DC) { } + ConstraintGenerator(ConstraintSystem &CS, DeclContext *DC) + : CS(CS), CurDC(DC ? DC : CS.DC) { } + virtual ~ConstraintGenerator() { // We really ought to have this assertion: // assert(DCStack.empty() && CurDC == CS.DC); @@ -1408,6 +1410,8 @@ namespace { type = typeLoc.getType(); } else if (typeLoc.hasLocation()) { type = resolveTypeReferenceInExpression(typeLoc); + } else if (E->isImplicit() && CS.hasType(&typeLoc)) { + type = CS.getType(typeLoc); } if (!type || type->hasError()) return Type(); @@ -3096,8 +3100,7 @@ namespace { Type visitTapExpr(TapExpr *expr) { DeclContext *varDC = expr->getVar()->getDeclContext(); - assert(varDC == CS.DC || (varDC && isa(varDC) && - cast(varDC)->hasSingleExpressionBody()) && + assert(varDC == CS.DC || (varDC && isa(varDC)) && "TapExpr var should be in the same DeclContext we're checking it in!"); auto locator = CS.getConstraintLocator(expr); @@ -3625,8 +3628,6 @@ namespace { if (closureTy && closureTy->hasError()) return nullptr; - CS.setType(closure, closureTy); - // Visit the body. It's type needs to be convertible to the function's // return type. auto resultTy = closureTy->castTo()->getResult(); @@ -3719,7 +3720,7 @@ namespace { } // end anonymous namespace -Expr *ConstraintSystem::generateConstraints(Expr *expr) { +Expr *ConstraintSystem::generateConstraints(Expr *expr, DeclContext *dc) { // Remove implicit conversions from the expression. expr = expr->walk(SanitizeExpr(*this)); @@ -3727,7 +3728,7 @@ Expr *ConstraintSystem::generateConstraints(Expr *expr) { expr->walk(ArgumentLabelWalker(*this, expr)); // Walk the expression, generating constraints. - ConstraintGenerator cg(*this); + ConstraintGenerator cg(*this, dc); ConstraintWalker cw(cg); Expr* result = expr->walk(cw); @@ -3740,7 +3741,7 @@ Expr *ConstraintSystem::generateConstraints(Expr *expr) { Type ConstraintSystem::generateConstraints(Pattern *pattern, ConstraintLocatorBuilder locator) { - ConstraintGenerator cg(*this); + ConstraintGenerator cg(*this, nullptr); return cg.getTypeForPattern(pattern, locator); } diff --git a/lib/Sema/CSRanking.cpp b/lib/Sema/CSRanking.cpp index 9ebd37f0ba150..9e47f344b4416 100644 --- a/lib/Sema/CSRanking.cpp +++ b/lib/Sema/CSRanking.cpp @@ -668,7 +668,7 @@ static bool isDeclAsSpecializedAs(TypeChecker &tc, DeclContext *dc, return true; }; - auto defaultMap = computeDefaultMap( + ParameterListInfo paramInfo( params2, decl2, decl2->getDeclContext()->isTypeContext()); auto params2ForMatching = params2; if (compareTrailingClosureParamsSeparately) { @@ -676,7 +676,7 @@ static bool isDeclAsSpecializedAs(TypeChecker &tc, DeclContext *dc, params2ForMatching = params2.drop_back(); } - InputMatcher IM(params2ForMatching, defaultMap); + InputMatcher IM(params2ForMatching, paramInfo); if (IM.match(numParams1, pairMatcher) != InputMatcher::IM_Succeeded) return false; @@ -1444,8 +1444,8 @@ SolutionDiff::SolutionDiff(ArrayRef solutions) { } InputMatcher::InputMatcher(const ArrayRef params, - const SmallBitVector &defaultValueMap) - : NumSkippedParameters(0), DefaultValueMap(defaultValueMap), + const ParameterListInfo ¶mInfo) + : NumSkippedParameters(0), ParamInfo(paramInfo), Params(params) {} InputMatcher::Result @@ -1458,7 +1458,7 @@ InputMatcher::match(int numInputs, // If we've claimed all of the inputs, the rest of the parameters should // be either default or variadic. if (inputIdx == numInputs) { - if (!DefaultValueMap[i] && !Params[i].isVariadic()) + if (!ParamInfo.hasDefaultArgument(i) && !Params[i].isVariadic()) return IM_HasUnmatchedParam; ++NumSkippedParameters; continue; @@ -1476,7 +1476,8 @@ InputMatcher::match(int numInputs, // params: (a: Int, b: Int = 0, c: Int) // // and we shouldn't claim any input and just skip such parameter. - if ((numInputs - inputIdx) < (numParams - i) && DefaultValueMap[i]) { + if ((numInputs - inputIdx) < (numParams - i) && + ParamInfo.hasDefaultArgument(i)) { ++NumSkippedParameters; continue; } diff --git a/lib/Sema/CSSimplify.cpp b/lib/Sema/CSSimplify.cpp index c1d18a3a30c9b..a5a85c994a46c 100644 --- a/lib/Sema/CSSimplify.cpp +++ b/lib/Sema/CSSimplify.cpp @@ -172,13 +172,12 @@ bool constraints::areConservativelyCompatibleArgumentLabels( } auto params = levelTy->getParams(); - SmallBitVector defaultMap = - computeDefaultMap(params, decl, hasCurriedSelf); + ParameterListInfo paramInfo(params, decl, hasCurriedSelf); MatchCallArgumentListener listener; SmallVector unusedParamBindings; - return !matchCallArguments(argInfos, params, defaultMap, + return !matchCallArguments(argInfos, params, paramInfo, hasTrailingClosure, /*allow fixes*/ false, listener, unusedParamBindings); @@ -230,12 +229,12 @@ static bool acceptsTrailingClosure(const AnyFunctionType::Param ¶m) { bool constraints:: matchCallArguments(ArrayRef args, ArrayRef params, - const SmallBitVector &defaultMap, + const ParameterListInfo ¶mInfo, bool hasTrailingClosure, bool allowFixes, MatchCallArgumentListener &listener, SmallVectorImpl ¶meterBindings) { - assert(params.size() == defaultMap.size() && "Default map does not match"); + assert(params.size() == paramInfo.size() && "Default map does not match"); // Keep track of the parameter we're matching and what argument indices // got bound to each parameter. @@ -253,10 +252,6 @@ matchCallArguments(ArrayRef args, // requiring further checking at the end. bool potentiallyOutOfOrder = false; - auto hasDefault = [&defaultMap, &numParams](unsigned idx) -> bool { - return idx < numParams ? defaultMap.test(idx) : false; - }; - // Local function that claims the argument at \c argNumber, returning the // index of the claimed argument. This is primarily a helper for // \c claimNextNamed. @@ -346,7 +341,8 @@ matchCallArguments(ArrayRef args, // func foo(_ a: Int, _ b: Int = 0, c: Int = 0, _ d: Int) {} // foo(1, c: 2, 3) // -> `3` will be claimed as '_ b:'. // ``` - if (argLabel.empty() && (hasDefault(i) || !forVariadic)) + if (argLabel.empty() && + (paramInfo.hasDefaultArgument(i) || !forVariadic)) continue; potentiallyOutOfOrder = true; @@ -565,7 +561,7 @@ matchCallArguments(ArrayRef args, // now if label doesn't match because it's incorrect // or argument belongs to some other parameter, so // we just leave this parameter unfulfilled. - if (defaultMap.test(i)) + if (paramInfo.hasDefaultArgument(i)) continue; // Looks like there was no parameter claimed at the same @@ -602,7 +598,7 @@ matchCallArguments(ArrayRef args, continue; // Parameters with defaults can be unfulfilled. - if (hasDefault(paramIdx)) + if (paramInfo.hasDefaultArgument(paramIdx)) continue; listener.missingArgument(paramIdx); @@ -635,7 +631,7 @@ matchCallArguments(ArrayRef args, // If we are moving the the position with a different label // and there is no default value for it, can't diagnose the // problem as a simple re-ordering. - if (!defaultMap.test(actualIndex)) + if (!paramInfo.hasDefaultArgument(actualIndex)) return false; } @@ -643,7 +639,7 @@ matchCallArguments(ArrayRef args, if (oldLabel == params[i].getLabel()) break; - if (!defaultMap.test(i)) + if (!paramInfo.hasDefaultArgument(i)) return false; } @@ -734,7 +730,8 @@ matchCallArguments(ArrayRef args, /// Find the callee declaration and uncurry level for a given call /// locator. -static std::tuple, bool> +static std::tuple, bool, + ConstraintLocator *> getCalleeDeclAndArgs(ConstraintSystem &cs, ConstraintLocatorBuilder callLocator, SmallVectorImpl &argLabelsScratch) { @@ -746,14 +743,14 @@ getCalleeDeclAndArgs(ConstraintSystem &cs, auto callExpr = callLocator.getLocatorParts(path); if (!callExpr) return std::make_tuple(nullptr, /*hasCurriedSelf=*/false, argLabels, - hasTrailingClosure); + hasTrailingClosure, nullptr); // Our remaining path can only be 'ApplyArgument'. if (!path.empty() && !(path.size() <= 2 && path.back().getKind() == ConstraintLocator::ApplyArgument)) return std::make_tuple(nullptr, /*hasCurriedSelf=*/false, argLabels, - hasTrailingClosure); + hasTrailingClosure, nullptr); // Dig out the callee. ConstraintLocator *targetLocator; @@ -778,12 +775,12 @@ getCalleeDeclAndArgs(ConstraintSystem &cs, path[0].getKind() != ConstraintLocator::KeyPathComponent || path[1].getKind() != ConstraintLocator::ApplyArgument) return std::make_tuple(nullptr, /*hasCurriedSelf=*/false, argLabels, - hasTrailingClosure); + hasTrailingClosure, nullptr); auto componentIndex = path[0].getValue(); if (componentIndex >= keyPath->getComponents().size()) return std::make_tuple(nullptr, /*hasCurriedSelf=*/false, argLabels, - hasTrailingClosure); + hasTrailingClosure, nullptr); auto &component = keyPath->getComponents()[componentIndex]; switch (component.getKind()) { @@ -803,7 +800,7 @@ getCalleeDeclAndArgs(ConstraintSystem &cs, case KeyPathExpr::Component::Kind::Identity: case KeyPathExpr::Component::Kind::TupleElement: return std::make_tuple(nullptr, /*hasCurriedSelf=*/false, argLabels, - hasTrailingClosure); + hasTrailingClosure, nullptr); } } else { @@ -815,13 +812,14 @@ getCalleeDeclAndArgs(ConstraintSystem &cs, hasTrailingClosure = objectLiteral->hasTrailingClosure(); } return std::make_tuple(nullptr, /*hasCurriedSelf=*/false, argLabels, - hasTrailingClosure); + hasTrailingClosure, nullptr); } // Find the overload choice corresponding to the callee locator. // FIXME: This linearly walks the list of resolved overloads, which is // potentially very expensive. Optional choice; + ConstraintLocator *calleeLocator = nullptr; for (auto resolved = cs.getResolvedOverloadSets(); resolved; resolved = resolved->Previous) { // FIXME: Workaround null locators. @@ -848,6 +846,7 @@ getCalleeDeclAndArgs(ConstraintSystem &cs, resolvedLocator = simplifyLocator(cs, resolvedLocator, range); if (resolvedLocator == targetLocator) { + calleeLocator = resolved->Locator; choice = resolved->Choice; break; } @@ -856,7 +855,7 @@ getCalleeDeclAndArgs(ConstraintSystem &cs, // If we didn't find any matching overloads, we're done. if (!choice) return std::make_tuple(nullptr, /*hasCurriedSelf=*/false, argLabels, - hasTrailingClosure); + hasTrailingClosure, nullptr); // If there's a declaration, return it. if (auto *decl = choice->getDeclOrNull()) { @@ -879,11 +878,12 @@ getCalleeDeclAndArgs(ConstraintSystem &cs, } } - return std::make_tuple(decl, hasCurriedSelf, argLabels, hasTrailingClosure); + return std::make_tuple(decl, hasCurriedSelf, argLabels, hasTrailingClosure, + calleeLocator); } return std::make_tuple(nullptr, /*hasCurriedSelf=*/false, argLabels, - hasTrailingClosure); + hasTrailingClosure, calleeLocator); } class ArgumentFailureTracker : public MatchCallArgumentListener { @@ -945,11 +945,12 @@ ConstraintSystem::TypeMatchResult constraints::matchCallArguments( ArrayRef argLabels; SmallVector argLabelsScratch; bool hasTrailingClosure = false; - std::tie(callee, hasCurriedSelf, argLabels, hasTrailingClosure) = + ConstraintLocator *calleeLocator; + std::tie(callee, hasCurriedSelf, argLabels, hasTrailingClosure, + calleeLocator) = getCalleeDeclAndArgs(cs, locator, argLabelsScratch); - SmallBitVector defaultMap = - computeDefaultMap(params, callee, hasCurriedSelf); + ParameterListInfo paramInfo(params, callee, hasCurriedSelf); // Apply labels to arguments. SmallVector argsWithLabels; @@ -960,7 +961,7 @@ ConstraintSystem::TypeMatchResult constraints::matchCallArguments( SmallVector parameterBindings; ArgumentFailureTracker listener(cs, parameterBindings, locator); if (constraints::matchCallArguments(argsWithLabels, params, - defaultMap, + paramInfo, hasTrailingClosure, cs.shouldAttemptFixes(), listener, parameterBindings)) @@ -1012,6 +1013,20 @@ ConstraintSystem::TypeMatchResult constraints::matchCallArguments( } } + // If the parameter has a function builder type and the argument is a + // closure, apply the function builder transformation. + if (Type functionBuilderType + = paramInfo.getFunctionBuilderType(paramIdx)) { + Expr *arg = getArgumentExpr(locator.getAnchor(), argIdx); + if (auto closure = dyn_cast_or_null(arg)) { + auto result = + cs.applyFunctionBuilder(closure, functionBuilderType, + calleeLocator, loc); + if (result.isFailure()) + return result; + } + } + // If argument comes for declaration it should loose // `@autoclosure` flag, because in context it's used // as a function type represented by autoclosure. @@ -6647,7 +6662,8 @@ ConstraintSystem::SolutionKind ConstraintSystem::simplifyFixConstraint( case FixKind::SkipSameTypeRequirement: case FixKind::SkipSuperclassRequirement: case FixKind::ContextualMismatch: - case FixKind::AddMissingArguments: { + case FixKind::AddMissingArguments: + case FixKind::SkipUnhandledConstructInFunctionBuilder: { return recordFix(fix) ? SolutionKind::Error : SolutionKind::Solved; } diff --git a/lib/Sema/CSSolver.cpp b/lib/Sema/CSSolver.cpp index 9ff2b5f94edfd..3c3d0dff4a612 100644 --- a/lib/Sema/CSSolver.cpp +++ b/lib/Sema/CSSolver.cpp @@ -169,9 +169,25 @@ Solution ConstraintSystem::finalize() { solution.DefaultedConstraints.insert(DefaultedConstraints.begin(), DefaultedConstraints.end()); + for (auto &nodeType : addedNodeTypes) { + solution.addedNodeTypes.push_back(nodeType); + } + for (auto &e : CheckedConformances) solution.Conformances.push_back({e.first, e.second}); + for (const auto &transformed : builderTransformedClosures) { + auto known = + solution.builderTransformedClosures.find(std::get<0>(transformed)); + if (known != solution.builderTransformedClosures.end()) { + assert(known->second.second == std::get<2>(transformed)); + } + solution.builderTransformedClosures.insert( + std::make_pair(std::get<0>(transformed), + std::make_pair(std::get<1>(transformed), + std::get<2>(transformed)))); + } + return solution; } @@ -233,10 +249,23 @@ void ConstraintSystem::applySolution(const Solution &solution) { solution.DefaultedConstraints.begin(), solution.DefaultedConstraints.end()); + // Add the node types back. + for (auto &nodeType : solution.addedNodeTypes) { + if (!hasType(nodeType.first)) + setType(nodeType.first, nodeType.second); + } + // Register the conformances checked along the way to arrive to solution. for (auto &conformance : solution.Conformances) CheckedConformances.push_back(conformance); + for (const auto &transformed : solution.builderTransformedClosures) { + builderTransformedClosures.push_back( + std::make_tuple(transformed.first, + transformed.second.first, + transformed.second.second)); + } + // Register any fixes produced along this path. Fixes.append(solution.Fixes.begin(), solution.Fixes.end()); @@ -428,10 +457,12 @@ ConstraintSystem::SolverScope::SolverScope(ConstraintSystem &cs) numOpenedTypes = cs.OpenedTypes.size(); numOpenedExistentialTypes = cs.OpenedExistentialTypes.size(); numDefaultedConstraints = cs.DefaultedConstraints.size(); + numAddedNodeTypes = cs.addedNodeTypes.size(); numCheckedConformances = cs.CheckedConformances.size(); numMissingMembers = cs.MissingMembers.size(); numDisabledConstraints = cs.solverState->getNumDisabledConstraints(); numFavoredConstraints = cs.solverState->getNumFavoredConstraints(); + numBuilderTransformedClosures = cs.builderTransformedClosures.size(); PreviousScore = cs.CurrentScore; @@ -480,12 +511,21 @@ ConstraintSystem::SolverScope::~SolverScope() { // Remove any defaulted type variables. truncate(cs.DefaultedConstraints, numDefaultedConstraints); + // Remove any node types we registered. + for (unsigned i : range(numAddedNodeTypes, cs.addedNodeTypes.size())) { + cs.eraseType(cs.addedNodeTypes[i].first); + } + truncate(cs.addedNodeTypes, numAddedNodeTypes); + // Remove any conformances checked along the current path. truncate(cs.CheckedConformances, numCheckedConformances); // Remove any missing members found along the current path. truncate(cs.MissingMembers, numMissingMembers); + /// Remove any builder transformed closures. + truncate(cs.builderTransformedClosures, numBuilderTransformedClosures); + // Reset the previous score. cs.CurrentScore = PreviousScore; diff --git a/lib/Sema/CalleeCandidateInfo.cpp b/lib/Sema/CalleeCandidateInfo.cpp index b74beb9366fab..1a39554a29c4b 100644 --- a/lib/Sema/CalleeCandidateInfo.cpp +++ b/lib/Sema/CalleeCandidateInfo.cpp @@ -320,8 +320,8 @@ CalleeCandidateInfo::ClosenessResultTy CalleeCandidateInfo::evaluateCloseness( return {CC_GeneralMismatch, {}}; auto candArgs = candidate.getParameters(); - SmallBitVector candDefaultMap = - computeDefaultMap(candArgs, candidate.getDecl(), candidate.skipCurriedSelf); + ParameterListInfo candParamInfo(candArgs, candidate.getDecl(), + candidate.skipCurriedSelf); struct OurListener : public MatchCallArgumentListener { CandidateCloseness result = CC_ExactMatch; @@ -366,7 +366,7 @@ CalleeCandidateInfo::ClosenessResultTy CalleeCandidateInfo::evaluateCloseness( // types of the arguments, looking only at the argument labels etc. SmallVector paramBindings; if (matchCallArguments(actualArgs, candArgs, - candDefaultMap, + candParamInfo, hasTrailingClosure, /*allowFixes:*/ true, listener, paramBindings)) diff --git a/lib/Sema/ConstraintSystem.cpp b/lib/Sema/ConstraintSystem.cpp index 78380ce0c4dad..d8a16cdbd83fb 100644 --- a/lib/Sema/ConstraintSystem.cpp +++ b/lib/Sema/ConstraintSystem.cpp @@ -18,6 +18,7 @@ #include "ConstraintSystem.h" #include "ConstraintGraph.h" #include "CSDiagnostics.h" +#include "CSFix.h" #include "TypeCheckType.h" #include "swift/AST/GenericEnvironment.h" #include "swift/AST/ParameterList.h" diff --git a/lib/Sema/ConstraintSystem.h b/lib/Sema/ConstraintSystem.h index 393366d01c8f6..05185b5fa4cc6 100644 --- a/lib/Sema/ConstraintSystem.h +++ b/lib/Sema/ConstraintSystem.h @@ -570,6 +570,11 @@ struct Score { }; +/// An AST node that can gain type information while solving. +using TypedNode = + llvm::PointerUnion3; + /// Display a score. llvm::raw_ostream &operator<<(llvm::raw_ostream &out, const Score &score); @@ -642,9 +647,16 @@ class Solution { /// The locators of \c Defaultable constraints whose defaults were used. llvm::SmallPtrSet DefaultedConstraints; + /// The node -> type mappings introduced by this solution. + llvm::SmallVector, 8> addedNodeTypes; + std::vector> Conformances; + /// The set of closures that have been transformed by a function builder. + llvm::MapVector> + builderTransformedClosures; + /// Simplify the given type by substituting all occurrences of /// type variables for their fixed types. Type simplifyType(Type type) const; @@ -1102,9 +1114,16 @@ class ConstraintSystem { SmallVector, 4> OpenedExistentialTypes; + /// The node -> type mappings introduced by generating constraints. + llvm::SmallVector, 8> addedNodeTypes; + std::vector> CheckedConformances; + /// The set of closures that have been transformed by a function builder. + SmallVector, 4> + builderTransformedClosures; + public: /// The locators of \c Defaultable constraints whose defaults were used. std::vector DefaultedConstraints; @@ -1578,6 +1597,8 @@ class ConstraintSystem { /// The length of \c DefaultedConstraints. unsigned numDefaultedConstraints; + unsigned numAddedNodeTypes; + unsigned numCheckedConformances; unsigned numMissingMembers; @@ -1586,6 +1607,8 @@ class ConstraintSystem { unsigned numFavoredConstraints; + unsigned numBuilderTransformedClosures; + /// The previous score. Score PreviousScore; @@ -1726,33 +1749,48 @@ class ConstraintSystem { this->FavoredTypes[E] = T; } - /// Set the type in our type map for a given expression. The side - /// map is used throughout the expression type checker in order to - /// avoid mutating expressions until we know we have successfully - /// type-checked them. - void setType(Expr *E, Type T) { - assert(E != nullptr && "Expected non-null expression!"); - assert(T && "Expected non-null type!"); + /// Set the type in our type map for the given node. + /// + /// The side tables are used through the expression type checker to avoid mutating nodes until + /// we know we have successfully type-checked them. + void setType(TypedNode node, Type type) { + assert(!node.isNull() && "Cannot set type information on null node"); + assert(type && "Expected non-null type"); - // FIXME: We sometimes set the type and then later set it to a - // value that is slightly different, e.g. not an lvalue. - // assert((ExprTypes.find(E) == ExprTypes.end() || - // ExprTypes.find(E)->second->isEqual(T) || - // ExprTypes.find(E)->second->hasTypeVariable()) && - // "Expected type to be invariant!"); + // Record the type. + if (auto expr = node.dyn_cast()) { + ExprTypes[expr] = type.getPointer(); + } else if (auto typeLoc = node.dyn_cast()) { + TypeLocTypes[typeLoc] = type.getPointer(); + } else { + auto param = node.get(); + ParamTypes[param] = type.getPointer(); + } - ExprTypes[E] = T.getPointer(); + // Record the fact that we ascribed a type to this node. + if (solverState && solverState->depth > 0) { + addedNodeTypes.push_back({node, type}); + } } + /// Set the type in our type map for a given expression. The side + /// map is used throughout the expression type checker in order to + /// avoid mutating expressions until we know we have successfully + /// type-checked them. void setType(TypeLoc &L, Type T) { - assert(T && "Expected non-null type!"); - TypeLocTypes[&L] = T.getPointer(); + setType(TypedNode(&L), T); } - void setType(ParamDecl *P, Type T) { - assert(P && "Expected non-null parameter!"); - assert(T && "Expected non-null type!"); - ParamTypes[P] = T.getPointer(); + /// Erase the type for the given node. + void eraseType(TypedNode node) { + if (auto expr = node.dyn_cast()) { + ExprTypes.erase(expr); + } else if (auto typeLoc = node.dyn_cast()) { + TypeLocTypes.erase(typeLoc); + } else { + auto param = node.get(); + ParamTypes.erase(param); + } } void setType(KeyPathExpr *KP, unsigned I, Type T) { @@ -1768,12 +1806,20 @@ class ConstraintSystem { } bool hasType(const TypeLoc &L) const { - return TypeLocTypes.find(&L) != TypeLocTypes.end(); + return hasType(TypedNode(&L)); } - bool hasType(const ParamDecl *P) const { - assert(P != nullptr && "Expected non-null parameter!"); - return ParamTypes.find(P) != ParamTypes.end(); + /// Check to see if we have a type for a node. + bool hasType(TypedNode node) const { + assert(!node.isNull() && "Expected non-null node"); + if (auto expr = node.dyn_cast()) { + return ExprTypes.find(expr) != ExprTypes.end(); + } else if (auto typeLoc = node.dyn_cast()) { + return TypeLocTypes.find(typeLoc) != TypeLocTypes.end(); + } else { + auto param = node.get(); + return ParamTypes.find(param) != ParamTypes.end(); + } } bool hasType(const KeyPathExpr *KP, unsigned I) const { @@ -2519,7 +2565,7 @@ class ConstraintSystem { /// Generate constraints for the given (unchecked) expression. /// /// \returns a possibly-sanitized expression, or null if an error occurred. - Expr *generateConstraints(Expr *E); + Expr *generateConstraints(Expr *E, DeclContext *dc = nullptr); /// Generate constraints for binding the given pattern to the /// value of the given expression. @@ -3006,6 +3052,11 @@ class ConstraintSystem { /// Simplify the given disjunction choice. void simplifyDisjunctionChoice(Constraint *choice); + /// Apply the given function builder to the closure expression. + TypeMatchResult applyFunctionBuilder(ClosureExpr *closure, Type builderType, + ConstraintLocator *calleeLocator, + ConstraintLocatorBuilder locator); + private: /// The kind of bindings that are permitted. enum class AllowedBindingKind : uint8_t { @@ -3700,7 +3751,7 @@ class MatchCallArgumentListener { /// /// \param args The arguments. /// \param params The parameters. -/// \param defaultMap A map indicating if the parameter at that index has a default value. +/// \param paramInfo Declaration-level information about the parameters. /// \param hasTrailingClosure Whether the last argument is a trailing closure. /// \param allowFixes Whether to allow fixes when matching arguments. /// @@ -3712,7 +3763,7 @@ class MatchCallArgumentListener { /// \returns true if the call arguments could not be matched to the parameters. bool matchCallArguments(ArrayRef args, ArrayRef params, - const SmallBitVector &defaultMap, + const ParameterListInfo ¶mInfo, bool hasTrailingClosure, bool allowFixes, MatchCallArgumentListener &listener, @@ -4086,7 +4137,7 @@ class OverloadSetCounter : public ASTWalker { /// in the custom function when necessary. class InputMatcher { size_t NumSkippedParameters; - const SmallBitVector DefaultValueMap; + const ParameterListInfo &ParamInfo; const ArrayRef Params; public: @@ -4102,7 +4153,7 @@ class InputMatcher { }; InputMatcher(const ArrayRef params, - const SmallBitVector &defaultValueMap); + const ParameterListInfo ¶mInfo); /// Matching a given array of inputs. /// diff --git a/lib/Sema/TypeCheckAttr.cpp b/lib/Sema/TypeCheckAttr.cpp index ac6f128c5170b..fcd276f870da1 100644 --- a/lib/Sema/TypeCheckAttr.cpp +++ b/lib/Sema/TypeCheckAttr.cpp @@ -131,6 +131,7 @@ class AttributeEarlyChecker : public AttributeVisitor { IGNORED_ATTR(Custom) IGNORED_ATTR(PropertyWrapper) IGNORED_ATTR(DisfavoredOverload) + IGNORED_ATTR(FunctionBuilder) #undef IGNORED_ATTR void visitAlignmentAttr(AlignmentAttr *attr) { @@ -853,6 +854,7 @@ class AttributeChecker : public AttributeVisitor { void visitNonOverrideAttr(NonOverrideAttr *attr); void visitCustomAttr(CustomAttr *attr); void visitPropertyWrapperAttr(PropertyWrapperAttr *attr); + void visitFunctionBuilderAttr(FunctionBuilderAttr *attr); }; } // end anonymous namespace @@ -2563,6 +2565,59 @@ void AttributeChecker::visitCustomAttr(CustomAttr *attr) { return; } + // If the nominal type is a function builder type, verify that D is a + // function, storage with an explicit getter, or parameter of function type. + if (nominal->getAttrs().hasAttribute()) { + ValueDecl *decl; + if (auto param = dyn_cast(D)) { + decl = param; + } else if (auto func = dyn_cast(D)) { + decl = func; + } else if (auto storage = dyn_cast(D)) { + decl = storage; + auto getter = storage->getGetter(); + if (!getter || getter->isImplicit() || !getter->hasBody()) { + TC.diagnose(attr->getLocation(), + diag::function_builder_attribute_on_storage_without_getter, + nominal->getFullName(), + isa(storage) ? 0 + : storage->getDeclContext()->isTypeContext() ? 1 + : cast(storage)->isLet() ? 2 : 3); + attr->setInvalid(); + return; + } + } else { + TC.diagnose(attr->getLocation(), + diag::function_builder_attribute_not_allowed_here, + nominal->getFullName()); + attr->setInvalid(); + return; + } + + // Diagnose and ignore arguments. + if (attr->getArg()) { + TC.diagnose(attr->getLocation(), diag::function_builder_arguments) + .highlight(attr->getArg()->getSourceRange()); + } + + // Complain if this isn't the primary function-builder attribute. + auto attached = decl->getAttachedFunctionBuilder(); + if (attached != attr) { + TC.diagnose(attr->getLocation(), diag::function_builder_multiple, + isa(decl)); + TC.diagnose(attached->getLocation(), + diag::previous_function_builder_here); + attr->setInvalid(); + return; + } else { + // Force any diagnostics associated with computing the function-builder + // type. + (void) decl->getFunctionBuilderType(); + } + + return; + } + TC.diagnose(attr->getLocation(), diag::nominal_type_not_attribute, nominal->getDescriptiveKind(), nominal->getFullName()); nominal->diagnose(diag::decl_declared_here, nominal->getFullName()); @@ -2570,12 +2625,6 @@ void AttributeChecker::visitCustomAttr(CustomAttr *attr) { } -void TypeChecker::checkParameterAttributes(ParameterList *params) { - for (auto param: *params) { - checkDeclAttributes(param); - } -} - void AttributeChecker::visitPropertyWrapperAttr(PropertyWrapperAttr *attr) { auto nominal = dyn_cast(D); if (!nominal) @@ -2585,6 +2634,17 @@ void AttributeChecker::visitPropertyWrapperAttr(PropertyWrapperAttr *attr) { (void)nominal->getPropertyWrapperTypeInfo(); } +void AttributeChecker::visitFunctionBuilderAttr(FunctionBuilderAttr *attr) { + // TODO: check that the type at least provides a `sequence` factory? + // Any other validation? +} + +void TypeChecker::checkParameterAttributes(ParameterList *params) { + for (auto param: *params) { + checkDeclAttributes(param); + } +} + void TypeChecker::checkDeclAttributes(Decl *D) { AttributeChecker Checker(*this, D); diff --git a/lib/Sema/TypeCheckDeclOverride.cpp b/lib/Sema/TypeCheckDeclOverride.cpp index f391e37d007af..7eaadbb66ffc9 100644 --- a/lib/Sema/TypeCheckDeclOverride.cpp +++ b/lib/Sema/TypeCheckDeclOverride.cpp @@ -1329,7 +1329,7 @@ namespace { UNINTERESTING_ATTR(Custom) UNINTERESTING_ATTR(PropertyWrapper) UNINTERESTING_ATTR(DisfavoredOverload) - + UNINTERESTING_ATTR(FunctionBuilder) #undef UNINTERESTING_ATTR void visitAvailableAttr(AvailableAttr *attr) { diff --git a/lib/Sema/TypeCheckRequestFunctions.cpp b/lib/Sema/TypeCheckRequestFunctions.cpp index d74e026474000..3b22da813bbe1 100644 --- a/lib/Sema/TypeCheckRequestFunctions.cpp +++ b/lib/Sema/TypeCheckRequestFunctions.cpp @@ -12,9 +12,11 @@ #include "TypeChecker.h" #include "TypeCheckType.h" #include "swift/AST/PropertyWrappers.h" +#include "swift/AST/Attr.h" #include "swift/AST/TypeCheckRequests.h" #include "swift/AST/Decl.h" #include "swift/AST/ExistentialLayout.h" +#include "swift/AST/NameLookupRequests.h" #include "swift/AST/TypeLoc.h" #include "swift/AST/Types.h" #include "swift/Subsystems.h" @@ -146,6 +148,80 @@ EnumRawTypeRequest::evaluate(Evaluator &evaluator, EnumDecl *enumDecl, return Type(); } +llvm::Expected +AttachedFunctionBuilderRequest::evaluate(Evaluator &evaluator, + ValueDecl *decl) const { + ASTContext &ctx = decl->getASTContext(); + auto dc = decl->getDeclContext(); + for (auto attr : decl->getAttrs().getAttributes()) { + auto mutableAttr = const_cast(attr); + // Figure out which nominal declaration this custom attribute refers to. + auto nominal = evaluateOrDefault(ctx.evaluator, + CustomAttrNominalRequest{mutableAttr, dc}, + nullptr); + + // Ignore unresolvable custom attributes. + if (!nominal) + continue; + + // Return the first custom attribute that is a function builder type. + if (nominal->getAttrs().hasAttribute()) + return mutableAttr; + } + + return nullptr; +} + +llvm::Expected +FunctionBuilderTypeRequest::evaluate(Evaluator &evaluator, + ValueDecl *decl) const { + // Look for a function-builder custom attribute. + auto attr = decl->getAttachedFunctionBuilder(); + if (!attr) return Type(); + + // Resolve a type for the attribute. + auto mutableAttr = const_cast(attr); + auto dc = decl->getDeclContext(); + auto &ctx = dc->getASTContext(); + Type type = resolveCustomAttrType(mutableAttr, dc, + CustomAttrTypeKind::NonGeneric); + if (!type) return Type(); + + auto nominal = type->getAnyNominal(); + if (!nominal) { + assert(ctx.Diags.hadAnyError()); + return Type(); + } + + // Do some additional checking on parameters. + if (auto param = dyn_cast(decl)) { + // The parameter had better already have an interface type. + Type paramType = param->getInterfaceType(); + assert(paramType); + auto paramFnType = paramType->getAs(); + + // Require the parameter to be an interface type. + if (!paramFnType) { + ctx.Diags.diagnose(attr->getLocation(), + diag::function_builder_parameter_not_of_function_type, + nominal->getFullName()); + mutableAttr->setInvalid(); + return Type(); + } + + // Forbid the parameter to be an autoclosure. + if (param->isAutoClosure()) { + ctx.Diags.diagnose(attr->getLocation(), + diag::function_builder_parameter_autoclosure, + nominal->getFullName()); + mutableAttr->setInvalid(); + return Type(); + } + } + + return type->mapTypeOutOfContext(); +} + // Define request evaluation functions for each of the type checker requests. static AbstractRequestFunction *typeCheckerRequestFunctions[] = { #define SWIFT_TYPEID(Name) \ diff --git a/lib/Sema/TypeCheckStmt.cpp b/lib/Sema/TypeCheckStmt.cpp index 1cdfc4fc54083..6dd790f2430ba 100644 --- a/lib/Sema/TypeCheckStmt.cpp +++ b/lib/Sema/TypeCheckStmt.cpp @@ -1913,6 +1913,20 @@ bool TypeChecker::typeCheckAbstractFunctionBody(AbstractFunctionDecl *AFD) { return false; } +static Type getFunctionBuilderType(FuncDecl *FD) { + Type builderType = FD->getFunctionBuilderType(); + + // For getters, fall back on looking on the attribute on the storage. + if (!builderType) { + auto accessor = dyn_cast(FD); + if (accessor && accessor->getAccessorKind() == AccessorKind::Get) { + builderType = accessor->getStorage()->getFunctionBuilderType(); + } + } + + return builderType; +} + // Type check a function body (defined with the func keyword) that is either a // named function or an anonymous func expression. bool TypeChecker::typeCheckFunctionBodyUntil(FuncDecl *FD, @@ -1925,6 +1939,10 @@ bool TypeChecker::typeCheckFunctionBodyUntil(FuncDecl *FD, BraceStmt *BS = FD->getBody(); assert(BS && "Should have a body"); + if (Type builderType = getFunctionBuilderType(FD)) { + return typeCheckFunctionBuilderFuncBody(FD, builderType); + } + if (FD->hasSingleExpressionBody()) { auto resultTypeLoc = FD->getBodyResultTypeLoc(); auto E = FD->getSingleExpressionBody(); diff --git a/lib/Sema/TypeCheckType.cpp b/lib/Sema/TypeCheckType.cpp index 6bd0e57fc67c8..ecd31787e5045 100644 --- a/lib/Sema/TypeCheckType.cpp +++ b/lib/Sema/TypeCheckType.cpp @@ -3580,3 +3580,27 @@ void TypeChecker::checkUnsupportedProtocolType(GenericParamList *genericParams) UnsupportedProtocolVisitor visitor(*this, /*checkStatements=*/false); visitor.visitRequirements(genericParams->getRequirements()); } + +Type swift::resolveCustomAttrType(CustomAttr *attr, DeclContext *dc, + CustomAttrTypeKind typeKind) { + auto resolution = TypeResolution::forContextual(dc); + TypeResolutionOptions options(TypeResolverContext::PatternBindingDecl); + + // Property delegates allow their type to be an unbound generic. + if (typeKind == CustomAttrTypeKind::PropertyDelegate) + options |= TypeResolutionFlags::AllowUnboundGenerics; + + ASTContext &ctx = dc->getASTContext(); + auto &tc = *static_cast(ctx.getLazyResolver()); + if (tc.validateType(attr->getTypeLoc(), resolution, options)) + return Type(); + + // We always require the type to resolve to a nominal type. + Type type = attr->getTypeLoc().getType(); + if (!type->getAnyNominal()) { + assert(ctx.Diags.hadAnyError()); + return Type(); + } + + return type; +} diff --git a/lib/Sema/TypeCheckType.h b/lib/Sema/TypeCheckType.h index 7d7b066f904e9..397904ccdb2ed 100644 --- a/lib/Sema/TypeCheckType.h +++ b/lib/Sema/TypeCheckType.h @@ -377,6 +377,21 @@ class TypeResolution { bool areSameType(Type type1, Type type2) const; }; +/// Kinds of types for CustomAttr. +enum class CustomAttrTypeKind { + /// The type is required to not be expressed in terms of + /// any contextual type parameters. + NonGeneric, + + /// Property delegates have some funky rules, like allowing + /// unbound generic types. + PropertyDelegate, +}; + +/// Attempt to resolve a concrete type for a custom attribute. +Type resolveCustomAttrType(CustomAttr *attr, DeclContext *dc, + CustomAttrTypeKind typeKind); + } // end namespace swift #endif /* SWIFT_SEMA_TYPE_CHECK_TYPE_H */ diff --git a/lib/Sema/TypeChecker.h b/lib/Sema/TypeChecker.h index 7da97ccf04ffa..53f1fb396835e 100644 --- a/lib/Sema/TypeChecker.h +++ b/lib/Sema/TypeChecker.h @@ -658,6 +658,9 @@ class TypeChecker final : public LazyResolver { /// when executing scripts. bool InImmediateMode = false; + /// Closure expressions that have already been prechecked. + llvm::SmallPtrSet precheckedClosures; + /// A helper to construct and typecheck call to super.init(). /// /// \returns NULL if the constructed expression does not typecheck. @@ -665,6 +668,7 @@ class TypeChecker final : public LazyResolver { TypeChecker(ASTContext &Ctx); friend class ASTContext; + friend class constraints::ConstraintSystem; public: /// Create a new type checker instance for the given ASTContext, if it @@ -1024,6 +1028,7 @@ class TypeChecker final : public LazyResolver { bool typeCheckDestructorBodyUntil(DestructorDecl *DD, SourceLoc EndTypeCheckLoc); + bool typeCheckFunctionBuilderFuncBody(FuncDecl *FD, Type builderType); bool typeCheckClosureBody(ClosureExpr *closure); bool typeCheckTapBody(TapExpr *expr, DeclContext *DC); diff --git a/test/Constraints/function_builder.swift b/test/Constraints/function_builder.swift new file mode 100644 index 0000000000000..9d3a2a2376512 --- /dev/null +++ b/test/Constraints/function_builder.swift @@ -0,0 +1,290 @@ +// RUN: %target-run-simple-swift | %FileCheck %s +// REQUIRES: executable_test + +enum Either { + case first(T) + case second(U) +} + +@_functionBuilder +struct TupleBuilder { + static func buildBlock(_ t1: T1, _ t2: T2) -> (T1, T2) { + return (t1, t2) + } + + static func buildBlock(_ t1: T1, _ t2: T2, _ t3: T3) + -> (T1, T2, T3) { + return (t1, t2, t3) + } + + static func buildBlock(_ t1: T1, _ t2: T2, _ t3: T3, _ t4: T4) + -> (T1, T2, T3, T4) { + return (t1, t2, t3, t4) + } + + static func buildBlock( + _ t1: T1, _ t2: T2, _ t3: T3, _ t4: T4, _ t5: T5 + ) -> (T1, T2, T3, T4, T5) { + return (t1, t2, t3, t4, t5) + } + + static func buildDo(_ value: T) -> T { return value } + static func buildIf(_ value: T?) -> T? { return value } + + static func buildEither(first value: T) -> Either { + return .first(value) + } + static func buildEither(second value: U) -> Either { + return .second(value) + } +} + +func tuplify(_ cond: Bool, @TupleBuilder body: (Bool) -> T) { + print(body(cond)) +} + +// CHECK: (17, 3.14159, "Hello, DSL", (["nested", "do"], 6), Optional((2.71828, ["if", "stmt"]))) +let name = "dsl" +tuplify(true) { + 17 + 3.14159 + "Hello, \(name.map { $0.uppercased() }.joined())" + do { + ["nested", "do"] + 1 + 2 + 3 + } + if $0 { + 2.71828 + ["if", "stmt"] + } +} + +// CHECK: ("Empty optional", nil) +tuplify(false) { + "Empty optional" + if $0 { + 2.71828 + ["if", "stmt"] + } +} + +// CHECK: ("chain0", main.Either<(Swift.String, Swift.Double), (Swift.Double, Swift.String)>.second(2.8, "capable")) +tuplify(false) { + "chain0" + if $0 { + "marginal" + 2.9 + } else { + 2.8 + "capable" + } +} + +// CHECK: ("chain1", nil) +tuplify(false) { + "chain1" + if $0 { + "marginal" + 2.9 + } else if $0 { + 2.8 + "capable" + } +} + +// CHECK: ("chain2", Optional(main.Either<(Swift.String, Swift.Double), (Swift.Double, Swift.String)>.first("marginal", 2.9))) +tuplify(true) { + "chain2" + if $0 { + "marginal" + 2.9 + } else if $0 { + 2.8 + "capable" + } +} + +// CHECK: ("chain3", main.Either, main.Either<(Swift.Double, Swift.Double), (Swift.String, Swift.String)>>.first(main.Either<(Swift.String, Swift.Double), (Swift.Double, Swift.String)>.first("marginal", 2.9))) +tuplify(true) { + "chain3" + if $0 { + "marginal" + 2.9 + } else if $0 { + 2.8 + "capable" + } else if $0 { + 2.8 + 1.0 + } else { + "wild" + "broken" + } +} + +// CHECK: ("chain4", main.Either, main.Either<(Swift.String, Swift.Int), (Swift.String, Swift.Int)>>, main.Either, (Swift.String, Swift.Int)>>.first +tuplify(true) { + "chain4" + if $0 { + "0" + 0 + } else if $0 { + "1" + 1 + } else if $0 { + "2" + 2 + } else if $0 { + "3" + 3 + } else if $0 { + "4" + 4 + } else if $0 { + "5" + 5 + } else { + "6" + 6 + } +} + +// CHECK: ("getterBuilder", 0, 4, 12) +@TupleBuilder +var globalBuilder: (String, Int, Int, Int) { + "getterBuilder" + 0 + 4 + 12 +} +print(globalBuilder) + +// CHECK: ("funcBuilder", 13, 45.0) +@TupleBuilder +func funcBuilder(d: Double) -> (String, Int, Double) { + "funcBuilder" + 13 + d +} +print(funcBuilder(d: 45)) + +struct MemberBuilders { + @TupleBuilder + func methodBuilder(_ i: Int) -> (String, Int) { + "methodBuilder" + i + } + + @TupleBuilder + static func staticMethodBuilder(_ i: Int) -> (String, Int) { + "staticMethodBuilder" + i + 14 + } + + @TupleBuilder + var propertyBuilder: (String, Int) { + "propertyBuilder" + 12 + } +} + +// CHECK: ("staticMethodBuilder", 27) +print(MemberBuilders.staticMethodBuilder(13)) + +let mbuilders = MemberBuilders() + +// CHECK: ("methodBuilder", 13) +print(mbuilders.methodBuilder(13)) + +// CHECK: ("propertyBuilder", 12) +print(mbuilders.propertyBuilder) + +struct Tagged { + let tag: Tag + let entity: Entity +} + +protocol Taggable { +} + +extension Taggable { + func tag(_ tag: Tag) -> Tagged { + return Tagged(tag: tag, entity: self) + } +} + +extension Int: Taggable { } +extension String: Taggable { } +extension Double: Taggable { } + +@_functionBuilder +struct TaggedBuilder { + static func buildBlock() -> () { } + + static func buildBlock(_ t1: Tagged) -> Tagged { + return t1 + } + + static func buildBlock(_ t1: Tagged, _ t2: Tagged) -> (Tagged, Tagged) { + return (t1, t2) + } + + static func buildBlock(_ t1: Tagged, _ t2: Tagged, _ t3: Tagged) + -> (Tagged, Tagged, Tagged) { + return (t1, t2, t3) + } + + static func buildBlock(_ t1: Tagged, _ t2: Tagged, _ t3: Tagged, _ t4: Tagged) + -> (Tagged, Tagged, Tagged, Tagged) { + return (t1, t2, t3, t4) + } + + static func buildBlock( + _ t1: Tagged, _ t2: Tagged, _ t3: Tagged, _ t4: Tagged, _ t5: Tagged + ) -> (Tagged, Tagged, Tagged, Tagged, Tagged) { + return (t1, t2, t3, t4, t5) + } + + static func buildIf(_ value: Tagged?) -> Tagged? { return value } +} + +enum Color { + case red, green, blue +} + +func acceptColorTagged(@TaggedBuilder body: () -> Result) { + print(body()) +} + +struct TagAccepter { + static func acceptTagged(@TaggedBuilder body: () -> Result) { + print(body()) + } +} + +func testAcceptColorTagged(b: Bool, i: Int, s: String, d: Double) { + // CHECK: Tagged< + acceptColorTagged { + i.tag(.red) + s.tag(.green) + d.tag(.blue) + } + + // CHECK: Tagged< + TagAccepter.acceptTagged { + i.tag(.red) + s.tag(.green) + d.tag(.blue) + } + + // CHECK: Tagged< + TagAccepter.acceptTagged { () -> Tagged in + if b { + return i.tag(Color.green) + } else { + return i.tag(Color.blue) + } + } +} + +testAcceptColorTagged(b: true, i: 17, s: "Hello", d: 3.14159) diff --git a/test/Constraints/function_builder_diags.swift b/test/Constraints/function_builder_diags.swift new file mode 100644 index 0000000000000..923849bf65c85 --- /dev/null +++ b/test/Constraints/function_builder_diags.swift @@ -0,0 +1,128 @@ +// RUN: %target-typecheck-verify-swift + +@_functionBuilder +struct TupleBuilder { // expected-note 2{{struct 'TupleBuilder' declared here}} + static func buildBlock() -> () { } + + static func buildBlock(_ t1: T1) -> T1 { + return t1 + } + + static func buildBlock(_ t1: T1, _ t2: T2) -> (T1, T2) { + return (t1, t2) + } + + static func buildBlock(_ t1: T1, _ t2: T2, _ t3: T3) + -> (T1, T2, T3) { + return (t1, t2, t3) + } + + static func buildBlock(_ t1: T1, _ t2: T2, _ t3: T3, _ t4: T4) + -> (T1, T2, T3, T4) { + return (t1, t2, t3, t4) + } + + static func buildBlock( + _ t1: T1, _ t2: T2, _ t3: T3, _ t4: T4, _ t5: T5 + ) -> (T1, T2, T3, T4, T5) { + return (t1, t2, t3, t4, t5) + } + + static func buildDo(_ value: T) -> T { return value } + static func buildIf(_ value: T?) -> T? { return value } +} + +@_functionBuilder +struct TupleBuilderWithoutIf { // expected-note {{struct 'TupleBuilderWithoutIf' declared here}} + static func buildBlock() -> () { } + + static func buildBlock(_ t1: T1) -> T1 { + return t1 + } + + static func buildBlock(_ t1: T1, _ t2: T2) -> (T1, T2) { + return (t1, t2) + } + + static func buildBlock(_ t1: T1, _ t2: T2, _ t3: T3) + -> (T1, T2, T3) { + return (t1, t2, t3) + } + + static func buildBlock(_ t1: T1, _ t2: T2, _ t3: T3, _ t4: T4) + -> (T1, T2, T3, T4) { + return (t1, t2, t3, t4) + } + + static func buildBlock( + _ t1: T1, _ t2: T2, _ t3: T3, _ t4: T4, _ t5: T5 + ) -> (T1, T2, T3, T4, T5) { + return (t1, t2, t3, t4, t5) + } + + static func buildDo(_ value: T) -> T { return value } +} + +func tuplify(_ cond: Bool, @TupleBuilder body: (Bool) -> T) { + print(body(cond)) +} + +func tuplifyWithoutIf(_ cond: Bool, @TupleBuilderWithoutIf body: (Bool) -> T) { + print(body(cond)) +} + +func testDiags() { + // For loop + tuplify(true) { _ in + 17 + for c in name { // expected-error{{closure containing control flow statement cannot be used with function builder 'TupleBuilder'}} + } + } + + // Declarations + tuplify(true) { _ in + 17 + let x = 17 // expected-error{{closure containing a declaration cannot be used with function builder 'TupleBuilder'}} + x + 25 + } + + // Statements unsupported by the particular builder. + tuplifyWithoutIf(true) { + if $0 { // expected-error{{closure containing control flow statement cannot be used with function builder 'TupleBuilderWithoutIf'}} + "hello" + } + } +} + +struct A { } +struct B { } + +func overloadedTuplify(_ cond: Bool, @TupleBuilder body: (Bool) -> T) -> A { + return A() +} + +func overloadedTuplify(_ cond: Bool, @TupleBuilderWithoutIf body: (Bool) -> T) -> B { + return B() +} + +func testOverloading(name: String) { + let a1 = overloadedTuplify(true) { b in + if b { + "Hello, \(name)" + } + } + + let _: A = a1 + + _ = overloadedTuplify(true) { b in + b ? "Hello, \(name)" : "Goodbye" + 42 + overloadedTuplify(false) { + $0 ? "Hello, \(name)" : "Goodbye" + 42 + if $0 { + "Hello, \(name)" + } + } + } +} diff --git a/test/IDE/coloring.swift b/test/IDE/coloring.swift index 4ed74031182bc..87793ba44f5ee 100644 --- a/test/IDE/coloring.swift +++ b/test/IDE/coloring.swift @@ -439,3 +439,9 @@ class PropertyDelgate { }) var something } + +// CHECK: func acceptBuilder( +func acceptBuilder( + // CHECK: @SomeBuilder label param: () -> T + @SomeBuilder label param: () -> T +) {} diff --git a/test/IDE/complete_decl_attribute.swift b/test/IDE/complete_decl_attribute.swift index d3bdd5d7db4ac..32dec634f0a7c 100644 --- a/test/IDE/complete_decl_attribute.swift +++ b/test/IDE/complete_decl_attribute.swift @@ -9,6 +9,7 @@ // RUN: %target-swift-ide-test -code-completion -source-filename %s -code-completion-token=ON_INIT | %FileCheck %s -check-prefix=ON_INIT // RUN: %target-swift-ide-test -code-completion -source-filename %s -code-completion-token=ON_PROPERTY | %FileCheck %s -check-prefix=ON_PROPERTY // RUN: %target-swift-ide-test -code-completion -source-filename %s -code-completion-token=ON_METHOD | %FileCheck %s -check-prefix=ON_METHOD +// RUN: %target-swift-ide-test -code-completion -source-filename %s -code-completion-token=ON_PARAM | %FileCheck %s -check-prefix=ON_PARAM // RUN: %target-swift-ide-test -code-completion -source-filename %s -code-completion-token=ON_MEMBER_LAST | %FileCheck %s -check-prefix=ON_MEMBER_LAST // RUN: %target-swift-ide-test -code-completion -source-filename %s -code-completion-token=KEYWORD_LAST | %FileCheck %s -check-prefix=KEYWORD_LAST @@ -75,6 +76,7 @@ class C {} // KEYWORD3-NEXT: Keyword/None: NSApplicationMain[#Class Attribute#]; name=NSApplicationMain{{$}} // KEYWORD3-NEXT: Keyword/None: usableFromInline[#Class Attribute#]; name=usableFromInline // KEYWORD3-NEXT: Keyword/None: _propertyWrapper[#Class Attribute#]; name=_propertyWrapper +// KEYWORD3-NEXT: Keyword/None: _functionBuilder[#Class Attribute#]; name=_functionBuilder // KEYWORD3-NEXT: End completions @#^KEYWORD3_2^#IB @@ -90,6 +92,7 @@ enum E {} // KEYWORD4-NEXT: Keyword/None: dynamicMemberLookup[#Enum Attribute#]; name=dynamicMemberLookup // KEYWORD4-NEXT: Keyword/None: usableFromInline[#Enum Attribute#]; name=usableFromInline // KEYWORD4-NEXT: Keyword/None: _propertyWrapper[#Enum Attribute#]; name=_propertyWrapper +// KEYWORD4-NEXT: Keyword/None: _functionBuilder[#Enum Attribute#]; name=_functionBuilder // KEYWORD4-NEXT: End completions @@ -101,6 +104,7 @@ struct S{} // KEYWORD5-NEXT: Keyword/None: dynamicMemberLookup[#Struct Attribute#]; name=dynamicMemberLookup // KEYWORD5-NEXT: Keyword/None: usableFromInline[#Struct Attribute#]; name=usableFromInline // KEYWORD5-NEXT: Keyword/None: _propertyWrapper[#Struct Attribute#]; name=_propertyWrapper +// KEYWORD5-NEXT: Keyword/None: _functionBuilder[#Struct Attribute#]; name=_functionBuilder // KEYWORD5-NEXT: End completions @#^ON_GLOBALVAR^# @@ -169,6 +173,13 @@ struct _S { // ON_METHOD-DAG: Keyword/None: IBSegueAction[#Func Attribute#]; name=IBSegueAction // ON_METHOD: End completions + func bar(@#^ON_PARAM^#) +// ON_PARAM: Begin completions +// ON_PARAM-NOT: Keyword +// ON_PARAM: Decl[Struct]/CurrModule: MyStruct[#MyStruct#]; name=MyStruct +// ON_PARAM-NOT: Keyword +// ON_PARAM: End completions + @#^ON_MEMBER_LAST^# // ON_MEMBER_LAST: Begin completions // ON_MEMBER_LAST-DAG: Keyword/None: available[#Declaration Attribute#]; name=available @@ -194,6 +205,7 @@ struct _S { // ON_MEMBER_LAST-DAG: Keyword/None: GKInspectable[#Declaration Attribute#]; name=GKInspectable // ON_MEMBER_LAST-DAG: Keyword/None: IBSegueAction[#Declaration Attribute#]; name=IBSegueAction // ON_MEMBER_LAST-DAG: Keyword/None: _propertyWrapper[#Declaration Attribute#]; name=_propertyWrapper +// ON_MEMBER_LAST-DAG: Keyword/None: _functionBuilder[#Declaration Attribute#]; name=_functionBuilder // ON_MEMBER_LAST-NOT: Keyword // ON_MEMBER_LAST: Decl[Struct]/CurrModule: MyStruct[#MyStruct#]; name=MyStruct // ON_MEMBER_LAST-NOT: Decl[PrecedenceGroup] @@ -225,6 +237,8 @@ struct _S { // KEYWORD_LAST-NEXT: Keyword/None: discardableResult[#Declaration Attribute#]; name=discardableResult // KEYWORD_LAST-NEXT: Keyword/None: GKInspectable[#Declaration Attribute#]; name=GKInspectable{{$}} // KEYWORD_LAST-NEXT: Keyword/None: _propertyWrapper[#Declaration Attribute#]; name=_propertyWrapper +// KEYWORD_LAST-NEXT: Keyword/None: _functionBuilder[#Declaration Attribute#]; name=_functionBuilder{{$}} // KEYWORD_LAST-NEXT: Keyword/None: IBSegueAction[#Declaration Attribute#]; name=IBSegueAction{{$}} +// KEYWORD_LAST-NOT: Keyword // KEYWORD_LAST: Decl[Struct]/CurrModule: MyStruct[#MyStruct#]; name=MyStruct // KEYWORD_LAST: End completions diff --git a/test/IDE/complete_function_builder.swift b/test/IDE/complete_function_builder.swift new file mode 100644 index 0000000000000..eb5867e3d4573 --- /dev/null +++ b/test/IDE/complete_function_builder.swift @@ -0,0 +1,150 @@ +// RUN: %target-swift-ide-test -code-completion -source-filename %s -code-completion-token=IN_CLOSURE_TOP | %FileCheck %s -check-prefix=IN_CLOSURE_TOP +// RUN: %target-swift-ide-test -code-completion -source-filename %s -code-completion-token=IN_CLOSURE_NONTOP | %FileCheck %s -check-prefix=IN_CLOSURE_TOP +// RUN: %target-swift-ide-test -code-completion -source-filename %s -code-completion-token=IN_CLOSURE_COLOR_CONTEXT | %FileCheck %s -check-prefix=IN_CLOSURE_COLOR_CONTEXT +// RUN: %target-swift-ide-test -code-completion -source-filename %s -code-completion-token=IN_CLOSURE_COLOR_CONTEXT_DOT | %FileCheck %s -check-prefix=IN_CLOSURE_COLOR_CONTEXT_DOT + +// RUN: %target-swift-ide-test -code-completion -source-filename %s -code-completion-token=CONTEXTUAL_TYPE_1 | %FileCheck %s -check-prefix=CONTEXTUAL_TYPE_INVALID +// RUN: %target-swift-ide-test -code-completion -source-filename %s -code-completion-token=CONTEXTUAL_TYPE_2 | %FileCheck %s -check-prefix=CONTEXTUAL_TYPE_INVALID +// RUN: %target-swift-ide-test -code-completion -source-filename %s -code-completion-token=CONTEXTUAL_TYPE_3 | %FileCheck %s -check-prefix=CONTEXTUAL_TYPE_VALID +// RUN: %target-swift-ide-test -code-completion -source-filename %s -code-completion-token=CONTEXTUAL_TYPE_4 | %FileCheck %s -check-prefix=CONTEXTUAL_TYPE_VALID +// RUN: %target-swift-ide-test -code-completion -source-filename %s -code-completion-token=CONTEXTUAL_TYPE_5 | %FileCheck %s -check-prefix=CONTEXTUAL_TYPE_INVALID + +struct Tagged { + let tag: Tag + let entity: Entity + + static func fo +} + +protocol Taggable { +} + +extension Taggable { + func tag(_ tag: Tag) -> Tagged { + return Tagged(tag: tag, entity: self) + } +} + +extension Int: Taggable { } +extension String: Taggable { } + +@_functionBuilder +struct TaggedBuilder { + static func buildBlock() -> () { } + + static func buildBlock(_ t1: Tagged) -> Tagged { + return t1 + } + + static func buildBlock(_ t1: Tagged, _ t2: Tagged) -> (Tagged, Tagged) { + return (t1, t2) + } + static func buildBlock(_ t1: Tagged, _ t2: Tagged, _ t2: Tagged) -> (Tagged, Tagged, Tagged) { + return (t1, t2, t3) + } +} + +enum Color { + case red, green, blue +} + +func acceptColorTagged(@TaggedBuilder body: (Color) -> Result) { + print(body(.blue)) +} + +var globalIntVal: Int = 1 +let globalStringVal: String = "" + +func testAcceptColorTagged(paramIntVal: Int, paramStringVal: String) { + + let taggedValue = paramIntVal.tag(Color.red) + + acceptColorTagged { color in + #^IN_CLOSURE_TOP^# +// IN_CLOSURE_TOP_CONTEXT: Begin completions +// IN_CLOSURE_TOP-DAG: Decl[LocalVar]/Local: taggedValue[#Tagged#]; name=taggedValue +// IN_CLOSURE_TOP-DAG: Decl[GlobalVar]/CurrModule: globalIntVal[#Int#]; name=globalIntVal +// IN_CLOSURE_TOP-DAG: Decl[GlobalVar]/CurrModule: globalStringVal[#String#]; name=globalStringVal +// IN_CLOSURE_TOP-DAG: Decl[LocalVar]/Local: color; name=color +// IN_CLOSURE_TOP-DAG: Decl[LocalVar]/Local: paramIntVal[#Int#]; name=paramIntVal +// IN_CLOSURE_TOP-DAG: Decl[LocalVar]/Local: paramStringVal[#String#]; name=paramStringVal +// IN_CLOSURE_TOP: End completions + } + + acceptColorTagged { color in + paramIntVal.tag(.red) + #^IN_CLOSURE_NONTOP^# +// Same as IN_CLOSURE_TOP. + } + + acceptColorTagged { color in + paramIntVal.tag(#^IN_CLOSURE_COLOR_CONTEXT^#) +// IN_CLOSURE_COLOR_CONTEXT: Begin completions +// IN_CLOSURE_COLOR_CONTEXT-DAG: Decl[InstanceMethod]/CurrNominal: ['(']{#(tag): Color#}[')'][#Tagged#]; name=tag: Color +// IN_CLOSURE_COLOR_CONTEXT-DAG: Decl[LocalVar]/Local/TypeRelation[Identical]: color[#Color#]; name=color +// IN_CLOSURE_COLOR_CONTEXT-DAG: Decl[LocalVar]/Local: taggedValue[#Tagged#]; name=taggedValue +// IN_CLOSURE_COLOR_CONTEXT-DAG: Decl[LocalVar]/Local: paramIntVal[#Int#]; name=paramIntVal +// IN_CLOSURE_COLOR_CONTEXT-DAG: Decl[LocalVar]/Local: paramStringVal[#String#]; name=paramStringVal +// IN_CLOSURE_COLOR_CONTEXT-DAG: Decl[Enum]/CurrModule/TypeRelation[Identical]: Color[#Color#]; name=Color +// IN_CLOSURE_COLOR_CONTEXT: End completions + } + + acceptColorTagged { color in + paramIntVal.tag(.#^IN_CLOSURE_COLOR_CONTEXT_DOT^#) +// IN_CLOSURE_COLOR_CONTEXT_DOT: Begin completions, 3 items +// IN_CLOSURE_COLOR_CONTEXT_DOT-DAG: Decl[EnumElement]/ExprSpecific: red[#Color#]; name=red +// IN_CLOSURE_COLOR_CONTEXT_DOT-DAG: Decl[EnumElement]/ExprSpecific: green[#Color#]; name=green +// IN_CLOSURE_COLOR_CONTEXT_DOT-DAG: Decl[EnumElement]/ExprSpecific: blue[#Color#]; name=blue +// IN_CLOSURE_COLOR_CONTEXT_DOT: End completions + } +} + +enum MyEnum { + case east, west + case north, south +} +@_functionBuilder +struct EnumToVoidBuilder { + static func buildBlock() {} + static func buildBlock(_ :MyEnum) {} + static func buildBlock(_ :MyEnum, _: MyEnum) {} + static func buildBlock(_ :MyEnum, _: MyEnum, _: MyEnum) {} +} +func acceptBuilder(@EnumToVoidBuilder body: () -> Void) {} + +// CONTEXTUAL_TYPE_INVALID-NOT: Begin completions + +// CONTEXTUAL_TYPE_VALID: Begin completions, 4 items +// CONTEXTUAL_TYPE_VALID-DAG: Decl[EnumElement]/ExprSpecific: east[#MyEnum#]; name=east +// CONTEXTUAL_TYPE_VALID-DAG: Decl[EnumElement]/ExprSpecific: west[#MyEnum#]; name=west +// CONTEXTUAL_TYPE_VALID-DAG: Decl[EnumElement]/ExprSpecific: north[#MyEnum#]; name=north +// CONTEXTUAL_TYPE_VALID-DAG: Decl[EnumElement]/ExprSpecific: south[#MyEnum#]; name=south +// CONTEXTUAL_TYPE_VALID: End completions + +func testContextualType() { + acceptBuilder { +// FIXME: This should suggest enum values. + .#^CONTEXTUAL_TYPE_1^# + } + acceptBuilder { +// FIXME: This should suggest enum values. + .#^CONTEXTUAL_TYPE_2^#; + .north; + } + acceptBuilder { + .north; + .#^CONTEXTUAL_TYPE_3^#; + } + acceptBuilder { + .north; + .east; + .#^CONTEXTUAL_TYPE_4^# + } + acceptBuilder { + .north; + .east; + .south; +// NOTE: Invalid because 'EnumToVoidBuilder' doesn't have 4 params overload. + .#^CONTEXTUAL_TYPE_5^# + } +} diff --git a/test/Index/function_builders.swift b/test/Index/function_builders.swift new file mode 100644 index 0000000000000..f914ad5b77ebf --- /dev/null +++ b/test/Index/function_builders.swift @@ -0,0 +1,45 @@ +// RUN: %target-swift-ide-test -print-indexed-symbols -source-filename %s | %FileCheck -check-prefix=CHECK %s + +struct Tagged { + let tag: Tag + let entity: Entity +} + +protocol Taggable { +} + +extension Taggable { + func tag(_ tag: Tag) -> Tagged { + return Tagged(tag: tag, entity: self) + } +} + +extension Int: Taggable { } +extension String: Taggable { } +extension Double: Taggable { } + +@_functionBuilder +struct TaggedBuilder { + static func buildBlock() -> () { } + + static func buildBlock(_ t1: Tagged) -> Tagged { + return t1 + } + + static func buildBlock(_ t1: Tagged, _ t2: Tagged) -> (Tagged, Tagged) { + return (t1, t2) + } + + static func buildIf(_ value: Tagged?) -> Tagged? { return value } +} + +enum Color { + case red, green, blue +} + +func acceptColorTagged(@TaggedBuilder body: () -> Result) { + print(body()) +} + +// CHECK: 40:33 | struct/Swift | TaggedBuilder | s:14swift_ide_test13TaggedBuilderV | Ref,RelCont | rel: 1 +// CHECK: 40:47 | enum/Swift | Color | s:14swift_ide_test5ColorO | Ref,RelCont | rel: 1 diff --git a/test/ParseableInterface/Inputs/function_builders_client.swift b/test/ParseableInterface/Inputs/function_builders_client.swift new file mode 100644 index 0000000000000..8ce9da388385b --- /dev/null +++ b/test/ParseableInterface/Inputs/function_builders_client.swift @@ -0,0 +1,17 @@ +import FunctionBuilders + +let name = "dsl" +tuplify(true) { + 17 + 3.14159 + "Hello, \(name.map { $0.uppercased() }.joined())" + do { + ["nested", "do"] + 1 + 2 + 3 + } + if $0 { + 2.71828 + ["if", "stmt"] + } +} + diff --git a/test/ParseableInterface/function_builders.swift b/test/ParseableInterface/function_builders.swift new file mode 100644 index 0000000000000..28ba559237cc2 --- /dev/null +++ b/test/ParseableInterface/function_builders.swift @@ -0,0 +1,35 @@ +// RUN: %empty-directory(%t) +// RUN: %target-swift-frontend -typecheck -module-name FunctionBuilders -emit-parseable-module-interface-path %t/FunctionBuilders.swiftinterface %s +// RUN: %FileCheck %s < %t/FunctionBuilders.swiftinterface +// RUN: %target-swift-frontend -I %t -typecheck -verify %S/Inputs/function_builders_client.swift + +@_functionBuilder +public struct TupleBuilder { + public static func buildBlock(_ t1: T1, _ t2: T2) -> (T1, T2) { + return (t1, t2) + } + + public static func buildBlock(_ t1: T1, _ t2: T2, _ t3: T3) + -> (T1, T2, T3) { + return (t1, t2, t3) + } + + public static func buildBlock(_ t1: T1, _ t2: T2, _ t3: T3, _ t4: T4) + -> (T1, T2, T3, T4) { + return (t1, t2, t3, t4) + } + + public static func buildBlock( + _ t1: T1, _ t2: T2, _ t3: T3, _ t4: T4, _ t5: T5 + ) -> (T1, T2, T3, T4, T5) { + return (t1, t2, t3, t4, t5) + } + + public static func buildDo(_ value: T) -> T { return value } + public static func buildIf(_ value: T?) -> T? { return value } +} + +// CHECK-LABEL: public func tuplify(_ cond: Bool, @FunctionBuilders.TupleBuilder body: (Bool) -> T) +public func tuplify(_ cond: Bool, @TupleBuilder body: (Bool) -> T) { + print(body(cond)) +} diff --git a/test/SourceKit/CodeExpand/code-expand.swift b/test/SourceKit/CodeExpand/code-expand.swift index 6b0475206a0e9..af0b13daccb15 100644 --- a/test/SourceKit/CodeExpand/code-expand.swift +++ b/test/SourceKit/CodeExpand/code-expand.swift @@ -37,6 +37,16 @@ dispatch_after(<#T##when: dispatch_time_t##dispatch_time_t#>, <#T##queue: dispat // CHECK-NEXT: <#code#> // CHECK-NEXT: } +@_functionBuilder +struct MyBuilder {} +func acceptBuilder(@MyBuilder body: () -> Result) {} +do { + acceptBuilder(body: <#T##() -> Result#>) + // CHECK: acceptBuilder { + // CHECK-NEXT: <#code#> + // CHECK-NEXT: } +} + foo(x: <#T##Self.SegueIdentifier -> Void#>) // CHECK: foo { (<#Self.SegueIdentifier#>) in diff --git a/test/SourceKit/CursorInfo/function_builder.swift b/test/SourceKit/CursorInfo/function_builder.swift new file mode 100644 index 0000000000000..6e9f31d60f982 --- /dev/null +++ b/test/SourceKit/CursorInfo/function_builder.swift @@ -0,0 +1,115 @@ +struct Tagged { + let tag: Tag + let entity: Entity +} + +protocol Taggable { +} + +extension Taggable { + func tag(_ tag: Tag) -> Tagged { + return Tagged(tag: tag, entity: self) + } +} + +extension Int: Taggable { } +extension String: Taggable { } + +@_functionBuilder +struct TaggedBuilder { + static func buildBlock() -> () { } + + static func buildBlock(_ t1: Tagged) -> Tagged { + return t1 + } + + static func buildBlock(_ t1: Tagged, _ t2: Tagged) -> (Tagged, Tagged) { + return (t1, t2) + } +} + +enum Color { + case red, green, blue +} + +func acceptColorTagged(@TaggedBuilder body: (Color) -> Result) { + print(body(.blue)) +} + +func testAcceptColorTagged(i: Int, s: String) { + acceptColorTagged { color in + i.tag(color) + s.tag(.green) + } +} + +// Custom attribute name. +// RUN: %sourcekitd-test -req=cursor -pos=35:33 %s -- %s -module-name BuilderTest | %FileCheck %s --check-prefix=ATTR_NAME +// ATTR_NAME: source.lang.swift.ref.struct (19:8-19:21) +// ATTR_NAME-NEXT: TaggedBuilder +// ATTR_NAME-NEXT: s:11BuilderTest06TaggedA0V +// ATTR_NAME-NEXT: TaggedBuilder.Type +// ATTR_NAME-NEXT: $s11BuilderTest06TaggedA0VyxGmD +// ATTR_NAME-NEXT: @_functionBuilder struct TaggedBuilder<Tag> +// ATTR_NAME-NEXT: @_functionBuilder struct TaggedBuilder<Tag> + +// Generic argument in attribute name. +// RUN: %sourcekitd-test -req=cursor -pos=35:47 %s -- %s -module-name BuilderTest | %FileCheck %s --check-prefix=ATTR_GENERICARG +// ATTR_GENERICARG: source.lang.swift.ref.enum (31:6-31:11) +// ATTR_GENERICARG-NEXT: Color +// ATTR_GENERICARG-NEXT: s:11BuilderTest5ColorO +// ATTR_GENERICARG-NEXT: Color.Type +// ATTR_GENERICARG-NEXT: $s11BuilderTest5ColorOmD +// ATTR_GENERICARG-NEXT: enum Color +// ATTR_GENERICARG-NEXT: enum Color + +// Call for function with builder. +// RUN: %sourcekitd-test -req=cursor -pos=40:3 %s -- %s -module-name BuilderTest | %FileCheck %s --check-prefix=CALL_BUILDERFUNC +// CALL_BUILDERFUNC: source.lang.swift.ref.function.free (35:6-35:78) +// CALL_BUILDERFUNC-NEXT: acceptColorTagged(body:) +// CALL_BUILDERFUNC-NEXT: s:11BuilderTest17acceptColorTagged4bodyyxAA0D0OXE_tlF +// CALL_BUILDERFUNC-NEXT: (body: (Color) -> Result) -> () +// CALL_BUILDERFUNC-NEXT: $s4bodyyx11BuilderTest5ColorOXE_tcluD +// CALL_BUILDERFUNC-NEXT: func acceptColorTagged<Result>(@TaggedBuilder<Color> body: (Color) -> Result) +// CALL_BUILDERFUNC-NEXT: func acceptColorTagged<Result>(@TaggedBuilder<Color> body: (Color) -> Result) + +// Closure parameter - decl-site. +// RUN: %sourcekitd-test -req=cursor -pos=40:23 %s -- %s -module-name BuilderTest | %FileCheck %s --check-prefix=CLOSUREPARAM_DECL +// CLOSUREPARAM_DECL: source.lang.swift.decl.var.parameter (40:23-40:28) +// CLOSUREPARAM_DECL-NEXT: color +// CLOSUREPARAM_DECL-NEXT: s:11BuilderTest21testAcceptColorTagged1i1sySi_SStFAA0F0VyAA0E0OSiG_AFyAHSSGtAHXEfU_5colorL_AHvp +// CLOSUREPARAM_DECL-NEXT: Color +// CLOSUREPARAM_DECL-NEXT: $s11BuilderTest5ColorOD +// CLOSUREPARAM_DECL-NEXT: let color: Color +// CLOSUREPARAM_DECL-NEXT: let color: Color + +// Closure parameter - use-site. +// RUN: %sourcekitd-test -req=cursor -pos=41:11 %s -- %s -module-name BuilderTest | %FileCheck %s --check-prefix=CLOSUREPARAM_USER +// CLOSUREPARAM_USER: source.lang.swift.ref.var.local (40:23-40:28) +// CLOSUREPARAM_USER-NEXT: color +// CLOSUREPARAM_USER-NEXT: s:11BuilderTest21testAcceptColorTagged1i1sySi_SStFAA0F0VyAA0E0OSiG_AFyAHSSGtAHXEfU_5colorL_AHvp +// CLOSUREPARAM_USER-NEXT: Color +// CLOSUREPARAM_USER-NEXT: $s11BuilderTest5ColorOD +// CLOSUREPARAM_USER-NEXT: let color: Color +// CLOSUREPARAM_USER-NEXT: let color: Color + +// Captured variable. +// RUN: %sourcekitd-test -req=cursor -pos=41:5 %s -- %s -module-name BuilderTest | %FileCheck %s --check-prefix=CAPTURED_VALUE +// CAPTURED_VALUE: source.lang.swift.ref.var.local (39:28-39:29) +// CAPTURED_VALUE-NEXT: i +// CAPTURED_VALUE-NEXT: s:11BuilderTest21testAcceptColorTagged1i1sySi_SStFACL_Sivp +// CAPTURED_VALUE-NEXT: Int +// CAPTURED_VALUE-NEXT: $sSiD +// CAPTURED_VALUE-NEXT: let i: Int +// CAPTURED_VALUE-NEXT: let i: Int + +// Method call to captured variable. +// RUN: %sourcekitd-test -req=cursor -pos=41:7 %s -- %s -module-name BuilderTest | %FileCheck %s --check-prefix=CAPTURED_VALUE_METHOD +// CAPTURED_VALUE_METHOD: source.lang.swift.ref.function.method.instance (10:8-10:28) +// CAPTURED_VALUE_METHOD: tag(_:) +// CAPTURED_VALUE_METHOD: s:11BuilderTest8TaggablePAAE3tagyAA6TaggedVyqd__xGqd__lF +// CAPTURED_VALUE_METHOD: (Self) -> (Tag) -> Tagged +// CAPTURED_VALUE_METHOD: $sy11BuilderTest6TaggedVyqd__xGqd__cluD +// CAPTURED_VALUE_METHOD: $sSiD +// CAPTURED_VALUE_METHOD: func tag<Tag>(_ tag: Tag) -> Tagged<Tag, Int> +// CAPTURED_VALUE_METHOD: func tag<Tag>(_ tag: Tag) -> Tagged<Tag, Int> diff --git a/test/decl/var/function_builders.swift b/test/decl/var/function_builders.swift new file mode 100644 index 0000000000000..ec56a0d27677f --- /dev/null +++ b/test/decl/var/function_builders.swift @@ -0,0 +1,101 @@ +// RUN: %target-typecheck-verify-swift + +@_functionBuilder // expected-error {{'@_functionBuilder' attribute cannot be applied to this declaration}} +var globalBuilder: Int + +@_functionBuilder // expected-error {{'@_functionBuilder' attribute cannot be applied to this declaration}} +func globalBuilderFunction() -> Int { return 0 } + +@_functionBuilder +struct Maker {} + +@_functionBuilder +class Inventor {} + +@Maker // expected-error {{function builder attribute 'Maker' can only be applied to a parameter, function, or computed property}} +typealias typename = Inventor + +@Maker // expected-error {{function builder attribute 'Maker' can only be applied to a variable if it defines a getter}} +var global: Int + +// FIXME: should this be allowed? +@Maker +var globalWithEmptyImplicitGetter: Int {} +// expected-error@-1 {{computed property must have accessors specified}} +// expected-error@-3 {{function builder attribute 'Maker' can only be applied to a variable if it defines a getter}} + +@Maker +var globalWithEmptyExplicitGetter: Int { get {} } // expected-error {{ype 'Maker' has no member 'buildBlock'}} + +@Maker +var globalWithSingleGetter: Int { 0 } // expected-error {{ype 'Maker' has no member 'buildBlock'}} + +@Maker +var globalWithMultiGetter: Int { 0; 0 } // expected-error {{ype 'Maker' has no member 'buildBlock'}} + +@Maker +func globalFunction() {} // expected-error {{ype 'Maker' has no member 'buildBlock'}} + +@Maker +func globalFunctionWithFunctionParam(fn: () -> ()) {} // expected-error {{ype 'Maker' has no member 'buildBlock'}} + +func makerParam(@Maker + fn: () -> ()) {} + +// FIXME: these diagnostics are reversed? +func makerParamRedundant(@Maker // expected-error {{only one function builder attribute can be attached to a parameter}} + @Maker // expected-note {{previous function builder specified here}} + fn: () -> ()) {} + +func makerParamConflict(@Maker // expected-error {{only one function builder attribute can be attached to a parameter}} + @Inventor // expected-note {{previous function builder specified here}} + fn: () -> ()) {} + +func makerParamMissing1(@Missing // expected-error {{unknown attribute 'Missing'}} + @Maker + fn: () -> ()) {} + +func makerParamMissing2(@Maker + @Missing // expected-error {{unknown attribute 'Missing'}} + fn: () -> ()) {} + +func makerParamExtra(@Maker(5) // expected-error {{function builder attributes cannot have arguments}} + fn: () -> ()) {} + +func makerParamAutoclosure(@Maker // expected-error {{function builder attribute 'Maker' cannot be applied to an autoclosure parameter}} + fn: @autoclosure () -> ()) {} + +@_functionBuilder +struct GenericMaker {} // expected-note {{generic type 'GenericMaker' declared here}} + +struct GenericContainer { // expected-note {{generic type 'GenericContainer' declared here}} + @_functionBuilder + struct Maker {} +} + +func makeParamUnbound(@GenericMaker // expected-error {{reference to generic type 'GenericMaker' requires arguments}} + fn: () -> ()) {} + +func makeParamBound(@GenericMaker + fn: () -> ()) {} + +func makeParamNestedUnbound(@GenericContainer.Maker // expected-error {{reference to generic type 'GenericContainer' requires arguments}} + fn: () -> ()) {} + +func makeParamNestedBound(@GenericContainer.Maker + fn: () -> ()) {} + + +protocol P { } + +@_functionBuilder +struct ConstrainedGenericMaker {} + + +struct WithinGeneric { + func makeParamBoundInContext(@GenericMaker fn: () -> ()) {} + + // expected-error@+1{{type 'U' does not conform to protocol 'P'}} + func makeParamBoundInContextBad(@ConstrainedGenericMaker + fn: () -> ()) {} +} diff --git a/test/multifile/Inputs/function_builder_definition.swift b/test/multifile/Inputs/function_builder_definition.swift new file mode 100644 index 0000000000000..0ea2a999db246 --- /dev/null +++ b/test/multifile/Inputs/function_builder_definition.swift @@ -0,0 +1,26 @@ +@_functionBuilder +struct TupleBuilder { + static func buildBlock(_ t1: T1, _ t2: T2) -> (T1, T2) { + return (t1, t2) + } + + static func buildBlock(_ t1: T1, _ t2: T2, _ t3: T3) + -> (T1, T2, T3) { + return (t1, t2, t3) + } + + static func buildBlock(_ t1: T1, _ t2: T2, _ t3: T3, _ t4: T4) + -> (T1, T2, T3, T4) { + return (t1, t2, t3, t4) + } + + static func buildBlock( + _ t1: T1, _ t2: T2, _ t3: T3, _ t4: T4, _ t5: T5 + ) -> (T1, T2, T3, T4, T5) { + return (t1, t2, t3, t4, t5) + } +} + +func tuplify(@TupleBuilder body: () -> T) -> T { + return body() +} diff --git a/test/multifile/function_builder_multifile.swift b/test/multifile/function_builder_multifile.swift new file mode 100644 index 0000000000000..de6850df25737 --- /dev/null +++ b/test/multifile/function_builder_multifile.swift @@ -0,0 +1,9 @@ +// RUN: %target-swift-frontend -typecheck %S/Inputs/function_builder_definition.swift -primary-file %s + +func test0() -> (Int, Float, String) { + return tuplify { + 17 + 3.14159 + "Hello" + } +}