From 61fe8a9b8ea83be605b367c092688b68d67f0f97 Mon Sep 17 00:00:00 2001 From: Kavon Farvardin Date: Tue, 16 Sep 2025 13:18:08 -0700 Subject: [PATCH] introduce @_manualOwnership performance attribute This attribute forces programmers to acknowledge every copy that is required to happen in the body of the function. Only those copies that make sense according to Swift's ownership rules should be "required". The way this is implemented as of now is to flag each non-explicit copy in a function, coming from SILGen, as an error through PerformanceDiagnostics. --- .../Sources/SIL/Function.swift | 2 + include/swift/AST/DeclAttr.def | 5 +- include/swift/AST/DiagnosticsSIL.def | 2 + include/swift/AST/DiagnosticsSema.def | 4 + include/swift/Basic/Features.def | 3 + include/swift/SIL/SILBridging.h | 3 +- include/swift/SIL/SILBuilder.h | 8 + include/swift/SIL/SILCloner.h | 19 +++ include/swift/SIL/SILFunction.h | 3 +- lib/AST/ASTDumper.cpp | 1 + lib/AST/FeatureSet.cpp | 1 + lib/ASTGen/Sources/ASTGen/DeclAttrs.swift | 1 + lib/SIL/IR/SILFunctionBuilder.cpp | 2 + lib/SIL/IR/SILPrinter.cpp | 1 + lib/SIL/Parser/ParseSIL.cpp | 2 + lib/SIL/Utils/SILBridging.cpp | 1 + .../Mandatory/PerformanceDiagnostics.cpp | 65 ++++++++ lib/Sema/MiscDiagnostics.cpp | 5 + lib/Sema/TypeCheckAttr.cpp | 8 + lib/Sema/TypeCheckDeclOverride.cpp | 1 + test/SIL/manual_ownership.swift | 154 ++++++++++++++++++ 21 files changed, 288 insertions(+), 3 deletions(-) create mode 100644 test/SIL/manual_ownership.swift diff --git a/SwiftCompilerSources/Sources/SIL/Function.swift b/SwiftCompilerSources/Sources/SIL/Function.swift index 4c316f5f55f1e..41a78b616872f 100644 --- a/SwiftCompilerSources/Sources/SIL/Function.swift +++ b/SwiftCompilerSources/Sources/SIL/Function.swift @@ -293,6 +293,7 @@ final public class Function : CustomStringConvertible, HasShortDescription, Hash case noRuntime case noExistentials case noObjCRuntime + case manualOwnership } public var performanceConstraints: PerformanceConstraints { @@ -303,6 +304,7 @@ final public class Function : CustomStringConvertible, HasShortDescription, Hash case .NoRuntime: return .noRuntime case .NoExistentials: return .noExistentials case .NoObjCBridging: return .noObjCRuntime + case .ManualOwnership: return .manualOwnership default: fatalError("unknown performance constraint") } } diff --git a/include/swift/AST/DeclAttr.def b/include/swift/AST/DeclAttr.def index 8da6f1541ac01..734e0ec0fdfc8 100644 --- a/include/swift/AST/DeclAttr.def +++ b/include/swift/AST/DeclAttr.def @@ -815,7 +815,10 @@ SIMPLE_DECL_ATTR(_noObjCBridging, NoObjCBridging, UserInaccessible | ABIStableToAdd | ABIStableToRemove | APIStableToAdd | APIStableToRemove | ForbiddenInABIAttr, 155) -// Unused '156': Used to be `_distributedThunkTarget` but completed implementation in Swift 6.0 does not need it after all +SIMPLE_DECL_ATTR(_manualOwnership, ManualOwnership, + OnAbstractFunction | OnSubscript, + UserInaccessible | ABIStableToAdd | ABIStableToRemove | APIStableToAdd | APIStableToRemove | ForbiddenInABIAttr, + 156) DECL_ATTR(_allowFeatureSuppression, AllowFeatureSuppression, OnAnyDecl, diff --git a/include/swift/AST/DiagnosticsSIL.def b/include/swift/AST/DiagnosticsSIL.def index 0ffdcf6508ef6..b31fe07a8e030 100644 --- a/include/swift/AST/DiagnosticsSIL.def +++ b/include/swift/AST/DiagnosticsSIL.def @@ -428,6 +428,8 @@ ERROR(wrong_linkage_for_serialized_function,none, "function has wrong linkage to be called from %0", (StringRef)) NOTE(performance_called_from,none, "called from here", ()) +ERROR(manualownership_copy,none, + "explicit 'copy' required here", ()) // 'transparent' diagnostics ERROR(circular_transparent,none, diff --git a/include/swift/AST/DiagnosticsSema.def b/include/swift/AST/DiagnosticsSema.def index 19d39a86bec97..0b1dfd6ba8da1 100644 --- a/include/swift/AST/DiagnosticsSema.def +++ b/include/swift/AST/DiagnosticsSema.def @@ -2145,6 +2145,10 @@ ERROR(attr_static_exclusive_only_mutating,none, ERROR(attr_static_exclusive_no_setters,none, "variable of type %0 must not have a setter", (Type)) +// @_manualOwnership +ERROR(attr_manual_ownership_experimental,none, + "'@_manualOwnership' requires '-enable-experimental-feature ManualOwnership'", ()) + // @extractConstantsFromMembers ERROR(attr_extractConstantsFromMembers_experimental,none, "'@extractConstantsFromMembers' requires " diff --git a/include/swift/Basic/Features.def b/include/swift/Basic/Features.def index 5c3e165eaf30d..f71e9df63d222 100644 --- a/include/swift/Basic/Features.def +++ b/include/swift/Basic/Features.def @@ -447,6 +447,9 @@ EXPERIMENTAL_FEATURE(LifetimeDependence, true) /// Enable the `@_staticExclusiveOnly` attribute. EXPERIMENTAL_FEATURE(StaticExclusiveOnly, true) +/// Enable the `@_manualOwnership` attribute. +EXPERIMENTAL_FEATURE(ManualOwnership, false) + /// Enable the @extractConstantsFromMembers attribute. EXPERIMENTAL_FEATURE(ExtractConstantsFromMembers, false) diff --git a/include/swift/SIL/SILBridging.h b/include/swift/SIL/SILBridging.h index aa3eaca5e51f3..6b7a10a6a09e1 100644 --- a/include/swift/SIL/SILBridging.h +++ b/include/swift/SIL/SILBridging.h @@ -463,7 +463,8 @@ struct BridgedFunction { NoLocks = 2, NoRuntime = 3, NoExistentials = 4, - NoObjCBridging = 5 + NoObjCBridging = 5, + ManualOwnership = 6, }; enum class InlineStrategy { diff --git a/include/swift/SIL/SILBuilder.h b/include/swift/SIL/SILBuilder.h index 01fc361410a07..aa928be7301aa 100644 --- a/include/swift/SIL/SILBuilder.h +++ b/include/swift/SIL/SILBuilder.h @@ -280,6 +280,14 @@ class SILBuilder { return false; } + /// If we have a SILFunction, return true if it has a ManualOwnership + /// PerformanceConstraint, which corresponds to an attribute on the FuncDecl. + bool hasManualOwnershipAttr() const { + if (F) + return F->getPerfConstraints() == PerformanceConstraints::ManualOwnership; + return false; + } + //===--------------------------------------------------------------------===// // Insertion Point Management //===--------------------------------------------------------------------===// diff --git a/include/swift/SIL/SILCloner.h b/include/swift/SIL/SILCloner.h index 78fb5a264eb1f..6a396047c2e95 100644 --- a/include/swift/SIL/SILCloner.h +++ b/include/swift/SIL/SILCloner.h @@ -1698,6 +1698,16 @@ template void SILCloner::visitCopyAddrInst(CopyAddrInst *Inst) { getBuilder().setCurrentDebugScope(getOpScope(Inst->getDebugScope())); + // When cloning into a function using manual ownership, convert to explicit + // copies, in order to preserve the local nature of the perf constraint. + if (getBuilder().hasManualOwnershipAttr() && getBuilder().hasOwnership()) { + recordClonedInstruction( + Inst, getBuilder().createExplicitCopyAddr( + getOpLocation(Inst->getLoc()), getOpValue(Inst->getSrc()), + getOpValue(Inst->getDest()), Inst->isTakeOfSrc(), + Inst->isInitializationOfDest())); + return; + } recordClonedInstruction( Inst, getBuilder().createCopyAddr( getOpLocation(Inst->getLoc()), getOpValue(Inst->getSrc()), @@ -2092,6 +2102,15 @@ void SILCloner::visitCopyValueInst(CopyValueInst *Inst) { return recordFoldedValue(Inst, newValue); } + // When cloning into a function using manual ownership, convert to explicit + // copies, in order to preserve the local nature of the perf constraint. + if (getBuilder().hasManualOwnershipAttr() && getBuilder().hasOwnership()) { + recordClonedInstruction( + Inst, getBuilder().createExplicitCopyValue(getOpLocation(Inst->getLoc()), + getOpValue(Inst->getOperand()))); + return; + } + recordClonedInstruction( Inst, getBuilder().createCopyValue(getOpLocation(Inst->getLoc()), getOpValue(Inst->getOperand()))); diff --git a/include/swift/SIL/SILFunction.h b/include/swift/SIL/SILFunction.h index c1ed6a24b16ad..d85bf7d68d649 100644 --- a/include/swift/SIL/SILFunction.h +++ b/include/swift/SIL/SILFunction.h @@ -93,7 +93,8 @@ enum class PerformanceConstraints : uint8_t { NoLocks = 2, NoRuntime = 3, NoExistentials = 4, - NoObjCBridging = 5 + NoObjCBridging = 5, + ManualOwnership = 6, }; class SILSpecializeAttr final { diff --git a/lib/AST/ASTDumper.cpp b/lib/AST/ASTDumper.cpp index 0ed2c08a21fe5..6852a74275c66 100644 --- a/lib/AST/ASTDumper.cpp +++ b/lib/AST/ASTDumper.cpp @@ -5030,6 +5030,7 @@ class PrintAttribute : public AttributeVisitor, TRIVIAL_ATTR_PRINTER(NoLocks, no_locks) TRIVIAL_ATTR_PRINTER(NoMetadata, no_metadata) TRIVIAL_ATTR_PRINTER(NoObjCBridging, no_objc_bridging) + TRIVIAL_ATTR_PRINTER(ManualOwnership, manual_ownership) TRIVIAL_ATTR_PRINTER(NoRuntime, no_runtime) TRIVIAL_ATTR_PRINTER(NonEphemeral, non_ephemeral) TRIVIAL_ATTR_PRINTER(NonEscapable, non_escapable) diff --git a/lib/AST/FeatureSet.cpp b/lib/AST/FeatureSet.cpp index 382453371d536..87902abb0612b 100644 --- a/lib/AST/FeatureSet.cpp +++ b/lib/AST/FeatureSet.cpp @@ -138,6 +138,7 @@ static bool usesFeatureInlineArrayTypeSugar(Decl *D) { } UNINTERESTING_FEATURE(StaticExclusiveOnly) +UNINTERESTING_FEATURE(ManualOwnership) UNINTERESTING_FEATURE(ExtractConstantsFromMembers) UNINTERESTING_FEATURE(GroupActorErrors) UNINTERESTING_FEATURE(SameElementRequirements) diff --git a/lib/ASTGen/Sources/ASTGen/DeclAttrs.swift b/lib/ASTGen/Sources/ASTGen/DeclAttrs.swift index f6a5a96aed226..50b26985daf02 100644 --- a/lib/ASTGen/Sources/ASTGen/DeclAttrs.swift +++ b/lib/ASTGen/Sources/ASTGen/DeclAttrs.swift @@ -265,6 +265,7 @@ extension ASTGenVisitor { .LexicalLifetimes, .LLDBDebuggerFunction, .MainType, + .ManualOwnership, .Marker, .MoveOnly, .NeverEmitIntoClient, diff --git a/lib/SIL/IR/SILFunctionBuilder.cpp b/lib/SIL/IR/SILFunctionBuilder.cpp index 70ef8c782b3a1..274077a57487e 100644 --- a/lib/SIL/IR/SILFunctionBuilder.cpp +++ b/lib/SIL/IR/SILFunctionBuilder.cpp @@ -203,6 +203,8 @@ void SILFunctionBuilder::addFunctionAttributes( F->setPerfConstraints(PerformanceConstraints::NoExistentials); } else if (Attrs.hasAttribute()) { F->setPerfConstraints(PerformanceConstraints::NoObjCBridging); + } else if (Attrs.hasAttribute()) { + F->setPerfConstraints(PerformanceConstraints::ManualOwnership); } if (Attrs.hasAttribute()) { diff --git a/lib/SIL/IR/SILPrinter.cpp b/lib/SIL/IR/SILPrinter.cpp index 835cb7223e6e0..37e1b5b7fe019 100644 --- a/lib/SIL/IR/SILPrinter.cpp +++ b/lib/SIL/IR/SILPrinter.cpp @@ -3658,6 +3658,7 @@ void SILFunction::print(SILPrintContext &PrintCtx) const { case PerformanceConstraints::NoRuntime: OS << "[no_runtime] "; break; case PerformanceConstraints::NoExistentials: OS << "[no_existentials] "; break; case PerformanceConstraints::NoObjCBridging: OS << "[no_objc_bridging] "; break; + case PerformanceConstraints::ManualOwnership: OS << "[manual_ownership] "; break; } if (isPerformanceConstraint()) diff --git a/lib/SIL/Parser/ParseSIL.cpp b/lib/SIL/Parser/ParseSIL.cpp index ca36191ae4a79..d54c4f9175337 100644 --- a/lib/SIL/Parser/ParseSIL.cpp +++ b/lib/SIL/Parser/ParseSIL.cpp @@ -783,6 +783,8 @@ static bool parseDeclSILOptional( *perfConstraints = PerformanceConstraints::NoExistentials; else if (perfConstraints && SP.P.Tok.getText() == "no_objc_bridging") *perfConstraints = PerformanceConstraints::NoObjCBridging; + else if (perfConstraints && SP.P.Tok.getText() == "manual_ownership") + *perfConstraints = PerformanceConstraints::ManualOwnership; else if (isPerformanceConstraint && SP.P.Tok.getText() == "perf_constraint") *isPerformanceConstraint = true; else if (markedAsUsed && SP.P.Tok.getText() == "used") diff --git a/lib/SIL/Utils/SILBridging.cpp b/lib/SIL/Utils/SILBridging.cpp index 23f463cad8569..0d3ad956fe6f3 100644 --- a/lib/SIL/Utils/SILBridging.cpp +++ b/lib/SIL/Utils/SILBridging.cpp @@ -186,6 +186,7 @@ static_assert((int)BridgedFunction::PerformanceConstraints::NoLocks == (int)swif static_assert((int)BridgedFunction::PerformanceConstraints::NoRuntime == (int)swift::PerformanceConstraints::NoRuntime); static_assert((int)BridgedFunction::PerformanceConstraints::NoExistentials == (int)swift::PerformanceConstraints::NoExistentials); static_assert((int)BridgedFunction::PerformanceConstraints::NoObjCBridging == (int)swift::PerformanceConstraints::NoObjCBridging); +static_assert((int)BridgedFunction::PerformanceConstraints::ManualOwnership == (int)swift::PerformanceConstraints::ManualOwnership); static_assert((int)BridgedFunction::InlineStrategy::InlineDefault == (int)swift::InlineDefault); static_assert((int)BridgedFunction::InlineStrategy::NoInline == (int)swift::NoInline); diff --git a/lib/SILOptimizer/Mandatory/PerformanceDiagnostics.cpp b/lib/SILOptimizer/Mandatory/PerformanceDiagnostics.cpp index f3cca112462fa..56802e32cb328 100644 --- a/lib/SILOptimizer/Mandatory/PerformanceDiagnostics.cpp +++ b/lib/SILOptimizer/Mandatory/PerformanceDiagnostics.cpp @@ -293,6 +293,11 @@ bool PerformanceDiagnostics::visitCallee(SILInstruction *callInst, CalleeList callees, PerformanceConstraints perfConstr, LocWithParent *parentLoc) { + // Manual Ownership is not checked recursively within callees of the function, + // it's a local, non-viral performance annotation. + if (perfConstr == PerformanceConstraints::ManualOwnership) + return false; + LocWithParent asLoc(callInst->getLoc().getSourceLoc(), parentLoc); LocWithParent *loc = &asLoc; if (parentLoc && asLoc.loc == callInst->getFunction()->getLocation().getSourceLoc()) @@ -370,6 +375,66 @@ bool PerformanceDiagnostics::visitInst(SILInstruction *inst, RuntimeEffect impact = getRuntimeEffect(inst, impactType); LocWithParent loc(inst->getLoc().getSourceLoc(), parentLoc); + if (perfConstr == PerformanceConstraints::ManualOwnership) { + if (impact == RuntimeEffect::RefCounting) { + bool shouldDiagnose = false; + switch (inst->getKind()) { + case SILInstructionKind::ExplicitCopyAddrInst: + case SILInstructionKind::ExplicitCopyValueInst: + break; + case SILInstructionKind::LoadInst: { + // FIXME: we don't have an `explicit_load` and transparent functions can + // end up bringing in a `load [copy]`. A better approach is needed to + // handle transparent functions, in general, as rewriting them during + // inlining of transparent functions is also not so great, as it + // may hide a copy that semantically is there, but we happened to + // tuck away in a transparent synthesized function, like those + // synthesized getters! + // + // For now look to see if the load copy's only non-destroying users are + // explicit_copy's, as that would indicate the user has acknowledged it: + // + // %y = load [copy] %x + // %z = explicit_copy_value %y + // destroy_value %y + // + // In all other cases, it's safer to diagnose. + // + auto load = cast(inst); + if (load->getOwnershipQualifier() != LoadOwnershipQualifier::Copy) + break; + + for (auto *use : load->getUsers()) { + if (!isa(use)) { + shouldDiagnose = true; + break; + } + } + break; + } + default: + shouldDiagnose = true; + break; + } + if (shouldDiagnose) { + diagnose(loc, diag::manualownership_copy); + llvm::dbgs() << "function " << inst->getFunction()->getName(); + llvm::dbgs() << "\n has unexpected copying instruction: "; + inst->dump(); + return false; // Don't bail-out early; diagnose more issues in the func. + } + } else if (impact == RuntimeEffect::ExclusivityChecking) { + // TODO: diagnose only the nested exclusivity; perhaps as a warning + // since we don't yet have reference bindings? + + // diagnose(loc, diag::performance_arc); + // inst->dump(); + // return true; + } + return false; + } + if (perfConstr == PerformanceConstraints::NoExistentials && ((impact & RuntimeEffect::Existential) || (impact & RuntimeEffect::ExistentialClassBound))) { diff --git a/lib/Sema/MiscDiagnostics.cpp b/lib/Sema/MiscDiagnostics.cpp index 0418ad790ca95..bddbea73ee361 100644 --- a/lib/Sema/MiscDiagnostics.cpp +++ b/lib/Sema/MiscDiagnostics.cpp @@ -475,6 +475,11 @@ static void diagSyntacticUseRestrictions(const Expr *E, const DeclContext *DC, diag::copy_expression_cannot_be_used_with_noncopyable_types); } + /// FIXME: there really is no reason for the restriction on copy not being + /// permitted on fields, other than needing tests to ensure it works. + if (Ctx.LangOpts.hasFeature(ManualOwnership)) + return; + // We only allow for copy_expr to be applied directly to lvalues. We do // not allow currently for it to be applied to fields. auto *subExpr = copyExpr->getSubExpr(); diff --git a/lib/Sema/TypeCheckAttr.cpp b/lib/Sema/TypeCheckAttr.cpp index b0580b3fe7be3..7099f26ae5e02 100644 --- a/lib/Sema/TypeCheckAttr.cpp +++ b/lib/Sema/TypeCheckAttr.cpp @@ -470,6 +470,7 @@ class AttributeChecker : public AttributeVisitor { void visitUnsafeNonEscapableResultAttr(UnsafeNonEscapableResultAttr *attr); void visitStaticExclusiveOnlyAttr(StaticExclusiveOnlyAttr *attr); + void visitManualOwnershipAttr(ManualOwnershipAttr *attr); void visitWeakLinkedAttr(WeakLinkedAttr *attr); void visitSILGenNameAttr(SILGenNameAttr *attr); void visitLifetimeAttr(LifetimeAttr *attr); @@ -8358,6 +8359,13 @@ void AttributeChecker::visitStaticExclusiveOnlyAttr( } } +void AttributeChecker::visitManualOwnershipAttr(ManualOwnershipAttr *attr) { + if (Ctx.LangOpts.hasFeature(Feature::ManualOwnership)) + return; + + diagnoseAndRemoveAttr(attr, diag::attr_manual_ownership_experimental); +} + void AttributeChecker::visitWeakLinkedAttr(WeakLinkedAttr *attr) { if (!Ctx.LangOpts.Target.isOSBinFormatCOFF()) return; diff --git a/lib/Sema/TypeCheckDeclOverride.cpp b/lib/Sema/TypeCheckDeclOverride.cpp index 00a2ba5c657df..1050d396cfa67 100644 --- a/lib/Sema/TypeCheckDeclOverride.cpp +++ b/lib/Sema/TypeCheckDeclOverride.cpp @@ -1621,6 +1621,7 @@ namespace { UNINTERESTING_ATTR(NoRuntime) UNINTERESTING_ATTR(NoExistentials) UNINTERESTING_ATTR(NoObjCBridging) + UNINTERESTING_ATTR(ManualOwnership) UNINTERESTING_ATTR(Inlinable) UNINTERESTING_ATTR(Effects) UNINTERESTING_ATTR(Expose) diff --git a/test/SIL/manual_ownership.swift b/test/SIL/manual_ownership.swift new file mode 100644 index 0000000000000..b4e929542f78d --- /dev/null +++ b/test/SIL/manual_ownership.swift @@ -0,0 +1,154 @@ +// RUN: %target-swift-frontend %s -emit-sil -verify -enable-experimental-feature ManualOwnership + +// REQUIRES: swift_feature_ManualOwnership + +// MARK: types + +public class Whatever { + var a = Pair(x: 0, y: 0) +} + +public struct Pair { + var x: Int + var y: Int + + consuming func midpoint(_ other: borrowing Pair) -> Pair { + return Pair(x: (x + other.x) / 2, y: (y + other.y) / 2) + } +} + +public class Triangle { + var a = Pair(x: 0, y: 0) + var b = Pair(x: 0, y: 1) + var c = Pair(x: 1, y: 1) + + var nontrivial = Whatever() + + consuming func consuming() {} + borrowing func borrowing() {} +} + +/// MARK: return statements + +@_manualOwnership +public func basic_return1() -> Triangle { + let x = Triangle() + return x // expected-error {{explicit 'copy' required here}} +} +@_manualOwnership +public func basic_return1_fixed() -> Triangle { + let x = Triangle() + return copy x +} + + +@_manualOwnership +public func basic_return2(t: Triangle) -> Triangle { + return t // expected-error {{explicit 'copy' required here}} +} +@_manualOwnership +public func basic_return2_fixed(t: Triangle) -> Triangle { + return copy t +} + +@_manualOwnership +public func basic_return3() -> Triangle { + return Triangle() +} + +/// MARK: method calls + +@_manualOwnership +func basic_methods_borrowing(_ t1: Triangle) { + let t2 = Triangle() + t1.borrowing() + t2.borrowing() +} + +@_manualOwnership +func basic_methods_consuming(_ t1: Triangle) { + let t2 = Triangle() + t1.consuming() // expected-error {{explicit 'copy' required here}} + t2.consuming() // expected-error {{explicit 'copy' required here}} +} +@_manualOwnership +func basic_methods_consuming_fixed(_ t1: Triangle) { + let t2 = Triangle() + var t3 = Triangle() + t3 = Triangle() + + (copy t1).consuming() + (copy t2).consuming() + (copy t3).consuming() +} + +func consumingFunc(_ t0: consuming Triangle) {} +func plainFunc(_ t0: Triangle) {} + +@_manualOwnership +func basic_function_call(_ t1: Triangle) { + consumingFunc(t1) // expected-error {{explicit 'copy' required here}} + consumingFunc(copy t1) + plainFunc(t1) +} + +/// MARK: control-flow + + +// FIXME: var's and assignments are a little busted + +// @_manualOwnership +// func reassignments_1() { +// var t3 = Triangle() +// t3 = Triangle() +// t3.borrowing() +// } +// @_manualOwnership +// func ressignments_2() { +// var t3 = Triangle() +// t3 = Triangle() +// t3.consuming() +// } + +@_manualOwnership +public func basic_loop_trivial_values(_ t: Triangle, _ xs: [Triangle]) { + var p: Pair = t.a + for x in xs { // expected-error {{explicit 'copy' required here}} + p = p.midpoint(x.a) + } + t.a = p +} +@_manualOwnership +public func basic_loop_trivial_values_fixed(_ t: Triangle, _ xs: [Triangle]) { + var p: Pair = t.a + for x in copy xs { + p = p.midpoint(x.a) + } + t.a = p +} + +// FIXME: the only reason for so many copies below is because +// `Triangle.nontrivial` only exposes get/set rather than read/modify by default +// +// We should figure out when the coroutine accessors are generated, and ensure +// that when it is available, it is used without copying the result, rather than +// calling the get/set + +@_manualOwnership +public func basic_loop_nontrivial_values(_ t: Triangle, _ xs: [Triangle]) { + var p: Pair = t.nontrivial.a // expected-error {{explicit 'copy' required here}} + for x in xs { // expected-error {{explicit 'copy' required here}} + p = p.midpoint(x.nontrivial.a) // expected-error {{explicit 'copy' required here}} + } + t.nontrivial.a = p // expected-error {{explicit 'copy' required here}} +} + +// FIXME: there should be no copies required in the below, other than what's already written. +@_manualOwnership +public func basic_loop_nontrivial_values_fixed(_ t: Triangle, _ xs: [Triangle]) { + var p: Pair = (copy t.nontrivial).a // expected-error {{explicit 'copy' required here}} + for x in copy xs { + p = p.midpoint((copy x.nontrivial).a) // expected-error {{explicit 'copy' required here}} + } + (copy t.nontrivial).a = p // expected-error {{explicit 'copy' required here}} +}