diff --git a/include/swift/AST/Decl.h b/include/swift/AST/Decl.h index 18ff79ee70602..e33c3d8b79df9 100644 --- a/include/swift/AST/Decl.h +++ b/include/swift/AST/Decl.h @@ -428,16 +428,15 @@ class alignas(1 << DeclAlignInBits) Decl { HasTopLevelLocalContextCaptures : 1 ); - SWIFT_INLINE_BITFIELD(AccessorDecl, FuncDecl, 4+1+1, - /// The kind of accessor this is. - AccessorKind : 4, + SWIFT_INLINE_BITFIELD(AccessorDecl, FuncDecl, 4 + 1 + 1, + /// The kind of accessor this is. + AccessorKind : 4, - /// Whether the accessor is transparent. - IsTransparent : 1, + /// Whether the accessor is transparent. + IsTransparent : 1, - /// Whether we have computed the above. - IsTransparentComputed : 1 - ); + /// Whether we have computed the above. + IsTransparentComputed : 1); SWIFT_INLINE_BITFIELD(ConstructorDecl, AbstractFunctionDecl, 3+1+1, /// The body initialization kind (+1), or zero if not yet computed. @@ -6502,6 +6501,11 @@ class AccessorDecl final : public FuncDecl { return isGetter() && getAccessorKeywordLoc().isInvalid(); } + /// Is this accessor a "simple" didSet? A "simple" didSet does not + /// use the implicit oldValue parameter in its body or does not have + /// an explicit parameter in its parameter list. + bool isSimpleDidSet() const; + void setIsTransparent(bool transparent) { Bits.AccessorDecl.IsTransparent = transparent; Bits.AccessorDecl.IsTransparentComputed = 1; diff --git a/include/swift/AST/KnownIdentifiers.def b/include/swift/AST/KnownIdentifiers.def index 58570dbc5d832..99dd81eb98611 100644 --- a/include/swift/AST/KnownIdentifiers.def +++ b/include/swift/AST/KnownIdentifiers.def @@ -98,6 +98,7 @@ IDENTIFIER(objectAtIndexedSubscript) IDENTIFIER(objectForKeyedSubscript) IDENTIFIER(ObjectiveC) IDENTIFIER_(ObjectiveCType) +IDENTIFIER(oldValue) IDENTIFIER(Optional) IDENTIFIER_(OptionalNilComparisonType) IDENTIFIER(parameter) diff --git a/include/swift/AST/StorageImpl.h b/include/swift/AST/StorageImpl.h index 96a3e002d27a1..69b3ba120103d 100644 --- a/include/swift/AST/StorageImpl.h +++ b/include/swift/AST/StorageImpl.h @@ -222,6 +222,12 @@ enum class ReadWriteImplKind { /// There's a modify coroutine. Modify, + + /// We have a didSet which doesn't use the oldValue + StoredWithSimpleDidSet, + + /// We have a didSet which doesn't use the oldValue + InheritedWithSimpleDidSet, }; enum { NumReadWriteImplKindBits = 4 }; @@ -266,12 +272,14 @@ class StorageImplInfo { case WriteImplKind::StoredWithObservers: assert(readImpl == ReadImplKind::Stored); - assert(readWriteImpl == ReadWriteImplKind::MaterializeToTemporary); + assert(readWriteImpl == ReadWriteImplKind::MaterializeToTemporary || + readWriteImpl == ReadWriteImplKind::StoredWithSimpleDidSet); return; case WriteImplKind::InheritedWithObservers: assert(readImpl == ReadImplKind::Inherited); - assert(readWriteImpl == ReadWriteImplKind::MaterializeToTemporary); + assert(readWriteImpl == ReadWriteImplKind::MaterializeToTemporary || + readWriteImpl == ReadWriteImplKind::InheritedWithSimpleDidSet); return; case WriteImplKind::Set: diff --git a/include/swift/AST/TypeCheckRequests.h b/include/swift/AST/TypeCheckRequests.h index e679a923e049a..850882c475632 100644 --- a/include/swift/AST/TypeCheckRequests.h +++ b/include/swift/AST/TypeCheckRequests.h @@ -2281,6 +2281,27 @@ class ResolveTypeEraserTypeRequest void cacheResult(Type value) const; }; +/// Determines whether this is a "simple" didSet i.e one that either does not +/// use the implicit oldValue parameter in the body or does not take an explicit +/// parameter (ex: 'didSet(oldValue)'). +class SimpleDidSetRequest + : public SimpleRequest { +public: + using SimpleRequest::SimpleRequest; + +private: + friend SimpleRequest; + + // Evaluation. + bool evaluate(Evaluator &evaluator, AccessorDecl *decl) const; + +public: + bool isCached() const { + return std::get<0>(getStorage())->getAccessorKind() == AccessorKind::DidSet; + } +}; + // Allow AnyValue to compare two Type values, even though Type doesn't // support ==. template<> diff --git a/include/swift/AST/TypeCheckerTypeIDZone.def b/include/swift/AST/TypeCheckerTypeIDZone.def index c80baa75fcd80..330fd2a895570 100644 --- a/include/swift/AST/TypeCheckerTypeIDZone.def +++ b/include/swift/AST/TypeCheckerTypeIDZone.def @@ -246,3 +246,5 @@ SWIFT_REQUEST(TypeChecker, ClosureHasExplicitResultRequest, SWIFT_REQUEST(TypeChecker, LookupAllConformancesInContextRequest, ProtocolConformanceLookupResult(const DeclContext *), Uncached, NoLocationInfo) +SWIFT_REQUEST(TypeChecker, SimpleDidSetRequest, + bool(AccessorDecl *), Cached, NoLocationInfo) diff --git a/lib/AST/ASTDumper.cpp b/lib/AST/ASTDumper.cpp index cf6331eaf0193..2444366905597 100644 --- a/lib/AST/ASTDumper.cpp +++ b/lib/AST/ASTDumper.cpp @@ -264,6 +264,10 @@ StringRef swift::getReadWriteImplKindName(ReadWriteImplKind kind) { return "materialize_to_temporary"; case ReadWriteImplKind::Modify: return "modify_coroutine"; + case ReadWriteImplKind::StoredWithSimpleDidSet: + return "stored_simple_didset"; + case ReadWriteImplKind::InheritedWithSimpleDidSet: + return "inherited_simple_didset"; } llvm_unreachable("bad kind"); } diff --git a/lib/AST/Decl.cpp b/lib/AST/Decl.cpp index 65a00d85635a4..41aebd7583853 100644 --- a/lib/AST/Decl.cpp +++ b/lib/AST/Decl.cpp @@ -2093,6 +2093,16 @@ getDirectReadWriteAccessStrategy(const AbstractStorageDecl *storage) { case ReadWriteImplKind::Modify: return AccessStrategy::getAccessor(AccessorKind::Modify, /*dispatch*/ false); + case ReadWriteImplKind::StoredWithSimpleDidSet: + case ReadWriteImplKind::InheritedWithSimpleDidSet: + if (storage->requiresOpaqueModifyCoroutine()) { + return AccessStrategy::getAccessor(AccessorKind::Modify, + /*dispatch*/ false); + } else { + return AccessStrategy::getMaterializeToTemporary( + getDirectReadAccessStrategy(storage), + getDirectWriteAccessStrategy(storage)); + } case ReadWriteImplKind::MaterializeToTemporary: return AccessStrategy::getMaterializeToTemporary( getDirectReadAccessStrategy(storage), @@ -7330,6 +7340,12 @@ bool AccessorDecl::isExplicitNonMutating() const { !getDeclContext()->getDeclaredInterfaceType()->hasReferenceSemantics(); } +bool AccessorDecl::isSimpleDidSet() const { + auto mutableThis = const_cast(this); + return evaluateOrDefault(getASTContext().evaluator, + SimpleDidSetRequest{mutableThis}, false); +} + StaticSpellingKind FuncDecl::getCorrectStaticSpelling() const { assert(getDeclContext()->isTypeContext()); if (!isStatic()) diff --git a/lib/AST/TypeCheckRequests.cpp b/lib/AST/TypeCheckRequests.cpp index fed71f90be796..b630fde935eb0 100644 --- a/lib/AST/TypeCheckRequests.cpp +++ b/lib/AST/TypeCheckRequests.cpp @@ -908,7 +908,7 @@ void EnumRawValuesRequest::diagnoseCycle(DiagnosticEngine &diags) const { } void EnumRawValuesRequest::noteCycleStep(DiagnosticEngine &diags) const { - + } //----------------------------------------------------------------------------// diff --git a/lib/SIL/IR/TypeLowering.cpp b/lib/SIL/IR/TypeLowering.cpp index f037cd6ea070a..afd26ebc87f2b 100644 --- a/lib/SIL/IR/TypeLowering.cpp +++ b/lib/SIL/IR/TypeLowering.cpp @@ -2483,6 +2483,8 @@ TypeConverter::getLoweredLocalCaptures(SILDeclRef fn) { collectAccessorCaptures(AccessorKind::MutableAddress); break; case ReadWriteImplKind::Modify: + case ReadWriteImplKind::StoredWithSimpleDidSet: + case ReadWriteImplKind::InheritedWithSimpleDidSet: collectAccessorCaptures(AccessorKind::Modify); break; } diff --git a/lib/Sema/TypeCheckDecl.cpp b/lib/Sema/TypeCheckDecl.cpp index 7fddd89c341dd..aa56cd226a3aa 100644 --- a/lib/Sema/TypeCheckDecl.cpp +++ b/lib/Sema/TypeCheckDecl.cpp @@ -1118,6 +1118,69 @@ EnumRawValuesRequest::evaluate(Evaluator &eval, EnumDecl *ED, return std::make_tuple<>(); } +bool SimpleDidSetRequest::evaluate(Evaluator &evaluator, + AccessorDecl *decl) const { + + class OldValueFinder : public ASTWalker { + const ParamDecl *OldValueParam; + bool foundOldValueRef = false; + + public: + OldValueFinder(const ParamDecl *param) : OldValueParam(param) {} + + virtual std::pair walkToExprPre(Expr *E) override { + if (!E) + return {true, E}; + if (auto DRE = dyn_cast(E)) { + if (auto decl = DRE->getDecl()) { + if (decl == OldValueParam) { + foundOldValueRef = true; + return {false, nullptr}; + } + } + } + + return {true, E}; + } + + bool didFindOldValueRef() { return foundOldValueRef; } + }; + + // If this is not a didSet accessor, bail out. + if (decl->getAccessorKind() != AccessorKind::DidSet) { + return false; + } + + // didSet must have a single parameter. + if (decl->getParameters()->size() != 1) { + return false; + } + + auto param = decl->getParameters()->get(0); + // If this parameter is not implicit, then it means it has been explicitly + // provided by the user (i.e. 'didSet(oldValue)'). This means we cannot + // consider this a "simple" didSet because we have to fetch the oldValue + // regardless of whether it's referenced in the body or not. + if (!param->isImplicit()) { + return false; + } + + // If we find a reference to the implicit 'oldValue' parameter, then it is + // not a "simple" didSet because we need to fetch it. + auto walker = OldValueFinder(param); + decl->getBody()->walk(walker); + auto hasOldValueRef = walker.didFindOldValueRef(); + if (!hasOldValueRef) { + // If the body does not refer to implicit 'oldValue', it means we can + // consider this as a "simple" didSet. Let's also erase the implicit + // oldValue as it is never used. + auto &ctx = decl->getASTContext(); + decl->setParameters(ParameterList::createEmpty(ctx)); + return true; + } + return false; +} + const ConstructorDecl * swift::findNonImplicitRequiredInit(const ConstructorDecl *CD) { while (CD->isImplicit()) { @@ -1577,6 +1640,12 @@ static ParamDecl *getOriginalParamFromAccessor(AbstractStorageDecl *storage, case AccessorKind::DidSet: case AccessorKind::WillSet: case AccessorKind::Set: + if (accessor->isSimpleDidSet()) { + // If this is a "simple" didSet, there won't be + // a parameter. + return nullptr; + } + if (param == accessorParams->get(0)) { // This is the 'newValue' parameter. return nullptr; @@ -2190,6 +2259,13 @@ InterfaceTypeRequest::evaluate(Evaluator &eval, ValueDecl *D) const { case DeclKind::Accessor: case DeclKind::Constructor: case DeclKind::Destructor: { + // If this is a didSet, then we need to check whether the body references + // the implicit 'oldValue' parameter or not, in order to correctly + // compute the interface type. + if (auto AD = dyn_cast(D)) { + (void)AD->isSimpleDidSet(); + } + auto *AFD = cast(D); auto sig = AFD->getGenericSignature(); diff --git a/lib/Sema/TypeCheckStorage.cpp b/lib/Sema/TypeCheckStorage.cpp index b5077f492a05c..d7b66f949e981 100644 --- a/lib/Sema/TypeCheckStorage.cpp +++ b/lib/Sema/TypeCheckStorage.cpp @@ -1454,8 +1454,12 @@ synthesizeObservedSetterBody(AccessorDecl *Set, TargetImpl target, auto type = observer->getInterfaceType().subst(subs); Expr *Callee = new (Ctx) DeclRefExpr(ref, DeclNameLoc(), /*imp*/true); Callee->setType(type); - auto *ValueDRE = new (Ctx) DeclRefExpr(arg, DeclNameLoc(), /*imp*/true); - ValueDRE->setType(arg->getType()); + + DeclRefExpr *ValueDRE = nullptr; + if (arg) { + ValueDRE = new (Ctx) DeclRefExpr(arg, DeclNameLoc(), /*imp*/ true); + ValueDRE->setType(arg->getType()); + } if (SelfDecl) { auto *SelfDRE = @@ -1470,8 +1474,13 @@ synthesizeObservedSetterBody(AccessorDecl *Set, TargetImpl target, Callee = DSCE; } - auto *Call = CallExpr::createImplicit(Ctx, Callee, { ValueDRE }, - { Identifier() }); + CallExpr *Call = nullptr; + if (arg) { + Call = CallExpr::createImplicit(Ctx, Callee, {ValueDRE}, {Identifier()}); + } else { + Call = CallExpr::createImplicit(Ctx, Callee, {}, {}); + } + if (auto funcType = type->getAs()) type = funcType->getResult(); Call->setType(type); @@ -1482,30 +1491,32 @@ synthesizeObservedSetterBody(AccessorDecl *Set, TargetImpl target, // If there is a didSet, it will take the old value. Load it into a temporary // 'let' so we have it for later. - // TODO: check the body of didSet to only do this load (which may call the - // superclass getter) if didSet takes an argument. VarDecl *OldValue = nullptr; - if (VD->getParsedAccessor(AccessorKind::DidSet)) { - Expr *OldValueExpr - = buildStorageReference(Set, VD, target, /*isLValue=*/true, Ctx); + if (auto didSet = VD->getParsedAccessor(AccessorKind::DidSet)) { + // Only do the load if the didSet body references the implicit oldValue + // parameter or it's provided explicitly in the parameter list. + if (!didSet->isSimpleDidSet()) { + Expr *OldValueExpr = + buildStorageReference(Set, VD, target, /*isLValue=*/true, Ctx); + + // Error recovery. + if (OldValueExpr == nullptr) { + OldValueExpr = new (Ctx) ErrorExpr(SourceRange(), VD->getType()); + } else { + OldValueExpr = new (Ctx) LoadExpr(OldValueExpr, VD->getType()); + } - // Error recovery. - if (OldValueExpr == nullptr) { - OldValueExpr = new (Ctx) ErrorExpr(SourceRange(), VD->getType()); - } else { - OldValueExpr = new (Ctx) LoadExpr(OldValueExpr, VD->getType()); + OldValue = new (Ctx) VarDecl(/*IsStatic*/ false, VarDecl::Introducer::Let, + /*IsCaptureList*/ false, SourceLoc(), + Ctx.getIdentifier("tmp"), Set); + OldValue->setImplicit(); + OldValue->setInterfaceType(VD->getValueInterfaceType()); + auto *tmpPattern = new (Ctx) NamedPattern(OldValue, /*implicit*/ true); + auto *tmpPBD = PatternBindingDecl::createImplicit( + Ctx, StaticSpellingKind::None, tmpPattern, OldValueExpr, Set); + SetterBody.push_back(tmpPBD); + SetterBody.push_back(OldValue); } - - OldValue = new (Ctx) VarDecl(/*IsStatic*/false, VarDecl::Introducer::Let, - /*IsCaptureList*/false, SourceLoc(), - Ctx.getIdentifier("tmp"), Set); - OldValue->setImplicit(); - OldValue->setInterfaceType(VD->getValueInterfaceType()); - auto *tmpPattern = new (Ctx) NamedPattern(OldValue, /*implicit*/ true); - auto *tmpPBD = PatternBindingDecl::createImplicit( - Ctx, StaticSpellingKind::None, tmpPattern, OldValueExpr, Set); - SetterBody.push_back(tmpPBD); - SetterBody.push_back(OldValue); } if (auto willSet = VD->getParsedAccessor(AccessorKind::WillSet)) @@ -1593,11 +1604,70 @@ synthesizeSetterBody(AccessorDecl *setter, ASTContext &ctx) { llvm_unreachable("bad ReadImplKind"); } +static std::pair +synthesizeModifyCoroutineBodyWithSimpleDidSet(AccessorDecl *accessor, + ASTContext &ctx) { + auto storage = accessor->getStorage(); + SourceLoc loc = storage->getLoc(); + auto isOverride = storage->getOverriddenDecl(); + auto target = isOverride ? TargetImpl::Super : TargetImpl::Storage; + + SmallVector body; + + Expr *ref = buildStorageReference(accessor, storage, target, true, ctx); + ref = maybeWrapInOutExpr(ref, ctx); + + YieldStmt *yield = YieldStmt::create(ctx, loc, loc, ref, loc, true); + body.push_back(yield); + + auto Set = storage->getAccessor(AccessorKind::Set); + auto DidSet = storage->getAccessor(AccessorKind::DidSet); + auto *SelfDecl = accessor->getImplicitSelfDecl(); + + SubstitutionMap subs; + if (auto *genericEnv = Set->getGenericEnvironment()) + subs = genericEnv->getForwardingSubstitutionMap(); + + auto callDidSet = [&]() { + ConcreteDeclRef ref(DidSet, subs); + auto type = DidSet->getInterfaceType().subst(subs); + Expr *Callee = new (ctx) DeclRefExpr(ref, DeclNameLoc(), /*imp*/ true); + Callee->setType(type); + + if (SelfDecl) { + auto *SelfDRE = buildSelfReference(SelfDecl, SelfAccessorKind::Peer, + storage->isSetterMutating()); + SelfDRE = maybeWrapInOutExpr(SelfDRE, ctx); + auto *DSCE = new (ctx) DotSyntaxCallExpr(Callee, SourceLoc(), SelfDRE); + + if (auto funcType = type->getAs()) + type = funcType->getResult(); + DSCE->setType(type); + DSCE->setThrows(false); + Callee = DSCE; + } + + auto *Call = CallExpr::createImplicit(ctx, Callee, {}, {}); + if (auto funcType = type->getAs()) + type = funcType->getResult(); + Call->setType(type); + Call->setThrows(false); + + body.push_back(Call); + }; + + callDidSet(); + + return {BraceStmt::create(ctx, loc, body, loc, true), + /*isTypeChecked=*/true}; +} + static std::pair synthesizeCoroutineAccessorBody(AccessorDecl *accessor, ASTContext &ctx) { assert(accessor->isCoroutine()); auto storage = accessor->getStorage(); + auto storageReadWriteImpl = storage->getReadWriteImpl(); auto target = (accessor->hasForcedStaticDispatch() ? TargetImpl::Ordinary : TargetImpl::Implementation); @@ -1620,6 +1690,12 @@ synthesizeCoroutineAccessorBody(AccessorDecl *accessor, ASTContext &ctx) { bool isLValue = accessor->getAccessorKind() == AccessorKind::Modify; + if (isLValue && + (storageReadWriteImpl == ReadWriteImplKind::StoredWithSimpleDidSet || + storageReadWriteImpl == ReadWriteImplKind::InheritedWithSimpleDidSet)) { + return synthesizeModifyCoroutineBodyWithSimpleDidSet(accessor, ctx); + } + // Build a reference to the storage. Expr *ref = buildStorageReference(accessor, storage, target, isLValue, ctx); if (ref != nullptr) { @@ -2103,6 +2179,14 @@ IsAccessorTransparentRequest::evaluate(Evaluator &evaluator, llvm_unreachable("bad synthesized function kind"); } + switch (storage->getReadWriteImpl()) { + case ReadWriteImplKind::StoredWithSimpleDidSet: + case ReadWriteImplKind::InheritedWithSimpleDidSet: + return false; + default: + break; + } + return true; } @@ -2784,6 +2868,10 @@ StorageImplInfoRequest::evaluate(Evaluator &evaluator, bool hasSetter = storage->getParsedAccessor(AccessorKind::Set); bool hasModify = storage->getParsedAccessor(AccessorKind::Modify); bool hasMutableAddress = storage->getParsedAccessor(AccessorKind::MutableAddress); + bool hasSimpleDidSet = + hasDidSet && const_cast( + storage->getParsedAccessor(AccessorKind::DidSet)) + ->isSimpleDidSet(); // 'get', 'read', and a non-mutable addressor are all exclusive. ReadImplKind readImpl; @@ -2840,12 +2928,20 @@ StorageImplInfoRequest::evaluate(Evaluator &evaluator, writeImpl = WriteImplKind::InheritedWithObservers; readWriteImpl = ReadWriteImplKind::MaterializeToTemporary; + if (!hasWillSet && hasSimpleDidSet) { + readWriteImpl = ReadWriteImplKind::InheritedWithSimpleDidSet; + } + // Otherwise, it's stored. } else if (readImpl == ReadImplKind::Stored && !cast(storage)->isLet()) { if (hasWillSet || hasDidSet) { writeImpl = WriteImplKind::StoredWithObservers; readWriteImpl = ReadWriteImplKind::MaterializeToTemporary; + + if (!hasWillSet && hasSimpleDidSet) { + readWriteImpl = ReadWriteImplKind::StoredWithSimpleDidSet; + } } else { writeImpl = WriteImplKind::Stored; readWriteImpl = ReadWriteImplKind::Stored; diff --git a/lib/Serialization/Deserialization.cpp b/lib/Serialization/Deserialization.cpp index 7f808356a8ddc..8b43a53b11eb4 100644 --- a/lib/Serialization/Deserialization.cpp +++ b/lib/Serialization/Deserialization.cpp @@ -2204,6 +2204,8 @@ getActualReadWriteImplKind(unsigned rawKind) { CASE(MutableAddress) CASE(MaterializeToTemporary) CASE(Modify) + CASE(StoredWithSimpleDidSet) + CASE(InheritedWithSimpleDidSet) #undef CASE } return None; diff --git a/lib/Serialization/ModuleFormat.h b/lib/Serialization/ModuleFormat.h index 14db3010597cc..c6d45467c1850 100644 --- a/lib/Serialization/ModuleFormat.h +++ b/lib/Serialization/ModuleFormat.h @@ -55,7 +55,7 @@ const uint16_t SWIFTMODULE_VERSION_MAJOR = 0; /// describe what change you made. The content of this comment isn't important; /// it just ensures a conflict if two people change the module format. /// Don't worry about adhering to the 80-column limit for this line. -const uint16_t SWIFTMODULE_VERSION_MINOR = 551; // derivative function configurations +const uint16_t SWIFTMODULE_VERSION_MINOR = 552; // simple didSet /// A standard hash seed used for all string hashes in a serialized module. /// @@ -208,6 +208,8 @@ enum class ReadWriteImplKind : uint8_t { MutableAddress, MaterializeToTemporary, Modify, + StoredWithSimpleDidSet, + InheritedWithSimpleDidSet, }; using ReadWriteImplKindField = BCFixed<3>; diff --git a/lib/Serialization/Serialization.cpp b/lib/Serialization/Serialization.cpp index c39f2f55a8b68..e74ffbf2de92b 100644 --- a/lib/Serialization/Serialization.cpp +++ b/lib/Serialization/Serialization.cpp @@ -512,6 +512,8 @@ static unsigned getRawReadWriteImplKind(swift::ReadWriteImplKind kind) { CASE(MutableAddress) CASE(MaterializeToTemporary) CASE(Modify) + CASE(StoredWithSimpleDidSet) + CASE(InheritedWithSimpleDidSet) #undef CASE } llvm_unreachable("bad kind"); diff --git a/test/IDE/complete_in_accessors.swift b/test/IDE/complete_in_accessors.swift index 283afe88db14f..b040f0d58c09d 100644 --- a/test/IDE/complete_in_accessors.swift +++ b/test/IDE/complete_in_accessors.swift @@ -247,18 +247,18 @@ willSet { } var globalAccessorDidSet1: Int { - didSet { + didSet(oldValue) { #^GLOBAL_ACCESSOR_DIDSET_1^# } } var globalAccessorDidSet2: Int { - didSet { + didSet(oldValue) { var fs = FooStruct() fs.#^GLOBAL_ACCESSOR_DIDSET_2^# } } var globalAccessorDidSet3 = 42 { -didSet { +didSet(oldValue) { #^GLOBAL_ACCESSOR_DIDSET_3^# } } @@ -337,18 +337,18 @@ struct MemberAccessors { } var memberAccessorDidSet1: Int { - didSet { + didSet(oldValue) { #^MEMBER_ACCESSOR_DIDSET_1^# } } var memberAccessorDidSet2: Int { - didSet { + didSet(oldValue) { var fs = FooStruct() fs.#^MEMBER_ACCESSOR_DIDSET_2^# } } var memberAccessorDidSet3 = 42 { - didSet { + didSet(oldValue) { #^MEMBER_ACCESSOR_DIDSET_3^# } } @@ -424,18 +424,18 @@ func accessorsInFunction(_ functionParam: Int) { } var memberAccessorDidSet1: Int { - didSet { + didSet(oldValue) { #^LOCAL_ACCESSOR_DIDSET_1^# } } var memberAccessorDidSet2: Int { - didSet { + didSet(oldValue) { var fs = FooStruct() fs.#^LOCAL_ACCESSOR_DIDSET_2^# } } var memberAccessorDidSet3: Int { - didSet { + didSet(oldValue) { #^LOCAL_ACCESSOR_DIDSET_3^# } } diff --git a/test/ModuleInterface/inlinable-function.swift b/test/ModuleInterface/inlinable-function.swift index 83f801c9a0cf3..259718164b802 100644 --- a/test/ModuleInterface/inlinable-function.swift +++ b/test/ModuleInterface/inlinable-function.swift @@ -30,7 +30,7 @@ public struct Foo: Hashable { // CHECK: public var hasDidSet: Swift.Int { public var hasDidSet: Int { - // CHECK-NEXT: @_transparent get{{$}} + // CHECK-NEXT: get{{$}} // CHECK-NEXT: set{{(\(value\))?}}{{$}} // CHECK-NOT: didSet didSet { diff --git a/test/PCMacro/didset.swift b/test/PCMacro/didset.swift index 295c21f44d938..22e5e06871153 100644 --- a/test/PCMacro/didset.swift +++ b/test/PCMacro/didset.swift @@ -17,7 +17,7 @@ import PlaygroundSupport #sourceLocation(file: "main.swift", line: 10) struct S { var a : [Int] = [] { - didSet { + didSet(oldValue) { print("Set") } } @@ -30,16 +30,16 @@ s.a.append(300) // CHECK: [18:1-18:12] pc before // CHECK-NEXT: [18:1-18:12] pc after // CHECK-NEXT: [19:1-19:12] pc before -// CHECK-NEXT: [12:9-12:15] pc before -// CHECK-NEXT: [12:9-12:15] pc after +// CHECK-NEXT: [12:9-12:25] pc before +// CHECK-NEXT: [12:9-12:25] pc after // CHECK-NEXT: [13:13-13:25] pc before // CHECK-NEXT: Set // CHECK-NEXT: [13:13-13:25] pc after // CHECK-NEXT: [19:1-19:12] pc after // CHECK-NEXT: [20:1-20:16] pc before -// CHECK-NEXT: [12:9-12:15] pc before -// CHECK-NEXT: [12:9-12:15] pc after +// CHECK-NEXT: [12:9-12:25] pc before +// CHECK-NEXT: [12:9-12:25] pc after // CHECK-NEXT: [13:13-13:25] pc before // CHECK-NEXT: Set // CHECK-NEXT: [13:13-13:25] pc after diff --git a/test/SILGen/accessors.swift b/test/SILGen/accessors.swift index 47ffc504d5fde..675665bf0acb1 100644 --- a/test/SILGen/accessors.swift +++ b/test/SILGen/accessors.swift @@ -156,7 +156,7 @@ class Parent { } class D : C {} class E : D { - // CHECK-LABEL: sil hidden [transparent] [ossa] @$s9accessors6ParentC1EC3fooSivgZ : $@convention(method) (@thick Parent.E.Type) -> Int + // CHECK-LABEL: sil hidden [ossa] @$s9accessors6ParentC1EC3fooSivgZ : $@convention(method) (@thick Parent.E.Type) -> Int // CHECK: [[TY:%.+]] = upcast {{%.+}} : $@thick Parent.E.Type to $@thick Parent.D.Type // CHECK: [[UPCASTTY:%.+]] = upcast [[TY]] : $@thick Parent.D.Type to $@thick Parent.C.Type // CHECK: [[GETTER:%.+]] = function_ref @$s9accessors6ParentC1CC3fooSivgZ : $@convention(method) <τ_0_0><τ_1_0, τ_1_1> (@thick Parent<τ_0_0>.C<τ_1_0, τ_1_1>.Type) -> Int diff --git a/test/SILGen/didset_oldvalue_not_accessed_silgen.swift b/test/SILGen/didset_oldvalue_not_accessed_silgen.swift new file mode 100644 index 0000000000000..380ad23108fd8 --- /dev/null +++ b/test/SILGen/didset_oldvalue_not_accessed_silgen.swift @@ -0,0 +1,53 @@ +// RUN: %target-swift-emit-silgen %s | %FileCheck %s + +// Make sure we do not call the getter to get the oldValue and pass it to didSet +// when the didSet does not reference the oldValue in its body. + +class Foo { + var value: T { + // CHECK-LABEL: sil private [ossa] @$s35didset_oldvalue_not_accessed_silgen3FooC5valuexvW : $@convention(method) (@guaranteed Foo) -> () + // CHECK: debug_value %0 : $Foo, let, name "self", argno {{[0-9]+}} + // CHECK-NOT: debug_value_addr %0 : $*T, let, name "oldValue", argno {{[0-9]+}} + didSet { print("didSet called!") } + } + + init(value: T) { + self.value = value + } +} + +let foo = Foo(value: "Hello") +// Foo.value.setter // + +// CHECK-LABEL: sil hidden [ossa] @$s35didset_oldvalue_not_accessed_silgen3FooC5valuexvs : $@convention(method) (@in T, @guaranteed Foo) -> () +// CHECK: debug_value_addr [[VALUE:%.*]] : $*T, let, name "value", argno {{[0-9+]}} +// CHECK-NEXT: debug_value [[SELF:%.*]] : $Foo, let, name "self", argno {{[0-9+]}} +// CHECK-NEXT: [[ALLOC_STACK:%.*]] = alloc_stack $T +// CHECK-NEXT: copy_addr [[VALUE]] to [initialization] [[ALLOC_STACK]] : $*T +// CHECK-NEXT: [[REF_ADDR:%.*]] = ref_element_addr [[SELF]] : $Foo, #Foo.value +// CHECK-NEXT: [[BEGIN_ACCESS:%.*]] = begin_access [modify] [dynamic] [[REF_ADDR]] : $*T +// CHECK-NEXT: copy_addr [take] [[ALLOC_STACK]] to [[BEGIN_ACCESS]] : $*T +// CHECK-NEXT: end_access [[BEGIN_ACCESS]] : $*T +// CHECK-NEXT: dealloc_stack [[ALLOC_STACK]] : $*T + +// CHECK: [[DIDSET:%.*]] = function_ref @$s35didset_oldvalue_not_accessed_silgen3FooC5valuexvW : $@convention(method) <τ_0_0> (@guaranteed Foo<τ_0_0>) -> () +// CHECK-NEXT: [[RESULT:%.*]] = apply [[DIDSET]]([[SELF]]) : $@convention(method) <τ_0_0> (@guaranteed Foo<τ_0_0>) -> () + +// Foo.value.modify // + +// CHECK-LABEL: sil hidden [ossa] @$s35didset_oldvalue_not_accessed_silgen3FooC5valuexvM : $@yield_once @convention(method) (@guaranteed Foo) -> @yields @inout T +// CHECK: debug_value [[SELF:%.*]] : $Foo, let, name "self", argno {{[0-9+]}} +// CHECK-NEXT: [[REF_ADDR:%.*]] = ref_element_addr [[SELF]] : $Foo, #Foo.value +// CHECK-NEXT: [[BEGIN_ACCESS:%.*]] = begin_access [modify] [dynamic] [[REF_ADDR]] : $*T +// CHECK-NEXT: yield [[BEGIN_ACCESS]] : $*T, resume bb1, unwind bb2 + +// CHECK: bb1: +// CHECK-NEXT: end_access [[BEGIN_ACCESS]] : $*T +// CHECK-NEXT: // function_ref Foo.value.didset +// CHECK-NEXT: [[DIDSET:%.*]] = function_ref @$s35didset_oldvalue_not_accessed_silgen3FooC5valuexvW : $@convention(method) <τ_0_0> (@guaranteed Foo<τ_0_0>) -> () +// CHECK-NEXT: [[RESULT:%.*]] = apply [[DIDSET]]([[SELF]]) : $@convention(method) <τ_0_0> (@guaranteed Foo<τ_0_0>) -> () + +// CHECK: bb2: +// CHECK-NEXT: end_access [[BEGIN_ACCESS]] : $*T +// CHECK-NEXT: unwind +foo.value = "World" diff --git a/test/SILGen/modify.swift b/test/SILGen/modify.swift index 89bc6195249c3..ecb14912256ff 100644 --- a/test/SILGen/modify.swift +++ b/test/SILGen/modify.swift @@ -171,18 +171,19 @@ class HasDidSet : Base { didSet {} } -// CHECK-LABEL: sil hidden [transparent] [ossa] @$s6modify9HasDidSetC6storedSivM : $@yield_once @convention(method) (@guaranteed HasDidSet) -> @yields @inout Int { -// CHECK: bb0([[SELF:%.*]] : @guaranteed $HasDidSet): -// CHECK: [[ADDR:%.*]] = alloc_stack $Int -// CHECK: [[T0:%.*]] = function_ref @$s6modify9HasDidSetC6storedSivg -// CHECK: [[T1:%.*]] = apply [[T0]]([[SELF]]) -// CHECK: store [[T1]] to [trivial] [[ADDR]] : $*Int -// CHECK: yield [[ADDR]] -// CHECK: [[T2:%.*]] = load [trivial] [[ADDR]] -// CHECK: [[T3:%.*]] = function_ref @$s6modify9HasDidSetC6storedSivs -// CHECK: apply [[T3]]([[T2]], [[SELF]]) -// CHECK: dealloc_stack [[ADDR]] -// CHECK: return {{%.*}} : $() +// CHECK-LABEL: sil hidden [ossa] @$s6modify9HasDidSetC6storedSivM : $@yield_once @convention(method) (@guaranteed HasDidSet) -> @yields @inout Int { +// CHECK: bb0([[SELF:%.*]] : @guaranteed $HasDidSet): +// CHECK: [[COPY_VAL:%.*]] = copy_value [[SELF]] : $HasDidSet +// CHECK-NEXT: [[UPCAST:%.*]] = upcast [[COPY_VAL]] : $HasDidSet to $Base +// CHECK-NEXT: [[BEGIN_BORROW:%.*]] = begin_borrow [[UPCAST]] +// CHECK-NEXT: // function_ref Base.stored.modify +// CHECK-NEXT: [[FUNC_REF:%.*]] = function_ref @$s6modify4BaseC6storedSivM +// CHECK-NEXT: ([[VAL:%.*]], [[IGNORE:%.*]]) = begin_apply [[FUNC_REF]]([[BEGIN_BORROW]]) +// CHECK-NEXT: yield [[VAL]] : $*Int, resume bb1, unwind bb2 +// CHECK: [[DIDSET:%.*]] = function_ref @$s6modify9HasDidSetC6storedSivW +// CHECK-NEXT: apply [[DIDSET]]([[SELF]]) +// CHECK-NEXT: [[TUPLE:%.*]] = tuple () +// CHECK-NEXT: return [[TUPLE]] // CHECK: } override var computed: Int { @@ -210,20 +211,15 @@ class HasStoredDidSet { didSet {} } -// CHECK-LABEL: sil hidden [transparent] [ossa] @$s6modify15HasStoredDidSetC6storedSivM : $@yield_once @convention(method) (@guaranteed HasStoredDidSet) -> @yields @inout Int { +// sil hidden [ossa] @$s6modify15HasStoredDidSetC6storedSivM : $@yield_once @convention(method) (@guaranteed HasStoredDidSet) -> @yields @inout Int { // CHECK: bb0([[SELF:%.*]] : @guaranteed $HasStoredDidSet): -// CHECK: [[ADDR:%.*]] = alloc_stack $Int -// CHECK: [[T0:%.*]] = ref_element_addr [[SELF]] : $HasStoredDidSet, #HasStoredDidSet.stored -// CHECK: [[T1:%.*]] = begin_access [read] [dynamic] [[T0]] : $*Int -// CHECK: [[T2:%.*]] = load [trivial] [[T1]] -// CHECK: end_access [[T1]] : $*Int -// CHECK: store [[T2]] to [trivial] [[ADDR]] -// CHECK: yield [[ADDR]] -// CHECK: [[T0:%.*]] = load [trivial] [[ADDR]] -// CHECK: [[SETTER:%.*]] = function_ref @$s6modify15HasStoredDidSetC6storedSivs -// CHECK: apply [[SETTER]]([[T0]], [[SELF]]) -// CHECK: dealloc_stack [[ADDR]] -// CHECK: return {{%.*}} : $() +// CHECK: [[REF_ADDR:%.*]] = ref_element_addr [[SELF]] : $HasStoredDidSet, #HasStoredDidSet.stored +// CHECK: [[BEGIN_ACCESS:%.*]] = begin_access [modify] [dynamic] [[REF_ADDR]] : $*Int +// CHECK: yield [[BEGIN_ACCESS]] +// CHECK: [[DIDSET:%.*]] = function_ref @$s6modify15HasStoredDidSetC6storedSivW +// CHECK: apply [[DIDSET]]([[SELF]]) +// CHECK: [[TUPLE:%.*]] = tuple () +// CHECK: return [[TUPLE]] // CHECK: } } diff --git a/test/SILGen/multi_file.swift b/test/SILGen/multi_file.swift index adc62e64eac43..3697ec245c538 100644 --- a/test/SILGen/multi_file.swift +++ b/test/SILGen/multi_file.swift @@ -37,10 +37,9 @@ func finalVarsAreDevirtualized(_ obj: FinalPropertyClass) { // CHECK-LABEL: sil hidden [ossa] @$s10multi_file34finalVarsDontNeedMaterializeForSetyyAA27ObservingPropertyFinalClassCF func finalVarsDontNeedMaterializeForSet(_ obj: ObservingPropertyFinalClass) { obj.foo += 1 - // CHECK: [[T0:%.*]] = ref_element_addr %0 : $ObservingPropertyFinalClass, #ObservingPropertyFinalClass.foo - // CHECK-NEXT: [[T1:%.*]] = begin_access [read] [dynamic] [[T0]] : $*Int - // CHECK-NEXT: load [trivial] [[T1]] : $*Int - // CHECK: function_ref @$s10multi_file27ObservingPropertyFinalClassC3fooSivs + // CHECK: bb0([[CLASS:%.*]] : @guaranteed $ObservingPropertyFinalClass): + // CHECK: [[REF:%.*]] = function_ref @$s10multi_file27ObservingPropertyFinalClassC3fooSivM : $@yield_once @convention(method) (@guaranteed ObservingPropertyFinalClass) -> @yields @inout Int + // CHECK-NEXT: ([[ONE:%.*]], [[TWO:%.*]]) = begin_apply [[REF]]([[CLASS]]) : $@yield_once @convention(method) (@guaranteed ObservingPropertyFinalClass) -> @yields @inout Int } // rdar://18503960 diff --git a/test/SILGen/objc_extensions.swift b/test/SILGen/objc_extensions.swift index 1ef1d175cec49..41625a9f4feb9 100644 --- a/test/SILGen/objc_extensions.swift +++ b/test/SILGen/objc_extensions.swift @@ -10,8 +10,9 @@ class Sub : Base {} extension Sub { @objc override var prop: String! { - didSet { - // Ignore it. + didSet(oldValue) { + // Ignore it, but explicitly specify 'oldValue' because this test + // checks that its correctly fetched. } // Make sure that we are generating the @objc thunk and are calling the actual method. @@ -165,7 +166,7 @@ extension SubSub { // CHECK: end_borrow [[BORROWED_UPCAST_SELF_COPY_2]] // CHECK: } // end sil function '$s15objc_extensions03SubC0C9otherPropSSvs' @objc override var otherProp: String { - didSet { + didSet(oldValue) { // Ignore it. } } diff --git a/test/SILGen/observers.swift b/test/SILGen/observers.swift index 145aa98b0989e..3051c0a1126c7 100644 --- a/test/SILGen/observers.swift +++ b/test/SILGen/observers.swift @@ -63,13 +63,12 @@ public struct DidSetWillSetTests { // CHECK-LABEL: sil private [ossa] @$s9observers010DidSetWillC5TestsV1a{{[_0-9a-zA-Z]*}}vW didSet { - // CHECK: bb0(%0 : $Int, %1 : $*DidSetWillSetTests): - // CHECK-NEXT: debug - // CHECK-NEXT: debug_value_addr %1 : $*DidSetWillSetTests + // CHECK: bb0(%0 : $*DidSetWillSetTests): + // CHECK-NEXT: debug_value_addr %0 : $*DidSetWillSetTests takeInt(a) - // CHECK: [[READ:%.*]] = begin_access [read] [unknown] %1 + // CHECK: [[READ:%.*]] = begin_access [read] [unknown] %0 // CHECK-NEXT: [[AADDR:%.*]] = struct_element_addr [[READ]] : $*DidSetWillSetTests, #DidSetWillSetTests.a // CHECK-NEXT: [[A:%.*]] = load [trivial] [[AADDR]] : $*Int // CHECK-NEXT: end_access [[READ]] @@ -86,7 +85,7 @@ public struct DidSetWillSetTests { // CHECK-NEXT: [[READ:%.*]] = begin_access [read] [dynamic] [[ZEROADDR]] : $*Int // CHECK-NEXT: [[ZERO:%.*]] = load [trivial] [[READ]] // CHECK-NEXT: end_access [[READ]] : $*Int - // CHECK-NEXT: [[WRITE:%.*]] = begin_access [modify] [unknown] %1 + // CHECK-NEXT: [[WRITE:%.*]] = begin_access [modify] [unknown] %0 // CHECK-NEXT: [[AADDR:%.*]] = struct_element_addr [[WRITE]] : $*DidSetWillSetTests, #DidSetWillSetTests.a // CHECK-NEXT: assign [[ZERO]] to [[AADDR]] } @@ -100,30 +99,29 @@ public struct DidSetWillSetTests { // CHECK-NEXT: return %2 : $Int{{.*}} // id: %3 - // CHECK-LABEL: sil [ossa] @$s9observers010DidSetWillC5TestsV1aSivs - // CHECK: bb0(%0 : $Int, %1 : $*DidSetWillSetTests): - // CHECK-NEXT: debug_value %0 + // CHECK-LABEL: sil [ossa] @$s9observers010DidSetWillC5TestsV1aSivs : $@convention(method) (Int, @inout DidSetWillSetTests) -> () { + // CHECK: bb0([[NEWVALUE:%.*]] : $Int, %1 : $*DidSetWillSetTests): + // CHECK-NEXT: debug_value [[NEWVALUE]] : $Int, let, name "value", argno 1 // CHECK-NEXT: debug_value_addr %1 - // CHECK-NEXT: [[READ:%.*]] = begin_access [read] [unknown] %1 - // CHECK-NEXT: [[AADDR:%.*]] = struct_element_addr [[READ]] : $*DidSetWillSetTests, #DidSetWillSetTests.a - // CHECK-NEXT: [[OLDVAL:%.*]] = load [trivial] [[AADDR]] : $*Int - // CHECK-NEXT: end_access [[READ]] - // CHECK-NEXT: debug_value [[OLDVAL]] : $Int, let, name "tmp" - - // CHECK: [[WRITE:%.*]] = begin_access [modify] [unknown] %1 - // CHECK-NEXT: // function_ref {{.*}}.DidSetWillSetTests.a.willset : Swift.Int - // CHECK-NEXT: [[WILLSETFN:%.*]] = function_ref @$s9observers010DidSetWillC5TestsV1a{{[_0-9a-zA-Z]*}}vw - // CHECK-NEXT: apply [[WILLSETFN]](%0, [[WRITE]]) : $@convention(method) (Int, @inout DidSetWillSetTests) -> () - // CHECK-NEXT: end_access [[WRITE]] - // CHECK-NEXT: [[WRITE:%.*]] = begin_access [modify] [unknown] %1 - // CHECK-NEXT: [[AADDR:%.*]] = struct_element_addr [[WRITE]] : $*DidSetWillSetTests, #DidSetWillSetTests.a - // CHECK-NEXT: assign %0 to [[AADDR]] : $*Int - // CHECK-NEXT: end_access [[WRITE]] - // CHECK-NEXT: [[WRITE:%.*]] = begin_access [modify] [unknown] %1 - // CHECK-NEXT: // function_ref {{.*}}.DidSetWillSetTests.a.didset : Swift.Int - // CHECK-NEXT: [[DIDSETFN:%.*]] = function_ref @$s9observers010DidSetWillC5TestsV1a{{[_0-9a-zA-Z]*}}vW : $@convention(method) (Int, @inout DidSetWillSetTests) -> () - // CHECK-NEXT: apply [[DIDSETFN]]([[OLDVAL]], [[WRITE]]) : $@convention(method) (Int, @inout DidSetWillSetTests) -> () + // CHECK-NEXT: [[MODIFY_ONE:%.*]] = begin_access [modify] [unknown] %1 : $*DidSetWillSetTests + // CHECK-NEXT: // function_ref observers.DidSetWillSetTests.a.willset : Swift.Int + // CHECK-NEXT: [[WILLSETFN:%.*]] = function_ref @$s9observers010DidSetWillC5TestsV1aSivw : $@convention(method) (Int, @inout DidSetWillSetTests) -> () + // CHECK-NEXT: [[RESULT:%.*]] = apply [[WILLSETFN]]([[NEWVALUE]], [[MODIFY_ONE]]) : $@convention(method) (Int, @inout DidSetWillSetTests) -> () + // CHECK-NEXT: end_access [[MODIFY_ONE]] : $*DidSetWillSetTests + + // CHECK-NEXT: [[MODIFY_TWO:%.*]] = begin_access [modify] [unknown] %1 : $*DidSetWillSetTests + // CHECK-NEXT: [[ADDR:%.*]] = struct_element_addr [[MODIFY_TWO]] : $*DidSetWillSetTests, #DidSetWillSetTests.a + // CHECK-NEXT: assign [[NEWVALUE]] to [[ADDR]] : $*Int + // CHECK-NEXT: end_access [[MODIFY_TWO]] : $*DidSetWillSetTests + + // CHECK-NEXT: [[MODIFY_THREE:%.*]] = begin_access [modify] [unknown] %1 : $*DidSetWillSetTests + // CHECK-NEXT: // function_ref observers.DidSetWillSetTests.a.didset : Swift.Int + // CHECK-NEXT: [[DIDSETFN:%.*]] = function_ref @$s9observers010DidSetWillC5TestsV1aSivW : $@convention(method) (@inout DidSetWillSetTests) -> () + // CHECK-NEXT: [[RESULT:%.*]] = apply [[DIDSETFN]]([[MODIFY_THREE]]) : $@convention(method) (@inout DidSetWillSetTests) -> () + // CHECK-NEXT: end_access [[MODIFY_THREE]] : $*DidSetWillSetTests + // CHECK-NEXT: [[TUPLE:%.*]] = tuple () + // CHECK-NEXT: return [[TUPLE]] : $() } // CHECK-LABEL: sil hidden [ossa] @$s9observers010DidSetWillC5TestsV8testReadSiyF @@ -248,8 +246,8 @@ func local_observing_property(_ arg: Int) { // Ensure that setting the variable from within its own didSet doesn't recursively call didSet. // CHECK-LABEL: sil private [ossa] @$s9observers24local_observing_property{{[_0-9a-zA-Z]*}}SiF13localproperty{{[_0-9a-zA-Z]*}}SivW -// CHECK: bb0(%0 : $Int, %1 : @guaranteed ${ var Int }) -// CHECK: [[POINTER:%.*]] = project_box %1 : ${ var Int }, 0 +// CHECK: bb0(%0 : @guaranteed ${ var Int }) +// CHECK: [[POINTER:%.*]] = project_box %0 : ${ var Int }, 0 // CHECK: // function_ref observers.zero.unsafeMutableAddressor : Swift.Int // CHECK-NEXT: [[ZEROFN:%.*]] = function_ref @$s9observers4zero{{[_0-9a-zA-Z]*}}vau // CHECK-NEXT: [[ZERORAW:%.*]] = apply [[ZEROFN]]() : $@convention(thin) () -> Builtin.RawPointer @@ -347,7 +345,7 @@ struct ObservingPropertiesWithOwnershipTypes { } } -// CHECK-LABEL: sil private [ossa] @$s9observers37ObservingPropertiesWithOwnershipTypesV13alwaysPresentAA3RefCvW : $@convention(method) (@guaranteed Ref, @inout ObservingPropertiesWithOwnershipTypes) -> () { +// CHECK-LABEL: sil private [ossa] @$s9observers37ObservingPropertiesWithOwnershipTypesV13alwaysPresentAA3RefCvW : $@convention(method) (@inout ObservingPropertiesWithOwnershipTypes) -> () { struct ObservingPropertiesWithOwnershipTypesInferred { unowned var alwaysPresent = Ref() { @@ -361,7 +359,7 @@ struct ObservingPropertiesWithOwnershipTypesInferred { } } -// CHECK-LABEL: sil private [ossa] @$s9observers45ObservingPropertiesWithOwnershipTypesInferredV13alwaysPresentAA3RefCvW : $@convention(method) (@guaranteed Ref, @inout ObservingPropertiesWithOwnershipTypesInferred) -> () { +// CHECK-LABEL: sil private [ossa] @$s9observers45ObservingPropertiesWithOwnershipTypesInferredV13alwaysPresentAA3RefCvW : $@convention(method) (@inout ObservingPropertiesWithOwnershipTypesInferred) -> () { // CHECK-LABEL: sil private [ossa] @$s9observers45ObservingPropertiesWithOwnershipTypesInferredV12maybePresentAA3RefCSgvw : $@convention(method) (@guaranteed Optional, @inout ObservingPropertiesWithOwnershipTypesInferred) -> () { // Initializing constructor tries to initialize computed property overridden with willSet/didSet @@ -375,7 +373,7 @@ class ObservedDerived : ObservedBase { } } -// CHECK-LABEL: sil private [ossa] @$s9observers15ObservedDerivedC9printInfoAA3RefCSgvW : $@convention(method) (@guaranteed Optional, @guaranteed ObservedDerived) -> () { +// CHECK-LABEL: sil private [ossa] @$s9observers15ObservedDerivedC9printInfoAA3RefCSgvW : $@convention(method) (@guaranteed ObservedDerived) -> () { /// crash when overriding internal property with @@ -393,13 +391,13 @@ public class DerivedClassWithPublicProperty : BaseClassWithInternalProperty { // CHECK-LABEL: sil hidden [transparent] [ossa] @$s9observers29BaseClassWithInternalPropertyC1xytvg -// CHECK-LABEL: sil [transparent] [serialized] [ossa] @$s9observers30DerivedClassWithPublicPropertyC1xytvg +// CHECK-LABEL: sil [ossa] @$s9observers30DerivedClassWithPublicPropertyC1xytvg // CHECK: bb0([[SELF:%.*]] : @guaranteed $DerivedClassWithPublicProperty): // CHECK: [[SELF_COPY:%.*]] = copy_value [[SELF]] : $DerivedClassWithPublicProperty // CHECK-NEXT: [[SUPER:%.*]] = upcast [[SELF_COPY]] : $DerivedClassWithPublicProperty to $BaseClassWithInternalProperty // CHECK-NEXT: [[BORROWED_SUPER:%.*]] = begin_borrow [[SUPER]] -// CHECK-NEXT: [[DOWNCAST_BORROWED_SUPER:%.*]] = unchecked_ref_cast [[BORROWED_SUPER]] : $BaseClassWithInternalProperty to $DerivedClassWithPublicProperty -// CHECK-NEXT: [[METHOD:%.*]] = super_method [[DOWNCAST_BORROWED_SUPER]] : $DerivedClassWithPublicProperty, #BaseClassWithInternalProperty.x!getter : (BaseClassWithInternalProperty) -> () -> (), $@convention(method) (@guaranteed BaseClassWithInternalProperty) -> () +// CHECK-NEXT: // function_ref observers.BaseClassWithInternalProperty.x.getter : () +// CHECK-NEXT: [[METHOD:%.*]] = function_ref @$s9observers29BaseClassWithInternalPropertyC1xytvg : $@convention(method) (@guaranteed BaseClassWithInternalProperty) -> () // CHECK-NEXT: [[RESULT:%.*]] = apply [[METHOD]]([[BORROWED_SUPER]]) : $@convention(method) (@guaranteed BaseClassWithInternalProperty) -> () // CHECK-NEXT: end_borrow [[BORROWED_SUPER]] // CHECK-NEXT: destroy_value [[SUPER]] : $BaseClassWithInternalProperty @@ -418,7 +416,7 @@ public class ConcreteDerived : GenericBase { } } -// CHECK-LABEL: sil private [ossa] @$s9observers15ConcreteDerivedC7storageSiSgvW : $@convention(method) (Optional, @guaranteed ConcreteDerived) -> () { +// CHECK-LABEL: sil private [ossa] @$s9observers15ConcreteDerivedC7storageSiSgvW : $@convention(method) (@guaranteed ConcreteDerived) -> () { // Make sure we upcast properly when the overridden property is in an ancestor @@ -435,4 +433,4 @@ public class DerivedObserved : MiddleObserved { } } -// CHECK-LABEL: sil private [ossa] @$s9observers15DerivedObservedC1xSivW : $@convention(method) (Int, @guaranteed DerivedObserved) -> () { +// CHECK-LABEL: sil private [ossa] @$s9observers15DerivedObservedC1xSivW : $@convention(method) (@guaranteed DerivedObserved) -> () { diff --git a/test/SILGen/properties_swift4.swift b/test/SILGen/properties_swift4.swift index 1fc547ab53ddf..6a759ec316ef0 100644 --- a/test/SILGen/properties_swift4.swift +++ b/test/SILGen/properties_swift4.swift @@ -68,7 +68,7 @@ struct DidSetWillSetTests: ForceAccessors { // CHECK-NEXT: [[READ:%.*]] = begin_access [read] [dynamic] [[ZEROADDR]] : $*Int // CHECK-NEXT: [[ZERO:%.*]] = load [trivial] [[READ]] // CHECK-NEXT: end_access [[READ]] : $*Int - // CHECK-NEXT: [[WRITE:%.*]] = begin_access [modify] [unknown] %1 + // CHECK-NEXT: [[WRITE:%.*]] = begin_access [modify] [unknown] %0 // CHECK-NEXT: [[AADDR:%.*]] = struct_element_addr [[WRITE]] : $*DidSetWillSetTests, #DidSetWillSetTests.a // CHECK-NEXT: assign [[ZERO]] to [[AADDR]] @@ -103,7 +103,7 @@ struct DidSetWillSetTests: ForceAccessors { // CHECK: [[BOX:%.*]] = alloc_box ${ var DidSetWillSetTests }, var, name "other" // CHECK-NEXT: [[BOXADDR:%.*]] = project_box [[BOX]] : ${ var DidSetWillSetTests }, 0 - // CHECK-NEXT: [[READ_SELF:%.*]] = begin_access [read] [unknown] %1 : $*DidSetWillSetTests + // CHECK-NEXT: [[READ_SELF:%.*]] = begin_access [read] [unknown] %0 : $*DidSetWillSetTests // CHECK-NEXT: copy_addr [[READ_SELF]] to [initialization] [[BOXADDR]] : $*DidSetWillSetTests // CHECK-NEXT: end_access [[READ_SELF]] : $*DidSetWillSetTests diff --git a/test/SILGen/properties_swift5.swift b/test/SILGen/properties_swift5.swift index 92b791a5f9e92..07bb50aca4ff4 100644 --- a/test/SILGen/properties_swift5.swift +++ b/test/SILGen/properties_swift5.swift @@ -70,7 +70,7 @@ struct DidSetWillSetTests: ForceAccessors { // CHECK-NEXT: [[READ:%.*]] = begin_access [read] [dynamic] [[ZEROADDR]] : $*Int // CHECK-NEXT: [[ZERO:%.*]] = load [trivial] [[READ]] // CHECK-NEXT: end_access [[READ]] : $*Int - // CHECK-NEXT: [[WRITE:%.*]] = begin_access [modify] [unknown] %1 + // CHECK-NEXT: [[WRITE:%.*]] = begin_access [modify] [unknown] %0 // CHECK-NEXT: [[AADDR:%.*]] = struct_element_addr [[WRITE]] : $*DidSetWillSetTests, #DidSetWillSetTests.a // CHECK-NEXT: assign [[ZERO]] to [[AADDR]] @@ -107,7 +107,7 @@ struct DidSetWillSetTests: ForceAccessors { // CHECK: [[BOX:%.*]] = alloc_box ${ var DidSetWillSetTests }, var, name "other" // CHECK-NEXT: [[BOXADDR:%.*]] = project_box [[BOX]] : ${ var DidSetWillSetTests }, 0 - // CHECK-NEXT: [[READ_SELF:%.*]] = begin_access [read] [unknown] %1 : $*DidSetWillSetTests + // CHECK-NEXT: [[READ_SELF:%.*]] = begin_access [read] [unknown] %0 : $*DidSetWillSetTests // CHECK-NEXT: copy_addr [[READ_SELF]] to [initialization] [[BOXADDR]] : $*DidSetWillSetTests // CHECK-NEXT: end_access [[READ_SELF]] : $*DidSetWillSetTests diff --git a/test/SILGen/property_wrappers.swift b/test/SILGen/property_wrappers.swift index b20acd5973192..6292a49db145f 100644 --- a/test/SILGen/property_wrappers.swift +++ b/test/SILGen/property_wrappers.swift @@ -669,12 +669,10 @@ class Somesubclass : Someclass { // to the superclass' accessors. // CHECK-LABEL: sil hidden [ossa] @$s17property_wrappers12SomesubclassC0A0Sivs : $@convention(method) (Int, @guaranteed Somesubclass) -> () // CHECK: bb0([[NEW:%.+]] : $Int, {{%.+}} : @guaranteed $Somesubclass): - // CHECK: [[GETTER:%.+]] = function_ref @$s17property_wrappers9SomeclassC0A0Sivg : $@convention(method) (@guaranteed Someclass) -> Int - // CHECK: [[OLD:%.+]] = apply [[GETTER]]({{%.+}}) : $@convention(method) (@guaranteed Someclass) -> Int // CHECK: [[SETTER:%.+]] = function_ref @$s17property_wrappers9SomeclassC0A0Sivs : $@convention(method) (Int, @guaranteed Someclass) -> () // CHECK: apply [[SETTER]]([[NEW]], {{%.+}}) : $@convention(method) (Int, @guaranteed Someclass) -> () - // CHECK: [[DIDSET:%.+]] = function_ref @$s17property_wrappers12SomesubclassC0A0SivW : $@convention(method) (Int, @guaranteed Somesubclass) -> () - // CHECK: apply [[DIDSET]]([[OLD]], {{%.+}}) : $@convention(method) (Int, @guaranteed Somesubclass) -> () + // CHECK: [[DIDSET:%.+]] = function_ref @$s17property_wrappers12SomesubclassC0A0SivW : $@convention(method) (@guaranteed Somesubclass) -> () + // CHECK: apply [[DIDSET]]({{%.+}}) : $@convention(method) (@guaranteed Somesubclass) -> () didSet { print("Subclass") } diff --git a/test/SILOptimizer/di_property_wrappers.swift b/test/SILOptimizer/di_property_wrappers.swift index 698e5d1c98a69..7129bc26d5821 100644 --- a/test/SILOptimizer/di_property_wrappers.swift +++ b/test/SILOptimizer/di_property_wrappers.swift @@ -219,8 +219,8 @@ func testRefStruct() { // CHECK-NEXT: + payload alloc 42 // CHECK-NEXT: .. init value = 42 // CHECK-NEXT: + payload alloc 27 - // CHECK-NEXT: .. set value = 27 // CHECK-NEXT: - payload free 42 + // CHECK-NEXT: .. set value = 27 let t1 = RefStruct() // CHECK-NEXT: value = 27 print(t1.wrapped) @@ -271,8 +271,8 @@ func testGenericClass() { // CHECK-NEXT: + payload alloc 42 // CHECK-NEXT: .. init value = 42 // CHECK-NEXT: + payload alloc 27 - // CHECK-NEXT: .. set value = 27 // CHECK-NEXT: - payload free 42 + // CHECK-NEXT: .. set value = 27 let t1 = GenericClass() // CHECK-NEXT: value = 27 print(t1.wrapped) diff --git a/test/attr/attr_native_dynamic.swift b/test/attr/attr_native_dynamic.swift index 5a9443a8ff130..4a19f0dac1ebc 100644 --- a/test/attr/attr_native_dynamic.swift +++ b/test/attr/attr_native_dynamic.swift @@ -70,7 +70,7 @@ struct Strukt { } } - // CHECK: (var_decl {{.*}} "storedWithObserver" type='Int' interface type='Int' access=internal dynamic readImpl=stored writeImpl=stored_with_observers readWriteImpl=materialize_to_temporary + // CHECK: (var_decl {{.*}} "storedWithObserver" type='Int' interface type='Int' access=internal dynamic readImpl=stored writeImpl=stored_with_observers readWriteImpl=stored_simple_didset // CHECK: (accessor_decl {{.*}}access=private dynamic didSet_for=storedWithObserver // CHECK: (accessor_decl {{.*}}access=internal dynamic get_for=storedWithObserver // CHECK: (accessor_decl {{.*}}access=internal set_for=storedWithObserver diff --git a/test/decl/var/didset_oldvalue.swift b/test/decl/var/didset_oldvalue.swift new file mode 100644 index 0000000000000..03e998f9d032b --- /dev/null +++ b/test/decl/var/didset_oldvalue.swift @@ -0,0 +1,62 @@ +// RUN: %target-run-simple-swift | %FileCheck %s +// REQUIRES: executable_test + +import Swift + +// SR-11297 + +// Do not call the getter to fetch the oldValue when oldValue is not +// referenced in the body +@propertyWrapper +struct Delayed { + var wrappedValue: Value { + get { + guard let value = value else { + preconditionFailure("Property \(String(describing: self)) has not been set yet") + } + return value + } + + set { + guard value == nil else { + preconditionFailure("Property \(String(describing: self)) has already been set") + } + value = newValue + } + } + + var value: Value? +} + +class Foo { + @Delayed var bar: Int { + // CHECK: Hello, world! + didSet { print("Hello, world!") } + } +} + +let foo = Foo() +foo.bar = 1 + +// Call the getter to fetch the oldValue when oldValue is referenced in body +class AnotherFoo { + var bar: Int = 0 { + // CHECK: 0 + didSet { print(oldValue) } + } +} + +let anotherFoo = AnotherFoo() +anotherFoo.bar = 1 + +// Call the getter to fetch the oldValue when oldValue is explicitly specified +// as a parameter, but not used in the body. +class YetAnotherFoo { + var bar: Int = 0 { + // CHECK: World, hello! + didSet(oldValue) { print("World, hello!") } + } +} + +let yetAnotherFoo = YetAnotherFoo() +yetAnotherFoo.bar = 1 \ No newline at end of file