diff --git a/include/swift/AST/Decl.h b/include/swift/AST/Decl.h index 891f8c1a46427..556ee0f456948 100644 --- a/include/swift/AST/Decl.h +++ b/include/swift/AST/Decl.h @@ -5317,6 +5317,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/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/lib/AST/Decl.cpp b/lib/AST/Decl.cpp index 065fd1e6e352a..dd55ebe4529a1 100644 --- a/lib/AST/Decl.cpp +++ b/lib/AST/Decl.cpp @@ -6720,6 +6720,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) diff --git a/lib/Parse/ParseExpr.cpp b/lib/Parse/ParseExpr.cpp index 0091424812568..5eee0d05ecb5a 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 4879d186e36a5..07add74e1b6ba 100644 --- a/lib/SILGen/SILGenConstructor.cpp +++ b/lib/SILGen/SILGenConstructor.cpp @@ -1474,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/Sema/CodeSynthesis.cpp b/lib/Sema/CodeSynthesis.cpp index c7cc86d589497..53f73bbe6354f 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()) { diff --git a/lib/Sema/TypeCheckDeclPrimary.cpp b/lib/Sema/TypeCheckDeclPrimary.cpp index 7b3283e5b959c..84b642e2d470f 100644 --- a/lib/Sema/TypeCheckDeclPrimary.cpp +++ b/lib/Sema/TypeCheckDeclPrimary.cpp @@ -2289,7 +2289,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 c7b0f454aca02..dafceccd3fe1d 100644 --- a/lib/Sema/TypeCheckMacros.cpp +++ b/lib/Sema/TypeCheckMacros.cpp @@ -1319,9 +1319,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 979654b18cc1b..7560c305665b4 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; } @@ -3355,7 +3359,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 df8be55a75d2f..ff8ba32daa435 100644 --- a/test/Interpreter/init_accessors.swift +++ b/test/Interpreter/init_accessors.swift @@ -393,3 +393,72 @@ 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/decl/var/init_accessors.swift b/test/decl/var/init_accessors.swift index 6d8633bac190f..3105ae28c478b 100644 --- a/test/decl/var/init_accessors.swift +++ b/test/decl/var/init_accessors.swift @@ -444,3 +444,24 @@ func test_memberwise_ordering() { _ = 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)'}} + } + } +}