From bec0aae7e5c983b62835d178e36a70703eb0794a Mon Sep 17 00:00:00 2001 From: Doug Gregor Date: Sat, 26 Sep 2020 21:54:28 -0700 Subject: [PATCH] [Function builders] Add support for function builders on stored struct properties. Implements SR-13188, which is part of the function builders proposal under review. --- lib/SILGen/SILGenConstructor.cpp | 19 ++++++- lib/Sema/CodeSynthesis.cpp | 22 +++++++ lib/Sema/TypeCheckAttr.cpp | 13 ++++- test/Constraints/function_builder.swift | 21 +++++++ test/SILGen/function_builder_memberwise.swift | 57 +++++++++++++++++++ 5 files changed, 130 insertions(+), 2 deletions(-) create mode 100644 test/SILGen/function_builder_memberwise.swift diff --git a/lib/SILGen/SILGenConstructor.cpp b/lib/SILGen/SILGenConstructor.cpp index 162ee955f83a7..d38ce95f27825 100644 --- a/lib/SILGen/SILGenConstructor.cpp +++ b/lib/SILGen/SILGenConstructor.cpp @@ -18,6 +18,7 @@ #include "SILGenFunctionBuilder.h" #include "Scope.h" #include "swift/AST/ASTMangler.h" +#include "swift/AST/ForeignErrorConvention.h" #include "swift/AST/GenericEnvironment.h" #include "swift/AST/ParameterList.h" #include "swift/AST/PropertyWrappers.h" @@ -211,8 +212,24 @@ static void emitImplicitValueConstructor(SILGenFunction &SGF, "number of args does not match number of fields"); (void)eltEnd; FullExpr scope(SGF.Cleanups, field->getParentPatternBinding()); + + RValue arg = std::move(*elti); + + // If the stored property has an attached function builder and its + // type is not a function type, the argument is a noescape closure + // that needs to be called. + if (field->getFunctionBuilderType()) { + if (!field->getValueInterfaceType() + ->lookThroughAllOptionalTypes()->is()) { + auto resultTy = cast(arg.getType()).getResult(); + arg = SGF.emitMonomorphicApply( + Loc, std::move(arg).getAsSingleValue(SGF, Loc), { }, resultTy, + resultTy, ApplyOptions::None, None, None); + } + } + maybeEmitPropertyWrapperInitFromValue(SGF, Loc, field, subs, - std::move(*elti)) + std::move(arg)) .forwardInto(SGF, Loc, init.get()); ++elti; } else { diff --git a/lib/Sema/CodeSynthesis.cpp b/lib/Sema/CodeSynthesis.cpp index 384cad9dcb45c..60676e4c241e6 100644 --- a/lib/Sema/CodeSynthesis.cpp +++ b/lib/Sema/CodeSynthesis.cpp @@ -261,6 +261,20 @@ static ConstructorDecl *createImplicitConstructor(NominalTypeDecl *decl, } } + Type functionBuilderType= var->getFunctionBuilderType(); + if (functionBuilderType) { + // If the variable's type is structurally a function type, use that + // type. Otherwise, form a non-escaping function type for the function + // parameter. + bool isStructuralFunctionType = + varInterfaceType->lookThroughAllOptionalTypes() + ->is(); + if (!isStructuralFunctionType) { + auto extInfo = ASTExtInfoBuilder().withNoEscape().build(); + varInterfaceType = FunctionType::get({ }, varInterfaceType, extInfo); + } + } + // Create the parameter. auto *arg = new (ctx) ParamDecl(SourceLoc(), Loc, @@ -273,6 +287,14 @@ static ConstructorDecl *createImplicitConstructor(NominalTypeDecl *decl, // Don't allow the parameter to accept temporary pointer conversions. arg->setNonEphemeralIfPossible(); + // Attach a function builder attribute if needed. + if (functionBuilderType) { + auto typeExpr = TypeExpr::createImplicit(functionBuilderType, ctx); + auto attr = CustomAttr::create( + ctx, SourceLoc(), typeExpr, /*implicit=*/true); + arg->getAttrs().add(attr); + } + maybeAddMemberwiseDefaultArg(arg, var, params.size(), ctx); params.push_back(arg); diff --git a/lib/Sema/TypeCheckAttr.cpp b/lib/Sema/TypeCheckAttr.cpp index c6d981e9cb911..549c2419bf5bc 100644 --- a/lib/Sema/TypeCheckAttr.cpp +++ b/lib/Sema/TypeCheckAttr.cpp @@ -2987,8 +2987,19 @@ void AttributeChecker::visitCustomAttr(CustomAttr *attr) { } else if (auto storage = dyn_cast(D)) { decl = storage; - // Check whether this is a property without an explicit getter. + // Check whether this is a storage declaration that is not permitted + // to have a function builder attached. auto shouldDiagnose = [&]() -> bool { + // An uninitialized stored property in a struct can have a function + // builder attached. + if (auto var = dyn_cast(decl)) { + if (var->isInstanceMember() && + isa(var->getDeclContext()) && + !var->getParentInitializer()) { + return false; + } + } + auto getter = storage->getParsedAccessor(AccessorKind::Get); if (!getter) return true; diff --git a/test/Constraints/function_builder.swift b/test/Constraints/function_builder.swift index d30a3a932f7ce..c6e7cf21a5a05 100644 --- a/test/Constraints/function_builder.swift +++ b/test/Constraints/function_builder.swift @@ -767,3 +767,24 @@ do { } catch { fatalError("Threw something else?") } + +// CHECK: testStoredProperties +struct MyTupleStruct { + @TupleBuilder let first: () -> T + @TupleBuilder let second: U +} + +print("testStoredProperties") +let ts1 = MyTupleStruct { + 1 + "hello" + if true { + "conditional" + } +} second: { + 3.14159 + "blah" +} + +// CHECK: MyTupleStruct<(Int, String, Optional), (Double, String)>(first: (Function), second: (3.14159, "blah")) +print(ts1) diff --git a/test/SILGen/function_builder_memberwise.swift b/test/SILGen/function_builder_memberwise.swift new file mode 100644 index 0000000000000..c87930e35aa06 --- /dev/null +++ b/test/SILGen/function_builder_memberwise.swift @@ -0,0 +1,57 @@ +// RUN: %target-swift-emit-silgen %s | %FileCheck %s + +@_functionBuilder +struct TupleBuilder { + 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 buildOptional(_ value: T?) -> T? { return value } +} + +struct MyTupleStruct { + @TupleBuilder let first: () -> T + @TupleBuilder let second: U + // CHECK: init(@TupleBuilder first: @escaping () -> T, @TupleBuilder second: () -> U) +} + +// CHECK-LABEL: sil hidden [ossa] @$s27function_builder_memberwise13MyTupleStructV5first6secondACyxq_Gxyc_q_yXEtcfC : $@convention(method) (@owned @callee_guaranteed @substituted <τ_0_0> () -> @out τ_0_0 for , @noescape @callee_guaranteed @substituted <τ_0_0> () -> @out τ_0_0 for , @thin MyTupleStruct.Type) -> @out MyTupleStruct { +// CHECK: bb0([[SELF:%.*]] : $*MyTupleStruct, [[FIRST:%.*]] : @owned $@callee_guaranteed @substituted <τ_0_0> () -> @out τ_0_0 for , [[SECOND:%.*]] : $@noescape @callee_guaranteed @substituted <τ_0_0> () -> @out τ_0_0 for , [[META:%.*]] : $@thin MyTupleStruct.Type): +// CHECK-NEXT: [[FIRST_ADDR:%.*]] = struct_element_addr [[SELF]] : $*MyTupleStruct, #MyTupleStruct.first +// CHECK-NEXT: store [[FIRST]] to [init] [[FIRST_ADDR]] : $*@callee_guaranteed @substituted <τ_0_0> () -> @out τ_0_0 for +// CHECK-NEXT: [[SECOND_ADDR:%.*]] = struct_element_addr [[SELF]] : $*MyTupleStruct, #MyTupleStruct.second +// CHECK-NEXT: [[CALL_RESULT:%.*]] = alloc_stack $U +// CHECK-NEXT: apply [[SECOND]]([[CALL_RESULT]]) : $@noescape @callee_guaranteed @substituted <τ_0_0> () -> @out τ_0_0 for +// CHECK-NEXT: copy_addr [take] [[CALL_RESULT]] to [initialization] [[SECOND_ADDR]] : $*U +func trigger(cond: Bool) { + _ = MyTupleStruct { + 1 + "hello" + if cond { + "conditional" + } + } second: { + 3.14159 + "blah" + } +}