From 63fa8b14abb021a1fca1c343be4d24206f24f707 Mon Sep 17 00:00:00 2001 From: Cal Stephens Date: Wed, 24 Dec 2025 09:48:17 -0800 Subject: [PATCH 01/12] Proof of concept, it works! --- lib/Sema/TypeCheckType.cpp | 101 ++++++++++++++++++++++++++++++++++++- 1 file changed, 99 insertions(+), 2 deletions(-) diff --git a/lib/Sema/TypeCheckType.cpp b/lib/Sema/TypeCheckType.cpp index 479106cfd1a62..8490d415eb0da 100644 --- a/lib/Sema/TypeCheckType.cpp +++ b/lib/Sema/TypeCheckType.cpp @@ -3681,7 +3681,7 @@ TypeResolver::resolveAttributedType(TypeRepr *repr, TypeResolutionOptions option else ty = resolveASTFunctionType(fnRepr, options, &attrs); - // Boxes + // Boxes } else if (auto boxRepr = dyn_cast(repr)) { ty = resolveSILBoxType(boxRepr, options, &attrs); @@ -7258,20 +7258,117 @@ void TypeChecker::checkExistentialTypes( checker.checkRequirements(genericParams->getRequirements()); } +// copied from elsewhere, to unify later +Type inferResultBuilderComponentType(NominalTypeDecl *builder) { + Type componentType; + + SmallVector potentialMatches; + ASTContext &ctx = builder->getASTContext(); + bool supportsBuildBlock = TypeChecker::typeSupportsBuilderOp( + builder->getDeclaredInterfaceType(), builder, ctx.Id_buildBlock, + /*argLabels=*/{}, &potentialMatches); + if (supportsBuildBlock) { + for (auto decl : potentialMatches) { + auto func = dyn_cast(decl); + if (!func || !func->isStatic()) + continue; + + // If we haven't seen a component type before, gather it. + if (!componentType) { + componentType = func->getResultInterfaceType(); + continue; + } + + // If there are inconsistent component types, bail out. + if (!componentType->isEqual(func->getResultInterfaceType())) { + componentType = Type(); + break; + } + } + } + + return componentType; +} + Type CustomAttrTypeRequest::evaluate(Evaluator &eval, CustomAttr *attr, DeclContext *dc, CustomAttrTypeKind typeKind) const { const TypeResolutionOptions options(TypeResolverContext::PatternBindingDecl); OpenUnboundGenericTypeFn unboundTyOpener = nullptr; + HandlePlaceholderTypeReprFn placeholderHandler = nullptr; // Property delegates allow their type to be an unbound generic. if (typeKind == CustomAttrTypeKind::PropertyWrapper) { unboundTyOpener = TypeResolution::defaultUnboundTypeOpener; + } else { + // TODO: Only do this for result builders + unboundTyOpener = [attr, dc](UnboundGenericType* unbound) -> Type { + Decl *owningDecl = attr->owner.getAsDecl(); + + if (auto builder = dyn_cast_or_null(unbound->getDecl())) { + // TODO: In this prototype we only support unbound result builders with a single generic parameter. + // A final implementation would support both. + if (builder->getGenericParams()->size() != 1) { + return unbound; + } + + auto genericParam = builder->getGenericParams()->getParams()[0]; + + if (auto varDecl = dyn_cast_or_null(owningDecl)) { + // Inspect the generic types of the ResultBuilder component and return of the attached function + auto resultType = varDecl->getInterfaceType(); + + auto componentType = inferResultBuilderComponentType(builder) + ->getAs(); + + using namespace constraints; + ConstraintSystem cs(dc, std::nullopt); + + // Replace any `GenericTypeParamType`s in the component type with a type variable + auto locator = cs.getConstraintLocator(builder); + auto typeVar = cs.createTypeVariable(locator, TVO_CanBindToHole); + + // TODO: Support multiple args by creating a type variable for each `GenericTypeParamType` + auto componentTypeWithTypeVar = BoundGenericType::get( + componentType->getDecl(), componentType->getParent(), {typeVar}); + +// auto componentTypeArgs = componentType->getGenericArgs() +// llvm::SmallVector componentArgsWithTypeVariables; +// componentArgsWithTypeVariables.reserve(args.size()); +// +// for (const Type &t : componentTypeArgs) { +// if (is generic type...) { +// copied.push_back(type var...); +// } else { +// copied.push_back(t); +// } +// } + + // The result builder result type should be equal to the return type of the attached declaration + cs.addConstraint(ConstraintKind::Equal, + resultType, + componentTypeWithTypeVar, + /*preparedOverload:*/ nullptr); + + auto solution = cs.solveSingle(); + + // Replace the type vars with the solved result + auto genericArgs = {solution->typeBindings[typeVar]}; + return BoundGenericType::get(builder, unbound->getParent(), genericArgs); + } + } + + // Could infer component type using `inferResultBuilderComponentType`? + + return unbound; + }; + + placeholderHandler = PlaceholderType::get; } const auto type = TypeResolution::resolveContextualType( attr->getTypeRepr(), dc, options, unboundTyOpener, - /*placeholderHandler*/ nullptr, /*packElementOpener*/ nullptr); + placeholderHandler, /*packElementOpener*/ nullptr); // We always require the type to resolve to a nominal type. If the type was // not a nominal type, we should have already diagnosed an error via From ab7e295522c86a12a60d039695a2547fdf2a864c Mon Sep 17 00:00:00 2001 From: Cal Stephens Date: Wed, 24 Dec 2025 11:51:15 -0800 Subject: [PATCH 02/12] Support more complex generics --- include/swift/AST/DiagnosticsSema.def | 2 + lib/Sema/TypeCheckType.cpp | 110 ++++++++++++++------------ 2 files changed, 60 insertions(+), 52 deletions(-) diff --git a/include/swift/AST/DiagnosticsSema.def b/include/swift/AST/DiagnosticsSema.def index 904f05a9a652e..d504ccb9912cd 100644 --- a/include/swift/AST/DiagnosticsSema.def +++ b/include/swift/AST/DiagnosticsSema.def @@ -7883,6 +7883,8 @@ NOTE(result_builder_infer_add_return, none, NOTE(result_builder_infer_pick_specific, none, "apply result builder %0 (inferred from %select{protocol|dynamic replacement of}1 %2)", (Type, unsigned, DeclName)) +ERROR(result_builder_generic_inference_failed, none, + "unable to infer generic parameters for result builder %0", (DeclName)) GROUPED_WARNING(result_builder_missing_limited_availability, ResultBuilderMethods, none, "result builder %0 does not implement 'buildLimitedAvailability'; " diff --git a/lib/Sema/TypeCheckType.cpp b/lib/Sema/TypeCheckType.cpp index 8490d415eb0da..a22a59160b7d1 100644 --- a/lib/Sema/TypeCheckType.cpp +++ b/lib/Sema/TypeCheckType.cpp @@ -7303,64 +7303,70 @@ Type CustomAttrTypeRequest::evaluate(Evaluator &eval, CustomAttr *attr, } else { // TODO: Only do this for result builders unboundTyOpener = [attr, dc](UnboundGenericType* unbound) -> Type { - Decl *owningDecl = attr->owner.getAsDecl(); + Decl *owningDecl = attr->getOwner().getAsDecl(); - if (auto builder = dyn_cast_or_null(unbound->getDecl())) { - // TODO: In this prototype we only support unbound result builders with a single generic parameter. - // A final implementation would support both. - if (builder->getGenericParams()->size() != 1) { - return unbound; - } - - auto genericParam = builder->getGenericParams()->getParams()[0]; - - if (auto varDecl = dyn_cast_or_null(owningDecl)) { - // Inspect the generic types of the ResultBuilder component and return of the attached function - auto resultType = varDecl->getInterfaceType(); - - auto componentType = inferResultBuilderComponentType(builder) - ->getAs(); - - using namespace constraints; - ConstraintSystem cs(dc, std::nullopt); - - // Replace any `GenericTypeParamType`s in the component type with a type variable + auto builder = dyn_cast_or_null(unbound->getNominalDecl()); + ASSERT(builder); + + if (auto varDecl = dyn_cast_or_null(owningDecl)) { + using namespace constraints; + ConstraintSystem cs(dc, std::nullopt); + + // Create a type variable for each of the result builder's generic params + auto genericSig = builder->getGenericSignature(); + + // Pre-create type variables for all generic parameters + llvm::SmallVector typeVarReplacements; + llvm::SmallVector typeVars; + for (unsigned i = 0; i < genericSig.getGenericParams().size(); ++i) { auto locator = cs.getConstraintLocator(builder); auto typeVar = cs.createTypeVariable(locator, TVO_CanBindToHole); - - // TODO: Support multiple args by creating a type variable for each `GenericTypeParamType` - auto componentTypeWithTypeVar = BoundGenericType::get( - componentType->getDecl(), componentType->getParent(), {typeVar}); - -// auto componentTypeArgs = componentType->getGenericArgs() -// llvm::SmallVector componentArgsWithTypeVariables; -// componentArgsWithTypeVariables.reserve(args.size()); -// -// for (const Type &t : componentTypeArgs) { -// if (is generic type...) { -// copied.push_back(type var...); -// } else { -// copied.push_back(t); -// } -// } - - // The result builder result type should be equal to the return type of the attached declaration - cs.addConstraint(ConstraintKind::Equal, - resultType, - componentTypeWithTypeVar, - /*preparedOverload:*/ nullptr); - - auto solution = cs.solveSingle(); - - // Replace the type vars with the solved result - auto genericArgs = {solution->typeBindings[typeVar]}; - return BoundGenericType::get(builder, unbound->getParent(), genericArgs); + typeVarReplacements.push_back(typeVar); + typeVars.push_back(typeVar); } + + // Retrieve the generic types of the ResultBuilder component and return type + // of the attached function, replacing any reference to the ResultBuilder's + // generic arguments with the corresponding type vars. + auto subMap = SubstitutionMap::get( + genericSig, + typeVarReplacements, + LookUpConformanceInModule()); + + auto resultType = varDecl->getInterfaceType(); + auto componentType = inferResultBuilderComponentType(builder).subst(subMap); + + // The result builder result type should be equal to the return type of the attached declaration + cs.addConstraint(ConstraintKind::Equal, + resultType, + componentType, + /*preparedOverload:*/ nullptr); + + auto solution = cs.solveSingle(); + + if (!solution) { + dc->getASTContext().Diags.diagnose( + attr->getTypeExpr()->getLoc(), + diag::result_builder_generic_inference_failed, + builder->getName()); + return ErrorType::get(dc->getASTContext()); + } + + // Bind the result builder type to the solved type parameters + llvm::SmallVector solvedReplacements; + for (auto typeVar : typeVars) { + solvedReplacements.push_back(solution->typeBindings[typeVar]); + } + + return BoundGenericType::get(builder, unbound->getParent(), solvedReplacements); } - - // Could infer component type using `inferResultBuilderComponentType`? - return unbound; + dc->getASTContext().Diags.diagnose( + attr->getTypeExpr()->getLoc(), + diag::result_builder_generic_inference_failed, + builder->getName()); + + return ErrorType::get(dc->getASTContext()); }; placeholderHandler = PlaceholderType::get; From 0eb7be5fe5e9ecbd364a9fbbb4005b009319056d Mon Sep 17 00:00:00 2001 From: Cal Stephens Date: Wed, 24 Dec 2025 16:13:20 -0800 Subject: [PATCH 03/12] Refactor, support functions --- include/swift/AST/TypeCheckRequests.h | 4 + lib/AST/Decl.cpp | 2 + lib/AST/TypeCheckRequests.cpp | 4 + lib/Sema/TypeCheckRequestFunctions.cpp | 2 +- lib/Sema/TypeCheckType.cpp | 155 ++++++++++++++----------- 5 files changed, 99 insertions(+), 68 deletions(-) diff --git a/include/swift/AST/TypeCheckRequests.h b/include/swift/AST/TypeCheckRequests.h index 53ce7d9c4ad38..7512736057d06 100644 --- a/include/swift/AST/TypeCheckRequests.h +++ b/include/swift/AST/TypeCheckRequests.h @@ -4160,6 +4160,10 @@ enum class CustomAttrTypeKind { /// Global actors are represented as custom type attributes. They don't /// have any particularly interesting semantics. GlobalActor, + + /// Result builder types can have their generics parameters inferred + /// from the attached declaration. + ResultBuilder, }; void simple_display(llvm::raw_ostream &out, CustomAttrTypeKind value); diff --git a/lib/AST/Decl.cpp b/lib/AST/Decl.cpp index 44828b5937170..ff67a31a39369 100644 --- a/lib/AST/Decl.cpp +++ b/lib/AST/Decl.cpp @@ -568,6 +568,8 @@ Type Decl::getResolvedCustomAttrType(CustomAttr *attr) const { kind = CustomAttrTypeKind::GlobalActor; } else if (nominal->getAttrs().hasAttribute()) { kind = CustomAttrTypeKind::PropertyWrapper; + } else if (nominal->getAttrs().hasAttribute()) { + kind = CustomAttrTypeKind::ResultBuilder; } else { kind = CustomAttrTypeKind::NonGeneric; } diff --git a/lib/AST/TypeCheckRequests.cpp b/lib/AST/TypeCheckRequests.cpp index c81be39752f0c..000890bfe1008 100644 --- a/lib/AST/TypeCheckRequests.cpp +++ b/lib/AST/TypeCheckRequests.cpp @@ -1874,6 +1874,10 @@ void swift::simple_display(llvm::raw_ostream &out, CustomAttrTypeKind value) { case CustomAttrTypeKind::GlobalActor: out << "global-actor"; return; + + case CustomAttrTypeKind::ResultBuilder: + out << "result-builder"; + return; } llvm_unreachable("bad kind"); diff --git a/lib/Sema/TypeCheckRequestFunctions.cpp b/lib/Sema/TypeCheckRequestFunctions.cpp index 4effb4bc8b2b5..542f4ffd138e2 100644 --- a/lib/Sema/TypeCheckRequestFunctions.cpp +++ b/lib/Sema/TypeCheckRequestFunctions.cpp @@ -443,7 +443,7 @@ Type ResultBuilderTypeRequest::evaluate(Evaluator &evaluator, auto &ctx = dc->getASTContext(); Type type = evaluateOrDefault( evaluator, - CustomAttrTypeRequest{mutableAttr, dc, CustomAttrTypeKind::NonGeneric}, + CustomAttrTypeRequest{mutableAttr, dc, CustomAttrTypeKind::ResultBuilder}, Type()); if (!type || type->hasError()) return Type(); diff --git a/lib/Sema/TypeCheckType.cpp b/lib/Sema/TypeCheckType.cpp index a22a59160b7d1..3f54d5a2068c7 100644 --- a/lib/Sema/TypeCheckType.cpp +++ b/lib/Sema/TypeCheckType.cpp @@ -30,6 +30,7 @@ #include "swift/AST/ASTVisitor.h" #include "swift/AST/ASTWalker.h" #include "swift/AST/ConformanceLookup.h" +#include "swift/AST/Decl.h" #include "swift/AST/DiagnosticsParse.h" #include "swift/AST/DiagnosticsSema.h" #include "swift/AST/ExistentialLayout.h" @@ -7290,6 +7291,85 @@ Type inferResultBuilderComponentType(NominalTypeDecl *builder) { return componentType; } +Type invalidResultBuilderType(UnboundGenericType* unboundTy, + CustomAttr *attr, DeclContext *dc) { + dc->getASTContext().Diags.diagnose( + attr->getTypeExpr()->getLoc(), + diag::result_builder_generic_inference_failed, + unboundTy->getDecl()->getName()); + + return ErrorType::get(dc->getASTContext()); +} + +Type openUnboundResultBuilderType(UnboundGenericType* unboundTy, + CustomAttr *attr, DeclContext *dc) { + auto builder = dyn_cast_or_null(unboundTy->getDecl()); + if (!builder) { + return invalidResultBuilderType(unboundTy, attr, dc); + } + + // Retrieve the return type of the owning declaration that + // provides type inference for the result builder type. + Type owningDeclResultType; + Decl *owningDecl = attr->getOwner().getAsDecl(); + + // TODO: Handle functions, closures, non-computed properties + if (auto varDecl = dyn_cast_or_null(owningDecl)) { + owningDeclResultType = varDecl->getInterfaceType(); + } else if (auto funcDecl = dyn_cast_or_null(owningDecl)) { + owningDeclResultType = funcDecl->getResultInterfaceType(); + } + + if (!owningDeclResultType) { + return invalidResultBuilderType(unboundTy, attr, dc); + } + + using namespace constraints; + ConstraintSystem cs(dc, std::nullopt); + + // Create a type variable for each of the result builder's generic params + auto genericSig = builder->getGenericSignature(); + + llvm::SmallVector typeVarReplacements; + llvm::SmallVector typeVars; + for (unsigned i = 0; i < genericSig.getGenericParams().size(); ++i) { + auto locator = cs.getConstraintLocator(builder); + auto typeVar = cs.createTypeVariable(locator, TVO_CanBindToHole); + typeVarReplacements.push_back(typeVar); + typeVars.push_back(typeVar); + } + + // Retrieve the generic types of the ResultBuilder component and return type + // of the attached function, replacing any reference to the ResultBuilder's + // generic arguments with the corresponding type vars. + auto subMap = SubstitutionMap::get( + genericSig, + typeVarReplacements, + LookUpConformanceInModule()); + + auto componentType = inferResultBuilderComponentType(builder).subst(subMap); + + // The result builder result type should be equal to the return type of the attached declaration + cs.addConstraint(ConstraintKind::Equal, + owningDeclResultType, + componentType, + /*preparedOverload:*/ nullptr); + + auto solution = cs.solveSingle(); + + if (!solution) { + return invalidResultBuilderType(unboundTy, attr, dc); + } + + // Bind the result builder type to the solved type parameters + llvm::SmallVector solvedReplacements; + for (auto typeVar : typeVars) { + solvedReplacements.push_back(solution->typeBindings[typeVar]); + } + + return BoundGenericType::get(builder, unboundTy->getParent(), solvedReplacements); +} + Type CustomAttrTypeRequest::evaluate(Evaluator &eval, CustomAttr *attr, DeclContext *dc, CustomAttrTypeKind typeKind) const { @@ -7297,76 +7377,17 @@ Type CustomAttrTypeRequest::evaluate(Evaluator &eval, CustomAttr *attr, OpenUnboundGenericTypeFn unboundTyOpener = nullptr; HandlePlaceholderTypeReprFn placeholderHandler = nullptr; + // Property delegates allow their type to be an unbound generic. if (typeKind == CustomAttrTypeKind::PropertyWrapper) { unboundTyOpener = TypeResolution::defaultUnboundTypeOpener; - } else { - // TODO: Only do this for result builders - unboundTyOpener = [attr, dc](UnboundGenericType* unbound) -> Type { - Decl *owningDecl = attr->getOwner().getAsDecl(); - - auto builder = dyn_cast_or_null(unbound->getNominalDecl()); - ASSERT(builder); - - if (auto varDecl = dyn_cast_or_null(owningDecl)) { - using namespace constraints; - ConstraintSystem cs(dc, std::nullopt); - - // Create a type variable for each of the result builder's generic params - auto genericSig = builder->getGenericSignature(); - - // Pre-create type variables for all generic parameters - llvm::SmallVector typeVarReplacements; - llvm::SmallVector typeVars; - for (unsigned i = 0; i < genericSig.getGenericParams().size(); ++i) { - auto locator = cs.getConstraintLocator(builder); - auto typeVar = cs.createTypeVariable(locator, TVO_CanBindToHole); - typeVarReplacements.push_back(typeVar); - typeVars.push_back(typeVar); - } - - // Retrieve the generic types of the ResultBuilder component and return type - // of the attached function, replacing any reference to the ResultBuilder's - // generic arguments with the corresponding type vars. - auto subMap = SubstitutionMap::get( - genericSig, - typeVarReplacements, - LookUpConformanceInModule()); - - auto resultType = varDecl->getInterfaceType(); - auto componentType = inferResultBuilderComponentType(builder).subst(subMap); - - // The result builder result type should be equal to the return type of the attached declaration - cs.addConstraint(ConstraintKind::Equal, - resultType, - componentType, - /*preparedOverload:*/ nullptr); - - auto solution = cs.solveSingle(); - - if (!solution) { - dc->getASTContext().Diags.diagnose( - attr->getTypeExpr()->getLoc(), - diag::result_builder_generic_inference_failed, - builder->getName()); - return ErrorType::get(dc->getASTContext()); - } - - // Bind the result builder type to the solved type parameters - llvm::SmallVector solvedReplacements; - for (auto typeVar : typeVars) { - solvedReplacements.push_back(solution->typeBindings[typeVar]); - } - - return BoundGenericType::get(builder, unbound->getParent(), solvedReplacements); - } - - dc->getASTContext().Diags.diagnose( - attr->getTypeExpr()->getLoc(), - diag::result_builder_generic_inference_failed, - builder->getName()); - - return ErrorType::get(dc->getASTContext()); + } + + /// Result builder types can have their generics parameters inferred + /// from the attached declaration. + else if (typeKind == CustomAttrTypeKind::ResultBuilder){ + unboundTyOpener = [attr, dc](UnboundGenericType* unboundTy) -> Type { + return openUnboundResultBuilderType(unboundTy, attr, dc); }; placeholderHandler = PlaceholderType::get; From 6113d1b7dc8327d96da959abc5cc3d81631746ca Mon Sep 17 00:00:00 2001 From: Cal Stephens Date: Thu, 25 Dec 2025 12:52:48 -0800 Subject: [PATCH 04/12] Update tests --- lib/Sema/TypeCheckType.cpp | 17 +++-- test/Constraints/result_builder_diags.swift | 80 +++++++++++++++++++++ test/decl/var/result_builders.swift | 8 +-- 3 files changed, 97 insertions(+), 8 deletions(-) diff --git a/lib/Sema/TypeCheckType.cpp b/lib/Sema/TypeCheckType.cpp index 3f54d5a2068c7..bba3d9a9eb049 100644 --- a/lib/Sema/TypeCheckType.cpp +++ b/lib/Sema/TypeCheckType.cpp @@ -7313,9 +7313,12 @@ Type openUnboundResultBuilderType(UnboundGenericType* unboundTy, Type owningDeclResultType; Decl *owningDecl = attr->getOwner().getAsDecl(); - // TODO: Handle functions, closures, non-computed properties if (auto varDecl = dyn_cast_or_null(owningDecl)) { - owningDeclResultType = varDecl->getInterfaceType(); + if (auto closureResultType = varDecl->getInterfaceType()->getAs()) { + owningDeclResultType = closureResultType->getResult(); + } else { + owningDeclResultType = varDecl->getInterfaceType(); + } } else if (auto funcDecl = dyn_cast_or_null(owningDecl)) { owningDeclResultType = funcDecl->getResultInterfaceType(); } @@ -7324,6 +7327,12 @@ Type openUnboundResultBuilderType(UnboundGenericType* unboundTy, return invalidResultBuilderType(unboundTy, attr, dc); } + // Retrieve the result type of the result builder itself + auto componentType = inferResultBuilderComponentType(builder); + if (!componentType) { + return invalidResultBuilderType(unboundTy, attr, dc); + } + using namespace constraints; ConstraintSystem cs(dc, std::nullopt); @@ -7347,12 +7356,12 @@ Type openUnboundResultBuilderType(UnboundGenericType* unboundTy, typeVarReplacements, LookUpConformanceInModule()); - auto componentType = inferResultBuilderComponentType(builder).subst(subMap); + auto componentTypeWithTypeVars = componentType.subst(subMap); // The result builder result type should be equal to the return type of the attached declaration cs.addConstraint(ConstraintKind::Equal, owningDeclResultType, - componentType, + componentTypeWithTypeVars, /*preparedOverload:*/ nullptr); auto solution = cs.solveSingle(); diff --git a/test/Constraints/result_builder_diags.swift b/test/Constraints/result_builder_diags.swift index 709b2dc6403a9..e84ca83f2be78 100644 --- a/test/Constraints/result_builder_diags.swift +++ b/test/Constraints/result_builder_diags.swift @@ -1079,3 +1079,83 @@ func testNoDuplicateStmtDiags() { } } } + +func testInferResultBuilderGenerics() { + @resultBuilder + struct SimpleArrayBuilder { + static func buildBlock(_ elements: Element...) -> [Element] { + elements + } + } + + @SimpleArrayBuilder + var stringArray: [String] { + "foo" + "bar" + } + + @SimpleArrayBuilder + func makeStringArray() -> Array { + "foo" + "bar" + } + + func takesClosure(@SimpleArrayBuilder _ makeArray: () -> [String]) { + print(makeArray()) + } + + @SimpleArrayBuilder // expected-error{{unable to infer generic parameters for result builder 'SimpleArrayBuilder'}} + var string: String { + "foo" + } + + struct MyValue { + @SimpleArrayBuilder var array1: [String] + @SimpleArrayBuilder var array2: () -> [String] + } + + _ = MyValue( + array1: { + "foo" + "bar" + }, + array2: { + "bar" + "baaz" + }, + ) + + @SimpleArrayBuilder + var makeStringArrayClosure: () -> [String] { // expected-error {{cannot convert return expression of type '[String]' to return type '() -> [String]'}} + { // expected-error {{cannot convert value of type '() -> [String]' to expected argument type 'String'}} + ["foo"] + } + } + + @resultBuilder + struct TupleArrayBuilder { + static func buildBlock(_ elements: (One, Two)...) -> [(One, Two)] { + elements + } + } + + @TupleArrayBuilder + var tupleArray: [(String, Int)] { + ("foo", 1) + ("bar", 2) + } + + @resultBuilder + struct TooManyArgsBuilder { + static func buildBlock(_ elements: (One, Two)...) -> [(One, Two)] { + elements + } + } + + @TooManyArgsBuilder // expected-error {{unable to infer generic parameters for result builder 'TooManyArgsBuilder'}} + var invalidTupleArray: [(String, Int)] { + ("foo", 1) // expected-warning {{expression of type '(String, Int)' is unused}} + ("bar", 2) // expected-warning {{expression of type '(String, Int)' is unused}} + } + +} diff --git a/test/decl/var/result_builders.swift b/test/decl/var/result_builders.swift index 9f3d09887d4f8..cb4486c24ff8c 100644 --- a/test/decl/var/result_builders.swift +++ b/test/decl/var/result_builders.swift @@ -65,20 +65,20 @@ func makerParamAutoclosure(@Maker // expected-error {{result builder attribute ' fn: @autoclosure () -> ()) {} @resultBuilder -struct GenericMaker {} // expected-note {{generic struct 'GenericMaker' declared here}} expected-error {{result builder must provide at least one static 'buildBlock' method}} +struct GenericMaker {} // expected-error {{result builder must provide at least one static 'buildBlock' method}} -struct GenericContainer { // expected-note {{generic struct 'GenericContainer' declared here}} +struct GenericContainer { @resultBuilder struct Maker {} // expected-error {{result builder must provide at least one static 'buildBlock' method}} } -func makeParamUnbound(@GenericMaker // expected-error {{reference to generic type 'GenericMaker' requires arguments}} +func makeParamUnbound(@GenericMaker // expected-error {{unable to infer generic parameters for result builder 'GenericMaker'}} fn: () -> ()) {} func makeParamBound(@GenericMaker fn: () -> ()) {} -func makeParamNestedUnbound(@GenericContainer.Maker // expected-error {{reference to generic type 'GenericContainer' requires arguments}} +func makeParamNestedUnbound(@GenericContainer.Maker // expected-error {{unable to infer generic parameters for result builder 'GenericContainer'}} fn: () -> ()) {} func makeParamNestedBound(@GenericContainer.Maker From 28a7d8c13c322268ed9a5ff38580a91ce51c220b Mon Sep 17 00:00:00 2001 From: Cal Stephens Date: Thu, 25 Dec 2025 13:31:51 -0800 Subject: [PATCH 05/12] Clean up --- lib/Sema/TypeCheckType.cpp | 141 ++++++++++++-------- test/Constraints/result_builder_diags.swift | 49 +++++++ 2 files changed, 131 insertions(+), 59 deletions(-) diff --git a/lib/Sema/TypeCheckType.cpp b/lib/Sema/TypeCheckType.cpp index bba3d9a9eb049..ffa87f52c5df2 100644 --- a/lib/Sema/TypeCheckType.cpp +++ b/lib/Sema/TypeCheckType.cpp @@ -7259,36 +7259,52 @@ void TypeChecker::checkExistentialTypes( checker.checkRequirements(genericParams->getRequirements()); } -// copied from elsewhere, to unify later -Type inferResultBuilderComponentType(NominalTypeDecl *builder) { - Type componentType; - - SmallVector potentialMatches; +/// Retrieves the valid result types of the result builder +/// (the return types of `buildFinalResult` / `buildBlock` / `buildPartialResult`). +llvm::SmallVector retrieveResultBuilderResultTypes(NominalTypeDecl *builder) { ASTContext &ctx = builder->getASTContext(); - bool supportsBuildBlock = TypeChecker::typeSupportsBuilderOp( - builder->getDeclaredInterfaceType(), builder, ctx.Id_buildBlock, - /*argLabels=*/{}, &potentialMatches); - if (supportsBuildBlock) { + llvm::SmallVector resultTypes; + + Identifier methodIds[] = { + ctx.Id_buildFinalResult, + ctx.Id_buildBlock, + ctx.Id_buildPartialBlock + }; + + for (auto methodId : methodIds) { + SmallVector potentialMatches; + bool supportsMethod = TypeChecker::typeSupportsBuilderOp( + builder->getDeclaredInterfaceType(), builder, methodId, + /*argLabels=*/{}, &potentialMatches); + + if (!supportsMethod) + continue; + for (auto decl : potentialMatches) { auto func = dyn_cast(decl); if (!func || !func->isStatic()) continue; - // If we haven't seen a component type before, gather it. - if (!componentType) { - componentType = func->getResultInterfaceType(); + auto resultType = func->getResultInterfaceType(); + if (!resultType || resultType->hasError()) continue; + + // Add the result type if we haven't seen it before + bool isDuplicate = false; + for (auto existingType : resultTypes) { + if (existingType->isEqual(resultType)) { + isDuplicate = true; + break; + } } - // If there are inconsistent component types, bail out. - if (!componentType->isEqual(func->getResultInterfaceType())) { - componentType = Type(); - break; + if (!isDuplicate) { + resultTypes.push_back(resultType); } } } - - return componentType; + + return resultTypes; } Type invalidResultBuilderType(UnboundGenericType* unboundTy, @@ -7301,10 +7317,13 @@ Type invalidResultBuilderType(UnboundGenericType* unboundTy, return ErrorType::get(dc->getASTContext()); } +/// Opens a result builder `UnboundGenericType` into a `BoundGenericType` +/// by creating and solving a constraint between the result builder's +/// result type and the return type of the attached declaration. Type openUnboundResultBuilderType(UnboundGenericType* unboundTy, CustomAttr *attr, DeclContext *dc) { - auto builder = dyn_cast_or_null(unboundTy->getDecl()); - if (!builder) { + auto resultBuilderDecl = dyn_cast_or_null(unboundTy->getDecl()); + if (!resultBuilderDecl) { return invalidResultBuilderType(unboundTy, attr, dc); } @@ -7326,57 +7345,61 @@ Type openUnboundResultBuilderType(UnboundGenericType* unboundTy, if (!owningDeclResultType) { return invalidResultBuilderType(unboundTy, attr, dc); } - - // Retrieve the result type of the result builder itself - auto componentType = inferResultBuilderComponentType(builder); - if (!componentType) { + + // Retrieve the supported result types of the result builder. + auto resultTypes = retrieveResultBuilderResultTypes(resultBuilderDecl); + if (resultTypes.empty()) { return invalidResultBuilderType(unboundTy, attr, dc); } - - using namespace constraints; - ConstraintSystem cs(dc, std::nullopt); - // Create a type variable for each of the result builder's generic params - auto genericSig = builder->getGenericSignature(); + auto genericSig = resultBuilderDecl->getGenericSignature(); - llvm::SmallVector typeVarReplacements; - llvm::SmallVector typeVars; - for (unsigned i = 0; i < genericSig.getGenericParams().size(); ++i) { - auto locator = cs.getConstraintLocator(builder); - auto typeVar = cs.createTypeVariable(locator, TVO_CanBindToHole); - typeVarReplacements.push_back(typeVar); - typeVars.push_back(typeVar); - } + for (auto componentType : resultTypes) { + using namespace constraints; + ConstraintSystem cs(dc, std::nullopt); - // Retrieve the generic types of the ResultBuilder component and return type - // of the attached function, replacing any reference to the ResultBuilder's - // generic arguments with the corresponding type vars. - auto subMap = SubstitutionMap::get( - genericSig, - typeVarReplacements, - LookUpConformanceInModule()); + // Create a type variable for each of the result builder's generic params + llvm::SmallVector typeVarReplacements; + llvm::SmallVector typeVars; + for (unsigned i = 0; i < genericSig.getGenericParams().size(); ++i) { + auto locator = cs.getConstraintLocator( + resultBuilderDecl, + {ConstraintLocator::GenericArgument, i}); + auto typeVar = cs.createTypeVariable(locator, TVO_CanBindToHole); + typeVarReplacements.push_back(typeVar); + typeVars.push_back(typeVar); + } - auto componentTypeWithTypeVars = componentType.subst(subMap); + // Replace any references to the result builder's generic params + // in the result type with the corresponding type variables. + auto subMap = SubstitutionMap::get( + genericSig, + typeVarReplacements, + LookUpConformanceInModule()); - // The result builder result type should be equal to the return type of the attached declaration - cs.addConstraint(ConstraintKind::Equal, - owningDeclResultType, - componentTypeWithTypeVars, - /*preparedOverload:*/ nullptr); + auto componentTypeWithTypeVars = componentType.subst(subMap); - auto solution = cs.solveSingle(); + // The result builder result type should be equal to the return type of the attached declaration. + cs.addConstraint(ConstraintKind::Equal, + owningDeclResultType, + componentTypeWithTypeVars, + /*preparedOverload:*/ nullptr); - if (!solution) { - return invalidResultBuilderType(unboundTy, attr, dc); - } + auto solution = cs.solveSingle(); - // Bind the result builder type to the solved type parameters - llvm::SmallVector solvedReplacements; - for (auto typeVar : typeVars) { - solvedReplacements.push_back(solution->typeBindings[typeVar]); + // If a solution exists, bind the result builder's generic params to the solved types. + if (solution) { + llvm::SmallVector solvedReplacements; + for (auto typeVar : typeVars) { + solvedReplacements.push_back(solution->typeBindings[typeVar]); + } + + return BoundGenericType::get(resultBuilderDecl, unboundTy->getParent(), solvedReplacements); + } } - return BoundGenericType::get(builder, unboundTy->getParent(), solvedReplacements); + // No result type produced a valid solution + return invalidResultBuilderType(unboundTy, attr, dc); } Type CustomAttrTypeRequest::evaluate(Evaluator &eval, CustomAttr *attr, diff --git a/test/Constraints/result_builder_diags.swift b/test/Constraints/result_builder_diags.swift index e84ca83f2be78..0b0fb465a941e 100644 --- a/test/Constraints/result_builder_diags.swift +++ b/test/Constraints/result_builder_diags.swift @@ -1157,5 +1157,54 @@ func testInferResultBuilderGenerics() { ("foo", 1) // expected-warning {{expression of type '(String, Int)' is unused}} ("bar", 2) // expected-warning {{expression of type '(String, Int)' is unused}} } + + @resultBuilder + enum ComplexListBuilder { + static func buildBlock(_ elements: Element...) -> [Element] { + elements + } + + static func buildFinalResult(_ component: [Element]) -> Set where Element: Hashable { + Set(component) + } + + static func buildFinalResult(_ component: [Element]) -> ContiguousArray { + ContiguousArray(component) + } + } + + @ComplexListBuilder + var stringSetFromBuildFinalResult: Set { + "foo" + "bar" + } + + @ComplexListBuilder + var contiguousStringsFromBuildFinalResult: ContiguousArray { + "foo" + "bar" + } + + @ComplexListBuilder + var stringArrayFromBuildFinalResult: [String] { // expected-error {{cannot convert return expression of type 'Set' to return type '[String]'}} + "foo" + "bar" + } + + @resultBuilder + enum PartialArrayBuilder { + static func buildPartialBlock(first: Element) -> [Element] { + [first] + } + + static func buildPartialBlock(accumulated: [Element], next: Element) -> [Element] { + accumulated + [next] + } + } + @PartialArrayBuilder + var stringArrayFromBuildPartial: [String] { + "foo" + "bar" + } } From 814d904b45684c250d8f5acca1ad6935aefcc603 Mon Sep 17 00:00:00 2001 From: Cal Stephens Date: Thu, 25 Dec 2025 13:32:19 -0800 Subject: [PATCH 06/12] git clang-format --- lib/Sema/TypeCheckType.cpp | 48 +++++++++++++++++--------------------- 1 file changed, 22 insertions(+), 26 deletions(-) diff --git a/lib/Sema/TypeCheckType.cpp b/lib/Sema/TypeCheckType.cpp index ffa87f52c5df2..403722177dc42 100644 --- a/lib/Sema/TypeCheckType.cpp +++ b/lib/Sema/TypeCheckType.cpp @@ -7260,16 +7260,15 @@ void TypeChecker::checkExistentialTypes( } /// Retrieves the valid result types of the result builder -/// (the return types of `buildFinalResult` / `buildBlock` / `buildPartialResult`). -llvm::SmallVector retrieveResultBuilderResultTypes(NominalTypeDecl *builder) { +/// (the return types of `buildFinalResult` / `buildBlock` / +/// `buildPartialResult`). +llvm::SmallVector +retrieveResultBuilderResultTypes(NominalTypeDecl *builder) { ASTContext &ctx = builder->getASTContext(); llvm::SmallVector resultTypes; - Identifier methodIds[] = { - ctx.Id_buildFinalResult, - ctx.Id_buildBlock, - ctx.Id_buildPartialBlock - }; + Identifier methodIds[] = {ctx.Id_buildFinalResult, ctx.Id_buildBlock, + ctx.Id_buildPartialBlock}; for (auto methodId : methodIds) { SmallVector potentialMatches; @@ -7303,7 +7302,7 @@ llvm::SmallVector retrieveResultBuilderResultTypes(NominalTypeDecl *bui } } } - + return resultTypes; } @@ -7322,11 +7321,12 @@ Type invalidResultBuilderType(UnboundGenericType* unboundTy, /// result type and the return type of the attached declaration. Type openUnboundResultBuilderType(UnboundGenericType* unboundTy, CustomAttr *attr, DeclContext *dc) { - auto resultBuilderDecl = dyn_cast_or_null(unboundTy->getDecl()); + auto resultBuilderDecl = + dyn_cast_or_null(unboundTy->getDecl()); if (!resultBuilderDecl) { return invalidResultBuilderType(unboundTy, attr, dc); } - + // Retrieve the return type of the owning declaration that // provides type inference for the result builder type. Type owningDeclResultType; @@ -7360,11 +7360,10 @@ Type openUnboundResultBuilderType(UnboundGenericType* unboundTy, // Create a type variable for each of the result builder's generic params llvm::SmallVector typeVarReplacements; - llvm::SmallVector typeVars; + llvm::SmallVector typeVars; for (unsigned i = 0; i < genericSig.getGenericParams().size(); ++i) { auto locator = cs.getConstraintLocator( - resultBuilderDecl, - {ConstraintLocator::GenericArgument, i}); + resultBuilderDecl, {ConstraintLocator::GenericArgument, i}); auto typeVar = cs.createTypeVariable(locator, TVO_CanBindToHole); typeVarReplacements.push_back(typeVar); typeVars.push_back(typeVar); @@ -7372,29 +7371,29 @@ Type openUnboundResultBuilderType(UnboundGenericType* unboundTy, // Replace any references to the result builder's generic params // in the result type with the corresponding type variables. - auto subMap = SubstitutionMap::get( - genericSig, - typeVarReplacements, - LookUpConformanceInModule()); + auto subMap = SubstitutionMap::get(genericSig, typeVarReplacements, + LookUpConformanceInModule()); auto componentTypeWithTypeVars = componentType.subst(subMap); - // The result builder result type should be equal to the return type of the attached declaration. - cs.addConstraint(ConstraintKind::Equal, - owningDeclResultType, + // The result builder result type should be equal to the return type of the + // attached declaration. + cs.addConstraint(ConstraintKind::Equal, owningDeclResultType, componentTypeWithTypeVars, /*preparedOverload:*/ nullptr); auto solution = cs.solveSingle(); - // If a solution exists, bind the result builder's generic params to the solved types. + // If a solution exists, bind the result builder's generic params to the + // solved types. if (solution) { llvm::SmallVector solvedReplacements; for (auto typeVar : typeVars) { solvedReplacements.push_back(solution->typeBindings[typeVar]); } - return BoundGenericType::get(resultBuilderDecl, unboundTy->getParent(), solvedReplacements); + return BoundGenericType::get(resultBuilderDecl, unboundTy->getParent(), + solvedReplacements); } } @@ -7408,7 +7407,6 @@ Type CustomAttrTypeRequest::evaluate(Evaluator &eval, CustomAttr *attr, const TypeResolutionOptions options(TypeResolverContext::PatternBindingDecl); OpenUnboundGenericTypeFn unboundTyOpener = nullptr; - HandlePlaceholderTypeReprFn placeholderHandler = nullptr; // Property delegates allow their type to be an unbound generic. if (typeKind == CustomAttrTypeKind::PropertyWrapper) { @@ -7421,13 +7419,11 @@ Type CustomAttrTypeRequest::evaluate(Evaluator &eval, CustomAttr *attr, unboundTyOpener = [attr, dc](UnboundGenericType* unboundTy) -> Type { return openUnboundResultBuilderType(unboundTy, attr, dc); }; - - placeholderHandler = PlaceholderType::get; } const auto type = TypeResolution::resolveContextualType( attr->getTypeRepr(), dc, options, unboundTyOpener, - placeholderHandler, /*packElementOpener*/ nullptr); + /*placeholderHandler*/ nullptr, /*packElementOpener*/ nullptr); // We always require the type to resolve to a nominal type. If the type was // not a nominal type, we should have already diagnosed an error via From 1f27228460ec047b3e8ce75141a863409a993661 Mon Sep 17 00:00:00 2001 From: Cal Stephens Date: Thu, 25 Dec 2025 20:28:51 -0800 Subject: [PATCH 07/12] Fix issue in release builds by allocating memory for captures --- lib/Sema/TypeCheckType.cpp | 267 +++++++++++++++++++------------------ 1 file changed, 138 insertions(+), 129 deletions(-) diff --git a/lib/Sema/TypeCheckType.cpp b/lib/Sema/TypeCheckType.cpp index 403722177dc42..7a246ec99741a 100644 --- a/lib/Sema/TypeCheckType.cpp +++ b/lib/Sema/TypeCheckType.cpp @@ -7259,147 +7259,158 @@ void TypeChecker::checkExistentialTypes( checker.checkRequirements(genericParams->getRequirements()); } -/// Retrieves the valid result types of the result builder -/// (the return types of `buildFinalResult` / `buildBlock` / -/// `buildPartialResult`). -llvm::SmallVector -retrieveResultBuilderResultTypes(NominalTypeDecl *builder) { - ASTContext &ctx = builder->getASTContext(); - llvm::SmallVector resultTypes; - - Identifier methodIds[] = {ctx.Id_buildFinalResult, ctx.Id_buildBlock, - ctx.Id_buildPartialBlock}; - - for (auto methodId : methodIds) { - SmallVector potentialMatches; - bool supportsMethod = TypeChecker::typeSupportsBuilderOp( - builder->getDeclaredInterfaceType(), builder, methodId, - /*argLabels=*/{}, &potentialMatches); - - if (!supportsMethod) - continue; +/// Opens a result builder `UnboundGenericType` into a `BoundGenericType` +/// by creating and solving a constraint between the result builder's +/// result type and the return type of the attached declaration. +struct ResultBuilderUnboundTypeOpener { + DeclContext *dc; + CustomAttr *attr; + + Type operator()(UnboundGenericType *unboundTy) const { + auto resultBuilderDecl = + dyn_cast_or_null(unboundTy->getDecl()); + if (!resultBuilderDecl || + !resultBuilderDecl->getAttrs().hasAttribute()) { + return invalidResultBuilderType(unboundTy); + } + + Decl *owningDecl = attr->getOwner().getAsDecl(); + if (!owningDecl) { + return invalidResultBuilderType(unboundTy); + } + + // Retrieve the return type of the owning declaration that + // provides type inference for the result builder type. + Type owningDeclResultType; + if (auto varDecl = dyn_cast(owningDecl)) { + if (auto closureResultType = + varDecl->getInterfaceType()->getAs()) { + owningDeclResultType = closureResultType->getResult(); + } else { + owningDeclResultType = varDecl->getInterfaceType(); + } + } else if (auto funcDecl = dyn_cast(owningDecl)) { + owningDeclResultType = funcDecl->getResultInterfaceType(); + } - for (auto decl : potentialMatches) { - auto func = dyn_cast(decl); - if (!func || !func->isStatic()) - continue; + if (!owningDeclResultType) { + return invalidResultBuilderType(unboundTy); + } - auto resultType = func->getResultInterfaceType(); - if (!resultType || resultType->hasError()) - continue; + // Retrieve the supported result types of the result builder. + auto resultTypes = retrieveResultBuilderResultTypes(resultBuilderDecl); + if (resultTypes.empty()) { + return invalidResultBuilderType(unboundTy); + } - // Add the result type if we haven't seen it before - bool isDuplicate = false; - for (auto existingType : resultTypes) { - if (existingType->isEqual(resultType)) { - isDuplicate = true; - break; - } - } + auto genericSig = resultBuilderDecl->getGenericSignature(); + + // Try each result type and return the first one that produces a valid + // solution + for (auto componentType : resultTypes) { + using namespace constraints; + ConstraintSystem cs(dc, std::nullopt); - if (!isDuplicate) { - resultTypes.push_back(resultType); + // Create a type variable for each of the result builder's generic params + llvm::SmallVector typeVarReplacements; + llvm::SmallVector typeVars; + for (unsigned i = 0; i < genericSig.getGenericParams().size(); ++i) { + auto locator = cs.getConstraintLocator( + resultBuilderDecl, {ConstraintLocator::GenericArgument, i}); + auto typeVar = cs.createTypeVariable(locator, TVO_CanBindToHole); + typeVarReplacements.push_back(typeVar); + typeVars.push_back(typeVar); } - } - } - return resultTypes; -} + // Replace any references to the result builder's generic params + // in the result type with the corresponding type variables. + auto subMap = SubstitutionMap::get(genericSig, typeVarReplacements, + LookUpConformanceInModule()); -Type invalidResultBuilderType(UnboundGenericType* unboundTy, - CustomAttr *attr, DeclContext *dc) { - dc->getASTContext().Diags.diagnose( - attr->getTypeExpr()->getLoc(), - diag::result_builder_generic_inference_failed, - unboundTy->getDecl()->getName()); - - return ErrorType::get(dc->getASTContext()); -} + auto componentTypeWithTypeVars = componentType.subst(subMap); -/// Opens a result builder `UnboundGenericType` into a `BoundGenericType` -/// by creating and solving a constraint between the result builder's -/// result type and the return type of the attached declaration. -Type openUnboundResultBuilderType(UnboundGenericType* unboundTy, - CustomAttr *attr, DeclContext *dc) { - auto resultBuilderDecl = - dyn_cast_or_null(unboundTy->getDecl()); - if (!resultBuilderDecl) { - return invalidResultBuilderType(unboundTy, attr, dc); - } - - // Retrieve the return type of the owning declaration that - // provides type inference for the result builder type. - Type owningDeclResultType; - Decl *owningDecl = attr->getOwner().getAsDecl(); - - if (auto varDecl = dyn_cast_or_null(owningDecl)) { - if (auto closureResultType = varDecl->getInterfaceType()->getAs()) { - owningDeclResultType = closureResultType->getResult(); - } else { - owningDeclResultType = varDecl->getInterfaceType(); + // The result builder result type should be equal to the return type of + // the attached declaration. + cs.addConstraint(ConstraintKind::Equal, owningDeclResultType, + componentTypeWithTypeVars, + /*preparedOverload:*/ nullptr); + + auto solution = cs.solveSingle(); + + // If a solution exists, bind the result builder's generic params to the + // solved types. + if (solution) { + llvm::SmallVector solvedReplacements; + for (auto typeVar : typeVars) { + solvedReplacements.push_back(solution->typeBindings[typeVar]); + } + + return BoundGenericType::get(resultBuilderDecl, unboundTy->getParent(), + solvedReplacements); + } } - } else if (auto funcDecl = dyn_cast_or_null(owningDecl)) { - owningDeclResultType = funcDecl->getResultInterfaceType(); - } - - if (!owningDeclResultType) { - return invalidResultBuilderType(unboundTy, attr, dc); + + // No result type produced a valid solution + return invalidResultBuilderType(unboundTy); } - // Retrieve the supported result types of the result builder. - auto resultTypes = retrieveResultBuilderResultTypes(resultBuilderDecl); - if (resultTypes.empty()) { - return invalidResultBuilderType(unboundTy, attr, dc); +private: + Type invalidResultBuilderType(UnboundGenericType *unboundTy) const { + dc->getASTContext().Diags.diagnose( + attr->getTypeExpr()->getLoc(), + diag::result_builder_generic_inference_failed, + unboundTy->getDecl()->getName()); + + return ErrorType::get(dc->getASTContext()); } - auto genericSig = resultBuilderDecl->getGenericSignature(); + /// Retrieves the valid result types of the result builder + /// (the return types of `buildFinalResult` / `buildBlock` / + /// `buildPartialBlock`). + llvm::SmallVector + retrieveResultBuilderResultTypes(NominalTypeDecl *builder) const { + ASTContext &ctx = builder->getASTContext(); + llvm::SmallVector resultTypes; - for (auto componentType : resultTypes) { - using namespace constraints; - ConstraintSystem cs(dc, std::nullopt); + Identifier methodIds[] = {ctx.Id_buildFinalResult, ctx.Id_buildBlock, + ctx.Id_buildPartialBlock}; - // Create a type variable for each of the result builder's generic params - llvm::SmallVector typeVarReplacements; - llvm::SmallVector typeVars; - for (unsigned i = 0; i < genericSig.getGenericParams().size(); ++i) { - auto locator = cs.getConstraintLocator( - resultBuilderDecl, {ConstraintLocator::GenericArgument, i}); - auto typeVar = cs.createTypeVariable(locator, TVO_CanBindToHole); - typeVarReplacements.push_back(typeVar); - typeVars.push_back(typeVar); - } + for (auto methodId : methodIds) { + SmallVector potentialMatches; + bool supportsMethod = TypeChecker::typeSupportsBuilderOp( + builder->getDeclaredInterfaceType(), builder, methodId, + /*argLabels=*/{}, &potentialMatches); - // Replace any references to the result builder's generic params - // in the result type with the corresponding type variables. - auto subMap = SubstitutionMap::get(genericSig, typeVarReplacements, - LookUpConformanceInModule()); + if (!supportsMethod) + continue; - auto componentTypeWithTypeVars = componentType.subst(subMap); + for (auto decl : potentialMatches) { + auto func = dyn_cast(decl); + if (!func || !func->isStatic()) + continue; - // The result builder result type should be equal to the return type of the - // attached declaration. - cs.addConstraint(ConstraintKind::Equal, owningDeclResultType, - componentTypeWithTypeVars, - /*preparedOverload:*/ nullptr); + auto resultType = func->getResultInterfaceType(); + if (!resultType || resultType->hasError()) + continue; - auto solution = cs.solveSingle(); + // Add the result type if we haven't seen it before + bool isDuplicate = false; + for (auto existingType : resultTypes) { + if (existingType->isEqual(resultType)) { + isDuplicate = true; + break; + } + } - // If a solution exists, bind the result builder's generic params to the - // solved types. - if (solution) { - llvm::SmallVector solvedReplacements; - for (auto typeVar : typeVars) { - solvedReplacements.push_back(solution->typeBindings[typeVar]); + if (!isDuplicate) { + resultTypes.push_back(resultType); + } } - - return BoundGenericType::get(resultBuilderDecl, unboundTy->getParent(), - solvedReplacements); } - } - // No result type produced a valid solution - return invalidResultBuilderType(unboundTy, attr, dc); -} + return resultTypes; + } +}; Type CustomAttrTypeRequest::evaluate(Evaluator &eval, CustomAttr *attr, DeclContext *dc, @@ -7407,21 +7418,19 @@ Type CustomAttrTypeRequest::evaluate(Evaluator &eval, CustomAttr *attr, const TypeResolutionOptions options(TypeResolverContext::PatternBindingDecl); OpenUnboundGenericTypeFn unboundTyOpener = nullptr; - - // Property delegates allow their type to be an unbound generic. + + // Property wrappers and result builders allow their type to be an unbound + // generic. if (typeKind == CustomAttrTypeKind::PropertyWrapper) { unboundTyOpener = TypeResolution::defaultUnboundTypeOpener; - } - - /// Result builder types can have their generics parameters inferred - /// from the attached declaration. - else if (typeKind == CustomAttrTypeKind::ResultBuilder){ - unboundTyOpener = [attr, dc](UnboundGenericType* unboundTy) -> Type { - return openUnboundResultBuilderType(unboundTy, attr, dc); - }; + } else if (typeKind == CustomAttrTypeKind::ResultBuilder) { + auto &ctx = dc->getASTContext(); + void *mem = ctx.Allocate(sizeof(ResultBuilderUnboundTypeOpener), + alignof(ResultBuilderUnboundTypeOpener)); + unboundTyOpener = *new (mem) ResultBuilderUnboundTypeOpener{dc, attr}; } - const auto type = TypeResolution::resolveContextualType( + auto type = TypeResolution::resolveContextualType( attr->getTypeRepr(), dc, options, unboundTyOpener, /*placeholderHandler*/ nullptr, /*packElementOpener*/ nullptr); From 0b9a02c4550d3b4c9fe76279fd22fe723bcdbf97 Mon Sep 17 00:00:00 2001 From: Cal Stephens Date: Sat, 3 Jan 2026 12:31:18 -0800 Subject: [PATCH 08/12] Add more test cases --- include/swift/Sema/ConstraintSystem.h | 9 +++ lib/Sema/TypeCheckType.cpp | 11 ++- test/Constraints/result_builder_diags.swift | 79 +++++++++++++++++++-- 3 files changed, 89 insertions(+), 10 deletions(-) diff --git a/include/swift/Sema/ConstraintSystem.h b/include/swift/Sema/ConstraintSystem.h index 4e94189a1c963..a527da54485bf 100644 --- a/include/swift/Sema/ConstraintSystem.h +++ b/include/swift/Sema/ConstraintSystem.h @@ -606,6 +606,12 @@ enum class ConstraintSystemFlags { /// Disable macro expansions. DisableMacroExpansions = 0x40, + + /// Enable old type-checker performance hacks. + EnablePerformanceHacks = 0x80, + + /// Don't record a failed constraint after adding an unsolvable constraint. + DisableRecordFailedConstraint = 0x100, }; /// Options that affect the constraint system as a whole. @@ -2287,6 +2293,9 @@ class ConstraintSystem { /// Whether we should record the failure of a constraint. bool shouldRecordFailedConstraint() const { + if (Options.contains(ConstraintSystemFlags::DisableRecordFailedConstraint)) + return false; + // If we're debugging, always note a failure so we can print it out. if (isDebugMode()) return true; diff --git a/lib/Sema/TypeCheckType.cpp b/lib/Sema/TypeCheckType.cpp index 7a246ec99741a..a0df04e4c3d9b 100644 --- a/lib/Sema/TypeCheckType.cpp +++ b/lib/Sema/TypeCheckType.cpp @@ -7305,11 +7305,16 @@ struct ResultBuilderUnboundTypeOpener { auto genericSig = resultBuilderDecl->getGenericSignature(); - // Try each result type and return the first one that produces a valid - // solution + // Try each result type and return the first one that + // produces a valid solution. for (auto componentType : resultTypes) { using namespace constraints; - ConstraintSystem cs(dc, std::nullopt); + + // Avoid recording failed constraints, which are not used here. + ConstraintSystemOptions options; + options |= ConstraintSystemFlags::DisableRecordFailedConstraint; + + ConstraintSystem cs(dc, options); // Create a type variable for each of the result builder's generic params llvm::SmallVector typeVarReplacements; diff --git a/test/Constraints/result_builder_diags.swift b/test/Constraints/result_builder_diags.swift index 0b0fb465a941e..2a115490effee 100644 --- a/test/Constraints/result_builder_diags.swift +++ b/test/Constraints/result_builder_diags.swift @@ -1080,14 +1080,29 @@ func testNoDuplicateStmtDiags() { } } -func testInferResultBuilderGenerics() { - @resultBuilder - struct SimpleArrayBuilder { - static func buildBlock(_ elements: Element...) -> [Element] { - elements - } +@resultBuilder +enum SimpleArrayBuilder { + static func buildBlock(_ elements: ElementKind...) -> [ElementKind] { + elements } +} + +@resultBuilder +enum CollectionBuilder { + static func buildBlock(_ component: Element...) -> [Element] { + component + } + static func buildFinalResult(_ component: [Element]) -> [Element] { + component + } + + static func buildFinalResult(_ component: [Element]) -> Set where Element: Hashable { + Set(component) + } +} + +func testInferResultBuilderGenerics() { @SimpleArrayBuilder var stringArray: [String] { "foo" @@ -1157,7 +1172,7 @@ func testInferResultBuilderGenerics() { ("foo", 1) // expected-warning {{expression of type '(String, Int)' is unused}} ("bar", 2) // expected-warning {{expression of type '(String, Int)' is unused}} } - + @resultBuilder enum ComplexListBuilder { static func buildBlock(_ elements: Element...) -> [Element] { @@ -1207,4 +1222,54 @@ func testInferResultBuilderGenerics() { "foo" "bar" } + + @CollectionBuilder + var array: [String] { + "a" + "b" + } + + @CollectionBuilder + var set: Set { + "c" + "d" + } + + @CollectionBuilder // expected-error {{unable to infer generic parameters for result builder 'CollectionBuilder'}} + var contiguousArray: ContiguousArray { + "c" // expected-warning {{string literal is unused}} + "d" // expected-warning {{string literal is unused}} + } +} + +extension Array { + init(@SimpleArrayBuilder build1: () -> Self) { + self = build1() + } + + init(@SimpleArrayBuilder build2: () -> [Element]) { + self = build2() + } + + init(@SimpleArrayBuilder build3: () -> Array) { + self = build3() + } + + init(@SimpleArrayBuilder build3: () -> ContiguousArray) { // expected-error {{unable to infer generic parameters for result builder 'SimpleArrayBuilder'}} + self = Array(build3()) + } +} + +extension Set { + init(@CollectionBuilder build: () -> Self) { + self = build() + } + + init(@CollectionBuilder build2: () -> [Element]) { + self = Set(build2()) + } + + init(@SimpleArrayBuilder build3: () -> Self) { // expected-error {{unable to infer generic parameters for result builder 'SimpleArrayBuilder'}} + self = build3() + } } From 916e23dd9a86c4fb22335f6d544968f2793c5a19 Mon Sep 17 00:00:00 2001 From: Cal Stephens Date: Sat, 3 Jan 2026 14:13:41 -0800 Subject: [PATCH 09/12] Support nested examples like @Array.Builder --- include/swift/AST/DiagnosticsSema.def | 2 +- lib/Sema/TypeCheckType.cpp | 84 +++++++++++---------- test/Constraints/result_builder_diags.swift | 54 +++++++++++-- test/decl/var/result_builders.swift | 4 +- 4 files changed, 98 insertions(+), 46 deletions(-) diff --git a/include/swift/AST/DiagnosticsSema.def b/include/swift/AST/DiagnosticsSema.def index d504ccb9912cd..a9bf1b0358c41 100644 --- a/include/swift/AST/DiagnosticsSema.def +++ b/include/swift/AST/DiagnosticsSema.def @@ -7884,7 +7884,7 @@ NOTE(result_builder_infer_pick_specific, none, "apply result builder %0 (inferred from %select{protocol|dynamic replacement of}1 %2)", (Type, unsigned, DeclName)) ERROR(result_builder_generic_inference_failed, none, - "unable to infer generic parameters for result builder %0", (DeclName)) + "unable to infer generic parameters for result builder %0", (DeclAttribute)) GROUPED_WARNING(result_builder_missing_limited_availability, ResultBuilderMethods, none, "result builder %0 does not implement 'buildLimitedAvailability'; " diff --git a/lib/Sema/TypeCheckType.cpp b/lib/Sema/TypeCheckType.cpp index a0df04e4c3d9b..07a21f96e418a 100644 --- a/lib/Sema/TypeCheckType.cpp +++ b/lib/Sema/TypeCheckType.cpp @@ -7267,47 +7267,33 @@ struct ResultBuilderUnboundTypeOpener { CustomAttr *attr; Type operator()(UnboundGenericType *unboundTy) const { - auto resultBuilderDecl = - dyn_cast_or_null(unboundTy->getDecl()); - if (!resultBuilderDecl || - !resultBuilderDecl->getAttrs().hasAttribute()) { - return invalidResultBuilderType(unboundTy); + auto resultBuilderDecl = attr->getNominalDecl(); + if (!resultBuilderDecl) { + return invalidResultBuilderType(); } - Decl *owningDecl = attr->getOwner().getAsDecl(); - if (!owningDecl) { - return invalidResultBuilderType(unboundTy); - } - - // Retrieve the return type of the owning declaration that - // provides type inference for the result builder type. - Type owningDeclResultType; - if (auto varDecl = dyn_cast(owningDecl)) { - if (auto closureResultType = - varDecl->getInterfaceType()->getAs()) { - owningDeclResultType = closureResultType->getResult(); - } else { - owningDeclResultType = varDecl->getInterfaceType(); - } - } else if (auto funcDecl = dyn_cast(owningDecl)) { - owningDeclResultType = funcDecl->getResultInterfaceType(); + auto unboundTyDecl = + dyn_cast_or_null(unboundTy->getDecl()); + if (!resultBuilderDecl) { + return invalidResultBuilderType(); } - if (!owningDeclResultType) { - return invalidResultBuilderType(unboundTy); + auto owningDeclReturnType = getOwningDeclReturnType(); + if (!owningDeclReturnType) { + return invalidResultBuilderType(); } // Retrieve the supported result types of the result builder. - auto resultTypes = retrieveResultBuilderResultTypes(resultBuilderDecl); + auto resultTypes = getResultBuilderResultTypes(resultBuilderDecl); if (resultTypes.empty()) { - return invalidResultBuilderType(unboundTy); + return invalidResultBuilderType(); } - auto genericSig = resultBuilderDecl->getGenericSignature(); + auto genericSig = unboundTyDecl->getGenericSignature(); // Try each result type and return the first one that // produces a valid solution. - for (auto componentType : resultTypes) { + for (auto resultType : resultTypes) { using namespace constraints; // Avoid recording failed constraints, which are not used here. @@ -7321,7 +7307,7 @@ struct ResultBuilderUnboundTypeOpener { llvm::SmallVector typeVars; for (unsigned i = 0; i < genericSig.getGenericParams().size(); ++i) { auto locator = cs.getConstraintLocator( - resultBuilderDecl, {ConstraintLocator::GenericArgument, i}); + unboundTyDecl, {ConstraintLocator::GenericArgument, i}); auto typeVar = cs.createTypeVariable(locator, TVO_CanBindToHole); typeVarReplacements.push_back(typeVar); typeVars.push_back(typeVar); @@ -7332,12 +7318,12 @@ struct ResultBuilderUnboundTypeOpener { auto subMap = SubstitutionMap::get(genericSig, typeVarReplacements, LookUpConformanceInModule()); - auto componentTypeWithTypeVars = componentType.subst(subMap); + auto resultTypeWithTypeVars = resultType.subst(subMap); // The result builder result type should be equal to the return type of // the attached declaration. - cs.addConstraint(ConstraintKind::Equal, owningDeclResultType, - componentTypeWithTypeVars, + cs.addConstraint(ConstraintKind::Equal, owningDeclReturnType, + resultTypeWithTypeVars, /*preparedOverload:*/ nullptr); auto solution = cs.solveSingle(); @@ -7350,30 +7336,52 @@ struct ResultBuilderUnboundTypeOpener { solvedReplacements.push_back(solution->typeBindings[typeVar]); } - return BoundGenericType::get(resultBuilderDecl, unboundTy->getParent(), + return BoundGenericType::get(unboundTyDecl, unboundTy->getParent(), solvedReplacements); } } // No result type produced a valid solution - return invalidResultBuilderType(unboundTy); + return invalidResultBuilderType(); } private: - Type invalidResultBuilderType(UnboundGenericType *unboundTy) const { + Type invalidResultBuilderType() const { dc->getASTContext().Diags.diagnose( attr->getTypeExpr()->getLoc(), - diag::result_builder_generic_inference_failed, - unboundTy->getDecl()->getName()); + diag::result_builder_generic_inference_failed, attr); return ErrorType::get(dc->getASTContext()); } + /// Retrieve the return type of the owning declaration that + /// provides type inference for the result builder type. + Type getOwningDeclReturnType() const { + Decl *owningDecl = attr->getOwner().getAsDecl(); + if (!owningDecl) { + return nullptr; + } + + Type owningDeclReturnType; + if (auto varDecl = dyn_cast(owningDecl)) { + if (auto closureResultType = + varDecl->getInterfaceType()->getAs()) { + owningDeclReturnType = closureResultType->getResult(); + } else { + owningDeclReturnType = varDecl->getInterfaceType(); + } + } else if (auto funcDecl = dyn_cast(owningDecl)) { + owningDeclReturnType = funcDecl->getResultInterfaceType(); + } + + return owningDeclReturnType; + } + /// Retrieves the valid result types of the result builder /// (the return types of `buildFinalResult` / `buildBlock` / /// `buildPartialBlock`). llvm::SmallVector - retrieveResultBuilderResultTypes(NominalTypeDecl *builder) const { + getResultBuilderResultTypes(NominalTypeDecl *builder) const { ASTContext &ctx = builder->getASTContext(); llvm::SmallVector resultTypes; diff --git a/test/Constraints/result_builder_diags.swift b/test/Constraints/result_builder_diags.swift index 2a115490effee..012d860554d72 100644 --- a/test/Constraints/result_builder_diags.swift +++ b/test/Constraints/result_builder_diags.swift @@ -1119,7 +1119,7 @@ func testInferResultBuilderGenerics() { print(makeArray()) } - @SimpleArrayBuilder // expected-error{{unable to infer generic parameters for result builder 'SimpleArrayBuilder'}} + @SimpleArrayBuilder // expected-error{{unable to infer generic parameters for result builder @SimpleArrayBuilder}} var string: String { "foo" } @@ -1167,7 +1167,7 @@ func testInferResultBuilderGenerics() { } } - @TooManyArgsBuilder // expected-error {{unable to infer generic parameters for result builder 'TooManyArgsBuilder'}} + @TooManyArgsBuilder // expected-error {{unable to infer generic parameters for result builder @TooManyArgsBuilder}} var invalidTupleArray: [(String, Int)] { ("foo", 1) // expected-warning {{expression of type '(String, Int)' is unused}} ("bar", 2) // expected-warning {{expression of type '(String, Int)' is unused}} @@ -1235,7 +1235,7 @@ func testInferResultBuilderGenerics() { "d" } - @CollectionBuilder // expected-error {{unable to infer generic parameters for result builder 'CollectionBuilder'}} + @CollectionBuilder // expected-error {{unable to infer generic parameters for result builder @CollectionBuilder}} var contiguousArray: ContiguousArray { "c" // expected-warning {{string literal is unused}} "d" // expected-warning {{string literal is unused}} @@ -1255,7 +1255,7 @@ extension Array { self = build3() } - init(@SimpleArrayBuilder build3: () -> ContiguousArray) { // expected-error {{unable to infer generic parameters for result builder 'SimpleArrayBuilder'}} + init(@SimpleArrayBuilder build3: () -> ContiguousArray) { // expected-error {{unable to infer generic parameters for result builder @SimpleArrayBuilder}} self = Array(build3()) } } @@ -1269,7 +1269,51 @@ extension Set { self = Set(build2()) } - init(@SimpleArrayBuilder build3: () -> Self) { // expected-error {{unable to infer generic parameters for result builder 'SimpleArrayBuilder'}} + init(@SimpleArrayBuilder build3: () -> Self) { // expected-error {{unable to infer generic parameters for result builder @SimpleArrayBuilder}} self = build3() } } + +extension Array { + @resultBuilder + struct Builder { + static func buildBlock(_ elements: Element...) -> [Element] { + elements + } + } +} + +extension Dictionary { + @resultBuilder + struct Builder { + static func buildBlock(_ elements: (Key, Value)...) -> Dictionary { + Dictionary(elements, uniquingKeysWith: { $1 }) + } + } +} + +func testNonGenericResultBuildersInGenericTypeExtensions() { + @Array.Builder + var elements: [String] { + "foo" + "bar" + } + + @Array.Builder // expected-error {{unable to infer generic parameters for result builder @Array.Builder}} + var set: Set { + "foo" // expected-warning {{string literal is unused}} + "bar" // expected-warning {{string literal is unused}} + } + + @Dictionary.Builder + var dictionary: [String: String] { + ("foo", "bar") + ("baaz", "quux") + } + + @Dictionary.Builder // expected-error {{unable to infer generic parameters for result builder @Dictionary.Builder}} + var dictionary2: [(String, String)] { + ("foo", "bar") // expected-warning {{expression of type '(String, String)' is unused}} + ("baaz", "quux") // expected-warning {{expression of type '(String, String)' is unused}} + } +} diff --git a/test/decl/var/result_builders.swift b/test/decl/var/result_builders.swift index cb4486c24ff8c..facd5affa796d 100644 --- a/test/decl/var/result_builders.swift +++ b/test/decl/var/result_builders.swift @@ -72,13 +72,13 @@ struct GenericContainer { struct Maker {} // expected-error {{result builder must provide at least one static 'buildBlock' method}} } -func makeParamUnbound(@GenericMaker // expected-error {{unable to infer generic parameters for result builder 'GenericMaker'}} +func makeParamUnbound(@GenericMaker // expected-error {{unable to infer generic parameters for result builder @GenericMaker}} fn: () -> ()) {} func makeParamBound(@GenericMaker fn: () -> ()) {} -func makeParamNestedUnbound(@GenericContainer.Maker // expected-error {{unable to infer generic parameters for result builder 'GenericContainer'}} +func makeParamNestedUnbound(@GenericContainer.Maker // expected-error {{unable to infer generic parameters for result builder @GenericContainer.Maker}} fn: () -> ()) {} func makeParamNestedBound(@GenericContainer.Maker From 6aae02b3aaa5d8c01b4769d19f816e3fc723e4c2 Mon Sep 17 00:00:00 2001 From: Cal Stephens Date: Sun, 4 Jan 2026 08:25:29 -0800 Subject: [PATCH 10/12] 'generic parameters' -> 'generic arguments' --- include/swift/AST/DiagnosticsSema.def | 2 +- include/swift/AST/TypeCheckRequests.h | 4 ++-- test/Constraints/result_builder_diags.swift | 14 +++++++------- test/decl/var/result_builders.swift | 4 ++-- 4 files changed, 12 insertions(+), 12 deletions(-) diff --git a/include/swift/AST/DiagnosticsSema.def b/include/swift/AST/DiagnosticsSema.def index a9bf1b0358c41..517cf8ba44d77 100644 --- a/include/swift/AST/DiagnosticsSema.def +++ b/include/swift/AST/DiagnosticsSema.def @@ -7884,7 +7884,7 @@ NOTE(result_builder_infer_pick_specific, none, "apply result builder %0 (inferred from %select{protocol|dynamic replacement of}1 %2)", (Type, unsigned, DeclName)) ERROR(result_builder_generic_inference_failed, none, - "unable to infer generic parameters for result builder %0", (DeclAttribute)) + "unable to infer generic arguments for result builder %0", (DeclAttribute)) GROUPED_WARNING(result_builder_missing_limited_availability, ResultBuilderMethods, none, "result builder %0 does not implement 'buildLimitedAvailability'; " diff --git a/include/swift/AST/TypeCheckRequests.h b/include/swift/AST/TypeCheckRequests.h index 7512736057d06..9dce96eed9dc2 100644 --- a/include/swift/AST/TypeCheckRequests.h +++ b/include/swift/AST/TypeCheckRequests.h @@ -4161,8 +4161,8 @@ enum class CustomAttrTypeKind { /// have any particularly interesting semantics. GlobalActor, - /// Result builder types can have their generics parameters inferred - /// from the attached declaration. + /// Result builder types can have their generics arguments + /// inferred from the attached declaration. ResultBuilder, }; diff --git a/test/Constraints/result_builder_diags.swift b/test/Constraints/result_builder_diags.swift index 012d860554d72..cd2e3b1d76be0 100644 --- a/test/Constraints/result_builder_diags.swift +++ b/test/Constraints/result_builder_diags.swift @@ -1119,7 +1119,7 @@ func testInferResultBuilderGenerics() { print(makeArray()) } - @SimpleArrayBuilder // expected-error{{unable to infer generic parameters for result builder @SimpleArrayBuilder}} + @SimpleArrayBuilder // expected-error{{unable to infer generic arguments for result builder @SimpleArrayBuilder}} var string: String { "foo" } @@ -1167,7 +1167,7 @@ func testInferResultBuilderGenerics() { } } - @TooManyArgsBuilder // expected-error {{unable to infer generic parameters for result builder @TooManyArgsBuilder}} + @TooManyArgsBuilder // expected-error {{unable to infer generic arguments for result builder @TooManyArgsBuilder}} var invalidTupleArray: [(String, Int)] { ("foo", 1) // expected-warning {{expression of type '(String, Int)' is unused}} ("bar", 2) // expected-warning {{expression of type '(String, Int)' is unused}} @@ -1235,7 +1235,7 @@ func testInferResultBuilderGenerics() { "d" } - @CollectionBuilder // expected-error {{unable to infer generic parameters for result builder @CollectionBuilder}} + @CollectionBuilder // expected-error {{unable to infer generic arguments for result builder @CollectionBuilder}} var contiguousArray: ContiguousArray { "c" // expected-warning {{string literal is unused}} "d" // expected-warning {{string literal is unused}} @@ -1255,7 +1255,7 @@ extension Array { self = build3() } - init(@SimpleArrayBuilder build3: () -> ContiguousArray) { // expected-error {{unable to infer generic parameters for result builder @SimpleArrayBuilder}} + init(@SimpleArrayBuilder build3: () -> ContiguousArray) { // expected-error {{unable to infer generic arguments for result builder @SimpleArrayBuilder}} self = Array(build3()) } } @@ -1269,7 +1269,7 @@ extension Set { self = Set(build2()) } - init(@SimpleArrayBuilder build3: () -> Self) { // expected-error {{unable to infer generic parameters for result builder @SimpleArrayBuilder}} + init(@SimpleArrayBuilder build3: () -> Self) { // expected-error {{unable to infer generic arguments for result builder @SimpleArrayBuilder}} self = build3() } } @@ -1299,7 +1299,7 @@ func testNonGenericResultBuildersInGenericTypeExtensions() { "bar" } - @Array.Builder // expected-error {{unable to infer generic parameters for result builder @Array.Builder}} + @Array.Builder // expected-error {{unable to infer generic arguments for result builder @Array.Builder}} var set: Set { "foo" // expected-warning {{string literal is unused}} "bar" // expected-warning {{string literal is unused}} @@ -1311,7 +1311,7 @@ func testNonGenericResultBuildersInGenericTypeExtensions() { ("baaz", "quux") } - @Dictionary.Builder // expected-error {{unable to infer generic parameters for result builder @Dictionary.Builder}} + @Dictionary.Builder // expected-error {{unable to infer generic arguments for result builder @Dictionary.Builder}} var dictionary2: [(String, String)] { ("foo", "bar") // expected-warning {{expression of type '(String, String)' is unused}} ("baaz", "quux") // expected-warning {{expression of type '(String, String)' is unused}} diff --git a/test/decl/var/result_builders.swift b/test/decl/var/result_builders.swift index facd5affa796d..60b7f6e1a3476 100644 --- a/test/decl/var/result_builders.swift +++ b/test/decl/var/result_builders.swift @@ -72,13 +72,13 @@ struct GenericContainer { struct Maker {} // expected-error {{result builder must provide at least one static 'buildBlock' method}} } -func makeParamUnbound(@GenericMaker // expected-error {{unable to infer generic parameters for result builder @GenericMaker}} +func makeParamUnbound(@GenericMaker // expected-error {{unable to infer generic arguments for result builder @GenericMaker}} fn: () -> ()) {} func makeParamBound(@GenericMaker fn: () -> ()) {} -func makeParamNestedUnbound(@GenericContainer.Maker // expected-error {{unable to infer generic parameters for result builder @GenericContainer.Maker}} +func makeParamNestedUnbound(@GenericContainer.Maker // expected-error {{unable to infer generic arguments for result builder @GenericContainer.Maker}} fn: () -> ()) {} func makeParamNestedBound(@GenericContainer.Maker From 7f637caae66a40b641e4fc5fb2e87a41b131c4d0 Mon Sep 17 00:00:00 2001 From: Cal Stephens Date: Fri, 13 Mar 2026 06:51:48 -0700 Subject: [PATCH 11/12] Support placeholder types --- lib/Sema/TypeCheckType.cpp | 113 +++++++++++++++++--- test/Constraints/result_builder_diags.swift | 109 +++++++++++++++++++ 2 files changed, 208 insertions(+), 14 deletions(-) diff --git a/lib/Sema/TypeCheckType.cpp b/lib/Sema/TypeCheckType.cpp index 07a21f96e418a..b304dadcd9b15 100644 --- a/lib/Sema/TypeCheckType.cpp +++ b/lib/Sema/TypeCheckType.cpp @@ -7267,6 +7267,57 @@ struct ResultBuilderUnboundTypeOpener { CustomAttr *attr; Type operator()(UnboundGenericType *unboundTy) const { + return open(unboundTy, /*genericArgs=*/{}); + } + + // Opens a partially-bound result builder type by evaluating any + // placeholder types in the generic signature + Type openPlaceholders(Type type) const { + auto &ctx = dc->getASTContext(); + + if (!type->hasPlaceholder()) { + return type; + } + + // @Builder<_>: placeholder is in the type's own generic args + if (auto *boundGeneric = type->getAs()) { + if (auto *nominalDecl = + dyn_cast(boundGeneric->getDecl())) { + auto *unboundTy = UnboundGenericType::get( + nominalDecl, boundGeneric->getParent(), ctx); + return open(unboundTy, boundGeneric->getGenericArgs()); + } + } + + // @Array<_>.Builder: placeholder is in the parent type + else if (auto *nominalType = type->getAs()) { + if (auto *parentBoundGeneric = + nominalType->getParent()->getAs()) { + if (auto *parentNominalDecl = + dyn_cast(parentBoundGeneric->getDecl())) { + auto *unboundParent = UnboundGenericType::get( + parentNominalDecl, parentBoundGeneric->getParent(), ctx); + auto solvedParent = + open(unboundParent, parentBoundGeneric->getGenericArgs()); + + if (solvedParent->hasError()) { + return solvedParent; + } + + return NominalType::get(nominalType->getDecl(), solvedParent, ctx); + } + } + } + return type; + } + + /// Opens the given result builder type by solving its generic parameters + /// against the owning declaration's return type. + /// + /// If `genericArgs` is provided, any non-placeholder type remains + /// constrained to that type, and only the placeholders are solved. + Type open(UnboundGenericType *unboundTy, + ArrayRef genericArgs = {}) const { auto resultBuilderDecl = attr->getNominalDecl(); if (!resultBuilderDecl) { return invalidResultBuilderType(); @@ -7274,7 +7325,7 @@ struct ResultBuilderUnboundTypeOpener { auto unboundTyDecl = dyn_cast_or_null(unboundTy->getDecl()); - if (!resultBuilderDecl) { + if (!unboundTyDecl) { return invalidResultBuilderType(); } @@ -7302,19 +7353,37 @@ struct ResultBuilderUnboundTypeOpener { ConstraintSystem cs(dc, options); - // Create a type variable for each of the result builder's generic params + // For each generic parameter position, either pin the fixed argument + // (when genericArgs supplies a non-placeholder at that index) or create a + // type variable to be solved. llvm::SmallVector typeVarReplacements; - llvm::SmallVector typeVars; for (unsigned i = 0; i < genericSig.getGenericParams().size(); ++i) { - auto locator = cs.getConstraintLocator( - unboundTyDecl, {ConstraintLocator::GenericArgument, i}); - auto typeVar = cs.createTypeVariable(locator, TVO_CanBindToHole); - typeVarReplacements.push_back(typeVar); - typeVars.push_back(typeVar); + if (i < genericArgs.size() && !genericArgs[i]->hasPlaceholder()) { + typeVarReplacements.push_back(genericArgs[i]); + } else { + auto locator = cs.getConstraintLocator( + unboundTyDecl, {ConstraintLocator::GenericArgument, i}); + auto makeTypeVar = [&] { + return Type(cs.createTypeVariable(locator, TVO_CanBindToHole)); + }; + + if (i >= genericArgs.size()) { + typeVarReplacements.push_back(makeTypeVar()); + } else { + typeVarReplacements.push_back( + genericArgs[i].transformRec([&](Type t) -> std::optional { + if (t->is()) { + return makeTypeVar(); + } else { + return std::nullopt; + } + })); + } + } } // Replace any references to the result builder's generic params - // in the result type with the corresponding type variables. + // in the result type with the corresponding replacements. auto subMap = SubstitutionMap::get(genericSig, typeVarReplacements, LookUpConformanceInModule()); @@ -7328,12 +7397,19 @@ struct ResultBuilderUnboundTypeOpener { auto solution = cs.solveSingle(); - // If a solution exists, bind the result builder's generic params to the - // solved types. + // If a solution exists, build the final replacement list by substituting + // all type variables with their solved bindings. if (solution) { llvm::SmallVector solvedReplacements; - for (auto typeVar : typeVars) { - solvedReplacements.push_back(solution->typeBindings[typeVar]); + for (auto replacement : typeVarReplacements) { + solvedReplacements.push_back( + replacement.transformRec([&](Type t) -> std::optional { + if (auto *typeVar = t->getAs()) { + return solution->typeBindings[typeVar]; + } else { + return std::nullopt; + } + })); } return BoundGenericType::get(unboundTyDecl, unboundTy->getParent(), @@ -7431,6 +7507,7 @@ Type CustomAttrTypeRequest::evaluate(Evaluator &eval, CustomAttr *attr, const TypeResolutionOptions options(TypeResolverContext::PatternBindingDecl); OpenUnboundGenericTypeFn unboundTyOpener = nullptr; + HandlePlaceholderTypeReprFn placeholderHandler = nullptr; // Property wrappers and result builders allow their type to be an unbound // generic. @@ -7441,11 +7518,19 @@ Type CustomAttrTypeRequest::evaluate(Evaluator &eval, CustomAttr *attr, void *mem = ctx.Allocate(sizeof(ResultBuilderUnboundTypeOpener), alignof(ResultBuilderUnboundTypeOpener)); unboundTyOpener = *new (mem) ResultBuilderUnboundTypeOpener{dc, attr}; + placeholderHandler = PlaceholderType::get; } auto type = TypeResolution::resolveContextualType( attr->getTypeRepr(), dc, options, unboundTyOpener, - /*placeholderHandler*/ nullptr, /*packElementOpener*/ nullptr); + /*placeholderHandler*/ placeholderHandler, + /*packElementOpener*/ nullptr); + + // Open any pl + if (typeKind == CustomAttrTypeKind::ResultBuilder && type->hasPlaceholder()) { + ResultBuilderUnboundTypeOpener opener{dc, attr}; + type = opener.openPlaceholders(type); + } // We always require the type to resolve to a nominal type. If the type was // not a nominal type, we should have already diagnosed an error via diff --git a/test/Constraints/result_builder_diags.swift b/test/Constraints/result_builder_diags.swift index cd2e3b1d76be0..d6fa00ae45dc1 100644 --- a/test/Constraints/result_builder_diags.swift +++ b/test/Constraints/result_builder_diags.swift @@ -1087,6 +1087,17 @@ enum SimpleArrayBuilder { } } +@resultBuilder +enum SimpleDictionaryBuilder { + static func buildBlock(_ elements: (Key, Value)...) -> [Key: Value] { + var dict = [Key: Value]() + for element in elements { + dict[element.0] = element.1 + } + return dict + } +} + @resultBuilder enum CollectionBuilder { static func buildBlock(_ component: Element...) -> [Element] { @@ -1124,6 +1135,67 @@ func testInferResultBuilderGenerics() { "foo" } + @SimpleArrayBuilder + var elements: [String] { + "foo" + "bar" + } + + @SimpleArrayBuilder<(_, _)> + var elements2: [(String, Int)] { + ("foo", 42) + } + + @SimpleArrayBuilder<(String, _)> + var elements3: [(String, Int)] { + ("foo", 42) + } + + @SimpleArrayBuilder<(_, Int)> + var elements4: [(String, Int)] { + ("foo", 42) + } + + @SimpleArrayBuilder<(Int, _)> // expected-error {{unable to infer generic arguments for result builder @SimpleArrayBuilder<(Int, _)>}} expeced-error {{cannot convert return expression of type '(String, Int)' to return type '[(String, Int)]'}} + var elements5: [(String, Int)] { + ("foo", 42) // expected-error {{cannot convert return expression of type '(String, Int)' to return type '[(String, Int)]'}} + } + + @SimpleArrayBuilder<(_, String)> + var elements6: [(String, Int)] { // expected-error {{cannot convert return expression of type '[(String, String)]' to return type '[(String, Int)]'}} expected-note {{arguments to generic parameter 'Element' ('(String, String)' and '(String, Int)') are expected to be equal}} + ("foo", 42) // expected-error {{cannot convert value of type '(String, Int)' to expected argument type '(String, String)'}} + } + + @SimpleArrayBuilder<(Int, String)> + var elements7: [(String, Int)] { // expected-error {{cannot convert return expression of type '[(Int, String)]' to return type '[(String, Int)]'}} expected-note {{arguments to generic parameter 'Element' ('(Int, String)' and '(String, Int)') are expected to be equal}} + ("foo", 42) // expected-error {{cannot convert value of type '(String, Int)' to expected argument type '(Int, String)'}} + } + + @SimpleDictionaryBuilder + var dictionary: [String: Int] { + ("foo", 42) + } + + @SimpleDictionaryBuilder<_, _> + var dictionary2: [String: Int] { + ("foo", 42) + } + + @SimpleDictionaryBuilder + var dictionary3: [String: Int] { + ("foo", 42) + } + + @SimpleDictionaryBuilder<_, Int> + var dictionary4: [String: Int] { + ("foo", 42) + } + + @SimpleDictionaryBuilder + var dictionary5: [String: Int] { // expected-error {{cannot convert return expression of type '[Int : Int]' to return type '[String : Int]'}} expected-note {{arguments to generic parameter 'Key' ('Int' and 'String') are expected to be equal}} + ("foo", 42) // expected-error {{cannot convert value of type '(String, Int)' to expected argument type '(Int, Int)'}} + } + struct MyValue { @SimpleArrayBuilder var array1: [String] @SimpleArrayBuilder var array2: () -> [String] @@ -1316,4 +1388,41 @@ func testNonGenericResultBuildersInGenericTypeExtensions() { ("foo", "bar") // expected-warning {{expression of type '(String, String)' is unused}} ("baaz", "quux") // expected-warning {{expression of type '(String, String)' is unused}} } + + @Array<_>.Builder + var elements2: [String] { + "foo" + "bar" + } + + @Array.Builder + var elements3: [String] { + "foo" + "bar" + } + + @Array<(_, _)>.Builder + var elements4: [(String, Int)] { + ("foo", 42) + } + + @Array<(String, _)>.Builder + var elements5: [(String, Int)] { + ("foo", 42) + } + + @Array<(_, Int)>.Builder + var elements6: [(String, Int)] { + ("foo", 42) + } + + @Array<(Int, _)>.Builder // expected-error {{unable to infer generic arguments for result builder @Array<(Int, _)>.Builder}} + var elements7: [(String, Int)] { + ("foo", 42) // expected-error {{cannot convert return expression of type '(String, Int)' to return type '[(String, Int)]'}} + } + + @Array<(Int, String)>.Builder + var elements8: [(String, Int)] { // expected-error {{cannot convert return expression of type '[(Int, String)]' to return type '[(String, Int)]'}} expected-note {{arguments to generic parameter 'Element' ('(Int, String)' and '(String, Int)') are expected to be equal}} + ("foo", 42) // expected-error {{cannot convert value of type '(String, Int)' to expected argument type '(Int, String)'}} + } } From 76cd5fd7879e370822b80eba607a95e80e21df6a Mon Sep 17 00:00:00 2001 From: Cal Stephens Date: Fri, 13 Mar 2026 06:59:40 -0700 Subject: [PATCH 12/12] Clean up --- include/swift/Sema/ConstraintSystem.h | 5 +---- lib/Sema/TypeCheckType.cpp | 1 + 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/include/swift/Sema/ConstraintSystem.h b/include/swift/Sema/ConstraintSystem.h index a527da54485bf..edc7bffb5c893 100644 --- a/include/swift/Sema/ConstraintSystem.h +++ b/include/swift/Sema/ConstraintSystem.h @@ -607,11 +607,8 @@ enum class ConstraintSystemFlags { /// Disable macro expansions. DisableMacroExpansions = 0x40, - /// Enable old type-checker performance hacks. - EnablePerformanceHacks = 0x80, - /// Don't record a failed constraint after adding an unsolvable constraint. - DisableRecordFailedConstraint = 0x100, + DisableRecordFailedConstraint = 0x80, }; /// Options that affect the constraint system as a whole. diff --git a/lib/Sema/TypeCheckType.cpp b/lib/Sema/TypeCheckType.cpp index b304dadcd9b15..95af241ddfca9 100644 --- a/lib/Sema/TypeCheckType.cpp +++ b/lib/Sema/TypeCheckType.cpp @@ -58,6 +58,7 @@ #include "swift/ClangImporter/ClangImporter.h" #include "swift/Parse/Lexer.h" #include "swift/Sema/SILTypeResolutionContext.h" +#include "swift/Sema/TypeVariableType.h" #include "swift/Strings.h" #include "swift/Subsystems.h" #include "clang/AST/ASTContext.h"