Skip to content

Commit 7c81f49

Browse files
authored
Merge pull request swiftlang#33798 from Jumhyn/function-builder-static-buildblock
[Sema] Require function builders to have at least one buildBlock method
2 parents 0ceaa58 + b82c57a commit 7c81f49

File tree

6 files changed

+205
-34
lines changed

6 files changed

+205
-34
lines changed

include/swift/AST/DiagnosticsSema.def

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5265,6 +5265,16 @@ WARNING(function_builder_missing_limited_availability, none,
52655265
"function builder %0 does not implement 'buildLimitedAvailability'; "
52665266
"this code may crash on earlier versions of the OS",
52675267
(Type))
5268+
ERROR(function_builder_static_buildblock, none,
5269+
"function builder must provide at least one static 'buildBlock' "
5270+
"method", ())
5271+
NOTE(function_builder_non_static_buildblock, none,
5272+
"did you mean to make instance method 'buildBlock' static?", ())
5273+
NOTE(function_builder_buildblock_enum_case, none,
5274+
"enum case 'buildBlock' cannot be used to satisfy the function builder "
5275+
"requirement", ())
5276+
NOTE(function_builder_buildblock_not_static_method, none,
5277+
"potential match 'buildBlock' is not a static method", ())
52685278

52695279
//------------------------------------------------------------------------------
52705280
// MARK: Tuple Shuffle Diagnostics

lib/Sema/BuilderTransform.cpp

Lines changed: 36 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -145,32 +145,8 @@ class BuilderClosureVisitor
145145
return known->second;
146146
}
147147

148-
bool found = false;
149-
SmallVector<ValueDecl *, 4> foundDecls;
150-
dc->lookupQualified(
151-
builderType, DeclNameRef(fnName),
152-
NL_QualifiedDefault | NL_ProtocolMembers, foundDecls);
153-
for (auto decl : foundDecls) {
154-
if (auto func = dyn_cast<FuncDecl>(decl)) {
155-
// Function must be static.
156-
if (!func->isStatic())
157-
continue;
158-
159-
// Function must have the right argument labels, if provided.
160-
if (!argLabels.empty()) {
161-
auto funcLabels = func->getName().getArgumentNames();
162-
if (argLabels.size() > funcLabels.size() ||
163-
funcLabels.slice(0, argLabels.size()) != argLabels)
164-
continue;
165-
}
166-
167-
// Okay, it's a good-enough match.
168-
found = true;
169-
break;
170-
}
171-
}
172-
173-
return supportedOps[fnName] = found;
148+
return supportedOps[fnName] = TypeChecker::typeSupportsBuilderOp(
149+
builderType, dc, fnName, argLabels);
174150
}
175151

176152
/// Build an implicit variable in this context.
@@ -1874,3 +1850,37 @@ std::vector<ReturnStmt *> TypeChecker::findReturnStatements(AnyFunctionRef fn) {
18741850
(void)precheck.run();
18751851
return precheck.getReturnStmts();
18761852
}
1853+
1854+
bool TypeChecker::typeSupportsBuilderOp(
1855+
Type builderType, DeclContext *dc, Identifier fnName,
1856+
ArrayRef<Identifier> argLabels, SmallVectorImpl<ValueDecl *> *allResults) {
1857+
bool foundMatch = false;
1858+
SmallVector<ValueDecl *, 4> foundDecls;
1859+
dc->lookupQualified(
1860+
builderType, DeclNameRef(fnName),
1861+
NL_QualifiedDefault | NL_ProtocolMembers, foundDecls);
1862+
for (auto decl : foundDecls) {
1863+
if (auto func = dyn_cast<FuncDecl>(decl)) {
1864+
// Function must be static.
1865+
if (!func->isStatic())
1866+
continue;
1867+
1868+
// Function must have the right argument labels, if provided.
1869+
if (!argLabels.empty()) {
1870+
auto funcLabels = func->getName().getArgumentNames();
1871+
if (argLabels.size() > funcLabels.size() ||
1872+
funcLabels.slice(0, argLabels.size()) != argLabels)
1873+
continue;
1874+
}
1875+
1876+
foundMatch = true;
1877+
break;
1878+
}
1879+
}
1880+
1881+
if (allResults)
1882+
allResults->append(foundDecls.begin(), foundDecls.end());
1883+
1884+
return foundMatch;
1885+
}
1886+

lib/Sema/TypeCheckAttr.cpp

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3008,8 +3008,32 @@ void AttributeChecker::visitPropertyWrapperAttr(PropertyWrapperAttr *attr) {
30083008
}
30093009

30103010
void AttributeChecker::visitFunctionBuilderAttr(FunctionBuilderAttr *attr) {
3011-
// TODO: check that the type at least provides a `sequence` factory?
3012-
// Any other validation?
3011+
auto *nominal = dyn_cast<NominalTypeDecl>(D);
3012+
SmallVector<ValueDecl *, 4> potentialMatches;
3013+
bool supportsBuildBlock = TypeChecker::typeSupportsBuilderOp(
3014+
nominal->getDeclaredType(), nominal, D->getASTContext().Id_buildBlock,
3015+
/*argLabels=*/{}, &potentialMatches);
3016+
3017+
if (!supportsBuildBlock) {
3018+
diagnose(nominal->getLoc(), diag::function_builder_static_buildblock);
3019+
3020+
// For any close matches, attempt to explain to the user why they aren't
3021+
// valid.
3022+
for (auto *member : potentialMatches) {
3023+
if (member->isStatic() && isa<FuncDecl>(member))
3024+
continue;
3025+
3026+
if (isa<FuncDecl>(member) &&
3027+
member->getDeclContext()->getSelfNominalTypeDecl() == nominal)
3028+
diagnose(member->getLoc(), diag::function_builder_non_static_buildblock)
3029+
.fixItInsert(member->getAttributeInsertionLoc(true), "static ");
3030+
else if (isa<EnumElementDecl>(member))
3031+
diagnose(member->getLoc(), diag::function_builder_buildblock_enum_case);
3032+
else
3033+
diagnose(member->getLoc(),
3034+
diag::function_builder_buildblock_not_static_method);
3035+
}
3036+
}
30133037
}
30143038

30153039
void

lib/Sema/TypeChecker.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1221,6 +1221,13 @@ bool requireArrayLiteralIntrinsics(ASTContext &ctx, SourceLoc loc);
12211221
/// If \c expr is not part of a member chain or the base is something other than
12221222
/// an \c UnresolvedMemberExpr, \c nullptr is returned.
12231223
UnresolvedMemberExpr *getUnresolvedMemberChainBase(Expr *expr);
1224+
1225+
/// Checks whether a function builder type has a well-formed function builder
1226+
/// method with the given name. If provided and non-empty, the argument labels
1227+
/// are verified against any candidates.
1228+
bool typeSupportsBuilderOp(Type builderType, DeclContext *dc, Identifier fnName,
1229+
ArrayRef<Identifier> argLabels = {},
1230+
SmallVectorImpl<ValueDecl *> *allResults = nullptr);
12241231
}; // namespace TypeChecker
12251232

12261233
/// Temporary on-stack storage and unescaping for encoded diagnostic

test/IDE/print_swift_module.swift

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,9 @@ public func returnsAlias() -> Alias<Int> {
2828
}
2929

3030
@_functionBuilder
31-
struct BridgeBuilder {}
31+
struct BridgeBuilder {
32+
static func buildBlock(_: Any...) {}
33+
}
3234

3335
public struct City {
3436
public init(@BridgeBuilder builder: () -> ()) {}

test/decl/var/function_builders.swift

Lines changed: 123 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,10 @@ var globalBuilder: Int
77
func globalBuilderFunction() -> Int { return 0 }
88

99
@_functionBuilder
10-
struct Maker {}
10+
struct Maker {} // expected-error {{function builder must provide at least one static 'buildBlock' method}}
1111

1212
@_functionBuilder
13-
class Inventor {}
13+
class Inventor {} // expected-error {{function builder must provide at least one static 'buildBlock' method}}
1414

1515
@Maker // expected-error {{function builder attribute 'Maker' can only be applied to a parameter, function, or computed property}}
1616
typealias typename = Inventor
@@ -66,11 +66,11 @@ func makerParamAutoclosure(@Maker // expected-error {{function builder attribute
6666
fn: @autoclosure () -> ()) {}
6767

6868
@_functionBuilder
69-
struct GenericMaker<T> {} // expected-note {{generic type 'GenericMaker' declared here}}
69+
struct GenericMaker<T> {} // expected-note {{generic type 'GenericMaker' declared here}} expected-error {{function builder must provide at least one static 'buildBlock' method}}
7070

7171
struct GenericContainer<T> { // expected-note {{generic type 'GenericContainer' declared here}}
7272
@_functionBuilder
73-
struct Maker {}
73+
struct Maker {} // expected-error {{function builder must provide at least one static 'buildBlock' method}}
7474
}
7575

7676
func makeParamUnbound(@GenericMaker // expected-error {{reference to generic type 'GenericMaker' requires arguments}}
@@ -89,7 +89,7 @@ func makeParamNestedBound(@GenericContainer<Int>.Maker
8989
protocol P { }
9090

9191
@_functionBuilder
92-
struct ConstrainedGenericMaker<T: P> {}
92+
struct ConstrainedGenericMaker<T: P> {} // expected-error {{function builder must provide at least one static 'buildBlock' method}}
9393

9494

9595
struct WithinGeneric<U> {
@@ -99,3 +99,121 @@ struct WithinGeneric<U> {
9999
func makeParamBoundInContextBad(@ConstrainedGenericMaker<U>
100100
fn: () -> ()) {}
101101
}
102+
103+
@_functionBuilder
104+
struct ValidBuilder1 {
105+
static func buildBlock(_ exprs: Any...) -> Int { return exprs.count }
106+
}
107+
108+
protocol BuilderFuncHelper {}
109+
110+
extension BuilderFuncHelper {
111+
static func buildBlock(_ exprs: Any...) -> Int { return exprs.count }
112+
}
113+
114+
@_functionBuilder
115+
struct ValidBuilder2: BuilderFuncHelper {}
116+
117+
class BuilderFuncBase {
118+
static func buildBlock(_ exprs: Any...) -> Int { return exprs.count }
119+
}
120+
121+
@_functionBuilder
122+
class ValidBuilder3: BuilderFuncBase {}
123+
124+
@_functionBuilder
125+
struct ValidBuilder4 {}
126+
extension ValidBuilder4 {
127+
static func buildBlock(_ exprs: Any...) -> Int { return exprs.count }
128+
}
129+
130+
@_functionBuilder
131+
struct ValidBuilder5 {
132+
static func buildBlock() -> Int { 0 }
133+
}
134+
135+
@_functionBuilder
136+
struct InvalidBuilder1 {} // expected-error {{function builder must provide at least one static 'buildBlock' method}}
137+
138+
@_functionBuilder
139+
struct InvalidBuilder2 { // expected-error {{function builder must provide at least one static 'buildBlock' method}}
140+
func buildBlock(_ exprs: Any...) -> Int { return exprs.count } // expected-note {{did you mean to make instance method 'buildBlock' static?}} {{3-3=static }}
141+
}
142+
143+
@_functionBuilder
144+
struct InvalidBuilder3 { // expected-error {{function builder must provide at least one static 'buildBlock' method}}
145+
var buildBlock: (Any...) -> Int = { return $0.count } // expected-note {{potential match 'buildBlock' is not a static method}}
146+
}
147+
148+
@_functionBuilder
149+
struct InvalidBuilder4 {} // expected-error {{function builder must provide at least one static 'buildBlock' method}}
150+
extension InvalidBuilder4 {
151+
func buildBlock(_ exprs: Any...) -> Int { return exprs.count } // expected-note {{did you mean to make instance method 'buildBlock' static?}} {{3-3=static }}
152+
}
153+
154+
protocol InvalidBuilderHelper {}
155+
extension InvalidBuilderHelper {
156+
func buildBlock(_ exprs: Any...) -> Int { return exprs.count } // expected-note {{potential match 'buildBlock' is not a static method}}
157+
}
158+
159+
@_functionBuilder
160+
struct InvalidBuilder5: InvalidBuilderHelper {} // expected-error {{function builder must provide at least one static 'buildBlock' method}}
161+
162+
@_functionBuilder
163+
struct InvalidBuilder6 { // expected-error {{function builder must provide at least one static 'buildBlock' method}}
164+
static var buildBlock: Int = 0 // expected-note {{potential match 'buildBlock' is not a static method}}
165+
}
166+
167+
struct Callable {
168+
func callAsFunction(_ exprs: Any...) -> Int { return exprs.count }
169+
}
170+
171+
@_functionBuilder
172+
struct InvalidBuilder7 { // expected-error {{function builder must provide at least one static 'buildBlock' method}}
173+
static var buildBlock = Callable() // expected-note {{potential match 'buildBlock' is not a static method}}
174+
}
175+
176+
class BuilderVarBase {
177+
static var buildBlock: (Any...) -> Int = { return $0.count } // expected-note {{potential match 'buildBlock' is not a static method}}
178+
}
179+
180+
@_functionBuilder
181+
class InvalidBuilder8: BuilderVarBase {} // expected-error {{function builder must provide at least one static 'buildBlock' method}}
182+
183+
protocol BuilderVarHelper {}
184+
185+
extension BuilderVarHelper {
186+
static var buildBlock: (Any...) -> Int { { return $0.count } } // expected-note {{potential match 'buildBlock' is not a static method}}
187+
}
188+
189+
@_functionBuilder
190+
struct InvalidBuilder9: BuilderVarHelper {} // expected-error {{function builder must provide at least one static 'buildBlock' method}}
191+
192+
@_functionBuilder
193+
struct InvalidBuilder10 { // expected-error {{function builder must provide at least one static 'buildBlock' method}}
194+
static var buildBlock: (Any...) -> Int = { return $0.count } // expected-note {{potential match 'buildBlock' is not a static method}}
195+
}
196+
197+
@_functionBuilder
198+
enum InvalidBuilder11 { // expected-error {{function builder must provide at least one static 'buildBlock' method}}
199+
case buildBlock(Any) // expected-note {{enum case 'buildBlock' cannot be used to satisfy the function builder requirement}}
200+
}
201+
202+
struct S {
203+
@ValidBuilder1 var v1: Int { 1 }
204+
@ValidBuilder2 var v2: Int { 1 }
205+
@ValidBuilder3 var v3: Int { 1 }
206+
@ValidBuilder4 var v4: Int { 1 }
207+
@ValidBuilder5 func v5() -> Int {}
208+
@InvalidBuilder1 var i1: Int { 1 } // expected-error {{type 'InvalidBuilder1' has no member 'buildBlock'}}
209+
@InvalidBuilder2 var i2: Int { 1 } // expected-error {{instance member 'buildBlock' cannot be used on type 'InvalidBuilder2'; did you mean to use a value of this type instead?}}
210+
@InvalidBuilder3 var i3: Int { 1 } // expected-error {{instance member 'buildBlock' cannot be used on type 'InvalidBuilder3'; did you mean to use a value of this type instead?}}
211+
@InvalidBuilder4 var i4: Int { 1 } // expected-error {{instance member 'buildBlock' cannot be used on type 'InvalidBuilder4'; did you mean to use a value of this type instead?}}
212+
@InvalidBuilder5 var i5: Int { 1 } // expected-error {{instance member 'buildBlock' cannot be used on type 'InvalidBuilder5'; did you mean to use a value of this type instead?}}
213+
@InvalidBuilder6 var i6: Int { 1 } // expected-error {{cannot call value of non-function type 'Int'}}
214+
@InvalidBuilder7 var i7: Int { 1 }
215+
@InvalidBuilder8 var i8: Int { 1 }
216+
@InvalidBuilder9 var i9: Int { 1 }
217+
@InvalidBuilder10 var i10: Int { 1 }
218+
@InvalidBuilder11 var i11: InvalidBuilder11 { 1 }
219+
}

0 commit comments

Comments
 (0)