diff --git a/include/swift/AST/Decl.h b/include/swift/AST/Decl.h index 7e914bcd57720..a56cab8a9acc0 100644 --- a/include/swift/AST/Decl.h +++ b/include/swift/AST/Decl.h @@ -5319,6 +5319,16 @@ class AbstractStorageDecl : public ValueDecl { /// it. bool hasStorage() const; + /// Return true if this is a VarDecl that has init accessor associated + /// with it. + bool hasInitAccessor() const; + + /// Return true if this is a property that either has storage + /// or init accessor associated with it. + bool supportsInitialization() const { + return hasStorage() || hasInitAccessor(); + } + /// Return true if this storage has the basic accessors/capability /// to be mutated. This is generally constant after the accessors are /// installed by the parser/importer/whatever. diff --git a/include/swift/AST/DeclContext.h b/include/swift/AST/DeclContext.h index 8de5bd8494950..889ba60edf92a 100644 --- a/include/swift/AST/DeclContext.h +++ b/include/swift/AST/DeclContext.h @@ -82,6 +82,7 @@ namespace swift { class SerializedDefaultArgumentInitializer; class SerializedTopLevelCodeDecl; class StructDecl; + class AccessorDecl; namespace serialization { using DeclID = llvm::PointerEmbeddedInt; @@ -457,6 +458,18 @@ class alignas(1 << DeclContextAlignInBits) DeclContext return const_cast(this)->getInnermostMethodContext(); } + /// Returns the innermost accessor context that belongs to a property. + /// + /// This routine looks through closure, initializer, and local function + /// contexts to find the innermost accessor declaration. + /// + /// \returns the innermost accessor, or null if there is no such context. + LLVM_READONLY + AccessorDecl *getInnermostPropertyAccessorContext(); + const AccessorDecl *getInnermostPropertyAccessorContext() const { + return const_cast(this)->getInnermostPropertyAccessorContext(); + } + /// Returns the innermost type context. /// /// This routine looks through closure, initializer, and local function diff --git a/include/swift/AST/DiagnosticsSema.def b/include/swift/AST/DiagnosticsSema.def index 47fb75e38625b..97c04c241a85c 100644 --- a/include/swift/AST/DiagnosticsSema.def +++ b/include/swift/AST/DiagnosticsSema.def @@ -7296,7 +7296,22 @@ ERROR(init_accessor_accesses_attribute_on_other_declaration,none, ERROR(init_accessor_property_both_init_and_accessed,none, "property %0 cannot be both initialized and accessed", (DeclName)) - +ERROR(invalid_use_of_self_in_init_accessor,none, + "'self' within init accessors can only be used to reference " + "properties listed in 'initializes' and 'accesses'; " + "init accessors are run before 'self' is fully available", ()) +ERROR(init_accessor_invalid_member_ref,none, + "cannot reference instance member %0; init accessors can only " + "refer to instance properties listed in 'initializes' and " + "'accesses' attributes", + (DeclNameRef)) +ERROR(cannot_synthesize_memberwise_due_to_property_init_order,none, + "cannot synthesize memberwise initializer", + ()) +NOTE(out_of_order_access_in_init_accessor,none, + "init accessor for %0 cannot access stored property %1 because it " + "is called before %1 is initialized", + (Identifier, Identifier)) #define UNDEFINE_DIAGNOSTIC_MACROS #include "DefineDiagnosticMacros.h" diff --git a/include/swift/AST/TypeCheckRequests.h b/include/swift/AST/TypeCheckRequests.h index 0dd81a6b654cf..80fcb212739e1 100644 --- a/include/swift/AST/TypeCheckRequests.h +++ b/include/swift/AST/TypeCheckRequests.h @@ -4312,6 +4312,22 @@ class IsNonUserModuleRequest bool isCached() const { return true; } }; +class HasInitAccessorRequest + : public SimpleRequest { +public: + using SimpleRequest::SimpleRequest; + +private: + friend SimpleRequest; + + // Evaluation. + bool evaluate(Evaluator &evaluator, AbstractStorageDecl *decl) const; + +public: + bool isCached() const { return true; } +}; + class InitAccessorReferencedVariablesRequest : public SimpleRequest(DeclAttribute *, AccessorDecl *, diff --git a/include/swift/AST/TypeCheckerTypeIDZone.def b/include/swift/AST/TypeCheckerTypeIDZone.def index e13aced855628..a7ae070b5aa99 100644 --- a/include/swift/AST/TypeCheckerTypeIDZone.def +++ b/include/swift/AST/TypeCheckerTypeIDZone.def @@ -487,6 +487,9 @@ SWIFT_REQUEST(TypeChecker, IsNonUserModuleRequest, SWIFT_REQUEST(TypeChecker, TypeCheckObjCImplementationRequest, unsigned(ExtensionDecl *), Cached, NoLocationInfo) +SWIFT_REQUEST(TypeChecker, HasInitAccessorRequest, + bool(AbstractStorageDecl *), Cached, + NoLocationInfo) SWIFT_REQUEST(TypeChecker, InitAccessorReferencedVariablesRequest, ArrayRef(DeclAttribute *, AccessorDecl *, ArrayRef), diff --git a/include/swift/Sema/CSFix.h b/include/swift/Sema/CSFix.h index 55d9349726ec3..4b05b20679351 100644 --- a/include/swift/Sema/CSFix.h +++ b/include/swift/Sema/CSFix.h @@ -447,6 +447,10 @@ enum class FixKind : uint8_t { /// Ignore missing 'each' keyword before value pack reference. IgnoreMissingEachKeyword, + + /// Ignore the fact that member couldn't be referenced within init accessor + /// because its name doesn't appear in 'initializes' or 'accesses' attributes. + AllowInvalidMemberReferenceInInitAccessor, }; class ConstraintFix { @@ -3536,6 +3540,39 @@ class IgnoreMissingEachKeyword final : public ConstraintFix { } }; +class AllowInvalidMemberReferenceInInitAccessor final : public ConstraintFix { + DeclNameRef MemberName; + + AllowInvalidMemberReferenceInInitAccessor(ConstraintSystem &cs, + DeclNameRef memberName, + ConstraintLocator *locator) + : ConstraintFix(cs, FixKind::AllowInvalidMemberReferenceInInitAccessor, + locator), + MemberName(memberName) {} + +public: + std::string getName() const override { + llvm::SmallVector scratch; + auto memberName = MemberName.getString(scratch); + return "allow reference to member '" + memberName.str() + + "' in init accessor"; + } + + bool diagnose(const Solution &solution, bool asNote = false) const override; + + bool diagnoseForAmbiguity(CommonFixesArray commonFixes) const override { + return diagnose(*commonFixes.front().first); + } + + static AllowInvalidMemberReferenceInInitAccessor * + create(ConstraintSystem &cs, DeclNameRef memberName, + ConstraintLocator *locator); + + static bool classof(const ConstraintFix *fix) { + return fix->getKind() == FixKind::AllowInvalidMemberReferenceInInitAccessor; + } +}; + } // end namespace constraints } // end namespace swift diff --git a/include/swift/Sema/ConstraintSystem.h b/include/swift/Sema/ConstraintSystem.h index 5a54d3fd8ae2d..ecdc07042797f 100644 --- a/include/swift/Sema/ConstraintSystem.h +++ b/include/swift/Sema/ConstraintSystem.h @@ -1879,6 +1879,11 @@ struct MemberLookupResult { /// This is a static member being access through a protocol metatype /// but its result type doesn't conform to this protocol. UR_InvalidStaticMemberOnProtocolMetatype, + + /// This is a member that doesn't appear in 'initializes' and/or + /// 'accesses' attributes of the init accessor and therefore canno + /// t be referenced in its body. + UR_UnavailableWithinInitAccessor, }; /// This is a list of considered (but rejected) candidates, along with a diff --git a/lib/AST/Decl.cpp b/lib/AST/Decl.cpp index 795e718e6f108..92eab30a68e2f 100644 --- a/lib/AST/Decl.cpp +++ b/lib/AST/Decl.cpp @@ -6711,6 +6711,12 @@ Type AbstractStorageDecl::getValueInterfaceType() const { return cast(this)->getElementInterfaceType(); } +bool AbstractStorageDecl::hasInitAccessor() const { + return evaluateOrDefault( + getASTContext().evaluator, + HasInitAccessorRequest{const_cast(this)}, false); +} + VarDecl::VarDecl(DeclKind kind, bool isStatic, VarDecl::Introducer introducer, SourceLoc nameLoc, Identifier name, DeclContext *dc, StorageIsMutable_t supportsMutation) @@ -7159,6 +7165,12 @@ bool VarDecl::isMemberwiseInitialized(bool preferDeclaredProperties) const { isBackingStorageForDeclaredProperty(this)) return false; + // If this is a computed property with `init` accessor, it's + // memberwise initializable when it could be used to initialize + // other stored properties. + if (auto *init = getAccessor(AccessorKind::Init)) + return init->getAttrs().hasAttribute(); + // If this is a computed property, it's not memberwise initialized unless // the caller has asked for the declared properties and it is either a // `lazy` property or a property with an attached wrapper. diff --git a/lib/AST/DeclContext.cpp b/lib/AST/DeclContext.cpp index 9a02c2f6cc9e0..88e9cf8da265b 100644 --- a/lib/AST/DeclContext.cpp +++ b/lib/AST/DeclContext.cpp @@ -211,6 +211,24 @@ AbstractFunctionDecl *DeclContext::getInnermostMethodContext() { return nullptr; } +AccessorDecl *DeclContext::getInnermostPropertyAccessorContext() { + auto dc = this; + do { + if (auto decl = dc->getAsDecl()) { + auto accessor = dyn_cast(decl); + // If we found a non-accessor decl, we're done. + if (accessor == nullptr) + return nullptr; + + auto *storage = accessor->getStorage(); + if (isa(storage) && storage->getDeclContext()->isTypeContext()) + return accessor; + } + } while ((dc = dc->getParent())); + + return nullptr; +} + bool DeclContext::isTypeContext() const { if (auto decl = getAsDecl()) return isa(decl) || isa(decl); diff --git a/lib/Parse/ParseExpr.cpp b/lib/Parse/ParseExpr.cpp index ab5d8f4ed1606..019ef7545e628 100644 --- a/lib/Parse/ParseExpr.cpp +++ b/lib/Parse/ParseExpr.cpp @@ -1059,14 +1059,14 @@ bool Parser::isStartOfGetSetAccessor() { // The only case this can happen is if the accessor label is immediately after // a brace (possibly preceded by attributes). "get" is implicit, so it can't // be checked for. Conveniently however, get/set properties are not allowed - // to have initializers, so we don't have an ambiguity, we just have to check - // for observing accessors. + // to have initializers unless they have `init` accessor, so we don't have an + // ambiguity, we just have to check for observing accessors and init accessor. // // If we have a 'didSet' or a 'willSet' label, disambiguate immediately as // an accessor block. Token NextToken = peekToken(); if (NextToken.isContextualKeyword("didSet") || - NextToken.isContextualKeyword("willSet")) + NextToken.isContextualKeyword("willSet") || NextToken.is(tok::kw_init)) return true; // If we don't have attributes, then it cannot be an accessor block. @@ -1087,9 +1087,9 @@ bool Parser::isStartOfGetSetAccessor() { skipSingle(); } - // Check if we have 'didSet'/'willSet' after attributes. + // Check if we have 'didSet'/'willSet' or 'init' after attributes. return Tok.isContextualKeyword("didSet") || - Tok.isContextualKeyword("willSet"); + Tok.isContextualKeyword("willSet") || Tok.is(tok::kw_init); } /// Recover invalid uses of trailing closures in a situation diff --git a/lib/SILGen/SILGenConstructor.cpp b/lib/SILGen/SILGenConstructor.cpp index 9f81aed99eed4..07add74e1b6ba 100644 --- a/lib/SILGen/SILGenConstructor.cpp +++ b/lib/SILGen/SILGenConstructor.cpp @@ -270,12 +270,13 @@ static RValue maybeEmitPropertyWrapperInitFromValue( subs, std::move(arg)); } -static void emitApplyOfInitAccessor(SILGenFunction &SGF, SILLocation loc, - AccessorDecl *accessor, SILValue selfValue, - SILType selfTy, RValue &&initialValue) { +static void +emitApplyOfInitAccessor(SILGenFunction &SGF, SILLocation loc, + AccessorDecl *accessor, SILValue selfValue, + SILType selfTy, RValue &&initialValue) { SmallVector arguments; - auto emitFieldReference = [&](VarDecl *field) { + auto emitFieldReference = [&](VarDecl *field, bool forInit = false) { auto fieldTy = selfTy.getFieldType(field, SGF.SGM.M, SGF.getTypeExpansionContext()); return SGF.B.createStructElementAddr(loc, selfValue, field, @@ -285,7 +286,7 @@ static void emitApplyOfInitAccessor(SILGenFunction &SGF, SILLocation loc, // First, let's emit all of the indirect results. if (auto *initAttr = accessor->getAttrs().getAttribute()) { for (auto *property : initAttr->getPropertyDecls(accessor)) { - arguments.push_back(emitFieldReference(property)); + arguments.push_back(emitFieldReference(property, /*forInit=*/true)); } } @@ -397,29 +398,39 @@ static void emitImplicitValueConstructor(SILGenFunction &SGF, // If we have an indirect return slot, initialize it in-place. if (resultSlot) { - // Tracks all the init accessors we have emitted - // because they can initialize more than one property. - llvm::SmallPtrSet emittedInitAccessors; - auto elti = elements.begin(), eltEnd = elements.end(); - for (VarDecl *field : decl->getStoredProperties()) { + + llvm::SmallPtrSet storedProperties; + { + auto properties = decl->getStoredProperties(); + storedProperties.insert(properties.begin(), properties.end()); + } + + for (auto *member : decl->getMembers()) { + auto *field = dyn_cast(member); + if (!field) + continue; + + if (initializedViaAccessor.count(field)) + continue; // Handle situations where this stored propery is initialized // via a call to an init accessor on some other property. - if (initializedViaAccessor.count(field)) { - auto *initProperty = initializedViaAccessor.find(field)->second; - auto *initAccessor = initProperty->getAccessor(AccessorKind::Init); - - if (emittedInitAccessors.count(initAccessor)) + if (auto *initAccessor = field->getAccessor(AccessorKind::Init)) { + if (field->isMemberwiseInitialized(/*preferDeclaredProperties=*/true)) { + assert(elti != eltEnd && + "number of args does not match number of fields"); + + emitApplyOfInitAccessor(SGF, Loc, initAccessor, resultSlot, selfTy, + std::move(*elti)); + ++elti; continue; + } + } - emitApplyOfInitAccessor(SGF, Loc, initAccessor, resultSlot, selfTy, - std::move(*elti)); - - emittedInitAccessors.insert(initAccessor); - ++elti; + // If this is not one of the stored properties, let's move on. + if (!storedProperties.count(field)) continue; - } auto fieldTy = selfTy.getFieldType(field, SGF.SGM.M, SGF.getTypeExpansionContext()); @@ -1463,6 +1474,13 @@ void SILGenFunction::emitMemberInitializers(DeclContext *dc, if (auto pbd = dyn_cast(member)) { if (pbd->isStatic()) continue; + // Skip properties with init accessors, they could only be used + // explicitly and in memberwise initializers. + if (auto *var = pbd->getSingleVar()) { + if (var->hasInitAccessor()) + continue; + } + for (auto i : range(pbd->getNumPatternEntries())) { auto init = pbd->getExecutableInit(i); if (!init) continue; diff --git a/lib/SILOptimizer/Mandatory/DefiniteInitialization.cpp b/lib/SILOptimizer/Mandatory/DefiniteInitialization.cpp index 12cb54a377583..4ac28a2b993b6 100644 --- a/lib/SILOptimizer/Mandatory/DefiniteInitialization.cpp +++ b/lib/SILOptimizer/Mandatory/DefiniteInitialization.cpp @@ -1154,14 +1154,38 @@ void LifetimeChecker::doIt() { // All of the indirect results marked as "out" have to be fully initialized // before their lifetime ends. - if (TheMemory.isOut() && Uses.empty()) { - auto loc = TheMemory.getLoc(); - - std::string propertyName; - auto *property = TheMemory.getPathStringToElement(0, propertyName); - diagnose(Module, F.getLocation(), - diag::ivar_not_initialized_by_init_accessor, property->getName()); - EmittedErrorLocs.push_back(loc); + if (TheMemory.isOut()) { + auto diagnoseMissingInit = [&]() { + std::string propertyName; + auto *property = TheMemory.getPathStringToElement(0, propertyName); + diagnose(Module, F.getLocation(), + diag::ivar_not_initialized_by_init_accessor, + property->getName()); + EmittedErrorLocs.push_back(TheMemory.getLoc()); + }; + + // No uses means that there was no initialization. + if (Uses.empty()) { + diagnoseMissingInit(); + return; + } + + // Go over every return block and check whether member is fully initialized + // because it's possible that there is branch that doesn't have any use of + // the memory and nothing else is going to diagnose that. This is different + // from `self`, for example, because it would always have either `copy_addr` + // or `load` before return. + + auto returnBB = F.findReturnBB(); + + while (returnBB != F.end()) { + auto *terminator = returnBB->getTerminator(); + + if (!isInitializedAtUse(DIMemoryUse(terminator, DIUseKind::Load, 0, 1))) + diagnoseMissingInit(); + + ++returnBB; + } } // If the memory object has nontrivial type, then any destroy/release of the diff --git a/lib/Sema/CSDiagnostics.cpp b/lib/Sema/CSDiagnostics.cpp index 0ff4a45ad3484..1dcae15e7f492 100644 --- a/lib/Sema/CSDiagnostics.cpp +++ b/lib/Sema/CSDiagnostics.cpp @@ -8990,3 +8990,8 @@ bool MissingEachForValuePackReference::diagnoseAsError() { return true; } + +bool InvalidMemberReferenceWithinInitAccessor::diagnoseAsError() { + emitDiagnostic(diag::init_accessor_invalid_member_ref, MemberName); + return true; +} diff --git a/lib/Sema/CSDiagnostics.h b/lib/Sema/CSDiagnostics.h index f598d03dea103..224a25a59cea4 100644 --- a/lib/Sema/CSDiagnostics.h +++ b/lib/Sema/CSDiagnostics.h @@ -3002,6 +3002,19 @@ class MissingEachForValuePackReference final : public FailureDiagnostic { bool diagnoseAsError() override; }; +class InvalidMemberReferenceWithinInitAccessor final + : public FailureDiagnostic { + DeclNameRef MemberName; + +public: + InvalidMemberReferenceWithinInitAccessor(const Solution &solution, + DeclNameRef memberName, + ConstraintLocator *locator) + : FailureDiagnostic(solution, locator), MemberName(memberName) {} + + bool diagnoseAsError() override; +}; + } // end namespace constraints } // end namespace swift diff --git a/lib/Sema/CSFix.cpp b/lib/Sema/CSFix.cpp index 7f6899bf484fc..bb671d8e9dcb5 100644 --- a/lib/Sema/CSFix.cpp +++ b/lib/Sema/CSFix.cpp @@ -2804,3 +2804,18 @@ IgnoreMissingEachKeyword::create(ConstraintSystem &cs, Type valuePackTy, return new (cs.getAllocator()) IgnoreMissingEachKeyword(cs, valuePackTy, locator); } + +bool AllowInvalidMemberReferenceInInitAccessor::diagnose( + const Solution &solution, bool asNote) const { + InvalidMemberReferenceWithinInitAccessor failure(solution, MemberName, + getLocator()); + return failure.diagnose(asNote); +} + +AllowInvalidMemberReferenceInInitAccessor * +AllowInvalidMemberReferenceInInitAccessor::create(ConstraintSystem &cs, + DeclNameRef memberName, + ConstraintLocator *locator) { + return new (cs.getAllocator()) + AllowInvalidMemberReferenceInInitAccessor(cs, memberName, locator); +} diff --git a/lib/Sema/CSSimplify.cpp b/lib/Sema/CSSimplify.cpp index 1a82aad8d528b..c227906d1cf5b 100644 --- a/lib/Sema/CSSimplify.cpp +++ b/lib/Sema/CSSimplify.cpp @@ -9638,6 +9638,44 @@ performMemberLookup(ConstraintKind constraintKind, DeclNameRef memberName, } } + if (auto *UDE = + getAsExpr(memberLocator->getAnchor())) { + auto *base = UDE->getBase(); + if (auto *accessor = DC->getInnermostPropertyAccessorContext()) { + if (accessor->isInitAccessor() && isa(base) && + accessor->getImplicitSelfDecl() == + cast(base)->getDecl()) { + bool isValidReference = false; + + // If name doesn't appear in either `initializes` or `accesses` + // then it's invalid instance member. + + if (auto *initializesAttr = + accessor->getAttrs().getAttribute()) { + isValidReference |= llvm::any_of( + initializesAttr->getProperties(), [&](Identifier name) { + return DeclNameRef(name) == memberName; + }); + } + + if (auto *accessesAttr = + accessor->getAttrs().getAttribute()) { + isValidReference |= llvm::any_of( + accessesAttr->getProperties(), [&](Identifier name) { + return DeclNameRef(name) == memberName; + }); + } + + if (!isValidReference) { + result.addUnviable( + candidate, + MemberLookupResult::UR_UnavailableWithinInitAccessor); + return; + } + } + } + } + // If the underlying type of a typealias is fully concrete, it is legal // to access the type with a protocol metatype base. } else if (instanceTy->isExistentialType() && @@ -10218,6 +10256,10 @@ fixMemberRef(ConstraintSystem &cs, Type baseTy, case MemberLookupResult::UR_InvalidStaticMemberOnProtocolMetatype: return AllowInvalidStaticMemberRefOnProtocolMetatype::create(cs, locator); + + case MemberLookupResult::UR_UnavailableWithinInitAccessor: + return AllowInvalidMemberReferenceInInitAccessor::create(cs, memberName, + locator); } } @@ -14676,6 +14718,10 @@ ConstraintSystem::SolutionKind ConstraintSystem::simplifyFixConstraint( return recordFix(fix, 100) ? SolutionKind::Error : SolutionKind::Solved; } + case FixKind::AllowInvalidMemberReferenceInInitAccessor: { + return recordFix(fix, 5) ? SolutionKind::Error : SolutionKind::Solved; + } + case FixKind::ExplicitlyConstructRawRepresentable: { // Let's increase impact of this fix for binary operators because // it's possible to get both `.rawValue` and construction fixes for diff --git a/lib/Sema/CodeSynthesis.cpp b/lib/Sema/CodeSynthesis.cpp index 6a9f951ea2a75..9208dbc29e0e8 100644 --- a/lib/Sema/CodeSynthesis.cpp +++ b/lib/Sema/CodeSynthesis.cpp @@ -144,12 +144,6 @@ static void maybeAddMemberwiseDefaultArg(ParamDecl *arg, VarDecl *var, if (!var->getParentPattern()->getSingleVar()) return; - // FIXME: Don't attempt to synthesize default arguments for init - // accessor properties because there could be multiple properties - // with default values they are going to initialize. - if (var->getAccessor(AccessorKind::Init)) - return; - // Whether we have explicit initialization. bool isExplicitlyInitialized = false; if (auto pbd = var->getParentPatternBinding()) { @@ -303,9 +297,6 @@ static ConstructorDecl *createImplicitConstructor(NominalTypeDecl *decl, std::multimap initializedViaAccessor; decl->collectPropertiesInitializableByInitAccessors(initializedViaAccessor); - // A single property could be used to initialize N other stored - // properties via a call to its init accessor. - llvm::SmallPtrSet usedInitProperties; for (auto member : decl->getMembers()) { auto var = dyn_cast(member); if (!var) @@ -314,22 +305,10 @@ static ConstructorDecl *createImplicitConstructor(NominalTypeDecl *decl, if (!var->isMemberwiseInitialized(/*preferDeclaredProperties=*/true)) continue; - // Check whether this property could be initialized via init accessor. - // - // Note that we check for a single match here because intersecting - // properties are going to be diagnosed. - if (initializedViaAccessor.count(var) == 1) { - auto *initializerProperty = initializedViaAccessor.find(var)->second; - // Parameter for this property is already emitted. - if (usedInitProperties.count(initializerProperty)) - continue; - - var = initializerProperty; - usedInitProperties.insert(initializerProperty); - } + if (initializedViaAccessor.count(var)) + continue; accessLevel = std::min(accessLevel, var->getFormalAccess()); - params.push_back(createMemberwiseInitParameter(decl, Loc, var)); } } else if (ICK == ImplicitConstructorKind::DefaultDistributedActor) { @@ -1318,6 +1297,12 @@ HasMemberwiseInitRequest::evaluate(Evaluator &evaluator, if (hasUserDefinedDesignatedInit(evaluator, decl)) return false; + std::multimap initializedViaAccessor; + decl->collectPropertiesInitializableByInitAccessors(initializedViaAccessor); + + llvm::SmallPtrSet initializedProperties; + llvm::SmallVector> invalidOrderings; + for (auto *member : decl->getMembers()) { if (auto *var = dyn_cast(member)) { // If this is a backing storage property for a property wrapper, @@ -1325,10 +1310,75 @@ HasMemberwiseInitRequest::evaluate(Evaluator &evaluator, if (var->getOriginalWrappedProperty()) continue; - if (var->isMemberwiseInitialized(/*preferDeclaredProperties=*/true)) + if (!var->isMemberwiseInitialized(/*preferDeclaredProperties=*/true)) + continue; + + // If init accessors are not involved, we are done. + if (initializedViaAccessor.empty()) return true; + + // Check whether use of init accessors results in access to uninitialized + // properties. + + if (auto *initAccessor = var->getAccessor(AccessorKind::Init)) { + // Make sure that all properties accessed by init accessor + // are previously initialized. + if (auto accessAttr = + initAccessor->getAttrs().getAttribute()) { + for (auto *property : accessAttr->getPropertyDecls(initAccessor)) { + if (!initializedProperties.count(property)) + invalidOrderings.push_back( + {var, property->getName()}); + } + } + + // Record all of the properties initialized by calling init accessor. + if (auto initAttr = + initAccessor->getAttrs().getAttribute()) { + auto properties = initAttr->getPropertyDecls(initAccessor); + initializedProperties.insert(properties.begin(), properties.end()); + } + + continue; + } + + switch (initializedViaAccessor.count(var)) { + // Not covered by an init accessor. + case 0: + initializedProperties.insert(var); + continue; + + // Covered by a single init accessor, we'll handle that + // once we get to the property with init accessor. + case 1: + continue; + + // Covered by more than one init accessor which means that we + // cannot synthesize memberwise initializer due to intersecting + // initializations. + default: + return false; + } } } + + if (invalidOrderings.empty()) + return !initializedProperties.empty(); + + { + auto &diags = decl->getASTContext().Diags; + + diags.diagnose( + decl, diag::cannot_synthesize_memberwise_due_to_property_init_order); + + for (const auto &invalid : invalidOrderings) { + auto *accessor = invalid.first->getAccessor(AccessorKind::Init); + diags.diagnose(accessor->getLoc(), + diag::out_of_order_access_in_init_accessor, + invalid.first->getName(), invalid.second); + } + } + return false; } diff --git a/lib/Sema/PreCheckExpr.cpp b/lib/Sema/PreCheckExpr.cpp index 08c56383184cf..2b195b22c430e 100644 --- a/lib/Sema/PreCheckExpr.cpp +++ b/lib/Sema/PreCheckExpr.cpp @@ -1072,8 +1072,24 @@ namespace { if (auto unresolved = dyn_cast(expr)) { TypeChecker::checkForForbiddenPrefix( getASTContext(), unresolved->getName().getBaseName()); - return finish(true, TypeChecker::resolveDeclRefExpr(unresolved, DC, - UseErrorExprs)); + auto *refExpr = + TypeChecker::resolveDeclRefExpr(unresolved, DC, UseErrorExprs); + + // Check whether this is standalone `self` in init accessor, which + // is invalid. + if (auto *accessor = DC->getInnermostPropertyAccessorContext()) { + if (accessor->isInitAccessor() && isa(refExpr)) { + auto *DRE = cast(refExpr); + if (accessor->getImplicitSelfDecl() == DRE->getDecl() && + !isa_and_nonnull(Parent.getAsExpr())) { + Ctx.Diags.diagnose(unresolved->getLoc(), + diag::invalid_use_of_self_in_init_accessor); + refExpr = new (Ctx) ErrorExpr(unresolved->getSourceRange()); + } + } + } + + return finish(true, refExpr); } // Let's try to figure out if `InOutExpr` is out of place early diff --git a/lib/Sema/TypeCheckDeclPrimary.cpp b/lib/Sema/TypeCheckDeclPrimary.cpp index 436c940246762..9f2831eb2a5f0 100644 --- a/lib/Sema/TypeCheckDeclPrimary.cpp +++ b/lib/Sema/TypeCheckDeclPrimary.cpp @@ -2290,7 +2290,7 @@ class DeclChecker : public DeclVisitor { if (PBD->isInitialized(i)) { // Add the attribute that preserves the "has an initializer" value // across module generation, as required for TBDGen. - if (var->hasStorage() && + if (var->supportsInitialization() && !var->getAttrs().hasAttribute()) { var->getAttrs().add(new (Ctx) HasInitialValueAttr(/*IsImplicit=*/true)); diff --git a/lib/Sema/TypeCheckMacros.cpp b/lib/Sema/TypeCheckMacros.cpp index 14d3dfa315f36..df55981d1b105 100644 --- a/lib/Sema/TypeCheckMacros.cpp +++ b/lib/Sema/TypeCheckMacros.cpp @@ -1303,9 +1303,11 @@ Optional swift::expandAccessors( !accessorMacroOnlyIntroducesObservers(macro, roleAttr); if (foundNonObservingAccessor) { // If any non-observing accessor was added, mark the initializer as - // subsumed. + // subsumed unless it has init accessor, because the initializer in + // such cases could be used for memberwise initialization. if (auto var = dyn_cast(storage)) { - if (auto binding = var->getParentPatternBinding()) { + if (auto binding = var->getParentPatternBinding(); + !var->getAccessor(AccessorKind::Init)) { unsigned index = binding->getPatternEntryIndexForVarDecl(var); binding->setInitializerSubsumed(index); } diff --git a/lib/Sema/TypeCheckStorage.cpp b/lib/Sema/TypeCheckStorage.cpp index fe0a15bd6e7c5..5123fcc554ae4 100644 --- a/lib/Sema/TypeCheckStorage.cpp +++ b/lib/Sema/TypeCheckStorage.cpp @@ -301,8 +301,12 @@ StoredPropertiesAndMissingMembersRequest::evaluate(Evaluator &evaluator, return decl->getASTContext().AllocateCopy(results); } -/// Determine whether the given variable has an init accessor. -static bool hasInitAccessor(VarDecl *var) { +bool HasInitAccessorRequest::evaluate(Evaluator &evaluator, + AbstractStorageDecl *decl) const { + auto *var = dyn_cast(decl); + if (!var) + return false; + if (var->getAccessor(AccessorKind::Init)) return true; @@ -340,7 +344,7 @@ InitAccessorPropertiesRequest::evaluate(Evaluator &evaluator, SmallVector results; for (auto *member : decl->getMembers()) { auto *var = dyn_cast(member); - if (!var || var->isStatic() || !hasInitAccessor(var)) { + if (!var || var->isStatic() || !var->hasInitAccessor()) { continue; } @@ -3368,7 +3372,7 @@ static void finishStorageImplInfo(AbstractStorageDecl *storage, auto dc = storage->getDeclContext(); if (auto var = dyn_cast(storage)) { - if (!info.hasStorage()) { + if (!info.hasStorage() && !var->hasInitAccessor()) { if (auto *init = var->getParentExecutableInitializer()) { auto &Diags = var->getASTContext().Diags; Diags.diagnose(init->getLoc(), diag::getset_init) diff --git a/test/Interpreter/init_accessors.swift b/test/Interpreter/init_accessors.swift index 69e5f52ea99fa..ff8ba32daa435 100644 --- a/test/Interpreter/init_accessors.swift +++ b/test/Interpreter/init_accessors.swift @@ -332,3 +332,133 @@ test_assignments() // CHECK-NEXT: test-assignments-1: (3, 42) // CHECK-NEXT: a-init-accessor: 0 // CHECK-NEXT: test-assignments-2: (0, 2) + +func test_memberwise_ordering() { + struct Test1 { + var _a: Int + var _b: Int + + var a: Int { + init(initialValue) initializes(_a) accesses(_b) { + _a = initialValue + } + + get { _a } + set { } + } + } + + let test1 = Test1(_b: 42, a: 0) + print("test-memberwise-ordering-1: \(test1)") + + struct Test2 { + var _a: Int + + var pair: (Int, Int) { + init(initialValue) initializes(_a, _b) { + _a = initialValue.0 + _b = initialValue.1 + } + + get { (_a, _b) } + set { } + } + + var _b: Int + } + + let test2 = Test2(pair: (-1, -2)) + print("test-memberwise-ordering-2: \(test2)") + + struct Test3 { + var _a: Int + var _b: Int + + var pair: (Int, Int) { + init(initialValue) accesses(_a, _b) { + } + + get { (_a, _b) } + set { } + } + + var _c: Int + } + + let test3 = Test3(_a: 1, _b: 2, _c: 3) + print("test-memberwise-ordering-3: \(test3)") +} + +test_memberwise_ordering() +// CHECK: test-memberwise-ordering-1: Test1(_a: 0, _b: 42) +// CHECK-NEXT: test-memberwise-ordering-2: Test2(_a: -1, _b: -2) +// CHECK-NEXT: test-memberwise-ordering-3: Test3(_a: 1, _b: 2, _c: 3) + +func test_memberwise_with_default_args() { + struct TestWithoutDefault { + var _a: Int + var _b: Int + + var pair: (Int, Int) = (-1, 42) { + init(initialValue) initializes(_a, _b) { + _a = initialValue.0 + _b = initialValue.1 + } + + get { (0, 42) } + set { } + } + } + + let test1 = TestWithoutDefault() + print("test-memberwise_with_default-1: \(test1)") + + let test2 = TestWithoutDefault(pair: (42, -1)) + print("test-memberwise_with_default-2: \(test2)") + + struct TestDefaulted { + var _a: Int = 0 + var _b: Int = 0 + + var pair: (Int, Int) = (1, 2) { + init(initialValue) initializes(_a, _b) { + _a = initialValue.0 + _b = initialValue.1 + } + + get { (_a, _b) } + set { } + } + } + + let test3 = TestDefaulted() + print("test-defaulted-1: \(test3)") + + let test4 = TestDefaulted(pair: (3, 4)) + print("test-defaulted-2: \(test4)") + + class TestClass { + var _q: String = "<>" + var _a: Int = 1 + + var pair: (String, Int) = ("", 42) { + init(initialValue) initializes(_q, _a) { + _q = initialValue.0 + _a = initialValue.1 + } + + get { (_q, _a) } + set { } + } + } + + let test5 = TestClass() + print("test-defaulted-class: \(test5.pair)") +} + +test_memberwise_with_default_args() +// CHECK: test-memberwise_with_default-1: TestWithoutDefault(_a: -1, _b: 42) +// CHECK-NEXT: test-memberwise_with_default-2: TestWithoutDefault(_a: 42, _b: -1) +// CHECK-NEXT: test-defaulted-1: TestDefaulted(_a: 0, _b: 0) +// CHECK-NEXT: test-defaulted-2: TestDefaulted(_a: 3, _b: 4) +// CHECK-NEXT: test-defaulted-class: ("<>", 1) diff --git a/test/SILOptimizer/init_accessor_definite_init_diagnostics.swift b/test/SILOptimizer/init_accessor_definite_init_diagnostics.swift index 5198db703c0c8..2f5137d25b814 100644 --- a/test/SILOptimizer/init_accessor_definite_init_diagnostics.swift +++ b/test/SILOptimizer/init_accessor_definite_init_diagnostics.swift @@ -139,3 +139,43 @@ struct TestAccessBeforeInit { self.y = y } } + +class TestInitWithGuard { + var _a: Int + var _b: Int + + var pair1: (Int, Int) { + init(initialValue) initializes(_a, _b) { // expected-error {{property '_b' not initialized by init accessor}} + _a = initialValue.0 + + if _a > 0 { + return + } + + _b = initialValue.1 + } + + get { (_a, _b) } + set { } + } + + var pair2: (Int, Int) { + init(initialValue) initializes(_a, _b) { // Ok + _a = initialValue.0 + + if _a > 0 { + _b = 0 + return + } + + _b = initialValue.1 + } + + get { (_a, _b) } + set { } + } + + init(a: Int, b: Int) { + self.pair2 = (a, b) + } +} diff --git a/test/decl/var/init_accessors.swift b/test/decl/var/init_accessors.swift index 93eeb37bdc5af..3105ae28c478b 100644 --- a/test/decl/var/init_accessors.swift +++ b/test/decl/var/init_accessors.swift @@ -50,6 +50,7 @@ func test_use_of_initializes_accesses_on_non_inits() { } get { x } + set { } } var _y: String { @@ -63,6 +64,11 @@ func test_use_of_initializes_accesses_on_non_inits() { set(initialValue) accesses(x) {} // expected-error@-1 {{accesses(...) attribute could only be used with init accessors}} } + + init(x: Int, y: String) { + self.y = y + self._x = x + } } } @@ -103,6 +109,13 @@ func test_assignment_to_let_properties() { self.x = initialValue.0 // Ok self.y = initialValue.1 // Ok } + + get { (x, y) } + set { } + } + + init(x: Int, y: Int) { + self.point = (x, y) } } } @@ -116,6 +129,12 @@ func test_duplicate_and_computed_lazy_properties() { init(initialValue) initializes(_b, _a) accesses(_a) { // expected-error@-1 {{property '_a' cannot be both initialized and accessed}} } + + get { _a } + set { } + } + + init() { } } @@ -136,3 +155,313 @@ func test_duplicate_and_computed_lazy_properties() { lazy var c: Int = 42 } } + +func test_invalid_self_uses() { + func capture(_: T) -> Int? { nil } + + struct Test { + var a: Int { + init(initialValue) { + let x = self + // expected-error@-1 {{'self' within init accessors can only be used to reference properties listed in 'initializes' and 'accesses'; init accessors are run before 'self' is fully available}} + + _ = { + print(self) + // expected-error@-1 {{'self' within init accessors can only be used to reference properties listed in 'initializes' and 'accesses'; init accessors are run before 'self' is fully available}} + } + + _ = { [weak self] in + // expected-error@-1 {{'self' within init accessors can only be used to reference properties listed in 'initializes' and 'accesses'; init accessors are run before 'self' is fully available}} + } + + _ = { + guard let _ = capture(self) else { + // expected-error@-1 {{'self' within init accessors can only be used to reference properties listed in 'initializes' and 'accesses'; init accessors are run before 'self' is fully available}} + return + } + } + + Test.test(self) + // expected-error@-1 {{'self' within init accessors can only be used to reference properties listed in 'initializes' and 'accesses'; init accessors are run before 'self' is fully available}} + } + + get { 42 } + set { } + } + + static func test(_: T) {} + } +} + +func test_invalid_references() { + struct Test { + var _a: Int + var _b: Int + + var c: String + static var c: String = "" + + var _d: String + + var data: Int { + init(initialValue) initializes(_a) accesses(_d) { + _a = initialValue // Ok + + print(_d) // Ok + self._d = "" // Ok + + if self._b > 0 { // expected-error {{cannot reference instance member '_b'; init accessors can only refer to instance properties listed in 'initializes' and 'accesses' attributes}} + } + + let x = c.lowercased() + // expected-error@-1 {{static member 'c' cannot be used on instance of type 'Test'}} + + print(Test.c.lowercased()) // Ok + + guard let v = test() else { + // expected-error@-1 {{cannot reference instance member 'test'; init accessors can only refer to instance properties listed in 'initializes' and 'accesses' attributes}} + return + } + + _ = { + if true { + print(_b) + // expected-error@-1 {{cannot reference instance member '_b'; init accessors can only refer to instance properties listed in 'initializes' and 'accesses' attributes}} + print(self._b) + // expected-error@-1 {{cannot reference instance member '_b'; init accessors can only refer to instance properties listed in 'initializes' and 'accesses' attributes}} + } + } + } + + get { _a } + set { } + } + + func test() -> Int? { 42 } + + init() {} + } +} + +func test_memberwise_with_overlaps_dont_synthesize_inits() { + struct Test1 { + var _a: T + var _b: Int + + var a: T { + init(initialValue) initializes(_a) { + _a = initialValue + } + + get { _a } + set { } + } + + var pair: (T, Int) { + init(initialValue) initializes(_a, _b) { + _a = initialValue.0 + _b = initialValue.1 + } + + get { (_a, _b) } + set { } + } + + var c: U + } + + _ = Test1(a: "a", pair: ("b", 1), c: [3.0]) + // expected-error@-1 {{'Test1' cannot be constructed because it has no accessible initializers}} + + struct Test2 { + var _a: T + var _b: Int + + var a: T { + init(initialValue) initializes(_a) { + _a = initialValue + } + + get { _a } + set { } + } + + var b: Int { + init(initialValue) initializes(_b) { + _b = initialValue + } + + get { _b } + set { } + } + + var _c: U + + var pair: (T, U) { + init(initialValue) initializes(_a, _c) { + _a = initialValue.0 + _c = initialValue.1 + } + + get { (_a, _c) } + set { } + } + } + + _ = Test2(a: "a", pair: ("c", 2), b: 0) + // expected-error@-1 {{'Test2' cannot be constructed because it has no accessible initializers}} + + struct Test3 { + var _a: T + var _b: Int + + var a: T { + init(initialValue) initializes(_a) { + _a = initialValue + } + + get { _a } + set { } + } + + var b: Int { + init(initialValue) initializes(_b) { + _b = initialValue + } + + get { _b } + set { } + } + + var _c: U + + var c: U { + init(initialValue) initializes(_c) { + _c = initialValue + } + + get { _c } + set { } + } + + var triple: (T, Int, U) { + init(initialValue) initializes(_a, _b, _c) { + _a = initialValue.0 + _b = initialValue.1 + _c = initialValue.2 + } + + get { (_a, _b, _c) } + set { } + } + } + + _ = Test3(a: "a", triple: ("b", 2, [1.0, 2.0]), b: 0, c: [1.0]) + // expected-error@-1 {{'Test3' cannot be constructed because it has no accessible initializers}} +} + +func test_memberwise_ordering() { + struct Test1 { + var _a: Int + var _b: Int + + var a: Int { + init(initialValue) initializes(_a) accesses(_b) { + _a = initialValue + } + + get { _a } + set { } + } + } + + _ = Test1(_b: 42, a: 0) // Ok + + struct Test2 { // expected-error {{cannot synthesize memberwise initializer}} + var _a: Int + + var a: Int { + init(initialValue) initializes(_a) accesses(_b) { + // expected-note@-1 {{init accessor for 'a' cannot access stored property '_b' because it is called before '_b' is initialized}} + _a = initialValue + } + + get { _a } + } + + var _b: Int + } + + struct Test3 { + var _a: Int + + var pair: (Int, Int) { + init(initialValue) initializes(_a, _b) { + _a = initialValue.0 + _b = initialValue.1 + } + + get { (_a, _b) } + set { } + } + + var _b: Int + } + + _ = Test3(pair: (0, 1)) // Ok + + struct Test4 { + var _a: Int + var _b: Int + + var pair: (Int, Int) { + init(initalValue) accesses(_a, _b) { + } + + get { (_a, _b) } + set { } + } + + var _c: Int + } + + _ = Test4(_a: 0, _b: 1, _c: 2) // Ok + + struct Test5 { + var _a: Int + var _b: Int + + var c: Int { + init(initalValue) initializes(_c) accesses(_a, _b) { + } + + get { _c } + set { } + } + + var _c: Int + } + + _ = Test5(_a: 0, _b: 1, c: 2) // Ok +} + +func test_default_arguments_are_analyzed() { + struct Test { + var pair: (Int, Int) = (0, 1) { // Ok + init {} + } + + var other: (Int, String) = ("", 42) { + // expected-error@-1 {{cannot convert value of type '(String, Int)' to specified type '(Int, String)'}} + init(initialValue) {} + } + + var otherPair = (0, 1) { + // expected-error@-1 {{computed property must have an explicit type}} + init(initalValue) {} + + get { 42 } + // expected-error@-1 {{cannot convert return expression of type 'Int' to return type '(Int, Int)'}} + } + } +}