Skip to content

[Function builders] Add support for "if #available". #29419

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Jan 24, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 6 additions & 46 deletions lib/Sema/BuilderTransform.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -324,27 +324,11 @@ class BuilderClosureVisitor
CONTROL_FLOW_STMT(Yield)
CONTROL_FLOW_STMT(Defer)

/// Whether we can handle all of the conditions for this statement.
static bool canHandleStmtConditions(StmtCondition condition) {
for (const auto &element : condition) {
switch (element.getKind()) {
case StmtConditionElement::CK_Boolean:
continue;

case StmtConditionElement::CK_PatternBinding:
case StmtConditionElement::CK_Availability:
return false;
}
}

return true;
}

static bool isBuildableIfChainRecursive(IfStmt *ifStmt,
unsigned &numPayloads,
bool &isOptional) {
// Check whether we can handle the conditional.
if (!canHandleStmtConditions(ifStmt->getCond()))
if (!ConstraintSystem::canGenerateConstraints(ifStmt->getCond()))
return false;

// The 'then' clause contributes a payload.
Expand Down Expand Up @@ -470,38 +454,12 @@ class BuilderClosureVisitor
payloadIndex + 1, numPayloads, isOptional);
}

// Condition must convert to Bool.
// FIXME: This should be folded into constraint generation for conditions.
auto boolDecl = ctx.getBoolDecl();
if (!boolDecl) {
// Generate constraints for the conditions.
if (cs->generateConstraints(ifStmt->getCond(), dc)) {
hadError = true;
return nullptr;
}

// Generate constraints for the conditions.
for (const auto &condElement : ifStmt->getCond()) {
switch (condElement.getKind()) {
case StmtConditionElement::CK_Boolean: {
Expr *condExpr = condElement.getBoolean();
condExpr = cs->generateConstraints(condExpr, dc);
if (!condExpr) {
hadError = true;
return nullptr;
}

cs->addConstraint(ConstraintKind::Conversion,
cs->getType(condExpr),
boolDecl->getDeclaredType(),
cs->getConstraintLocator(condExpr));
continue;
}

case StmtConditionElement::CK_PatternBinding:
case StmtConditionElement::CK_Availability:
llvm_unreachable("unhandled statement condition");
}
}

// 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 && isTopLevel) {
Expand Down Expand Up @@ -873,6 +831,9 @@ class BuilderClosureRewriter
auto condition = ifStmt->getCond();
for (auto &condElement : condition) {
switch (condElement.getKind()) {
case StmtConditionElement::CK_Availability:
continue;

case StmtConditionElement::CK_Boolean: {
auto condExpr = condElement.getBoolean();
auto finalCondExpr = rewriteExpr(condExpr);
Expand All @@ -888,7 +849,6 @@ class BuilderClosureRewriter
}

case StmtConditionElement::CK_PatternBinding:
case StmtConditionElement::CK_Availability:
llvm_unreachable("unhandled statement condition");
}
}
Expand Down
51 changes: 51 additions & 0 deletions lib/Sema/CSGen.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3798,6 +3798,57 @@ Type ConstraintSystem::generateConstraints(Pattern *pattern,
return cg.getTypeForPattern(pattern, locator);
}

bool ConstraintSystem::canGenerateConstraints(StmtCondition condition) {
for (const auto &element : condition) {
switch (element.getKind()) {
case StmtConditionElement::CK_Availability:
case StmtConditionElement::CK_Boolean:
continue;

case StmtConditionElement::CK_PatternBinding:
return false;
}
}

return true;
}

bool ConstraintSystem::generateConstraints(StmtCondition condition,
DeclContext *dc) {
// FIXME: This should be folded into constraint generation for conditions.
auto boolDecl = getASTContext().getBoolDecl();
if (!boolDecl) {
return true;
}

for (const auto &condElement : condition) {
switch (condElement.getKind()) {
case StmtConditionElement::CK_Availability:
// Nothing to do here.
continue;

case StmtConditionElement::CK_Boolean: {
Expr *condExpr = condElement.getBoolean();
condExpr = generateConstraints(condExpr, dc);
if (!condExpr) {
return true;
}

addConstraint(ConstraintKind::Conversion,
getType(condExpr),
boolDecl->getDeclaredType(),
getConstraintLocator(condExpr));
continue;
}

case StmtConditionElement::CK_PatternBinding:
llvm_unreachable("unhandled statement condition");
}
}

return false;
}

void ConstraintSystem::optimizeConstraints(Expr *e) {
if (getASTContext().TypeCheckerOpts.DisableConstraintSolverPerformanceHacks)
return;
Expand Down
10 changes: 10 additions & 0 deletions lib/Sema/ConstraintSystem.h
Original file line number Diff line number Diff line change
Expand Up @@ -3065,6 +3065,16 @@ class ConstraintSystem {
/// \returns a possibly-sanitized initializer, or null if an error occurred.
Type generateConstraints(Pattern *P, ConstraintLocatorBuilder locator);

/// Determines whether we can generate constraints for this statement
/// condition.
static bool canGenerateConstraints(StmtCondition condition);

/// Generate constraints for a statement condition.
///
/// \returns true if there was an error in constraint generation, false
/// if generation succeeded.
bool generateConstraints(StmtCondition condition, DeclContext *dc);

/// Generate constraints for a given set of overload choices.
///
/// \param constraints The container of generated constraint choices.
Expand Down
74 changes: 74 additions & 0 deletions test/Constraints/function_builder_availability.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
// RUN: %target-typecheck-verify-swift -target x86_64-apple-macosx10.50

// REQUIRES: OS=macosx

enum Either<T,U> {
case first(T)
case second(U)
}

@_functionBuilder
struct TupleBuilder {
static func buildBlock<T1>(_ t1: T1) -> (T1) {
return (t1)
}

static func buildBlock<T1, T2>(_ t1: T1, _ t2: T2) -> (T1, T2) {
return (t1, t2)
}

static func buildBlock<T1, T2, T3>(_ t1: T1, _ t2: T2, _ t3: T3)
-> (T1, T2, T3) {
return (t1, t2, t3)
}

static func buildBlock<T1, T2, T3, T4>(_ t1: T1, _ t2: T2, _ t3: T3, _ t4: T4)
-> (T1, T2, T3, T4) {
return (t1, t2, t3, t4)
}

static func buildBlock<T1, T2, T3, T4, T5>(
_ t1: T1, _ t2: T2, _ t3: T3, _ t4: T4, _ t5: T5
) -> (T1, T2, T3, T4, T5) {
return (t1, t2, t3, t4, t5)
}

static func buildDo<T>(_ value: T) -> T { return value }
static func buildIf<T>(_ value: T?) -> T? { return value }

static func buildEither<T,U>(first value: T) -> Either<T,U> {
return .first(value)
}
static func buildEither<T,U>(second value: U) -> Either<T,U> {
return .second(value)
}
}

func tuplify<T>(_ cond: Bool, @TupleBuilder body: (Bool) -> T) {
print(body(cond))
}

@available(OSX, introduced: 10.9)
func globalFuncAvailableOn10_9() -> Int { return 9 }

@available(OSX, introduced: 10.51)
func globalFuncAvailableOn10_51() -> Int { return 10 }

@available(OSX, introduced: 10.52)
func globalFuncAvailableOn10_52() -> Int { return 11 }

tuplify(true) { cond in
globalFuncAvailableOn10_9()
if #available(OSX 10.51, *) {
globalFuncAvailableOn10_51()
tuplify(false) { cond2 in
if cond, #available(OSX 10.52, *) {
cond2
globalFuncAvailableOn10_52()
} else {
globalFuncAvailableOn10_52() // expected-error{{'globalFuncAvailableOn10_52()' is only available in macOS 10.52 or newer}}
// expected-note@-1{{add 'if #available' version check}}
}
}
}
}