Skip to content

[ResultBuilders] Enable SE-0348 buildPartialBlock by default. #41966

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 1 commit into from
Apr 5, 2022
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
3 changes: 0 additions & 3 deletions include/swift/AST/DiagnosticsSema.def
Original file line number Diff line number Diff line change
Expand Up @@ -6122,9 +6122,6 @@ WARNING(result_builder_missing_limited_availability, none,
"result builder %0 does not implement 'buildLimitedAvailability'; "
"this code may crash on earlier versions of the OS",
(Type))
ERROR(result_builder_static_buildblock, none,
"result builder must provide at least one static 'buildBlock' "
"method", ())
ERROR(result_builder_static_buildblock_or_buildpartialblock, none,
"result builder must provide at least one static 'buildBlock' "
"method, or both 'buildPartialBlock(first:)' and "
Expand Down
2 changes: 1 addition & 1 deletion include/swift/AST/EducationalNotes.def
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ EDUCATIONAL_NOTES(type_cannot_conform, "protocol-type-non-conformance.md")
EDUCATIONAL_NOTES(unlabeled_trailing_closure_deprecated,
"trailing-closure-matching.md")

EDUCATIONAL_NOTES(result_builder_static_buildblock,
EDUCATIONAL_NOTES(result_builder_static_buildblock_or_buildpartialblock,
"result-builder-methods.md")
EDUCATIONAL_NOTES(result_builder_missing_limited_availability,
"result-builder-methods.md")
Expand Down
3 changes: 0 additions & 3 deletions include/swift/Basic/LangOptions.h
Original file line number Diff line number Diff line change
Expand Up @@ -349,9 +349,6 @@ namespace swift {
/// Enable experimental 'move only' features.
bool EnableExperimentalMoveOnly = false;

/// Enable experimental pairwise `buildBlock` for result builders.
bool EnableExperimentalPairwiseBuildBlock = false;

/// Enable variadic generics.
bool EnableExperimentalVariadicGenerics = false;

Expand Down
3 changes: 0 additions & 3 deletions lib/Frontend/CompilerInvocation.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -470,9 +470,6 @@ static bool ParseLangArgs(LangOptions &Opts, ArgList &Args,
Opts.EnableExperimentalMoveOnly |=
Args.hasArg(OPT_enable_experimental_move_only);

Opts.EnableExperimentalPairwiseBuildBlock |=
Args.hasArg(OPT_enable_experimental_pairwise_build_block);

Opts.EnableInferPublicSendable |=
Args.hasFlag(OPT_enable_infer_public_concurrent_value,
OPT_disable_infer_public_concurrent_value,
Expand Down
26 changes: 2 additions & 24 deletions lib/Sema/BuilderTransform.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -374,8 +374,7 @@ class BuilderClosureVisitor
// If the builder supports `buildPartialBlock(first:)` and
// `buildPartialBlock(accumulated:next:)`, use this to combine
// subexpressions pairwise.
if (ctx.LangOpts.EnableExperimentalPairwiseBuildBlock &&
!expressions.empty() &&
if (!expressions.empty() &&
builderSupports(ctx.Id_buildPartialBlock, {ctx.Id_first},
/*checkAvailability*/ true) &&
builderSupports(ctx.Id_buildPartialBlock,
Expand All @@ -398,31 +397,10 @@ class BuilderClosureVisitor
{ctx.Id_accumulated, ctx.Id_next});
}
}
// TODO: Remove support for the old method name,
// `buildBlock(combining:into:)`.
else if (ctx.LangOpts.EnableExperimentalPairwiseBuildBlock &&
!expressions.empty() &&
builderSupports(ctx.Id_buildBlock,
{ctx.Id_combining, ctx.Id_into})) {
// NOTE: The current implementation uses one-way constraints in between
// subexpressions. It's functionally equivalent to the following:
// let v0 = Builder.buildBlock(arg_0)
// let v1 = Builder.buildBlock(combining: arg_1, into: v0)
// ...
// return Builder.buildBlock(combining: arg_n, into: ...)
call = buildCallIfWanted(braceStmt->getStartLoc(), ctx.Id_buildBlock,
{expressions.front()}, /*argLabels=*/{});
for (auto *expr : llvm::drop_begin(expressions)) {
call = buildCallIfWanted(braceStmt->getStartLoc(), ctx.Id_buildBlock,
{expr, new (ctx) OneWayExpr(call)},
{ctx.Id_combining, ctx.Id_into});
}
}
// If `buildBlock` does not exist at this point, it could be the case that
// `buildPartialBlock` did not have the sufficient availability for this
// call site. Diagnose it.
else if (ctx.LangOpts.EnableExperimentalPairwiseBuildBlock &&
!builderSupports(ctx.Id_buildBlock)) {
else if (!builderSupports(ctx.Id_buildBlock)) {
ctx.Diags.diagnose(
braceStmt->getStartLoc(),
diag::result_builder_missing_available_buildpartialblock,
Expand Down
8 changes: 2 additions & 6 deletions lib/Sema/TypeCheckAttr.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3280,9 +3280,7 @@ void AttributeChecker::visitResultBuilderAttr(ResultBuilderAttr *attr) {
bool supportsBuildBlock = TypeChecker::typeSupportsBuilderOp(
nominal->getDeclaredType(), nominal, ctx.Id_buildBlock,
/*argLabels=*/{}, &potentialMatches);
bool isBuildPartialBlockFeatureEnabled =
ctx.LangOpts.EnableExperimentalPairwiseBuildBlock;
bool supportsBuildPartialBlock = isBuildPartialBlockFeatureEnabled &&
bool supportsBuildPartialBlock =
TypeChecker::typeSupportsBuilderOp(
nominal->getDeclaredType(), nominal,
ctx.Id_buildPartialBlock,
Expand All @@ -3296,9 +3294,7 @@ void AttributeChecker::visitResultBuilderAttr(ResultBuilderAttr *attr) {
{
auto diag = diagnose(
nominal->getLoc(),
isBuildPartialBlockFeatureEnabled
? diag::result_builder_static_buildblock_or_buildpartialblock
: diag::result_builder_static_buildblock);
diag::result_builder_static_buildblock_or_buildpartialblock);

// If there were no close matches, propose adding a stub.
SourceLoc buildInsertionLoc;
Expand Down
255 changes: 255 additions & 0 deletions test/Constraints/result_builder.swift
Original file line number Diff line number Diff line change
Expand Up @@ -919,3 +919,258 @@ func test_custom_tilde_equals_operator_matching() {
}
}
}

struct Values<T> {
var values: T

init(values: T) {
self.values = values
}

func map<R>(_ f: (T) -> R) -> Values<R> {
.init(values: f(values))
}
}

@resultBuilder
enum NestedTupleBuilder {
static func buildPartialBlock<T>(first x: T) -> Values<T> {
.init(values: x)
}

static func buildPartialBlock<T, U>(
accumulated: Values<T>, next: U
) -> Values<(T, U)> {
.init(values: (accumulated.values, next))
}
}

extension Values {
init(@NestedTupleBuilder nested values: () -> Self) {
self = values()
}
}

let nestedValues = Values(nested: {
1
"2"
3.0
"yes"
})
print(nestedValues)

@resultBuilder
enum NestedTupleBuilder_Not {
@available(*, unavailable)
static func buildPartialBlock<T>(first x: T) -> Values<T> {
.init(values: x)
}

@available(*, unavailable)
static func buildPartialBlock<T, U>(
accumulated: Values<T>, next: U
) -> Values<(T, U)> {
.init(values: (accumulated.values, next))
}

#if os(macOS)
@available(macOS 9999, *)
static func buildPartialBlock(first x: Never) -> Values<Never> {
fatalError()
}

@available(macOS 9999, *)
static func buildPartialBlock(
accumulated: Values<Never>, next: Never
) -> Values<Never> {
fatalError()
}
#endif

// This one will be called because no `buildPartialBlock` is available.
static func buildBlock(_ x: Any...) -> Values<[Any]> {
.init(values: x)
}
}

extension Values {
init(@NestedTupleBuilder_Not nested_not values: () -> Self) {
self = values()
}
}

let nestedValues_not = Values(nested_not: {
1
"2"
3.0
"yes"
})
print(nestedValues_not)

// CHECK: Values<Array<Any>>(values: [1, "2", 3.0, "yes"])

@resultBuilder
enum FlatTupleBuilder {
static func buildExpression<T>(_ x: T) -> Values<T> {
.init(values: x)
}

static func buildPartialBlock<T>(first x: Values<T>) -> Values<T> {
.init(values: x.values)
}

static func buildPartialBlock<T, N>(
accumulated: Values<T>,
next: Values<N>
) -> Values<(T, N)> {
.init(values: (accumulated.values, next.values))
}

static func buildPartialBlock<T0, T1, N>(
accumulated: Values<(T0, T1)>,
next: Values<N>
) -> Values<(T0, T1, N)> {
.init(values: (accumulated.values.0, accumulated.values.1, next.values))
}

static func buildPartialBlock<T0, T1, T2, N>(
accumulated: Values<(T0, T1, T2)>,
next: Values<N>
) -> Values<(T0, T1, T2, N)> {
.init(values: (accumulated.values.0, accumulated.values.1, accumulated.values.2, next.values))
}

static func buildPartialBlock<T0, T1, T2, T3, N>(
accumulated: Values<(T0, T1, T2, T3)>,
next: Values<N>
) -> Values<(T0, T1, T2, T3, N)> {
.init(values: (accumulated.values.0, accumulated.values.1, accumulated.values.2, accumulated.values.3, next.values))
}

static func buildBlock(_ x: Never...) -> Values<()> {
assert(x.isEmpty, "I should never be called unless it's nullary")
return .init(values: ())
}

static func buildEither<T>(first: T) -> T {
first
}

static func buildEither<T>(second: T) -> T {
second
}

static func buildOptional<T>(_ x: Values<T>?) -> Values<T?> {
x?.map { $0 } ?? .init(values: nil)
}

static func buildLimitedAvailability<T>(_ x: Values<T>) -> Values<T> {
x
}
}

extension Values {
init(@FlatTupleBuilder flat values: () -> Self) {
self = values()
}
}

let flatValues0 = Values(flat: {})
print(flatValues0)
// CHECK: Values<()>(values: ())

let flatValues1 = Values(flat: {
1
"2"
3.0
})
print(flatValues1)
// CHECK: Values<(Int, String, Double)>(values: (1, "2", 3.0))

let flatValues2 = Values(flat: {
1
"2"
let y = 3.0 + 4.0
#if false
"not gonna happen"
#endif
if true {
"yes"
} else {
"no"
}
#warning("Beware of pairwise block building")
#if true
if false {
"nah"
}
if #available(*) {
5.0
}
#endif
})
print(flatValues2)

// CHECK: Values<(Int, String, String, Optional<String>, Optional<Double>)>(values: (1, "2", "yes", nil, Optional(5.0)))

struct Nil: CustomStringConvertible {
var description: String {
"nil"
}
}
struct Cons<Head, Tail>: CustomStringConvertible {
var head: Head
var tail: Tail

var description: String {
"(cons \(String(reflecting: head)) \(tail))"
}
}

@resultBuilder
enum ListBuilder {
static func buildBlock() -> Nil {
Nil()
}

static func buildPartialBlock<T>(first x: T) -> Cons<T, Nil> {
.init(head: x, tail: Nil())
}

static func buildPartialBlock<New, T>(accumulated: T, next: New) -> Cons<New, T> {
.init(head: next, tail: accumulated)
}

static func buildBlock<T>(_ x: T...) -> [T] {
fatalError("I should never be called!")
}
}

func list<T>(@ListBuilder f: () -> T) -> T {
f()
}

let list0 = list {}
print(list0)
// CHECK: nil

let list1 = list { "1" }
print(list1)
// Check: (cons 1 nil)

let list2 = list {
1
2
}
print(list2)
// CHECK: (cons 2 (cons 1 nil))
let list3 = list {
1
list {
2.0
"3"
}
"4"
}
print(list3)
// CHECK: (cons "4" (cons (cons "3" (cons 2.0 nil)) (cons 1 nil)))
Loading