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..18e8d747e763b 100644 --- a/include/swift/AST/DiagnosticsSema.def +++ b/include/swift/AST/DiagnosticsSema.def @@ -6992,10 +6992,14 @@ 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", ()) +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..846fd7c451490 100644 --- a/include/swift/AST/Expr.h +++ b/include/swift/AST/Expr.h @@ -2050,31 +2050,61 @@ class AwaitExpr final : public IdentityExpr { } }; -/// MoveExpr - A 'move' 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; +/// ConsumeExpr - A 'consume' surrounding an lvalue expression marking the +/// lvalue as needing to be moved. +class ConsumeExpr final : public Expr { + Expr *SubExpr; + SourceLoc ConsumeLoc; public: - MoveExpr(SourceLoc moveLoc, Expr *sub, Type type = Type(), + ConsumeExpr(SourceLoc consumeLoc, Expr *sub, Type type = Type(), + bool implicit = false) + : Expr(ExprKind::Consume, implicit, type), SubExpr(sub), + ConsumeLoc(consumeLoc) {} + + 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 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) { + return e->getKind() == ExprKind::Consume; + } +}; + +/// 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) - : IdentityExpr(ExprKind::Move, sub, type, implicit), MoveLoc(moveLoc) {} + : Expr(ExprKind::Copy, implicit, type), SubExpr(sub), CopyLoc(copyLoc) {} - static MoveExpr *createImplicit(ASTContext &ctx, SourceLoc moveLoc, Expr *sub, + static CopyExpr *createImplicit(ASTContext &ctx, SourceLoc copyLoc, Expr *sub, Type type = Type()) { - return new (ctx) MoveExpr(moveLoc, sub, type, /*implicit=*/true); + return new (ctx) CopyExpr(copyLoc, sub, type, /*implicit=*/true); } - SourceLoc getLoc() const { return MoveLoc; } + SourceLoc getLoc() const { return CopyLoc; } + + Expr *getSubExpr() const { return SubExpr; } + void setSubExpr(Expr *E) { SubExpr = E; } - SourceLoc getStartLoc() const { return MoveLoc; } + SourceLoc getStartLoc() const { return CopyLoc; } 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::Copy; } }; /// BorrowExpr - A 'borrow' surrounding an lvalue/accessor expression at an diff --git a/include/swift/AST/ExprNodes.def b/include/swift/AST/ExprNodes.def index 1531f42ae97de..7adb8d6ca7edf 100644 --- a/include/swift/AST/ExprNodes.def +++ b/include/swift/AST/ExprNodes.def @@ -107,10 +107,11 @@ ABSTRACT_EXPR(Identity, Expr) EXPR(Paren, IdentityExpr) EXPR(DotSelf, IdentityExpr) EXPR(Await, IdentityExpr) - EXPR(Move, 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/ASTDumper.cpp b/lib/AST/ASTDumper.cpp index 177124bc814be..2f5aa8b9e61e5 100644 --- a/lib/AST/ASTDumper.cpp +++ b/lib/AST/ASTDumper.cpp @@ -2194,8 +2194,14 @@ 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) << ')'; + } + void visitCopyExpr(CopyExpr *E) { + printCommon(E, "copy_expr"); OS << '\n'; printRec(E->getSubExpr()); PrintWithColorRAII(OS, ParenthesisColor) << ')'; diff --git a/lib/AST/ASTPrinter.cpp b/lib/AST/ASTPrinter.cpp index 7faf7521c9fc1..00cb4a6d492d8 100644 --- a/lib/AST/ASTPrinter.cpp +++ b/lib/AST/ASTPrinter.cpp @@ -4995,8 +4995,13 @@ 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()); +} + +void PrintAST::visitCopyExpr(CopyExpr *expr) { + Printer << "copy "; visit(expr->getSubExpr()); } diff --git a/lib/AST/ASTWalker.cpp b/lib/AST/ASTWalker.cpp index 597b4a57b3f30..5427118c04f44 100644 --- a/lib/AST/ASTWalker.cpp +++ b/lib/AST/ASTWalker.cpp @@ -690,6 +690,23 @@ class Traversal : public ASTVisitorgetSubExpr())) { + E->setSubExpr(subExpr); + return E; + } + return nullptr; + } + + Expr *visitConsumeExpr(ConsumeExpr *E) { + if (Expr *subExpr = doIt(E->getSubExpr())) { + 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..674d2371face0 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(); @@ -371,7 +370,8 @@ 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); PASS_THROUGH_REFERENCE(ForceTry, getSubExpr); @@ -743,7 +743,8 @@ 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: case ExprKind::ForceTry: @@ -935,7 +936,8 @@ 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: case ExprKind::Try: diff --git a/lib/Parse/ParseExpr.cpp b/lib/Parse/ParseExpr.cpp index 964914ee9483a..d2d660cf94ccb 100644 --- a/lib/Parse/ParseExpr.cpp +++ b/lib/Parse/ParseExpr.cpp @@ -409,15 +409,31 @@ 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()) { - sub = makeParserResult(new (Context) MoveExpr(consumeLoc, sub.get())); + if (!sub.isNull()) { + sub = makeParserResult(new (Context) ConsumeExpr(consumeLoc, sub.get())); + } + 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; } @@ -432,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/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..58247913f2af8 100644 --- a/lib/SILGen/SILGenExpr.cpp +++ b/lib/SILGen/SILGenExpr.cpp @@ -551,7 +551,8 @@ 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); }; } // end anonymous namespace @@ -6114,28 +6115,48 @@ 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) { - auto *subExpr = cast(E->getSubExpr()); +RValue RValueEmitter::visitConsumeExpr(ConsumeExpr *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::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); @@ -6156,6 +6177,63 @@ 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..c8560a618910a 100644 --- a/lib/SILGen/SILGenLValue.cpp +++ b/lib/SILGen/SILGenLValue.cpp @@ -332,7 +332,9 @@ class LLVM_LIBRARY_VISIBILITY SILGenLValue LValue visitKeyPathApplicationExpr(KeyPathApplicationExpr *e, SGFAccessKind accessKind, LValueOptions options); - LValue visitMoveExpr(MoveExpr *e, SGFAccessKind accessKind, + LValue visitConsumeExpr(ConsumeExpr *e, SGFAccessKind accessKind, + LValueOptions options); + LValue visitCopyExpr(CopyExpr *e, SGFAccessKind accessKind, LValueOptions options); LValue visitABISafeConversionExpr(ABISafeConversionExpr *e, SGFAccessKind accessKind, @@ -4002,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()); @@ -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..c255fbd47887e 100644 --- a/lib/Sema/CSApply.cpp +++ b/lib/Sema/CSApply.cpp @@ -3525,6 +3525,48 @@ 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 *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 4907218c1f78e..4c9de01f75a0f 100644 --- a/lib/Sema/CSGen.cpp +++ b/lib/Sema/CSGen.cpp @@ -1889,6 +1889,26 @@ 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 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 40687e2e45657..1942131aa5f61 100644 --- a/lib/Sema/MiscDiagnostics.cpp +++ b/lib/Sema/MiscDiagnostics.cpp @@ -340,8 +340,14 @@ 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 + // expr. + if (auto *copyExpr = dyn_cast(E)) { + checkCopyExpr(copyExpr); } // Diagnose move expression uses where the sub expression is not a declref expr @@ -415,10 +421,33 @@ 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) { + 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); + } + } + + 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); } } 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/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/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/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 + } +} 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/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/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// ///////////////////////////////////////// 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) +} 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'}} +}