From e6f1691122369d53090a93347a919a9eed1b6e5a Mon Sep 17 00:00:00 2001 From: Michael Gottesman Date: Mon, 15 May 2023 18:26:33 -0700 Subject: [PATCH 1/5] [copy-operator] Add support for the copy operator in preparation for making consuming and borrowing no implicit copy. Some notes: 1. I implemented this as a contextual keyword that can only apply directly to lvalues. This ensures that we can still call functions called copy, define variables named copy, etc. I added tests for both the c++ and swift-syntax based parsers to validate this. So there shouldn't be any source breaks. 2. I did a little bit of type checker work to ensure that we do not treat copy_expr's result as an lvalue. Otherwise, one could call mutating functions on it or assign to it, which we do not want since the result of copy_value is 3. As expected, by creating a specific expr, I was able to have much greater control of the SILGen codegen and thus eliminate extraneous copies and other weirdness than if we used a function and had to go through SILGenApply. rdar://101862423 --- include/swift/AST/DiagnosticsParse.def | 2 + include/swift/AST/DiagnosticsSema.def | 4 + include/swift/AST/Expr.h | 27 ++ include/swift/AST/ExprNodes.def | 1 + lib/AST/ASTDumper.cpp | 6 + lib/AST/ASTPrinter.cpp | 5 + lib/AST/ASTWalker.cpp | 9 + lib/AST/Expr.cpp | 4 +- lib/Parse/ParseExpr.cpp | 15 + lib/SILGen/SILGenBuilder.cpp | 6 + lib/SILGen/SILGenBuilder.h | 7 + lib/SILGen/SILGenExpr.cpp | 59 +++ lib/SILGen/SILGenLValue.cpp | 26 ++ lib/Sema/CSApply.cpp | 21 ++ lib/Sema/CSGen.cpp | 10 + lib/Sema/MiscDiagnostics.cpp | 26 ++ test/IDE/copy_expr.swift | 7 + test/Parse/copy_expr.swift | 116 ++++++ test/SILGen/copy_expr.swift | 440 ++++++++++++++++++++++ test/Sema/copy_expr.swift | 97 +++++ test/Sema/copy_expr_noimplicit_copy.swift | 11 + 21 files changed, 898 insertions(+), 1 deletion(-) create mode 100644 test/IDE/copy_expr.swift create mode 100644 test/Parse/copy_expr.swift create mode 100644 test/SILGen/copy_expr.swift create mode 100644 test/Sema/copy_expr.swift create mode 100644 test/Sema/copy_expr_noimplicit_copy.swift diff --git a/include/swift/AST/DiagnosticsParse.def b/include/swift/AST/DiagnosticsParse.def index 96d7c48e93f6b..b5eb9bd703a94 100644 --- a/include/swift/AST/DiagnosticsParse.def +++ b/include/swift/AST/DiagnosticsParse.def @@ -1386,6 +1386,8 @@ ERROR(expected_expr_after_await, none, "expected expression after 'await'", ()) ERROR(expected_expr_after_move, none, "expected expression after 'consume'", ()) +ERROR(expected_expr_after_copy, none, + "expected expression after 'copy'", ()) ERROR(expected_expr_after_borrow, none, "expected expression after '_borrow'", ()) diff --git a/include/swift/AST/DiagnosticsSema.def b/include/swift/AST/DiagnosticsSema.def index 0c637ff6aa83a..8553d3e4115ec 100644 --- a/include/swift/AST/DiagnosticsSema.def +++ b/include/swift/AST/DiagnosticsSema.def @@ -6996,6 +6996,10 @@ ERROR(move_expression_not_passed_lvalue,none, "'consume' can only be applied to lvalues", ()) ERROR(borrow_expression_not_passed_lvalue,none, "'borrow' can only be applied to lvalues", ()) +ERROR(copy_expression_not_passed_lvalue,none, + "'copy' can only be applied to lvalues", ()) +ERROR(copy_expression_cannot_be_used_with_noncopyable_types,none, + "'copy' cannot be applied to noncopyable types", ()) ERROR(moveOnly_requires_lexical_lifetimes,none, "noncopyable types require lexical borrow scopes " diff --git a/include/swift/AST/Expr.h b/include/swift/AST/Expr.h index c37adef21bada..23a6850cd5121 100644 --- a/include/swift/AST/Expr.h +++ b/include/swift/AST/Expr.h @@ -2077,6 +2077,33 @@ class MoveExpr final : public IdentityExpr { static bool classof(const Expr *e) { return e->getKind() == ExprKind::Move; } }; +/// CopyExpr - A 'copy' surrounding an lvalue expression marking the lvalue as +/// needing a semantic copy. Used to force a copy of a no implicit copy type. +class CopyExpr final : public Expr { + Expr *SubExpr; + SourceLoc CopyLoc; + +public: + CopyExpr(SourceLoc copyLoc, Expr *sub, Type type = Type(), + bool implicit = false) + : Expr(ExprKind::Copy, implicit, type), SubExpr(sub), CopyLoc(copyLoc) {} + + static CopyExpr *createImplicit(ASTContext &ctx, SourceLoc copyLoc, Expr *sub, + Type type = Type()) { + return new (ctx) CopyExpr(copyLoc, sub, type, /*implicit=*/true); + } + + SourceLoc getLoc() const { return CopyLoc; } + + Expr *getSubExpr() const { return SubExpr; } + void setSubExpr(Expr *E) { SubExpr = E; } + + SourceLoc getStartLoc() const { return CopyLoc; } + SourceLoc getEndLoc() const { return getSubExpr()->getEndLoc(); } + + static bool classof(const Expr *e) { return e->getKind() == ExprKind::Copy; } +}; + /// BorrowExpr - A 'borrow' surrounding an lvalue/accessor expression at an /// apply site marking the lvalue/accessor as being borrowed when passed to the /// callee. diff --git a/include/swift/AST/ExprNodes.def b/include/swift/AST/ExprNodes.def index 1531f42ae97de..c09a2f654f100 100644 --- a/include/swift/AST/ExprNodes.def +++ b/include/swift/AST/ExprNodes.def @@ -111,6 +111,7 @@ ABSTRACT_EXPR(Identity, Expr) EXPR(Borrow, IdentityExpr) EXPR(UnresolvedMemberChainResult, IdentityExpr) EXPR_RANGE(Identity, Paren, UnresolvedMemberChainResult) +EXPR(Copy, Expr) ABSTRACT_EXPR(AnyTry, Expr) EXPR(Try, AnyTryExpr) EXPR(ForceTry, AnyTryExpr) diff --git a/lib/AST/ASTDumper.cpp b/lib/AST/ASTDumper.cpp index 177124bc814be..7461bb31499cd 100644 --- a/lib/AST/ASTDumper.cpp +++ b/lib/AST/ASTDumper.cpp @@ -2200,6 +2200,12 @@ class PrintExpr : public ExprVisitor { printRec(E->getSubExpr()); PrintWithColorRAII(OS, ParenthesisColor) << ')'; } + void visitCopyExpr(CopyExpr *E) { + printCommon(E, "copy_expr"); + OS << '\n'; + printRec(E->getSubExpr()); + PrintWithColorRAII(OS, ParenthesisColor) << ')'; + } void visitBorrowExpr(BorrowExpr *E) { printCommon(E, "borrow_expr"); OS << '\n'; diff --git a/lib/AST/ASTPrinter.cpp b/lib/AST/ASTPrinter.cpp index 7faf7521c9fc1..10f77b76f30d7 100644 --- a/lib/AST/ASTPrinter.cpp +++ b/lib/AST/ASTPrinter.cpp @@ -5000,6 +5000,11 @@ void PrintAST::visitMoveExpr(MoveExpr *expr) { visit(expr->getSubExpr()); } +void PrintAST::visitCopyExpr(CopyExpr *expr) { + Printer << "copy "; + visit(expr->getSubExpr()); +} + void PrintAST::visitBorrowExpr(BorrowExpr *expr) { Printer << "borrow "; visit(expr->getSubExpr()); diff --git a/lib/AST/ASTWalker.cpp b/lib/AST/ASTWalker.cpp index 597b4a57b3f30..25dc8b641b410 100644 --- a/lib/AST/ASTWalker.cpp +++ b/lib/AST/ASTWalker.cpp @@ -690,6 +690,15 @@ class Traversal : public ASTVisitorgetSubExpr())) { + E->setSubExpr(subExpr); + return E; + } + return nullptr; + } + Expr *visitTupleExpr(TupleExpr *E) { for (unsigned i = 0, e = E->getNumElements(); i != e; ++i) if (E->getElement(i)) { diff --git a/lib/AST/Expr.cpp b/lib/AST/Expr.cpp index e6ccbe595d8d0..733af464ecd42 100644 --- a/lib/AST/Expr.cpp +++ b/lib/AST/Expr.cpp @@ -192,7 +192,6 @@ SourceLoc Expr::getLoc() const { Expr *Expr::getSemanticsProvidingExpr() { if (auto *IE = dyn_cast(this)) return IE->getSubExpr()->getSemanticsProvidingExpr(); - if (auto *TE = dyn_cast(this)) return TE->getSubExpr()->getSemanticsProvidingExpr(); @@ -372,6 +371,7 @@ ConcreteDeclRef Expr::getReferencedDecl(bool stopAtParenExpr) const { PASS_THROUGH_REFERENCE(DotSelf, getSubExpr); PASS_THROUGH_REFERENCE(Await, getSubExpr); PASS_THROUGH_REFERENCE(Move, getSubExpr); + PASS_THROUGH_REFERENCE(Copy, getSubExpr); PASS_THROUGH_REFERENCE(Borrow, getSubExpr); PASS_THROUGH_REFERENCE(Try, getSubExpr); PASS_THROUGH_REFERENCE(ForceTry, getSubExpr); @@ -744,6 +744,7 @@ bool Expr::canAppendPostfixExpression(bool appendingPostfixOperator) const { case ExprKind::Await: case ExprKind::Move: + case ExprKind::Copy: case ExprKind::Borrow: case ExprKind::Try: case ExprKind::ForceTry: @@ -936,6 +937,7 @@ bool Expr::isValidParentOfTypeExpr(Expr *typeExpr) const { case ExprKind::Paren: case ExprKind::Await: case ExprKind::Move: + case ExprKind::Copy: case ExprKind::Borrow: case ExprKind::UnresolvedMemberChainResult: case ExprKind::Try: diff --git a/lib/Parse/ParseExpr.cpp b/lib/Parse/ParseExpr.cpp index 964914ee9483a..2e622e5f55324 100644 --- a/lib/Parse/ParseExpr.cpp +++ b/lib/Parse/ParseExpr.cpp @@ -422,6 +422,21 @@ ParserResult Parser::parseExprSequenceElement(Diag<> message, return sub; } + if (Tok.isContextualKeyword("copy") && + peekToken().isAny(tok::identifier, tok::kw_self, tok::dollarident, + tok::code_complete) && + !peekToken().isAtStartOfLine()) { + Tok.setKind(tok::contextual_keyword); + + SourceLoc copyLoc = consumeToken(); + ParserResult sub = + parseExprSequenceElement(diag::expected_expr_after_copy, isExprBasic); + if (!sub.isNull()) { + sub = makeParserResult(new (Context) CopyExpr(copyLoc, sub.get())); + } + return sub; + } + if (Context.LangOpts.hasFeature(Feature::OldOwnershipOperatorSpellings)) { if (Tok.isContextualKeyword("_move")) { Tok.setKind(tok::contextual_keyword); diff --git a/lib/SILGen/SILGenBuilder.cpp b/lib/SILGen/SILGenBuilder.cpp index 84e6304780aa5..4fd7137a7d638 100644 --- a/lib/SILGen/SILGenBuilder.cpp +++ b/lib/SILGen/SILGenBuilder.cpp @@ -1050,3 +1050,9 @@ void SILGenBuilder::emitCopyAddrOperation(SILLocation loc, SILValue srcAddr, auto &lowering = getTypeLowering(srcAddr->getType()); lowering.emitCopyInto(*this, loc, srcAddr, destAddr, isTake, isInitialize); } + +ManagedValue SILGenBuilder::createExplicitCopyValue(SILLocation loc, + ManagedValue operand) { + auto cvi = SILBuilder::createExplicitCopyValue(loc, operand.getValue()); + return SGF.emitManagedRValueWithCleanup(cvi); +} diff --git a/lib/SILGen/SILGenBuilder.h b/lib/SILGen/SILGenBuilder.h index 61a2d21a983bf..8e89291a6b411 100644 --- a/lib/SILGen/SILGenBuilder.h +++ b/lib/SILGen/SILGenBuilder.h @@ -125,6 +125,13 @@ class SILGenBuilder : public SILBuilder { ManagedValue createFormalAccessCopyValue(SILLocation loc, ManagedValue originalValue); + using SILBuilder::createExplicitCopyValue; + + /// A copy_value operation that to the move checker looks like just a normal + /// liveness use. Used to implement an explicit copy for no implicit copy + /// values. + ManagedValue createExplicitCopyValue(SILLocation Loc, ManagedValue operand); + #define ALWAYS_OR_SOMETIMES_LOADABLE_CHECKED_REF_STORAGE(Name, ...) \ using SILBuilder::createStrongCopy##Name##Value; \ ManagedValue createStrongCopy##Name##Value(SILLocation loc, \ diff --git a/lib/SILGen/SILGenExpr.cpp b/lib/SILGen/SILGenExpr.cpp index 2891642f46d12..c340f20d64b51 100644 --- a/lib/SILGen/SILGenExpr.cpp +++ b/lib/SILGen/SILGenExpr.cpp @@ -552,6 +552,7 @@ namespace { RValue visitLinearToDifferentiableFunctionExpr( LinearToDifferentiableFunctionExpr *E, SGFContext C); RValue visitMoveExpr(MoveExpr *E, SGFContext C); + RValue visitCopyExpr(CopyExpr *E, SGFContext C); RValue visitMacroExpansionExpr(MacroExpansionExpr *E, SGFContext C); }; } // end anonymous namespace @@ -6156,6 +6157,64 @@ RValue RValueEmitter::visitMoveExpr(MoveExpr *E, SGFContext C) { return RValue(SGF, {optTemp->getManagedAddress()}, subType.getASTType()); } +RValue RValueEmitter::visitCopyExpr(CopyExpr *E, SGFContext C) { + auto *subExpr = E->getSubExpr(); + auto subASTType = subExpr->getType()->getCanonicalType(); + auto subType = SGF.getLoweredType(subASTType); + + if (auto *li = dyn_cast(subExpr)) { + + FormalEvaluationScope writeback(SGF); + LValue lv = + SGF.emitLValue(li->getSubExpr(), SGFAccessKind::BorrowedAddressRead); + auto address = SGF.emitAddressOfLValue(subExpr, std::move(lv)); + + if (subType.isLoadable(SGF.F)) { + // Use a formal access load borrow so this closes in the writeback scope + // above. + ManagedValue value = SGF.B.createFormalAccessLoadBorrow(E, address); + + // We purposely, use a lexical cleanup here so that the cleanup lasts + // through the formal evaluation scope. + ManagedValue copy = SGF.B.createExplicitCopyValue(E, value); + + return RValue(SGF, {copy}, subType.getASTType()); + } + + auto optTemp = SGF.emitTemporary(E, SGF.getTypeLowering(subType)); + SILValue toAddr = optTemp->getAddressForInPlaceInitialization(SGF, E); + SGF.B.createExplicitCopyAddr(subExpr, address.getLValueAddress(), toAddr, + IsNotTake, IsInitialization); + optTemp->finishInitialization(SGF); + return RValue(SGF, {optTemp->getManagedAddress()}, subType.getASTType()); + } + + if (subType.isLoadable(SGF.F)) { + ManagedValue mv = SGF.emitRValue(subExpr).getAsSingleValue(SGF, subExpr); + if (mv.getType().isTrivial(SGF.F)) + return RValue(SGF, {mv}, subType.getASTType()); + mv = SGF.B.createExplicitCopyValue(E, mv); + return RValue(SGF, {mv}, subType.getASTType()); + } + + // If we aren't loadable, then create a temporary initialization and + // explicit_copy_addr into that. + std::unique_ptr optTemp; + optTemp = SGF.emitTemporary(E, SGF.getTypeLowering(subType)); + SILValue toAddr = optTemp->getAddressForInPlaceInitialization(SGF, E); + assert(!isa(E->getType()->getCanonicalType()) && + "Shouldn't see an lvalue type here"); + + ManagedValue mv = + SGF.emitRValue(subExpr, SGFContext(SGFContext::AllowImmediatePlusZero)) + .getAsSingleValue(SGF, subExpr); + assert(mv.getType().isAddress()); + SGF.B.createExplicitCopyAddr(subExpr, mv.getValue(), toAddr, IsNotTake, + IsInitialization); + optTemp->finishInitialization(SGF); + return RValue(SGF, {optTemp->getManagedAddress()}, subType.getASTType()); +} + RValue RValueEmitter::visitMacroExpansionExpr(MacroExpansionExpr *E, SGFContext C) { if (auto *rewritten = E->getRewritten()) { diff --git a/lib/SILGen/SILGenLValue.cpp b/lib/SILGen/SILGenLValue.cpp index 41b4ba22f8eb9..d4cf44b30e5ae 100644 --- a/lib/SILGen/SILGenLValue.cpp +++ b/lib/SILGen/SILGenLValue.cpp @@ -334,6 +334,8 @@ class LLVM_LIBRARY_VISIBILITY SILGenLValue LValueOptions options); LValue visitMoveExpr(MoveExpr *e, SGFAccessKind accessKind, LValueOptions options); + LValue visitCopyExpr(CopyExpr *e, SGFAccessKind accessKind, + LValueOptions options); LValue visitABISafeConversionExpr(ABISafeConversionExpr *e, SGFAccessKind accessKind, LValueOptions options); @@ -4033,6 +4035,30 @@ LValue SILGenLValue::visitMoveExpr(MoveExpr *e, SGFAccessKind accessKind, toAddr->getType().getASTType()); } +LValue SILGenLValue::visitCopyExpr(CopyExpr *e, SGFAccessKind accessKind, + LValueOptions options) { + // Do formal evaluation of the base l-value. + LValue baseLV = visitRec(e->getSubExpr(), SGFAccessKind::BorrowedAddressRead, + options.forComputedBaseLValue()); + + ManagedValue addr = SGF.emitAddressOfLValue(e, std::move(baseLV)); + + // Now create the temporary and copy our value into there using an explicit + // copy_value. This ensures that the rest of the move checker views this as a + // liveness requiring use rather than a copy that must be eliminated. + auto temp = + SGF.emitFormalAccessTemporary(e, SGF.F.getTypeLowering(addr.getType())); + auto toAddr = temp->getAddressForInPlaceInitialization(SGF, e); + SGF.B.createExplicitCopyAddr(e, addr.getValue(), toAddr, IsNotTake, + IsInitialization); + temp->finishInitialization(SGF); + + // Now return the temporary in a value component. + return LValue::forValue(SGFAccessKind::BorrowedAddressRead, + temp->getManagedAddress(), + toAddr->getType().getASTType()); +} + LValue SILGenLValue::visitABISafeConversionExpr(ABISafeConversionExpr *e, SGFAccessKind accessKind, LValueOptions options) { diff --git a/lib/Sema/CSApply.cpp b/lib/Sema/CSApply.cpp index 5930b723a6719..a934233397ec3 100644 --- a/lib/Sema/CSApply.cpp +++ b/lib/Sema/CSApply.cpp @@ -3525,6 +3525,27 @@ namespace { return expr; } + Expr *visitCopyExpr(CopyExpr *expr) { + auto toType = simplifyType(cs.getType(expr)); + cs.setType(expr, toType); + + auto *subExpr = expr->getSubExpr(); + auto type = simplifyType(cs.getType(subExpr)); + + // Let's load the value associated with this try. + if (type->hasLValueType()) { + subExpr = coerceToType(subExpr, type->getRValueType(), + cs.getConstraintLocator(subExpr)); + + if (!subExpr) + return nullptr; + } + + expr->setSubExpr(subExpr); + + return expr; + } + Expr *visitAnyTryExpr(AnyTryExpr *expr) { auto *subExpr = expr->getSubExpr(); auto type = simplifyType(cs.getType(subExpr)); diff --git a/lib/Sema/CSGen.cpp b/lib/Sema/CSGen.cpp index 4907218c1f78e..3f094656d8859 100644 --- a/lib/Sema/CSGen.cpp +++ b/lib/Sema/CSGen.cpp @@ -1889,6 +1889,16 @@ namespace { return CS.getType(expr->getSubExpr()); } + Type visitCopyExpr(CopyExpr *expr) { + auto valueTy = CS.createTypeVariable(CS.getConstraintLocator(expr), + TVO_PrefersSubtypeBinding | + TVO_CanBindToNoEscape); + CS.addConstraint(ConstraintKind::Equal, valueTy, + CS.getType(expr->getSubExpr()), + CS.getConstraintLocator(expr)); + return valueTy; + } + Type visitAnyTryExpr(AnyTryExpr *expr) { return CS.getType(expr->getSubExpr()); } diff --git a/lib/Sema/MiscDiagnostics.cpp b/lib/Sema/MiscDiagnostics.cpp index 40687e2e45657..b3ef2ca3aeaa4 100644 --- a/lib/Sema/MiscDiagnostics.cpp +++ b/lib/Sema/MiscDiagnostics.cpp @@ -344,6 +344,12 @@ static void diagSyntacticUseRestrictions(const Expr *E, const DeclContext *DC, checkMoveExpr(moveExpr); } + // Diagnose copy expression uses where the sub expression is not a declref + // expr. + if (auto *copyExpr = dyn_cast(E)) { + checkCopyExpr(copyExpr); + } + // Diagnose move expression uses where the sub expression is not a declref expr if (auto *borrowExpr = dyn_cast(E)) { checkBorrowExpr(borrowExpr); @@ -422,6 +428,26 @@ static void diagSyntacticUseRestrictions(const Expr *E, const DeclContext *DC, } } + void checkCopyExpr(CopyExpr *copyExpr) { + // Do not allow for copy_expr to be used with pure move only types. We + // /do/ allow it to be used with no implicit copy types though. + if (copyExpr->getType()->isPureMoveOnly()) { + Ctx.Diags.diagnose( + copyExpr->getLoc(), + diag::copy_expression_cannot_be_used_with_noncopyable_types); + } + + // 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(); + if (auto *li = dyn_cast(subExpr)) + subExpr = li->getSubExpr(); + if (!isa(subExpr)) { + Ctx.Diags.diagnose(copyExpr->getLoc(), + diag::copy_expression_not_passed_lvalue); + } + } + void checkBorrowExpr(BorrowExpr *borrowExpr) { // Allow for a chain of member_ref exprs that end in a decl_ref expr. auto *subExpr = borrowExpr->getSubExpr(); diff --git a/test/IDE/copy_expr.swift b/test/IDE/copy_expr.swift new file mode 100644 index 0000000000000..782aac1848160 --- /dev/null +++ b/test/IDE/copy_expr.swift @@ -0,0 +1,7 @@ +// RUN: %empty-directory(%t) +// RUN: %target-swift-ide-test -batch-code-completion -source-filename %s -filecheck %raw-FileCheck -completion-output-dir %t + +func test(myParam: Int) { + copy #^COPY^# + // COPY: Decl[LocalVar]/Local: myParam[#Int#]; name=myParam +} diff --git a/test/Parse/copy_expr.swift b/test/Parse/copy_expr.swift new file mode 100644 index 0000000000000..1dd51814cb717 --- /dev/null +++ b/test/Parse/copy_expr.swift @@ -0,0 +1,116 @@ +// RUN: %target-typecheck-verify-swift -disable-availability-checking + +var global: Int = 5 +func testGlobal() { + let _ = copy global +} + +func testLet() { + let t = String() + let _ = copy t +} + +func testVar() { + var t = String() + t = String() + let _ = copy t +} + +func copy() {} +func copy(_: String) {} +func copy(_: String, _: Int) {} +func copy(x: String, y: Int) {} + +// Ensure that we can still call a function named copy. + +func useCopyFunc() { + var s = String() + var i = global + + copy() + copy(s) + copy(i) // expected-error{{cannot convert value of type 'Int' to expected argument type 'String'}} + copy(s, i) + copy(i, s) // expected-error{{unnamed argument #2 must precede unnamed argument #1}} + copy(x: s, y: i) + copy(y: i, x: s) // expected-error{{argument 'x' must precede argument 'y'}} +} + +// Ensure that we can still use a variable named copy. + +func useCopyVar(copy: inout String) { + let s = copy + copy = s + + // We can copy from a variable named `copy` + let t = copy copy + copy = t + + // We can do member access and subscript a variable named `copy` + let i = copy.startIndex + let _ = copy[i] +} + +@propertyWrapper +struct FooWrapper { + var value: T + + init(wrappedValue: T) { value = wrappedValue } + + var wrappedValue: T { + get { value } + nonmutating set {} + } + var projectedValue: T { + get { value } + nonmutating set {} + } +} + +struct Foo { + @FooWrapper var wrapperTest: String + + func copySelf() { + _ = copy self + } + + func copyPropertyWrapper() { + // Make sure that we can parse. + _ = copy wrapperTest // expected-error {{'copy' can only be applied to lvalues}} + _ = copy _wrapperTest // expected-error {{'copy' can only be applied to lvalues}} + _ = copy $wrapperTest // expected-error {{'copy' can only be applied to lvalues}} + } +} + +func testParseCopyWithDollarIdentifier() { + class Klass {} + let f: (Klass) -> () = { + let _ = copy $0 + } + _ = f +} + +func testParseCopySelf() { + class Klass { + func test() { + let _ = copy self + } + } +} + +func testForLoop() { + for copy in 0..<1024 { + _ = copy + } +} + +class ParentKlass {} +class ChildKlass : ParentKlass {} + +func testAsBindingVariableInSwitch(_ x: ChildKlass) { + switch x { + case let copy as ParentKlass: + _ = copy + break + } +} diff --git a/test/SILGen/copy_expr.swift b/test/SILGen/copy_expr.swift new file mode 100644 index 0000000000000..337e422cade64 --- /dev/null +++ b/test/SILGen/copy_expr.swift @@ -0,0 +1,440 @@ +// RUN: %target-swift-emit-silgen -enable-experimental-feature NoImplicitCopy %s | %FileCheck %s + +final class Klass { + var k: Klass? = nil +} +struct ContainKlass { + var k = Klass() + + consuming func consumeFunc() {} + mutating func mutatingFunc() {} + func borrowingFunc() {} + + var computedK: Klass { + get { + k + } + } + + var consumingComputedK: Klass { + __consuming get { + k + } + } +} + +// CHECK-LABEL: sil hidden [ossa] @$s9copy_expr22testCopyLoadableRValueyyF : $@convention(thin) () -> () { +// CHECK: [[X:%.*]] = apply {{%.*}}({{%.*}}) : $@convention(method) (@thin ContainKlass.Type) -> @owned ContainKlass +// CHECK: [[BORROW:%.*]] = begin_borrow [lexical] [[X]] +// CHECK: [[COPY_BORROW:%.*]] = copy_value [[BORROW]] +// CHECK: explicit_copy_value [[COPY_BORROW]] +// CHECK: } // end sil function '$s9copy_expr22testCopyLoadableRValueyyF' +func testCopyLoadableRValue() { + let x = ContainKlass() + let _ = copy x +} + +// CHECK-LABEL: sil hidden [ossa] @$s9copy_expr25testCopyLoadableVarLValueyyF : $@convention(thin) () -> () { +// CHECK: [[BOX:%.*]] = alloc_box ${ var ContainKlass }, var, name "x" +// CHECK: [[BORROW_BOX:%.*]] = begin_borrow [lexical] [[BOX]] +// CHECK: [[PROJECT_BOX:%.*]] = project_box [[BORROW_BOX]] +// +// CHECK: [[FINAL_ACCESS:%.*]] = begin_access [read] [unknown] [[PROJECT_BOX]] +// CHECK: [[LOAD_BORROW:%.*]] = load_borrow [[FINAL_ACCESS]] +// CHECK: explicit_copy_value [[LOAD_BORROW]] +// CHECK: end_access [[FINAL_ACCESS]] +// CHECK: } // end sil function '$s9copy_expr25testCopyLoadableVarLValueyyF' +func testCopyLoadableVarLValue() { + var x = ContainKlass() + x = ContainKlass() + let _ = copy x +} + +// CHECK-LABEL: sil hidden [ossa] @$s9copy_expr25testCopyLoadableVarLValueyyAA12ContainKlassVzF : $@convention(thin) (@inout ContainKlass) -> () { +// CHECK: bb0([[ARG:%.*]] : $*ContainKlass): +// CHECK: [[ACCESS:%.*]] = begin_access [read] [unknown] [[ARG]] +// CHECK: [[LOAD_BORROW:%.*]] = load_borrow [[ACCESS]] +// CHECK: explicit_copy_value [[LOAD_BORROW]] +// CHECK: } // end sil function '$s9copy_expr25testCopyLoadableVarLValueyyAA12ContainKlassVzF' +func testCopyLoadableVarLValue(_ x: inout ContainKlass) { + let _ = copy x +} + +protocol P { + static var value: Self { get } + + consuming func consumeFunc() + mutating func mutatingFunc() + func borrowingFunc() + + var computedK: Klass { + get + } + + var consumingComputedK: Klass { + __consuming get + } +} + +// CHECK-LABEL: sil hidden [ossa] @$s9copy_expr25testCopyAddressOnlyRValueyyxmAA1PRzlF : $@convention(thin) (@thick T.Type) -> () { +// CHECK: [[BOX:%.*]] = alloc_stack [lexical] $T, let, name "x" +// CHECK: [[TMP:%.*]] = alloc_stack $T +// CHECK: explicit_copy_addr [[BOX]] to [init] [[TMP]] +// CHECK: } // end sil function '$s9copy_expr25testCopyAddressOnlyRValueyyxmAA1PRzlF' +func testCopyAddressOnlyRValue(_ t: T.Type) { + let x = T.value + let _ = copy x +} + +// CHECK-LABEL: sil hidden [ossa] @$s9copy_expr25testCopyAddressOnlyLValueyyxmAA1PRzlF : $@convention(thin) (@thick T.Type) -> () { +// CHECK: [[BOX:%.*]] = alloc_box $<τ_0_0 where τ_0_0 : P> { var τ_0_0 } , var, name "x" +// CHECK: [[BORROW_BOX:%.*]] = begin_borrow [lexical] [[BOX]] +// CHECK: [[PROJECT_BOX:%.*]] = project_box [[BORROW_BOX]] +// +// CHECK: [[ACCESS:%.*]] = begin_access [read] [unknown] [[PROJECT_BOX]] +// CHECK: [[TMP:%.*]] = alloc_stack $T +// CHECK: explicit_copy_addr [[ACCESS]] to [init] [[TMP]] +// CHECK: end_access [[ACCESS]] +// CHECK: } // end sil function '$s9copy_expr25testCopyAddressOnlyLValueyyxmAA1PRzlF' +func testCopyAddressOnlyLValue(_ t: T.Type) { + var x = T.value + x = T.value + let _ = copy x +} + +// CHECK-LABEL: sil hidden [ossa] @$s9copy_expr28testCopyAddressOnlyLValueArgyyxzAA1PRzlF : $@convention(thin) (@inout T) -> () { +// CHECK: bb0([[ARG:%.*]] : $*T): +// CHECK: [[ACCESS:%.*]] = begin_access [read] [unknown] [[ARG]] +// CHECK: [[TMP:%.*]] = alloc_stack $T +// CHECK: explicit_copy_addr [[ACCESS]] to [init] [[TMP]] +// CHECK: } // end sil function '$s9copy_expr28testCopyAddressOnlyLValueArgyyxzAA1PRzlF' +func testCopyAddressOnlyLValueArg(_ x: inout T) { + let _ = copy x +} + +// CHECK-LABEL: sil hidden [ossa] @$s9copy_expr31testCallMethodOnLoadableLetCopyyyF : $@convention(thin) () -> () { +// CHECK: [[ORIG_X:%.*]] = apply {{%.*}}({{%.*}}) : $@convention(method) (@thin ContainKlass.Type) -> @owned ContainKlass +// CHECK: [[X:%.*]] = begin_borrow [lexical] [[ORIG_X]] +// +// Calling consumeFunc. +// CHECK: [[COPY_X:%.*]] = copy_value [[X]] +// CHECK: [[EXPLICIT_COPY_X:%.*]] = explicit_copy_value [[COPY_X]] +// CHECK: [[FUNC:%.*]] = function_ref @$s9copy_expr12ContainKlassV11consumeFuncyyF : $@convention(method) (@owned ContainKlass) -> () +// CHECK: apply [[FUNC]]([[EXPLICIT_COPY_X]]) +// CHECK: destroy_value [[COPY_X]] +// +// Calling borrowingFunc. +// CHECK: [[COPY_X:%.*]] = copy_value [[X]] +// CHECK: [[EXPLICIT_COPY_X:%.*]] = explicit_copy_value [[COPY_X]] +// CHECK: [[FUNC:%.*]] = function_ref @$s9copy_expr12ContainKlassV13borrowingFuncyyF : $@convention(method) (@guaranteed ContainKlass) -> () +// CHECK: apply [[FUNC]]([[EXPLICIT_COPY_X]]) +// CHECK: destroy_value [[EXPLICIT_COPY_X]] +// CHECK: destroy_value [[COPY_X]] +// +// Calling computedK. It is borrowed. +// CHECK: [[COPY_X:%.*]] = copy_value [[X]] +// CHECK: [[EXPLICIT_COPY_X:%.*]] = explicit_copy_value [[COPY_X]] +// CHECK: [[BORROW_EXPLICIT_COPY_X:%.*]] = begin_borrow [[EXPLICIT_COPY_X]] +// CHECK: [[FUNC:%.*]] = function_ref @$s9copy_expr12ContainKlassV9computedKAA0D0Cvg : $@convention(method) (@guaranteed ContainKlass) -> @owned Klass +// CHECK: apply [[FUNC]]([[BORROW_EXPLICIT_COPY_X]]) +// CHECK: end_borrow [[BORROW_EXPLICIT_COPY_X]] +// CHECK: destroy_value [[EXPLICIT_COPY_X]] +// CHECK: destroy_value [[COPY_X]] +// +// Calling computed getter. +// CHECK: [[COPY_X:%.*]] = copy_value [[X]] +// CHECK: [[EXPLICIT_COPY_X:%.*]] = explicit_copy_value [[COPY_X]] +// CHECK: [[BORROW_EXPLICIT_COPY_X:%.*]] = begin_borrow [[EXPLICIT_COPY_X]] +// CHECK: [[COPY_BORROW_EXPLICIT_COPY_X:%.*]] = copy_value [[BORROW_EXPLICIT_COPY_X]] +// CHECK: [[FUNC:%.*]] = function_ref @$s9copy_expr12ContainKlassV18consumingComputedKAA0D0Cvg : $@convention(method) (@owned ContainKlass) -> @owned Klass +// CHECK: apply [[FUNC]]([[COPY_BORROW_EXPLICIT_COPY_X]]) +// CHECK: end_borrow [[BORROW_EXPLICIT_COPY_X]] +// CHECK: destroy_value [[EXPLICIT_COPY_X]] +// CHECK: destroy_value [[COPY_X]] +// CHECK: } // end sil function '$s9copy_expr31testCallMethodOnLoadableLetCopyyyF' +func testCallMethodOnLoadableLetCopy() { + let x = ContainKlass() + (copy x).consumeFunc() + (copy x).borrowingFunc() + _ = (copy x).computedK + _ = (copy x).consumingComputedK +} + +// CHECK-LABEL: sil hidden [ossa] @$s9copy_expr31testCallMethodOnLoadableVarCopyyyF : $@convention(thin) () -> () { +// CHECK: [[BOX:%.*]] = alloc_box ${ var ContainKlass } +// CHECK: [[BORROW:%.*]] = begin_borrow [lexical] [[BOX]] +// CHECK: [[PROJECT:%.*]] = project_box [[BORROW]] +// +// Calling consumeFunc. +// CHECK: [[ACCESS:%.*]] = begin_access [read] [unknown] [[PROJECT]] +// CHECK: [[LOAD_BORROW:%.*]] = load_borrow [[ACCESS]] +// CHECK: [[LOAD:%.*]] = explicit_copy_value [[LOAD_BORROW]] +// CHECK: end_access [[ACCESS]] +// CHECK: [[FUNC:%.*]] = function_ref @$s9copy_expr12ContainKlassV11consumeFuncyyF : $@convention(method) (@owned ContainKlass) -> () +// CHECK: apply [[FUNC]]([[LOAD]]) +// +// Calling borrowing func. +// CHECK: [[ACCESS:%.*]] = begin_access [read] [unknown] [[PROJECT]] +// CHECK: [[LOAD_BORROW:%.*]] = load_borrow [[ACCESS]] +// CHECK: [[LOAD:%.*]] = explicit_copy_value [[LOAD_BORROW]] +// CHECK: end_access [[ACCESS]] +// CHECK: [[FUNC:%.*]] = function_ref @$s9copy_expr12ContainKlassV13borrowingFuncyyF : $@convention(method) (@guaranteed ContainKlass) -> () +// CHECK: apply [[FUNC]]([[LOAD]]) +// CHECK: destroy_value [[LOAD]] +// +// Calling borrowing computed getter +// CHECK: [[ACCESS:%.*]] = begin_access [read] [unknown] [[PROJECT]] +// CHECK: [[LOAD_BORROW:%.*]] = load_borrow [[ACCESS]] +// CHECK: [[LOAD:%.*]] = explicit_copy_value [[LOAD_BORROW]] +// CHECK: end_access [[ACCESS]] +// CHECK: [[BORROW_LOAD:%.*]] = begin_borrow [[LOAD]] +// CHECK: [[FUNC:%.*]] = function_ref @$s9copy_expr12ContainKlassV9computedKAA0D0Cvg : $@convention(method) (@guaranteed ContainKlass) -> @owned Klass +// CHECK: apply [[FUNC]]([[BORROW_LOAD]]) +// CHECK: end_borrow [[BORROW_LOAD]] +// CHECK: destroy_value [[LOAD]] +// +// Consuming computed getter. +// CHECK: [[ACCESS:%.*]] = begin_access [read] [unknown] [[PROJECT]] +// CHECK: [[LOAD_BORROW:%.*]] = load_borrow [[ACCESS]] +// CHECK: [[LOAD:%.*]] = explicit_copy_value [[LOAD_BORROW]] +// CHECK: end_access [[ACCESS]] +// CHECK: [[BORROW:%.*]] = begin_borrow [[LOAD]] +// CHECK: [[BORROW_COPY:%.*]] = copy_value [[BORROW]] +// CHECK: [[FUNC:%.*]] = function_ref @$s9copy_expr12ContainKlassV18consumingComputedKAA0D0Cvg : $@convention(method) (@owned ContainKlass) -> @owned Klass +// CHECK: apply [[FUNC]]([[BORROW_COPY]]) +// CHECK: } // end sil function '$s9copy_expr31testCallMethodOnLoadableVarCopyyyF' +func testCallMethodOnLoadableVarCopy() { + var x = ContainKlass() + x = ContainKlass() + (copy x).consumeFunc() + (copy x).borrowingFunc() + _ = (copy x).computedK + _ = (copy x).consumingComputedK +} + +// CHECK-LABEL: sil hidden [ossa] @$s9copy_expr33testCallMethodOnLoadableInOutCopyyyAA12ContainKlassVzF : $@convention(thin) (@inout ContainKlass) -> () { +// CHECK: bb0([[ARG:%.*]] : $*ContainKlass): +// +// Calling consumeFunc. +// CHECK: [[ACCESS:%.*]] = begin_access [read] [unknown] [[ARG]] +// CHECK: [[LOAD_BORROW:%.*]] = load_borrow [[ACCESS]] +// CHECK: [[LOAD:%.*]] = explicit_copy_value [[LOAD_BORROW]] +// CHECK: end_access [[ACCESS]] +// CHECK: [[FUNC:%.*]] = function_ref @$s9copy_expr12ContainKlassV11consumeFuncyyF : $@convention(method) (@owned ContainKlass) -> () +// CHECK: apply [[FUNC]]([[LOAD]]) +// +// Calling borrowing func. +// CHECK: [[ACCESS:%.*]] = begin_access [read] [unknown] [[ARG]] +// CHECK: [[LOAD_BORROW:%.*]] = load_borrow [[ACCESS]] +// CHECK: [[LOAD:%.*]] = explicit_copy_value [[LOAD_BORROW]] +// CHECK: end_access [[ACCESS]] +// CHECK: [[FUNC:%.*]] = function_ref @$s9copy_expr12ContainKlassV13borrowingFuncyyF : $@convention(method) (@guaranteed ContainKlass) -> () +// CHECK: apply [[FUNC]]([[LOAD]]) +// CHECK: destroy_value [[LOAD]] +// +// Calling borrowing computed getter +// CHECK: [[ACCESS:%.*]] = begin_access [read] [unknown] [[ARG]] +// CHECK: [[LOAD_BORROW:%.*]] = load_borrow [[ACCESS]] +// CHECK: [[LOAD:%.*]] = explicit_copy_value [[LOAD_BORROW]] +// CHECK: end_access [[ACCESS]] +// CHECK: [[BORROW_LOAD:%.*]] = begin_borrow [[LOAD]] +// CHECK: [[FUNC:%.*]] = function_ref @$s9copy_expr12ContainKlassV9computedKAA0D0Cvg : $@convention(method) (@guaranteed ContainKlass) -> @owned Klass +// CHECK: apply [[FUNC]]([[BORROW_LOAD]]) +// CHECK: end_borrow [[BORROW_LOAD]] +// CHECK: destroy_value [[LOAD]] +// +// Consuming computed getter. +// CHECK: [[ACCESS:%.*]] = begin_access [read] [unknown] [[ARG]] +// CHECK: [[LOAD_BORROW:%.*]] = load_borrow [[ACCESS]] +// CHECK: [[LOAD:%.*]] = explicit_copy_value [[LOAD_BORROW]] +// CHECK: end_access [[ACCESS]] +// CHECK: [[BORROW:%.*]] = begin_borrow [[LOAD]] +// CHECK: [[BORROW_COPY:%.*]] = copy_value [[BORROW]] +// CHECK: [[FUNC:%.*]] = function_ref @$s9copy_expr12ContainKlassV18consumingComputedKAA0D0Cvg : $@convention(method) (@owned ContainKlass) -> @owned Klass +// CHECK: apply [[FUNC]]([[BORROW_COPY]]) +// CHECK: } // end sil function '$s9copy_expr33testCallMethodOnLoadableInOutCopyyyAA12ContainKlassVzF' +func testCallMethodOnLoadableInOutCopy(_ x: inout ContainKlass) { + (copy x).consumeFunc() + (copy x).borrowingFunc() + _ = (copy x).computedK + _ = (copy x).consumingComputedK +} + +var containKlassGlobal: ContainKlass + +// CHECK-LABEL: sil hidden [ossa] @$s9copy_expr30testCallMethodOnLoadableGlobalyyF : $@convention(thin) () -> () { +// CHECK: [[ADDR:%.*]] = global_addr @$s9copy_expr18containKlassGlobalAA07ContainD0Vvp +// +// Calling consumeFunc. +// CHECK: [[ACCESS:%.*]] = begin_access [read] [dynamic] [[ADDR]] +// CHECK: [[LOAD_BORROW:%.*]] = load_borrow [[ACCESS]] +// CHECK: [[LOAD:%.*]] = explicit_copy_value [[LOAD_BORROW]] +// CHECK: end_access [[ACCESS]] +// CHECK: [[FUNC:%.*]] = function_ref @$s9copy_expr12ContainKlassV11consumeFuncyyF : $@convention(method) (@owned ContainKlass) -> () +// CHECK: apply [[FUNC]]([[LOAD]]) +// +// Calling borrowing func. +// CHECK: [[ACCESS:%.*]] = begin_access [read] [dynamic] [[ADDR]] +// CHECK: [[LOAD_BORROW:%.*]] = load_borrow [[ACCESS]] +// CHECK: [[LOAD:%.*]] = explicit_copy_value [[LOAD_BORROW]] +// CHECK: end_access [[ACCESS]] +// CHECK: [[FUNC:%.*]] = function_ref @$s9copy_expr12ContainKlassV13borrowingFuncyyF : $@convention(method) (@guaranteed ContainKlass) -> () +// CHECK: apply [[FUNC]]([[LOAD]]) +// CHECK: destroy_value [[LOAD]] +// +// Calling borrowing computed getter +// CHECK: [[ACCESS:%.*]] = begin_access [read] [dynamic] [[ADDR]] +// CHECK: [[LOAD_BORROW:%.*]] = load_borrow [[ACCESS]] +// CHECK: [[LOAD:%.*]] = explicit_copy_value [[LOAD_BORROW]] +// CHECK: end_access [[ACCESS]] +// CHECK: [[BORROW_LOAD:%.*]] = begin_borrow [[LOAD]] +// CHECK: [[FUNC:%.*]] = function_ref @$s9copy_expr12ContainKlassV9computedKAA0D0Cvg : $@convention(method) (@guaranteed ContainKlass) -> @owned Klass +// CHECK: apply [[FUNC]]([[BORROW_LOAD]]) +// CHECK: end_borrow [[BORROW_LOAD]] +// CHECK: destroy_value [[LOAD]] +// +// Consuming computed getter. +// CHECK: [[ACCESS:%.*]] = begin_access [read] [dynamic] [[ADDR]] +// CHECK: [[LOAD_BORROW:%.*]] = load_borrow [[ACCESS]] +// CHECK: [[LOAD:%.*]] = explicit_copy_value [[LOAD_BORROW]] +// CHECK: end_access [[ACCESS]] +// CHECK: [[BORROW:%.*]] = begin_borrow [[LOAD]] +// CHECK: [[BORROW_COPY:%.*]] = copy_value [[BORROW]] +// CHECK: [[FUNC:%.*]] = function_ref @$s9copy_expr12ContainKlassV18consumingComputedKAA0D0Cvg : $@convention(method) (@owned ContainKlass) -> @owned Klass +// CHECK: apply [[FUNC]]([[BORROW_COPY]]) +// CHECK: } // end sil function '$s9copy_expr30testCallMethodOnLoadableGlobalyyF' +func testCallMethodOnLoadableGlobal() { + (copy containKlassGlobal).consumeFunc() + (copy containKlassGlobal).borrowingFunc() + _ = (copy containKlassGlobal).computedK + _ = (copy containKlassGlobal).consumingComputedK +} + +// CHECK-LABEL: sil hidden [ossa] @$s9copy_expr34testCallMethodOnAddressOnlyLetCopyyyxmAA1PRzlF : $@convention(thin) (@thick T.Type) -> () { +// CHECK: [[X:%.*]] = alloc_stack [lexical] $T, let, name "x" +// +// Calling consumeFunc. +// CHECK: [[TEMP:%.*]] = alloc_stack $T +// CHECK: explicit_copy_addr [[X]] to [init] [[TEMP]] +// CHECK: [[FUNC:%.*]] = witness_method $T, #P.consumeFunc : (consuming Self) -> () -> () : $@convention(witness_method: P) <τ_0_0 where τ_0_0 : P> (@in τ_0_0) -> () +// CHECK: apply [[FUNC]]<(T)>([[TEMP]]) +// +// Calling borrowingFunc. +// CHECK: [[TEMP:%.*]] = alloc_stack $T +// CHECK: explicit_copy_addr [[X]] to [init] [[TEMP]] +// CHECK: [[FUNC:%.*]] = witness_method $T, #P.borrowingFunc : (Self) -> () -> () : $@convention(witness_method: P) <τ_0_0 where τ_0_0 : P> (@in_guaranteed τ_0_0) -> () +// CHECK: apply [[FUNC]]<(T)>([[TEMP]]) +// +// Calling computedK. It is borrowed. +// CHECK: [[TEMP:%.*]] = alloc_stack $T +// CHECK: explicit_copy_addr [[X]] to [init] [[TEMP]] +// CHECK: [[TEMP2:%.*]] = alloc_stack $T +// CHECK: copy_addr [[TEMP]] to [init] [[TEMP2]] +// CHECK: [[FUNC:%.*]] = witness_method $T, #P.computedK!getter : (Self) -> () -> Klass : $@convention(witness_method: P) <τ_0_0 where τ_0_0 : P> (@in_guaranteed τ_0_0) -> @owned Klass +// CHECK: apply [[FUNC]]<(T)>([[TEMP2]]) +// +// Calling computed consuming getter. +// CHECK: [[TEMP:%.*]] = alloc_stack $T +// CHECK: explicit_copy_addr [[X]] to [init] [[TEMP]] +// CHECK: [[TEMP2:%.*]] = alloc_stack $T +// CHECK: copy_addr [[TEMP]] to [init] [[TEMP2]] +// CHECK: [[FUNC:%.*]] = witness_method $T, #P.consumingComputedK!getter : (__owned Self) -> () -> Klass : $@convention(witness_method: P) <τ_0_0 where τ_0_0 : P> (@in τ_0_0) -> @owned Klass +// CHECK: apply [[FUNC]]<(T)>([[TEMP2]]) +// CHECK: } // end sil function '$s9copy_expr34testCallMethodOnAddressOnlyLetCopyyyxmAA1PRzlF' +func testCallMethodOnAddressOnlyLetCopy(_ t: T.Type) { + let x = T.value + (copy x).consumeFunc() + (copy x).borrowingFunc() + _ = (copy x).computedK + _ = (copy x).consumingComputedK +} + +// CHECK-LABEL: sil hidden [ossa] @$s9copy_expr34testCallMethodOnAddressOnlyVarCopyyyxmAA1PRzlF : $@convention(thin) (@thick T.Type) -> () { +// CHECK: [[BOX:%.*]] = alloc_box $ +// CHECK: [[BORROW:%.*]] = begin_borrow [lexical] [[BOX]] +// CHECK: [[PROJECT:%.*]] = project_box [[BORROW]] +// +// Calling consumeFunc. +// CHECK: [[ACCESS:%.*]] = begin_access [read] [unknown] [[PROJECT]] +// CHECK: [[TEMP:%.*]] = alloc_stack $ +// CHECK: explicit_copy_addr [[ACCESS]] to [init] [[TEMP]] +// CHECK: [[FUNC:%.*]] = witness_method $T, #P.consumeFunc : (consuming Self) -> () -> () : $@convention(witness_method: P) <τ_0_0 where τ_0_0 : P> (@in τ_0_0) -> () +// CHECK: apply [[FUNC]]<(T)>([[TEMP]]) +// +// Calling borrowing func. +// CHECK: [[ACCESS:%.*]] = begin_access [read] [unknown] [[PROJECT]] +// CHECK: [[TEMP:%.*]] = alloc_stack $ +// CHECK: explicit_copy_addr [[ACCESS]] to [init] [[TEMP]] +// CHECK: end_access [[ACCESS]] +// CHECK: [[FUNC:%.*]] = witness_method $T, #P.borrowingFunc : (Self) -> () -> () : $@convention(witness_method: P) <τ_0_0 where τ_0_0 : P> (@in_guaranteed τ_0_0) -> () +// CHECK: apply [[FUNC]]<(T)>([[TEMP]]) +// +// Calling borrowing computed getter +// CHECK: [[ACCESS:%.*]] = begin_access [read] [unknown] [[PROJECT]] +// CHECK: [[TEMP:%.*]] = alloc_stack $ +// CHECK: explicit_copy_addr [[ACCESS]] to [init] [[TEMP]] +// CHECK: [[TEMP2:%.*]] = alloc_stack $ +// CHECK: copy_addr [[TEMP]] to [init] [[TEMP2]] +// CHECK: [[FUNC:%.*]] = witness_method $T, #P.computedK!getter : (Self) -> () -> Klass : $@convention(witness_method: P) <τ_0_0 where τ_0_0 : P> (@in_guaranteed τ_0_0) -> @owned Klass +// CHECK: apply [[FUNC]]<(T)>([[TEMP2]]) +// +// Consuming computed getter. +// CHECK: [[ACCESS:%.*]] = begin_access [read] [unknown] [[PROJECT]] +// CHECK: [[TEMP:%.*]] = alloc_stack $ +// CHECK: explicit_copy_addr [[ACCESS]] to [init] [[TEMP]] +// CHECK: [[TEMP2:%.*]] = alloc_stack $ +// CHECK: copy_addr [[TEMP]] to [init] [[TEMP2]] +// CHECK: [[FUNC:%.*]] = witness_method $T, #P.consumingComputedK!getter : (__owned Self) -> () -> Klass : $@convention(witness_method: P) <τ_0_0 where τ_0_0 : P> (@in τ_0_0) -> @owned Klass +// CHECK: apply [[FUNC]]<(T)>([[TEMP2]]) +// CHECK: } // end sil function '$s9copy_expr34testCallMethodOnAddressOnlyVarCopyyyxmAA1PRzlF' +func testCallMethodOnAddressOnlyVarCopy(_ t: T.Type) { + var x = T.value + x = T.value + (copy x).consumeFunc() + (copy x).borrowingFunc() + _ = (copy x).computedK + _ = (copy x).consumingComputedK +} + +// CHECK-LABEL: sil hidden [ossa] @$s9copy_expr36testCallMethodOnAddressOnlyInOutCopyyyxzAA1PRzlF : $@convention(thin) (@inout T) -> () { +// CHECK: bb0([[ARG:%.*]] : $* +// +// Calling consumeFunc. +// CHECK: [[ACCESS:%.*]] = begin_access [read] [unknown] [[ARG]] +// CHECK: [[TEMP:%.*]] = alloc_stack $ +// CHECK: explicit_copy_addr [[ACCESS]] to [init] [[TEMP]] +// CHECK: [[FUNC:%.*]] = witness_method $T, #P.consumeFunc : (consuming Self) -> () -> () : $@convention(witness_method: P) <τ_0_0 where τ_0_0 : P> (@in τ_0_0) -> () +// CHECK: apply [[FUNC]]<(T)>([[TEMP]]) +// +// Calling borrowing func. +// CHECK: [[ACCESS:%.*]] = begin_access [read] [unknown] [[ARG]] +// CHECK: [[TEMP:%.*]] = alloc_stack $ +// CHECK: explicit_copy_addr [[ACCESS]] to [init] [[TEMP]] +// CHECK: end_access [[ACCESS]] +// CHECK: [[FUNC:%.*]] = witness_method $T, #P.borrowingFunc : (Self) -> () -> () : $@convention(witness_method: P) <τ_0_0 where τ_0_0 : P> (@in_guaranteed τ_0_0) -> () +// CHECK: apply [[FUNC]]<(T)>([[TEMP]]) +// +// Calling borrowing computed getter +// CHECK: [[ACCESS:%.*]] = begin_access [read] [unknown] [[ARG]] +// CHECK: [[TEMP:%.*]] = alloc_stack $ +// CHECK: explicit_copy_addr [[ACCESS]] to [init] [[TEMP]] +// CHECK: [[TEMP2:%.*]] = alloc_stack $ +// CHECK: copy_addr [[TEMP]] to [init] [[TEMP2]] +// CHECK: [[FUNC:%.*]] = witness_method $T, #P.computedK!getter : (Self) -> () -> Klass : $@convention(witness_method: P) <τ_0_0 where τ_0_0 : P> (@in_guaranteed τ_0_0) -> @owned Klass +// CHECK: apply [[FUNC]]<(T)>([[TEMP2]]) +// +// Consuming computed getter. +// CHECK: [[ACCESS:%.*]] = begin_access [read] [unknown] [[ARG]] +// CHECK: [[TEMP:%.*]] = alloc_stack $ +// CHECK: explicit_copy_addr [[ACCESS]] to [init] [[TEMP]] +// CHECK: [[TEMP2:%.*]] = alloc_stack $ +// CHECK: copy_addr [[TEMP]] to [init] [[TEMP2]] +// CHECK: [[FUNC:%.*]] = witness_method $T, #P.consumingComputedK!getter : (__owned Self) -> () -> Klass : $@convention(witness_method: P) <τ_0_0 where τ_0_0 : P> (@in τ_0_0) -> @owned Klass +// CHECK: apply [[FUNC]]<(T)>([[TEMP2]]) +// CHECK: } // end sil function '$s9copy_expr36testCallMethodOnAddressOnlyInOutCopyyyxzAA1PRzlF' +func testCallMethodOnAddressOnlyInOutCopy(_ x: inout T) { + (copy x).consumeFunc() + (copy x).borrowingFunc() + _ = (copy x).computedK + _ = (copy x).consumingComputedK +} diff --git a/test/Sema/copy_expr.swift b/test/Sema/copy_expr.swift new file mode 100644 index 0000000000000..06e98468cfcbb --- /dev/null +++ b/test/Sema/copy_expr.swift @@ -0,0 +1,97 @@ +// RUN: %target-typecheck-verify-swift -disable-availability-checking -enable-experimental-feature NoImplicitCopy + +class Klass { + var k: Klass? = nil +} + +var global: Int = 5 +func testGlobal() { + let _ = copy global +} + +func testLet() { + let t = String() + let _ = copy t +} + +func testVar() { + var t = String() + t = String() + let _ = copy t +} + +func testExprFailureLet() { + let t = 5 + // Next line is parsed as move(t) + t + let _ = copy t + t +} + +func testExprFailureVar() { + var t = 5 + t = 5 + // Next line is parsed as move(t) + t + let _ = copy t + t +} + +func letAddressOnly(_ v: T) { + let t = v + let _ = copy t +} + +struct StructWithField { + var k: Klass? = nil + var computedK: Klass? { nil } +} + +func testLetStructAccessField() { + let t = StructWithField() + let _ = copy t.k // expected-error {{'copy' can only be applied to lvalues}} +} + +func testLetStructAccessComputedField() { + let t = StructWithField() + let _ = copy t.computedK // expected-error {{'copy' can only be applied to lvalues}} +} + +func testVarStructAccessField() { + var t = StructWithField() + t = StructWithField() + let _ = copy t.k // expected-error {{'copy' can only be applied to lvalues}} +} + +func testLetClassAccessField() { + let t = Klass() + let _ = copy t.k // expected-error {{'copy' can only be applied to lvalues}} +} + +func testVarClassAccessField() { + var t = Klass() + t = Klass() + let _ = copy t.k // expected-error {{'copy' can only be applied to lvalues}} +} + +struct MoveOnly : ~Copyable {} + +func testNoMoveOnlyCopy(_ x: borrowing MoveOnly) { + let _ = copy x // expected-error {{'copy' cannot be applied to noncopyable types}} +} + +func testCopyResultImmutable() { + class Klass {} + + struct Test { + var k = Klass() + mutating func mutatingTest() {} + func borrowingTest() {} + consuming func consumingTest() {} + } + + var t = Test() + t.mutatingTest() + copy t.borrowingTest() // expected-error {{'copy' can only be applied to lvalues}} + (copy t).borrowingTest() + (copy t).consumingTest() + (copy t).mutatingTest() // expected-error {{cannot use mutating member on immutable value of type 'Test'}} + (copy t) = Test() // expected-error {{cannot assign to immutable expression of type 'Test'}} + copy t = Test() // expected-error {{cannot assign to immutable expression of type 'Test'}} +} diff --git a/test/Sema/copy_expr_noimplicit_copy.swift b/test/Sema/copy_expr_noimplicit_copy.swift new file mode 100644 index 0000000000000..ea906adfabd87 --- /dev/null +++ b/test/Sema/copy_expr_noimplicit_copy.swift @@ -0,0 +1,11 @@ +// RUN: %target-typecheck-verify-swift -disable-availability-checking -enable-experimental-feature NoImplicitCopy + +class Klass {} + +func consumeKlass(_ x: __owned Klass) {} + +func testNoImplicitCopyWorks() { + @_noImplicitCopy let x = Klass() + let _ = copy x + consumeKlass(x) +} From a155209e4ffc1d08fc0fb0b4af206a03034a0235 Mon Sep 17 00:00:00 2001 From: Michael Gottesman Date: Wed, 17 May 2023 14:23:40 -0700 Subject: [PATCH 2/5] [consume] Fix consume parsing so we handle code completion and dollaridentifiers correctly. This resulted from explorations around implementing the copy expr. rdar://109479131 --- lib/Parse/ParseExpr.cpp | 5 +++-- test/IDE/move_expr.swift | 7 +++++++ test/Parse/move_expr.swift | 19 +++++++++++++++++++ 3 files changed, 29 insertions(+), 2 deletions(-) create mode 100644 test/IDE/move_expr.swift diff --git a/lib/Parse/ParseExpr.cpp b/lib/Parse/ParseExpr.cpp index 2e622e5f55324..98d3b794432a7 100644 --- a/lib/Parse/ParseExpr.cpp +++ b/lib/Parse/ParseExpr.cpp @@ -409,14 +409,15 @@ ParserResult Parser::parseExprSequenceElement(Diag<> message, } if (Tok.isContextualKeyword("consume") - && peekToken().isAny(tok::identifier, tok::kw_self) + && peekToken().isAny(tok::identifier, tok::kw_self, tok::dollarident, + tok::code_complete) && !peekToken().isAtStartOfLine()) { Tok.setKind(tok::contextual_keyword); SourceLoc consumeLoc = consumeToken(); ParserResult sub = parseExprSequenceElement(diag::expected_expr_after_move, isExprBasic); - if (!sub.hasCodeCompletion() && !sub.isNull()) { + if (!sub.isNull()) { sub = makeParserResult(new (Context) MoveExpr(consumeLoc, sub.get())); } return sub; diff --git a/test/IDE/move_expr.swift b/test/IDE/move_expr.swift new file mode 100644 index 0000000000000..7e1fcd10b8a68 --- /dev/null +++ b/test/IDE/move_expr.swift @@ -0,0 +1,7 @@ +// RUN: %empty-directory(%t) +// RUN: %target-swift-ide-test -batch-code-completion -source-filename %s -filecheck %raw-FileCheck -completion-output-dir %t + +func test(myParam: Int) { + consume #^CONSUME^# + // CONSUME: Decl[LocalVar]/Local: myParam[#Int#]; name=myParam +} diff --git a/test/Parse/move_expr.swift b/test/Parse/move_expr.swift index 9639713fa6ee5..d8e73267ed5da 100644 --- a/test/Parse/move_expr.swift +++ b/test/Parse/move_expr.swift @@ -81,3 +81,22 @@ struct Foo { _ = consume $wrapperTest // expected-error{{can only be applied to lvalues}} } } + +func testParseConsumeWithDollarIdentifier() { + class Klass {} + let f: (Klass) -> () = { + let _ = consume $0 + } + _ = f +} + +class ParentKlass {} +class ChildKlass : ParentKlass {} + +func testAsBindingVariableInSwitch(_ x: ChildKlass) { + switch x { + case let consume as ParentKlass: + _ = consume + break + } +} From fd25cf379a65ce6261b32980683ae0428f507443 Mon Sep 17 00:00:00 2001 From: Michael Gottesman Date: Wed, 17 May 2023 14:30:41 -0700 Subject: [PATCH 3/5] Rename MoveExpr -> ConsumeExpr to reflect the final name. NFC. --- include/swift/AST/DiagnosticsSema.def | 2 +- include/swift/AST/Expr.h | 29 +++++++++++++++------------ include/swift/AST/ExprNodes.def | 2 +- lib/AST/ASTDumper.cpp | 4 ++-- lib/AST/ASTPrinter.cpp | 4 ++-- lib/AST/Expr.cpp | 6 +++--- lib/Parse/ParseExpr.cpp | 4 ++-- lib/SILGen/SILGenExpr.cpp | 4 ++-- lib/SILGen/SILGenLValue.cpp | 8 ++++---- lib/Sema/MiscDiagnostics.cpp | 12 +++++------ 10 files changed, 39 insertions(+), 36 deletions(-) diff --git a/include/swift/AST/DiagnosticsSema.def b/include/swift/AST/DiagnosticsSema.def index 8553d3e4115ec..18e8d747e763b 100644 --- a/include/swift/AST/DiagnosticsSema.def +++ b/include/swift/AST/DiagnosticsSema.def @@ -6992,7 +6992,7 @@ ERROR(concurrency_task_to_thread_model_global_actor_annotation,none, ERROR(moveOnly_not_allowed_here,none, "'moveOnly' only applies to structs or enums", ()) -ERROR(move_expression_not_passed_lvalue,none, +ERROR(consume_expression_not_passed_lvalue,none, "'consume' can only be applied to lvalues", ()) ERROR(borrow_expression_not_passed_lvalue,none, "'borrow' can only be applied to lvalues", ()) diff --git a/include/swift/AST/Expr.h b/include/swift/AST/Expr.h index 23a6850cd5121..a4729c56f611b 100644 --- a/include/swift/AST/Expr.h +++ b/include/swift/AST/Expr.h @@ -2050,31 +2050,34 @@ class AwaitExpr final : public IdentityExpr { } }; -/// MoveExpr - A 'move' surrounding an lvalue expression marking the lvalue as -/// needing to be moved. +/// ConsumeExpr - A 'consume' surrounding an lvalue expression marking the +/// lvalue as needing to be moved. /// /// getSemanticsProvidingExpr() looks through this because it doesn't /// provide the value and only very specific clients care where the /// 'move' was written. -class MoveExpr final : public IdentityExpr { - SourceLoc MoveLoc; +class ConsumeExpr final : public IdentityExpr { + SourceLoc ConsumeLoc; public: - MoveExpr(SourceLoc moveLoc, Expr *sub, Type type = Type(), - bool implicit = false) - : IdentityExpr(ExprKind::Move, sub, type, implicit), MoveLoc(moveLoc) {} + ConsumeExpr(SourceLoc consumeLoc, Expr *sub, Type type = Type(), + bool implicit = false) + : IdentityExpr(ExprKind::Consume, sub, type, implicit), + ConsumeLoc(consumeLoc) {} - static MoveExpr *createImplicit(ASTContext &ctx, SourceLoc moveLoc, Expr *sub, - Type type = Type()) { - return new (ctx) MoveExpr(moveLoc, sub, type, /*implicit=*/true); + static ConsumeExpr *createImplicit(ASTContext &ctx, SourceLoc moveLoc, + Expr *sub, Type type = Type()) { + return new (ctx) ConsumeExpr(moveLoc, sub, type, /*implicit=*/true); } - SourceLoc getLoc() const { return MoveLoc; } + SourceLoc getLoc() const { return ConsumeLoc; } - SourceLoc getStartLoc() const { return MoveLoc; } + SourceLoc getStartLoc() const { return ConsumeLoc; } SourceLoc getEndLoc() const { return getSubExpr()->getEndLoc(); } - static bool classof(const Expr *e) { return e->getKind() == ExprKind::Move; } + static bool classof(const Expr *e) { + return e->getKind() == ExprKind::Consume; + } }; /// CopyExpr - A 'copy' surrounding an lvalue expression marking the lvalue as diff --git a/include/swift/AST/ExprNodes.def b/include/swift/AST/ExprNodes.def index c09a2f654f100..b2f97002dc3c9 100644 --- a/include/swift/AST/ExprNodes.def +++ b/include/swift/AST/ExprNodes.def @@ -107,7 +107,7 @@ ABSTRACT_EXPR(Identity, Expr) EXPR(Paren, IdentityExpr) EXPR(DotSelf, IdentityExpr) EXPR(Await, IdentityExpr) - EXPR(Move, IdentityExpr) + EXPR(Consume, IdentityExpr) EXPR(Borrow, IdentityExpr) EXPR(UnresolvedMemberChainResult, IdentityExpr) EXPR_RANGE(Identity, Paren, UnresolvedMemberChainResult) diff --git a/lib/AST/ASTDumper.cpp b/lib/AST/ASTDumper.cpp index 7461bb31499cd..2f5aa8b9e61e5 100644 --- a/lib/AST/ASTDumper.cpp +++ b/lib/AST/ASTDumper.cpp @@ -2194,8 +2194,8 @@ class PrintExpr : public ExprVisitor { printRec(E->getSubExpr()); PrintWithColorRAII(OS, ParenthesisColor) << ')'; } - void visitMoveExpr(MoveExpr *E) { - printCommon(E, "move_expr"); + void visitConsumeExpr(ConsumeExpr *E) { + printCommon(E, "consume_expr"); OS << '\n'; printRec(E->getSubExpr()); PrintWithColorRAII(OS, ParenthesisColor) << ')'; diff --git a/lib/AST/ASTPrinter.cpp b/lib/AST/ASTPrinter.cpp index 10f77b76f30d7..00cb4a6d492d8 100644 --- a/lib/AST/ASTPrinter.cpp +++ b/lib/AST/ASTPrinter.cpp @@ -4995,8 +4995,8 @@ void PrintAST::visitAwaitExpr(AwaitExpr *expr) { visit(expr->getSubExpr()); } -void PrintAST::visitMoveExpr(MoveExpr *expr) { - Printer << "move "; +void PrintAST::visitConsumeExpr(ConsumeExpr *expr) { + Printer << "consume "; visit(expr->getSubExpr()); } diff --git a/lib/AST/Expr.cpp b/lib/AST/Expr.cpp index 733af464ecd42..674d2371face0 100644 --- a/lib/AST/Expr.cpp +++ b/lib/AST/Expr.cpp @@ -370,7 +370,7 @@ ConcreteDeclRef Expr::getReferencedDecl(bool stopAtParenExpr) const { PASS_THROUGH_REFERENCE(UnresolvedMemberChainResult, getSubExpr); PASS_THROUGH_REFERENCE(DotSelf, getSubExpr); PASS_THROUGH_REFERENCE(Await, getSubExpr); - PASS_THROUGH_REFERENCE(Move, getSubExpr); + PASS_THROUGH_REFERENCE(Consume, getSubExpr); PASS_THROUGH_REFERENCE(Copy, getSubExpr); PASS_THROUGH_REFERENCE(Borrow, getSubExpr); PASS_THROUGH_REFERENCE(Try, getSubExpr); @@ -743,7 +743,7 @@ bool Expr::canAppendPostfixExpression(bool appendingPostfixOperator) const { return true; case ExprKind::Await: - case ExprKind::Move: + case ExprKind::Consume: case ExprKind::Copy: case ExprKind::Borrow: case ExprKind::Try: @@ -936,7 +936,7 @@ bool Expr::isValidParentOfTypeExpr(Expr *typeExpr) const { case ExprKind::Sequence: case ExprKind::Paren: case ExprKind::Await: - case ExprKind::Move: + case ExprKind::Consume: case ExprKind::Copy: case ExprKind::Borrow: case ExprKind::UnresolvedMemberChainResult: diff --git a/lib/Parse/ParseExpr.cpp b/lib/Parse/ParseExpr.cpp index 98d3b794432a7..d2d660cf94ccb 100644 --- a/lib/Parse/ParseExpr.cpp +++ b/lib/Parse/ParseExpr.cpp @@ -418,7 +418,7 @@ ParserResult Parser::parseExprSequenceElement(Diag<> message, ParserResult sub = parseExprSequenceElement(diag::expected_expr_after_move, isExprBasic); if (!sub.isNull()) { - sub = makeParserResult(new (Context) MoveExpr(consumeLoc, sub.get())); + sub = makeParserResult(new (Context) ConsumeExpr(consumeLoc, sub.get())); } return sub; } @@ -448,7 +448,7 @@ ParserResult Parser::parseExprSequenceElement(Diag<> message, ParserResult sub = parseExprSequenceElement(diag::expected_expr_after_move, isExprBasic); if (!sub.hasCodeCompletion() && !sub.isNull()) { - sub = makeParserResult(new (Context) MoveExpr(awaitLoc, sub.get())); + sub = makeParserResult(new (Context) ConsumeExpr(awaitLoc, sub.get())); } return sub; } diff --git a/lib/SILGen/SILGenExpr.cpp b/lib/SILGen/SILGenExpr.cpp index c340f20d64b51..afc34261cd4d8 100644 --- a/lib/SILGen/SILGenExpr.cpp +++ b/lib/SILGen/SILGenExpr.cpp @@ -551,7 +551,7 @@ namespace { LinearFunctionExtractOriginalExpr *E, SGFContext C); RValue visitLinearToDifferentiableFunctionExpr( LinearToDifferentiableFunctionExpr *E, SGFContext C); - RValue visitMoveExpr(MoveExpr *E, SGFContext C); + RValue visitConsumeExpr(ConsumeExpr *E, SGFContext C); RValue visitCopyExpr(CopyExpr *E, SGFContext C); RValue visitMacroExpansionExpr(MacroExpansionExpr *E, SGFContext C); }; @@ -6115,7 +6115,7 @@ RValue RValueEmitter::visitErrorExpr(ErrorExpr *E, SGFContext C) { llvm::report_fatal_error("Found an ErrorExpr but didn't emit an error?"); } -RValue RValueEmitter::visitMoveExpr(MoveExpr *E, SGFContext C) { +RValue RValueEmitter::visitConsumeExpr(ConsumeExpr *E, SGFContext C) { auto *subExpr = cast(E->getSubExpr()); auto subASTType = subExpr->getType()->getCanonicalType(); diff --git a/lib/SILGen/SILGenLValue.cpp b/lib/SILGen/SILGenLValue.cpp index d4cf44b30e5ae..c8560a618910a 100644 --- a/lib/SILGen/SILGenLValue.cpp +++ b/lib/SILGen/SILGenLValue.cpp @@ -332,8 +332,8 @@ class LLVM_LIBRARY_VISIBILITY SILGenLValue LValue visitKeyPathApplicationExpr(KeyPathApplicationExpr *e, SGFAccessKind accessKind, LValueOptions options); - LValue visitMoveExpr(MoveExpr *e, SGFAccessKind accessKind, - LValueOptions options); + LValue visitConsumeExpr(ConsumeExpr *e, SGFAccessKind accessKind, + LValueOptions options); LValue visitCopyExpr(CopyExpr *e, SGFAccessKind accessKind, LValueOptions options); LValue visitABISafeConversionExpr(ABISafeConversionExpr *e, @@ -4004,8 +4004,8 @@ LValue SILGenLValue::visitInOutExpr(InOutExpr *e, SGFAccessKind accessKind, return visitRec(e->getSubExpr(), accessKind, options); } -LValue SILGenLValue::visitMoveExpr(MoveExpr *e, SGFAccessKind accessKind, - LValueOptions options) { +LValue SILGenLValue::visitConsumeExpr(ConsumeExpr *e, SGFAccessKind accessKind, + LValueOptions options) { // Do formal evaluation of the base l-value. LValue baseLV = visitRec(e->getSubExpr(), SGFAccessKind::ReadWrite, options.forComputedBaseLValue()); diff --git a/lib/Sema/MiscDiagnostics.cpp b/lib/Sema/MiscDiagnostics.cpp index b3ef2ca3aeaa4..a410b2252c259 100644 --- a/lib/Sema/MiscDiagnostics.cpp +++ b/lib/Sema/MiscDiagnostics.cpp @@ -340,8 +340,8 @@ static void diagSyntacticUseRestrictions(const Expr *E, const DeclContext *DC, // Diagnose move expression uses where the sub expression is not a declref // expr. - if (auto *moveExpr = dyn_cast(E)) { - checkMoveExpr(moveExpr); + if (auto *consumeExpr = dyn_cast(E)) { + checkConsumeExpr(consumeExpr); } // Diagnose copy expression uses where the sub expression is not a declref @@ -421,10 +421,10 @@ static void diagSyntacticUseRestrictions(const Expr *E, const DeclContext *DC, } } - void checkMoveExpr(MoveExpr *moveExpr) { - if (!isa(moveExpr->getSubExpr())) { - Ctx.Diags.diagnose(moveExpr->getLoc(), - diag::move_expression_not_passed_lvalue); + void checkConsumeExpr(ConsumeExpr *consumeExpr) { + if (!isa(consumeExpr->getSubExpr())) { + Ctx.Diags.diagnose(consumeExpr->getLoc(), + diag::consume_expression_not_passed_lvalue); } } From 67fcb1c86e541652aef5ddc4b2cc37183dbeedc9 Mon Sep 17 00:00:00 2001 From: Michael Gottesman Date: Wed, 17 May 2023 15:27:17 -0700 Subject: [PATCH 4/5] [consume-operator] Change consume so that its result is not considered an lvalue. Before this change it was possible to: 1. Call mutating methods on a consume result. 2. assign into a consume (e.x.: var x = ...; (consume x) = value. From an implementation perspective, this involved just taking the logic I already used for the CopyExpr and reusing it for ConsumeExpr with some small tweaks. rdar://109479440 --- include/swift/AST/Expr.h | 14 +++--- include/swift/AST/ExprNodes.def | 2 +- lib/AST/ASTWalker.cpp | 8 ++++ lib/SILGen/SILGenExpr.cpp | 41 ++++++++++++----- lib/Sema/CSApply.cpp | 21 +++++++++ lib/Sema/CSGen.cpp | 10 ++++ lib/Sema/MiscDiagnostics.cpp | 5 +- test/SILGen/consume_operator.swift | 73 ++++++++++++++++++++++++++++++ test/Sema/move_expr.swift | 22 ++++++++- 9 files changed, 175 insertions(+), 21 deletions(-) create mode 100644 test/SILGen/consume_operator.swift diff --git a/include/swift/AST/Expr.h b/include/swift/AST/Expr.h index a4729c56f611b..846fd7c451490 100644 --- a/include/swift/AST/Expr.h +++ b/include/swift/AST/Expr.h @@ -2052,17 +2052,14 @@ class AwaitExpr final : public IdentityExpr { /// ConsumeExpr - A 'consume' surrounding an lvalue expression marking the /// lvalue as needing to be moved. -/// -/// getSemanticsProvidingExpr() looks through this because it doesn't -/// provide the value and only very specific clients care where the -/// 'move' was written. -class ConsumeExpr final : public IdentityExpr { +class ConsumeExpr final : public Expr { + Expr *SubExpr; SourceLoc ConsumeLoc; public: ConsumeExpr(SourceLoc consumeLoc, Expr *sub, Type type = Type(), bool implicit = false) - : IdentityExpr(ExprKind::Consume, sub, type, implicit), + : Expr(ExprKind::Consume, implicit, type), SubExpr(sub), ConsumeLoc(consumeLoc) {} static ConsumeExpr *createImplicit(ASTContext &ctx, SourceLoc moveLoc, @@ -2072,7 +2069,10 @@ class ConsumeExpr final : public IdentityExpr { SourceLoc getLoc() const { return ConsumeLoc; } - SourceLoc getStartLoc() const { return ConsumeLoc; } + Expr *getSubExpr() const { return SubExpr; } + void setSubExpr(Expr *E) { SubExpr = E; } + + SourceLoc getStartLoc() const { return getLoc(); } SourceLoc getEndLoc() const { return getSubExpr()->getEndLoc(); } static bool classof(const Expr *e) { diff --git a/include/swift/AST/ExprNodes.def b/include/swift/AST/ExprNodes.def index b2f97002dc3c9..7adb8d6ca7edf 100644 --- a/include/swift/AST/ExprNodes.def +++ b/include/swift/AST/ExprNodes.def @@ -107,11 +107,11 @@ ABSTRACT_EXPR(Identity, Expr) EXPR(Paren, IdentityExpr) EXPR(DotSelf, IdentityExpr) EXPR(Await, IdentityExpr) - EXPR(Consume, IdentityExpr) EXPR(Borrow, IdentityExpr) EXPR(UnresolvedMemberChainResult, IdentityExpr) EXPR_RANGE(Identity, Paren, UnresolvedMemberChainResult) EXPR(Copy, Expr) +EXPR(Consume, Expr) ABSTRACT_EXPR(AnyTry, Expr) EXPR(Try, AnyTryExpr) EXPR(ForceTry, AnyTryExpr) diff --git a/lib/AST/ASTWalker.cpp b/lib/AST/ASTWalker.cpp index 25dc8b641b410..5427118c04f44 100644 --- a/lib/AST/ASTWalker.cpp +++ b/lib/AST/ASTWalker.cpp @@ -699,6 +699,14 @@ class Traversal : public ASTVisitorgetSubExpr())) { + E->setSubExpr(subExpr); + return E; + } + return nullptr; + } + Expr *visitTupleExpr(TupleExpr *E) { for (unsigned i = 0, e = E->getNumElements(); i != e; ++i) if (E->getElement(i)) { diff --git a/lib/SILGen/SILGenExpr.cpp b/lib/SILGen/SILGenExpr.cpp index afc34261cd4d8..58247913f2af8 100644 --- a/lib/SILGen/SILGenExpr.cpp +++ b/lib/SILGen/SILGenExpr.cpp @@ -6116,27 +6116,47 @@ RValue RValueEmitter::visitErrorExpr(ErrorExpr *E, SGFContext C) { } RValue RValueEmitter::visitConsumeExpr(ConsumeExpr *E, SGFContext C) { - auto *subExpr = cast(E->getSubExpr()); + auto *subExpr = E->getSubExpr(); auto subASTType = subExpr->getType()->getCanonicalType(); - auto subType = SGF.getLoweredType(subASTType); + if (auto *li = dyn_cast(subExpr)) { + FormalEvaluationScope writeback(SGF); + LValue lv = + SGF.emitLValue(li->getSubExpr(), SGFAccessKind::ReadWrite); + auto address = SGF.emitAddressOfLValue(subExpr, std::move(lv)); + auto optTemp = SGF.emitTemporary(E, SGF.getTypeLowering(subType)); + SILValue toAddr = optTemp->getAddressForInPlaceInitialization(SGF, E); + if (address.getType().isMoveOnly()) { + SGF.B.createCopyAddr(subExpr, address.getLValueAddress(), toAddr, IsNotTake, + IsInitialization); + } else { + SGF.B.createMarkUnresolvedMoveAddr(subExpr, address.getLValueAddress(), toAddr); + } + optTemp->finishInitialization(SGF); + + if (subType.isLoadable(SGF.F)) { + ManagedValue value = SGF.B.createLoadTake(E, optTemp->getManagedAddress()); + if (value.getType().isTrivial(SGF.F)) + return RValue(SGF, {value}, subType.getASTType()); + return RValue(SGF, {value}, subType.getASTType()); + } + + return RValue(SGF, {optTemp->getManagedAddress()}, subType.getASTType()); + } + if (subType.isLoadable(SGF.F)) { - auto mv = SGF.emitRValue(subExpr).getAsSingleValue(SGF, subExpr); + ManagedValue mv = SGF.emitRValue(subExpr).getAsSingleValue(SGF, subExpr); if (mv.getType().isTrivial(SGF.F)) return RValue(SGF, {mv}, subType.getASTType()); mv = SGF.B.createMoveValue(E, mv); - auto *movedValue = cast(mv.getValue()); - movedValue->setAllowsDiagnostics(true /*set allows diagnostics*/); + // Set the flag so we check this. + cast(mv.getValue())->setAllowsDiagnostics(true); return RValue(SGF, {mv}, subType.getASTType()); } // If we aren't loadable, then create a temporary initialization and - // mark_unresolved_move into that if we have a copyable type if we have a move - // only, just add a copy_addr init. - // - // The reason why we do this is that we only use mark_unresolved_move_addr and - // the move operator checker for copyable values. + // explicit_copy_addr into that. std::unique_ptr optTemp; optTemp = SGF.emitTemporary(E, SGF.getTypeLowering(subType)); SILValue toAddr = optTemp->getAddressForInPlaceInitialization(SGF, E); @@ -6163,7 +6183,6 @@ RValue RValueEmitter::visitCopyExpr(CopyExpr *E, SGFContext C) { auto subType = SGF.getLoweredType(subASTType); if (auto *li = dyn_cast(subExpr)) { - FormalEvaluationScope writeback(SGF); LValue lv = SGF.emitLValue(li->getSubExpr(), SGFAccessKind::BorrowedAddressRead); diff --git a/lib/Sema/CSApply.cpp b/lib/Sema/CSApply.cpp index a934233397ec3..c255fbd47887e 100644 --- a/lib/Sema/CSApply.cpp +++ b/lib/Sema/CSApply.cpp @@ -3546,6 +3546,27 @@ namespace { return expr; } + Expr *visitConsumeExpr(ConsumeExpr *expr) { + auto toType = simplifyType(cs.getType(expr)); + cs.setType(expr, toType); + + auto *subExpr = expr->getSubExpr(); + auto type = simplifyType(cs.getType(subExpr)); + + // Let's load the value associated with this consume. + if (type->hasLValueType()) { + subExpr = coerceToType(subExpr, type->getRValueType(), + cs.getConstraintLocator(subExpr)); + + if (!subExpr) + return nullptr; + } + + expr->setSubExpr(subExpr); + + return expr; + } + Expr *visitAnyTryExpr(AnyTryExpr *expr) { auto *subExpr = expr->getSubExpr(); auto type = simplifyType(cs.getType(subExpr)); diff --git a/lib/Sema/CSGen.cpp b/lib/Sema/CSGen.cpp index 3f094656d8859..4c9de01f75a0f 100644 --- a/lib/Sema/CSGen.cpp +++ b/lib/Sema/CSGen.cpp @@ -1899,6 +1899,16 @@ namespace { return valueTy; } + Type visitConsumeExpr(ConsumeExpr *expr) { + auto valueTy = CS.createTypeVariable(CS.getConstraintLocator(expr), + TVO_PrefersSubtypeBinding | + TVO_CanBindToNoEscape); + CS.addConstraint(ConstraintKind::Equal, valueTy, + CS.getType(expr->getSubExpr()), + CS.getConstraintLocator(expr)); + return valueTy; + } + Type visitAnyTryExpr(AnyTryExpr *expr) { return CS.getType(expr->getSubExpr()); } diff --git a/lib/Sema/MiscDiagnostics.cpp b/lib/Sema/MiscDiagnostics.cpp index a410b2252c259..1942131aa5f61 100644 --- a/lib/Sema/MiscDiagnostics.cpp +++ b/lib/Sema/MiscDiagnostics.cpp @@ -422,7 +422,10 @@ static void diagSyntacticUseRestrictions(const Expr *E, const DeclContext *DC, } void checkConsumeExpr(ConsumeExpr *consumeExpr) { - if (!isa(consumeExpr->getSubExpr())) { + auto *subExpr = consumeExpr->getSubExpr(); + if (auto *li = dyn_cast(subExpr)) + subExpr = li->getSubExpr(); + if (!isa(subExpr)) { Ctx.Diags.diagnose(consumeExpr->getLoc(), diag::consume_expression_not_passed_lvalue); } diff --git a/test/SILGen/consume_operator.swift b/test/SILGen/consume_operator.swift new file mode 100644 index 0000000000000..225af458759f7 --- /dev/null +++ b/test/SILGen/consume_operator.swift @@ -0,0 +1,73 @@ +// RUN: %target-swift-emit-silgen -enable-copy-propagation=requested-passes-only -module-name consume %s -disable-objc-attr-requires-foundation-module | %FileCheck %s + +class Klass {} + +protocol P { + static var value: Self { get } +} + +// CHECK-LABEL: sil hidden [ossa] @$s7consume15testLoadableLetyyF : $@convention(thin) () -> () { +// CHECK: [[ORIG_VALUE:%.*]] = apply {{%.*}}({{%.*}}) : $@convention(method) (@thick Klass.Type) -> @owned Klass +// CHECK: [[BORROWED_VALUE:%.*]] = begin_borrow [lexical] [[ORIG_VALUE]] +// CHECK: [[COPY:%.*]] = copy_value [[BORROWED_VALUE:%.*]] +// CHECK: move_value [allows_diagnostics] [[COPY]] +// CHECK: } // end sil function '$s7consume15testLoadableLetyyF' +func testLoadableLet() { + let k = Klass() + let _ = consume k +} + +// CHECK-LABEL: sil hidden [ossa] @$s7consume15testLoadableVaryyF : $@convention(thin) () -> () { +// CHECK: [[BOX:%.*]] = alloc_box $ +// CHECK: [[BORROW_BOX:%.*]] = begin_borrow [lexical] [[BOX]] +// CHECK: [[PROJECT:%.*]] = project_box [[BORROW_BOX]] +// +// CHECK: [[ACCESS:%.*]] = begin_access [modify] [unknown] [[PROJECT]] +// CHECK: assign {{%.*}} to [[ACCESS]] +// CHECK: end_access [[ACCESS]] +// +// CHECK: [[ACCESS:%.*]] = begin_access [modify] [unknown] [[PROJECT]] +// CHECK: [[STACK:%.*]] = alloc_stack $Klass +// CHECK: mark_unresolved_move_addr [[ACCESS]] to [[STACK]] +// CHECK: [[VALUE:%.*]] = load [take] [[STACK]] +// CHECK: end_access [[ACCESS]] +// +// CHECK: } // end sil function '$s7consume15testLoadableVaryyF' +func testLoadableVar() { + var k = Klass() + k = Klass() + let _ = consume k +} + +// CHECK-LABEL: sil hidden [ossa] @$s7consume18testAddressOnlyLetyyxmAA1PRzlF : $@convention(thin) (@thick T.Type) -> () { +// CHECK: [[BOX:%.*]] = alloc_stack [lexical] $T +// +// CHECK: [[STACK:%.*]] = alloc_stack $T +// CHECK: mark_unresolved_move_addr [[BOX]] to [[STACK]] +// +// CHECK: } // end sil function '$s7consume18testAddressOnlyLetyyxmAA1PRzlF' +func testAddressOnlyLet(_ t: T.Type) { + let k = T.value + let _ = consume k +} + +// CHECK-LABEL: sil hidden [ossa] @$s7consume18testAddressOnlyVaryyxmAA1PRzlF : $@convention(thin) (@thick T.Type) -> () { +// CHECK: [[BOX:%.*]] = alloc_box $ +// CHECK: [[BORROW_BOX:%.*]] = begin_borrow [lexical] [[BOX]] +// CHECK: [[PROJECT:%.*]] = project_box [[BORROW_BOX]] +// +// CHECK: [[ACCESS:%.*]] = begin_access [modify] [unknown] [[PROJECT]] +// CHECK: copy_addr [take] {{%.*}} to [[ACCESS]] +// CHECK: end_access [[ACCESS]] +// +// CHECK: [[ACCESS:%.*]] = begin_access [modify] [unknown] [[PROJECT]] +// CHECK: [[STACK:%.*]] = alloc_stack $ +// CHECK: mark_unresolved_move_addr [[ACCESS]] to [[STACK]] +// CHECK: end_access [[ACCESS]] +// +// CHECK: } // end sil function '$s7consume18testAddressOnlyVaryyxmAA1PRzlF' +func testAddressOnlyVar(_ t: T.Type) { + var k = T.value + k = T.value + let _ = consume k +} diff --git a/test/Sema/move_expr.swift b/test/Sema/move_expr.swift index 967cf1cc6a898..0c8a1a88f38e9 100644 --- a/test/Sema/move_expr.swift +++ b/test/Sema/move_expr.swift @@ -1,4 +1,4 @@ -// RUN: %target-typecheck-verify-swift -disable-availability-checking +// RUN: %target-typecheck-verify-swift -disable-availability-checking -enable-experimental-feature NoImplicitCopy class Klass { var k: Klass? = nil @@ -63,3 +63,23 @@ func testVarClassAccessField() { t = Klass() let _ = consume t.k // expected-error {{'consume' can only be applied to lvalues}} } + +func testConsumeResultImmutable() { + class Klass {} + + struct Test { + var k = Klass() + mutating func mutatingTest() {} + func borrowingTest() {} + consuming func consumingTest() {} + } + + var t = Test() + t.mutatingTest() + consume t.borrowingTest() // expected-error {{'consume' can only be applied to lvalues}} + (consume t).borrowingTest() + (consume t).consumingTest() + (consume t).mutatingTest() // expected-error {{cannot use mutating member on immutable value of type 'Test'}} + (consume t) = Test() // expected-error {{cannot assign to immutable expression of type 'Test'}} + consume t = Test() // expected-error {{cannot assign to immutable expression of type 'Test'}} +} From 2a07d762a0042b254093da2eb1ea2b76943b9bd7 Mon Sep 17 00:00:00 2001 From: Michael Gottesman Date: Wed, 17 May 2023 22:40:25 -0700 Subject: [PATCH 5/5] [move-only] Add a test that shows the previous commit allows consume on var without errors to work. I found that in the given case, an extra allocation was being emitted causing us to in the success case to have an extra copy. In the previous commit, as part of updating the codegen for consume, I also ensured that we wouldn't have that extra allocation any more by handling the lvalue directly. rdar://109222496 --- .../SILOptimizer/moveonly_addresschecker_diagnostics.swift | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/test/SILOptimizer/moveonly_addresschecker_diagnostics.swift b/test/SILOptimizer/moveonly_addresschecker_diagnostics.swift index d23cefc834286..0700a6ea57fb4 100644 --- a/test/SILOptimizer/moveonly_addresschecker_diagnostics.swift +++ b/test/SILOptimizer/moveonly_addresschecker_diagnostics.swift @@ -3749,6 +3749,13 @@ func moveOperatorTest2(_ k: consuming Klass) { let _ = k3 } +// No diagnostics here. +func moveOperatorTestSuccess() { + var k = Klass() + k = Klass() + let _ = consume k +} + ///////////////////////////////////////// // Black hole initialization test case// /////////////////////////////////////////