Skip to content

Commit 8c45dd8

Browse files
committed
[ResultBuilders] Restrict use of uninitialized variable declarations
Support uninitialized variable declarations only if result builder implementation has `buildExpression(_: Void) -> ...` function.
1 parent 480bcca commit 8c45dd8

File tree

4 files changed

+60
-7
lines changed

4 files changed

+60
-7
lines changed

include/swift/Sema/ConstraintSystem.h

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,12 @@ struct ResultBuilder {
148148

149149
Identifier BuildOptionalId;
150150

151+
/// Identifies whether this builder supports assignment expressions.
152+
///
153+
/// Unfortunately this cannot be cached together with other operations
154+
/// because this check examines the parameter type of `buildExpression`.
155+
Optional<bool> SupportsAssignments;
156+
151157
/// Counter used to give unique names to the variables that are
152158
/// created implicitly.
153159
unsigned VarCounter = 0;
@@ -172,6 +178,9 @@ struct ResultBuilder {
172178

173179
bool supportsOptional() { return supports(getBuildOptionalId()); }
174180

181+
/// Checks whether the builder supports assignment expressions.
182+
bool supportsAssignments();
183+
175184
/// Checks whether the `buildPartialBlock` method is supported.
176185
bool supportsBuildPartialBlock(bool checkAvailability);
177186

lib/Sema/BuilderTransform.cpp

Lines changed: 39 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1022,8 +1022,21 @@ class ResultBuilderTransform
10221022
case DeclKind::IfConfig:
10231023
// Skip #warning/#error; we'll handle them when applying
10241024
// the builder.
1025+
case DeclKind::PatternBinding: {
1026+
auto *PB = cast<PatternBindingDecl>(decl);
1027+
// If builder doesn't support assignments, we need to
1028+
// make sure that all local variables are explicitly
1029+
// initialized.
1030+
if (!builder.supportsAssignments()) {
1031+
for (unsigned i : range(PB->getNumPatternEntries())) {
1032+
if (!PB->isExplicitlyInitialized(i))
1033+
return failTransform(decl);
1034+
}
1035+
}
1036+
1037+
LLVM_FALLTHROUGH;
1038+
}
10251039
case DeclKind::PoundDiagnostic:
1026-
case DeclKind::PatternBinding:
10271040
case DeclKind::Var:
10281041
case DeclKind::Param:
10291042
newBody.push_back(element);
@@ -2759,7 +2772,9 @@ std::vector<ReturnStmt *> TypeChecker::findReturnStatements(AnyFunctionRef fn) {
27592772

27602773
ResultBuilderOpSupport TypeChecker::checkBuilderOpSupport(
27612774
Type builderType, DeclContext *dc, Identifier fnName,
2762-
ArrayRef<Identifier> argLabels, SmallVectorImpl<ValueDecl *> *allResults) {
2775+
ArrayRef<Identifier> argLabels,
2776+
SmallVectorImpl<ValueDecl *> *allResults,
2777+
llvm::function_ref<bool(FuncDecl *)> additionalChecks) {
27632778

27642779
auto isUnavailable = [&](Decl *D) -> bool {
27652780
if (AvailableAttr::isUnavailable(D))
@@ -2791,6 +2806,9 @@ ResultBuilderOpSupport TypeChecker::checkBuilderOpSupport(
27912806
continue;
27922807
}
27932808

2809+
if (!additionalChecks(func))
2810+
continue;
2811+
27942812
// Check if the the candidate has a suitable availability for the
27952813
// calling context.
27962814
if (isUnavailable(func)) {
@@ -3020,6 +3038,25 @@ bool ResultBuilder::supports(Identifier fnBaseName,
30203038
return support.isSupported(checkAvailability);
30213039
}
30223040

3041+
bool ResultBuilder::supportsAssignments() {
3042+
if (SupportsAssignments)
3043+
return *SupportsAssignments;
3044+
3045+
auto &ctx = DC->getASTContext();
3046+
3047+
auto candidate = TypeChecker::checkBuilderOpSupport(
3048+
BuilderType, DC, ctx.Id_buildExpression, /*argLabels=*/{Identifier()},
3049+
/*allResults=*/{}, /*additionalChecks=*/[&](FuncDecl *candidate) {
3050+
auto *param = candidate->getParameters()->get(0);
3051+
return param->getInterfaceType()->isEqual(ctx.TheEmptyTupleType);
3052+
});
3053+
3054+
bool isSupported = candidate.isSupported(/*requireAvailable=*/true);
3055+
SupportsAssignments.emplace(isSupported);
3056+
3057+
return isSupported;
3058+
}
3059+
30233060
Expr *ResultBuilder::buildCall(SourceLoc loc, Identifier fnName,
30243061
ArrayRef<Expr *> argExprs,
30253062
ArrayRef<Identifier> argLabels) const {

lib/Sema/TypeChecker.h

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1238,11 +1238,15 @@ UnresolvedMemberExpr *getUnresolvedMemberChainBase(Expr *expr);
12381238

12391239
/// Checks whether a result builder type has a well-formed result builder
12401240
/// method with the given name. If provided and non-empty, the argument labels
1241-
/// are verified against any candidates.
1242-
ResultBuilderOpSupport
1243-
checkBuilderOpSupport(Type builderType, DeclContext *dc, Identifier fnName,
1244-
ArrayRef<Identifier> argLabels = {},
1245-
SmallVectorImpl<ValueDecl *> *allResults = nullptr);
1241+
/// are verified against any candidates. Additional checks on otherwise matching
1242+
/// candidates could be performed by passing "additionalChecks" closure.
1243+
ResultBuilderOpSupport checkBuilderOpSupport(
1244+
Type builderType, DeclContext *dc, Identifier fnName,
1245+
ArrayRef<Identifier> argLabels = {},
1246+
SmallVectorImpl<ValueDecl *> *allResults = nullptr,
1247+
llvm::function_ref<bool(FuncDecl *)> additionalChecks = [](FuncDecl *) {
1248+
return true;
1249+
});
12461250

12471251
/// Checks whether a result builder type has a well-formed result builder
12481252
/// method with the given name. If provided and non-empty, the argument labels

test/Constraints/result_builder_ast_transform.swift

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,9 @@ enum Either<T,U> {
3636

3737
@resultBuilder
3838
struct TupleBuilder {
39+
static func buildExpression(_: Void) {}
40+
static func buildExpression<T>(_ t: T) -> T { t }
41+
3942
static func buildBlock() -> () { }
4043

4144
static func buildBlock<T1>(_ t1: T1) -> T1 {

0 commit comments

Comments
 (0)