From 38765fbd9aaed958ed7f71e95550c52ae4107973 Mon Sep 17 00:00:00 2001 From: Pavel Yaskevich Date: Tue, 26 Jul 2022 10:37:50 -0700 Subject: [PATCH 01/27] [AST] Add a new `@typeWrapper` attribute --- include/swift/AST/Attr.def | 5 + include/swift/AST/DiagnosticsSema.def | 35 +++++ lib/Sema/TypeCheckAttr.cpp | 178 +++++++++++++++++++++++++ lib/Sema/TypeCheckDeclOverride.cpp | 1 + lib/Serialization/ModuleFormat.h | 2 +- test/IDE/complete_decl_attribute.swift | 5 + test/type/type_wrapper.swift | 84 ++++++++++++ 7 files changed, 309 insertions(+), 1 deletion(-) create mode 100644 test/type/type_wrapper.swift diff --git a/include/swift/AST/Attr.def b/include/swift/AST/Attr.def index 1df82de37cac8..23c2e438b0092 100644 --- a/include/swift/AST/Attr.def +++ b/include/swift/AST/Attr.def @@ -763,6 +763,11 @@ DECL_ATTR(_expose, Expose, ABIStableToAdd | ABIStableToRemove | APIStableToAdd | APIStableToRemove, 133) +SIMPLE_DECL_ATTR(typeWrapper, TypeWrapper, + OnStruct | OnClass | OnEnum | + ABIStableToAdd | ABIStableToRemove | APIStableToAdd | APIStableToRemove, + 134) + // If you're adding a new underscored attribute here, please document it in // docs/ReferenceGuides/UnderscoredAttributes.md. diff --git a/include/swift/AST/DiagnosticsSema.def b/include/swift/AST/DiagnosticsSema.def index df75bf4a2e063..3b2a563189ed6 100644 --- a/include/swift/AST/DiagnosticsSema.def +++ b/include/swift/AST/DiagnosticsSema.def @@ -6476,5 +6476,40 @@ ERROR(moveOnly_not_allowed_here,none, ERROR(move_expression_not_passed_lvalue,none, "'move' can only be applied to lvalues", ()) +//------------------------------------------------------------------------------ +// MARK: Type Wrappers +//------------------------------------------------------------------------------ + +ERROR(type_wrapper_attribute_not_allowed_here,none, + "type wrapper attribute %0 can only be applied to a class, struct", + (Identifier)) + +ERROR(type_wrapper_requires_a_single_generic_param,none, + "type wrapper has to declare a single generic parameter " + "for underlying storage type", ()) + +ERROR(type_wrapper_requires_memberwise_init,none, + "type wrapper type %0 does not contain a required initializer" + " - init(memberwise:)", + (DeclName)) + +ERROR(type_wrapper_requires_subscript,none, + "type wrapper type %0 does not contain a required subscript" + " - subscript(storedKeyPath:)", + (DeclName)) + +ERROR(type_wrapper_failable_init,none, + "type wrapper initializer %0 cannot be failable", (DeclName)) + +ERROR(type_wrapper_invalid_subscript_param_type, none, + "type wrapper subscript expects a key path parameter type (got: %0)", + (Type)) + +ERROR(type_wrapper_type_requirement_not_accessible,none, + "%select{private|fileprivate|internal|public|open}0 %1 %2 cannot have " + "more restrictive access than its enclosing type wrapper type %3 " + "(which is %select{private|fileprivate|internal|public|open}4)", + (AccessLevel, DescriptiveDeclKind, DeclName, Type, AccessLevel)) + #define UNDEFINE_DIAGNOSTIC_MACROS #include "DefineDiagnosticMacros.h" diff --git a/lib/Sema/TypeCheckAttr.cpp b/lib/Sema/TypeCheckAttr.cpp index febce6745b5ad..281362d4c599c 100644 --- a/lib/Sema/TypeCheckAttr.cpp +++ b/lib/Sema/TypeCheckAttr.cpp @@ -292,6 +292,7 @@ class AttributeChecker : public AttributeVisitor { void visitCustomAttr(CustomAttr *attr); void visitPropertyWrapperAttr(PropertyWrapperAttr *attr); + void visitTypeWrapperAttr(TypeWrapperAttr *attr); void visitResultBuilderAttr(ResultBuilderAttr *attr); void visitImplementationOnlyAttr(ImplementationOnlyAttr *attr); @@ -3457,6 +3458,18 @@ void AttributeChecker::visitCustomAttr(CustomAttr *attr) { return; } + if (nominal->getAttrs().hasAttribute()) { + if (!(isa(D) || isa(D))) { + diagnose(attr->getLocation(), + diag::type_wrapper_attribute_not_allowed_here, + nominal->getName()); + attr->setInvalid(); + return; + } + + return; + } + // If the nominal type is a result builder type, verify that D is a // function, storage with an explicit getter, or parameter of function type. if (nominal->getAttrs().hasAttribute()) { @@ -3573,6 +3586,171 @@ void AttributeChecker::visitPropertyWrapperAttr(PropertyWrapperAttr *attr) { (void)nominal->getPropertyWrapperTypeInfo(); } +void AttributeChecker::visitTypeWrapperAttr(TypeWrapperAttr *attr) { + auto nominal = dyn_cast(D); + if (!nominal) + return; + + auto &ctx = D->getASTContext(); + + auto isLessAccessibleThanType = [&](ValueDecl *decl) { + return decl->getFormalAccess() < + std::min(nominal->getFormalAccess(), AccessLevel::Public); + }; + + enum class UnviabilityReason { Failable, InvalidType, Inaccessible }; + + auto findMembersOrDiagnose = [&](DeclName memberName, + SmallVectorImpl &results, + Diag notFoundDiagnostic) -> bool { + nominal->lookupQualified(nominal, DeclNameRef(memberName), + NL_QualifiedDefault, results); + + if (results.empty()) { + diagnose(nominal->getLoc(), notFoundDiagnostic, nominal->getName()); + attr->setInvalid(); + return true; + } + return false; + }; + + // Check whether type marked as @typeWrapper is valid: + // + // - Has a single generic parameter + // - Has `init(memberwise: )` + // - Has at least one `subscript(storedKeyPath: KeyPath<...>)` overload + + // Has a single generic parameter. + { + auto *genericParams = nominal->getGenericParams(); + if (!genericParams || genericParams->size() != 1) { + diagnose(nominal->getLoc(), + diag::type_wrapper_requires_a_single_generic_param); + attr->setInvalid(); + return; + } + } + + // `init(memberwise:)` + { + DeclName initName(ctx, DeclBaseName::createConstructor(), + ArrayRef(ctx.getIdentifier("memberwise"))); + + SmallVector inits; + if (findMembersOrDiagnose(initName, inits, + diag::type_wrapper_requires_memberwise_init)) + return; + + llvm::SmallDenseMap, 2> + nonViableInits; + for (auto *decl : inits) { + auto *init = cast(decl); + + if (init->isFailable()) + nonViableInits[init].push_back(UnviabilityReason::Failable); + + if (isLessAccessibleThanType(init)) + nonViableInits[init].push_back(UnviabilityReason::Inaccessible); + } + + // If there are no viable initializers, let's complain. + if (inits.size() - nonViableInits.size() == 0) { + for (const auto &entry : nonViableInits) { + auto *init = entry.first; + + for (auto reason : entry.second) { + switch (reason) { + case UnviabilityReason::Failable: + diagnose(init, diag::type_wrapper_failable_init, init->getName()); + break; + + case UnviabilityReason::Inaccessible: + diagnose(init, diag::type_wrapper_type_requirement_not_accessible, + init->getFormalAccess(), init->getDescriptiveKind(), + init->getName(), nominal->getDeclaredType(), + nominal->getFormalAccess()); + break; + + case UnviabilityReason::InvalidType: + llvm_unreachable("init(memberwise:) type is not checked"); + } + } + } + + attr->setInvalid(); + return; + } + } + + // subscript(storedKeypath: KeyPath<...>) + { + DeclName subscriptName( + ctx, DeclBaseName::createSubscript(), + ArrayRef(ctx.getIdentifier("storageKeyPath"))); + + SmallVector subscripts; + if (findMembersOrDiagnose(subscriptName, subscripts, + diag::type_wrapper_requires_subscript)) + return; + + llvm::SmallDenseMap, 2> + nonViableSubscripts; + + for (auto *decl : subscripts) { + auto *subscript = cast(decl); + + auto *keyPathParam = subscript->getIndices()->get(0); + + if (auto *BGT = + keyPathParam->getInterfaceType()->getAs()) { + if (!(BGT->isKeyPath() || BGT->isWritableKeyPath() || + BGT->isReferenceWritableKeyPath())) { + nonViableSubscripts[subscript].push_back( + UnviabilityReason::InvalidType); + } + } else { + nonViableSubscripts[subscript].push_back( + UnviabilityReason::InvalidType); + } + + if (isLessAccessibleThanType(subscript)) + nonViableSubscripts[subscript].push_back( + UnviabilityReason::Inaccessible); + } + + if (subscripts.size() - nonViableSubscripts.size() == 0) { + for (const auto &entry : nonViableSubscripts) { + auto *subscript = entry.first; + + for (auto reason : entry.second) { + switch (reason) { + case UnviabilityReason::InvalidType: { + auto paramTy = subscript->getIndices()->get(0)->getInterfaceType(); + diagnose(subscript, diag::type_wrapper_invalid_subscript_param_type, + paramTy); + break; + } + + case UnviabilityReason::Inaccessible: + diagnose(subscript, + diag::type_wrapper_type_requirement_not_accessible, + subscript->getFormalAccess(), + subscript->getDescriptiveKind(), subscript->getName(), + nominal->getDeclaredType(), nominal->getFormalAccess()); + break; + + case UnviabilityReason::Failable: + llvm_unreachable("subscripts cannot be failable"); + } + } + } + + attr->setInvalid(); + return; + } + } +} + void AttributeChecker::visitResultBuilderAttr(ResultBuilderAttr *attr) { auto *nominal = dyn_cast(D); auto &ctx = D->getASTContext(); diff --git a/lib/Sema/TypeCheckDeclOverride.cpp b/lib/Sema/TypeCheckDeclOverride.cpp index 4297fcf41c021..5071591c636ae 100644 --- a/lib/Sema/TypeCheckDeclOverride.cpp +++ b/lib/Sema/TypeCheckDeclOverride.cpp @@ -1584,6 +1584,7 @@ namespace { UNINTERESTING_ATTR(ImplementationOnly) UNINTERESTING_ATTR(Custom) UNINTERESTING_ATTR(PropertyWrapper) + UNINTERESTING_ATTR(TypeWrapper) UNINTERESTING_ATTR(DisfavoredOverload) UNINTERESTING_ATTR(ResultBuilder) UNINTERESTING_ATTR(ProjectedValueProperty) diff --git a/lib/Serialization/ModuleFormat.h b/lib/Serialization/ModuleFormat.h index 2cdcdff28a282..037480054ca7b 100644 --- a/lib/Serialization/ModuleFormat.h +++ b/lib/Serialization/ModuleFormat.h @@ -58,7 +58,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 = 706; // @_expose attribute +const uint16_t SWIFTMODULE_VERSION_MINOR = 707; // @typeWrapper attribute /// A standard hash seed used for all string hashes in a serialized module. /// diff --git a/test/IDE/complete_decl_attribute.swift b/test/IDE/complete_decl_attribute.swift index 21dd3a05f10ae..13a7168a7e7bc 100644 --- a/test/IDE/complete_decl_attribute.swift +++ b/test/IDE/complete_decl_attribute.swift @@ -117,6 +117,7 @@ actor MyGlobalActor { // KEYWORD3-NEXT: Keyword/None: resultBuilder[#Class Attribute#]; name=resultBuilder // KEYWORD3-NEXT: Keyword/None: globalActor[#Class Attribute#]; name=globalActor // KEYWORD3-NEXT: Keyword/None: preconcurrency[#Class Attribute#]; name=preconcurrency +// KEYWORD3-NEXT: Keyword/None: typeWrapper[#Class Attribute#]; name=typeWrapper // KEYWORD3-NEXT: End completions @#^KEYWORD3_2^#IB class C2 {} @@ -135,6 +136,7 @@ actor MyGlobalActor { // KEYWORD4-NEXT: Keyword/None: resultBuilder[#Enum Attribute#]; name=resultBuilder // KEYWORD4-NEXT: Keyword/None: globalActor[#Enum Attribute#]; name=globalActor // KEYWORD4-NEXT: Keyword/None: preconcurrency[#Enum Attribute#]; name=preconcurrency +// KEYWORD4-NEXT: Keyword/None: typeWrapper[#Enum Attribute#]; name=typeWrapper // KEYWORD4-NEXT: End completions @@ -150,6 +152,7 @@ actor MyGlobalActor { // KEYWORD5-NEXT: Keyword/None: resultBuilder[#Struct Attribute#]; name=resultBuilder // KEYWORD5-NEXT: Keyword/None: globalActor[#Struct Attribute#]; name=globalActor // KEYWORD5-NEXT: Keyword/None: preconcurrency[#Struct Attribute#]; name=preconcurrency +// KEYWORD5-NEXT: Keyword/None: typeWrapper[#Struct Attribute#]; name=typeWrapper // KEYWORD5-NEXT: End completions @#^ON_GLOBALVAR^# var globalVar @@ -305,6 +308,7 @@ struct _S { // ON_MEMBER_LAST-DAG: Keyword/None: Sendable[#Declaration Attribute#]; name=Sendable // ON_MEMBER_LAST-DAG: Keyword/None: exclusivity[#Declaration Attribute#]; name=exclusivity // ON_MEMBER_LAST-DAG: Keyword/None: preconcurrency[#Declaration Attribute#]; name=preconcurrency +// ON_MEMBER_LAST-DAG: Keyword/None: typeWrapper[#Declaration Attribute#]; name=typeWrapper // ON_MEMBER_LAST-NOT: Keyword // ON_MEMBER_LAST-DAG: Decl[Struct]/CurrModule: MyStruct[#MyStruct#]; name=MyStruct // ON_MEMBER_LAST-DAG: Decl[Struct]/CurrModule/TypeRelation[Convertible]: MyPropertyWrapper[#MyPropertyWrapper#]; name=MyPropertyWrapper @@ -374,6 +378,7 @@ func dummy2() {} // KEYWORD_LAST-DAG: Keyword/None: Sendable[#Declaration Attribute#]; name=Sendable // KEYWORD_LAST-DAG: Keyword/None: exclusivity[#Declaration Attribute#]; name=exclusivity // KEYWORD_LAST-DAG: Keyword/None: preconcurrency[#Declaration Attribute#]; name=preconcurrency +// KEYWORD_LAST-DAG: Keyword/None: typeWrapper[#Declaration Attribute#]; name=typeWrapper // KEYWORD_LAST-NOT: Keyword // KEYWORD_LAST-DAG: Decl[Struct]/CurrModule: MyStruct[#MyStruct#]; name=MyStruct // KEYWORD_LAST-DAG: Decl[Struct]/CurrModule/TypeRelation[Convertible]: MyPropertyWrapper[#MyPropertyWrapper#]; name=MyPropertyWrapper diff --git a/test/type/type_wrapper.swift b/test/type/type_wrapper.swift new file mode 100644 index 0000000000000..d09a64a366ded --- /dev/null +++ b/test/type/type_wrapper.swift @@ -0,0 +1,84 @@ +// RUN: %target-typecheck-verify-swift + +@typeWrapper +struct ConcreteTypeWrapper { // expected-error {{type wrapper has to declare a single generic parameter for underlying storage type}} + init(memberwise: Int) {} +} + +@typeWrapper +struct EmptyTypeWrapper { // expected-error {{type wrapper type 'EmptyTypeWrapper' does not contain a required initializer - init(memberwise:)}} +} + +@typeWrapper +struct NoMemberwiseInit { + // expected-error@-1 {{type wrapper type 'NoMemberwiseInit' does not contain a required initializer - init(memberwise:)}} + + subscript(storageKeyPath path: KeyPath) -> V { + get { fatalError() } + } +} + +@typeWrapper +struct FailableInit { + init?(memberwise: S) { // expected-error {{type wrapper initializer 'init(memberwise:)' cannot be failable}} + } + + subscript(storageKeyPath path: KeyPath) -> V { + get { fatalError() } + } +} + +// Okay because there is a valid `init(memberwise:)` overload. +@typeWrapper +struct FailableAndValidInit { + init(memberwise: S) { + } + + init?(memberwise: S) where S == Int { + } + + subscript(storageKeyPath path: KeyPath) -> V { + get { fatalError() } + } +} + +@typeWrapper +public struct InaccessibleInit { + fileprivate init(memberwise: S) { + // expected-error@-1 {{fileprivate initializer 'init(memberwise:)' cannot have more restrictive access than its enclosing type wrapper type 'InaccessibleInit' (which is public)}} + } + + private init?(memberwise: S) where S: AnyObject { + // expected-error@-1 {{private initializer 'init(memberwise:)' cannot have more restrictive access than its enclosing type wrapper type 'InaccessibleInit' (which is public)}} + // expected-error@-2 {{type wrapper initializer 'init(memberwise:)' cannot be failable}} + } + + subscript(storageKeyPath path: KeyPath) -> V { + get { fatalError() } + } +} + +@typeWrapper +struct NoSubscripts { + // expected-error@-1 {{type wrapper type 'NoSubscripts' does not contain a required subscript - subscript(storedKeyPath:)}} + init(memberwise: S) {} +} + +@typeWrapper +struct InaccessibleOrInvalidSubscripts { + init(memberwise: S) {} + + fileprivate subscript(storageKeyPath path: KeyPath) -> V { + // expected-error@-1 {{fileprivate subscript 'subscript(storageKeyPath:)' cannot have more restrictive access than its enclosing type wrapper type 'InaccessibleOrInvalidSubscripts' (which is internal)}} + get { fatalError() } + } + + private subscript(storageKeyPath path: KeyPath) -> [V] { + // expected-error@-1 {{private subscript 'subscript(storageKeyPath:)' cannot have more restrictive access than its enclosing type wrapper type 'InaccessibleOrInvalidSubscripts' (which is internal)}} + get { fatalError() } + } + + subscript(storageKeyPath path: Int) -> Bool { // expected-error {{type wrapper subscript expects a key path parameter type (got: 'Int')}} + get { true } + } +} From 69d80dc738a8db914f093b8bc2e6755230420874 Mon Sep 17 00:00:00 2001 From: Pavel Yaskevich Date: Tue, 26 Jul 2022 10:42:21 -0700 Subject: [PATCH 02/27] [AST] Add a way to check whether type has a type wrapper --- include/swift/AST/Decl.h | 6 ++ include/swift/AST/DiagnosticsSema.def | 3 + include/swift/AST/TypeCheckRequests.h | 16 ++++++ include/swift/AST/TypeCheckerTypeIDZone.def | 3 + lib/Sema/CMakeLists.txt | 1 + lib/Sema/TypeCheckTypeWrapper.cpp | 62 +++++++++++++++++++++ 6 files changed, 91 insertions(+) create mode 100644 lib/Sema/TypeCheckTypeWrapper.cpp diff --git a/include/swift/AST/Decl.h b/include/swift/AST/Decl.h index 11f3ce7117ea2..1cebb75affb6f 100644 --- a/include/swift/AST/Decl.h +++ b/include/swift/AST/Decl.h @@ -3754,6 +3754,12 @@ class NominalTypeDecl : public GenericTypeDecl, public IterableDeclContext { return getGlobalActorInstance() != nullptr; } + /// Returns true if this type has a type wrapper custom attribute. + bool hasTypeWrapper() const { return bool(getTypeWrapper()); } + + /// Return a type wrapper (if any) associated with this type. + NominalTypeDecl *getTypeWrapper() const; + // Implement isa/cast/dyncast/etc. static bool classof(const Decl *D) { return D->getKind() >= DeclKind::First_NominalTypeDecl && diff --git a/include/swift/AST/DiagnosticsSema.def b/include/swift/AST/DiagnosticsSema.def index 3b2a563189ed6..ffead969c6238 100644 --- a/include/swift/AST/DiagnosticsSema.def +++ b/include/swift/AST/DiagnosticsSema.def @@ -6488,6 +6488,9 @@ ERROR(type_wrapper_requires_a_single_generic_param,none, "type wrapper has to declare a single generic parameter " "for underlying storage type", ()) +ERROR(cannot_use_multiple_type_wrappers,none, + "type %0 cannot use more than one type wrapper", ()) + ERROR(type_wrapper_requires_memberwise_init,none, "type wrapper type %0 does not contain a required initializer" " - init(memberwise:)", diff --git a/include/swift/AST/TypeCheckRequests.h b/include/swift/AST/TypeCheckRequests.h index 88afc583edcf0..81b68bee7245c 100644 --- a/include/swift/AST/TypeCheckRequests.h +++ b/include/swift/AST/TypeCheckRequests.h @@ -3499,6 +3499,22 @@ class GetSourceFileAsyncNode bool isCached() const { return true; } }; +/// Return a type wrapper (if any) associated with the given declaration. +class GetTypeWrapper + : public SimpleRequest { +public: + using SimpleRequest::SimpleRequest; + +private: + friend SimpleRequest; + + NominalTypeDecl *evaluate(Evaluator &evaluator, NominalTypeDecl *) const; + +public: + bool isCached() const { return true; } +}; + void simple_display(llvm::raw_ostream &out, ASTNode node); void simple_display(llvm::raw_ostream &out, Type value); void simple_display(llvm::raw_ostream &out, const TypeRepr *TyR); diff --git a/include/swift/AST/TypeCheckerTypeIDZone.def b/include/swift/AST/TypeCheckerTypeIDZone.def index 56ec61eeb2051..932ea201c4ddc 100644 --- a/include/swift/AST/TypeCheckerTypeIDZone.def +++ b/include/swift/AST/TypeCheckerTypeIDZone.def @@ -399,3 +399,6 @@ SWIFT_REQUEST(TypeChecker, ClosureEffectsRequest, SWIFT_REQUEST(TypeChecker, GetSourceFileAsyncNode, AwaitExpr *(const SourceFile *), Cached, NoLocationInfo) +SWIFT_REQUEST(TypeChecker, GetTypeWrapper, + NominalTypeDecl *(NominalTypeDecl *), + Cached, NoLocationInfo) diff --git a/lib/Sema/CMakeLists.txt b/lib/Sema/CMakeLists.txt index 18b2bce38a322..4fe5be9a6c061 100644 --- a/lib/Sema/CMakeLists.txt +++ b/lib/Sema/CMakeLists.txt @@ -61,6 +61,7 @@ add_swift_host_library(swiftSema STATIC TypeCheckNameLookup.cpp TypeCheckPattern.cpp TypeCheckPropertyWrapper.cpp + TypeCheckTypeWrapper.cpp TypeCheckProtocol.cpp TypeCheckProtocolInference.cpp TypeCheckRegex.cpp diff --git a/lib/Sema/TypeCheckTypeWrapper.cpp b/lib/Sema/TypeCheckTypeWrapper.cpp new file mode 100644 index 0000000000000..374d97ff1cec2 --- /dev/null +++ b/lib/Sema/TypeCheckTypeWrapper.cpp @@ -0,0 +1,62 @@ +//===--- TypeCheckTypeWrapper.cpp - type wrappers -------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2014 - 2022 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See https://swift.org/LICENSE.txt for license information +// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors +// +//===----------------------------------------------------------------------===// +// +// This file implements semantic analysis for type wrappers. +// +//===----------------------------------------------------------------------===// +#include "TypeChecker.h" +#include "swift/AST/ASTContext.h" +#include "swift/AST/Decl.h" +#include "swift/AST/NameLookupRequests.h" +#include "swift/AST/TypeCheckRequests.h" +#include "swift/Basic/LLVM.h" +#include "swift/Basic/SourceLoc.h" + +using namespace swift; + +NominalTypeDecl *NominalTypeDecl::getTypeWrapper() const { + auto *mutableSelf = const_cast(this); + return evaluateOrDefault(getASTContext().evaluator, + GetTypeWrapper{mutableSelf}, nullptr); +} + +NominalTypeDecl *GetTypeWrapper::evaluate(Evaluator &evaluator, + NominalTypeDecl *decl) const { + auto &ctx = decl->getASTContext(); + + // Note that we don't actually care whether there are duplicates, + // using the same type wrapper multiple times is still an error. + SmallVector typeWrappers; + + for (auto *attr : decl->getAttrs().getAttributes()) { + auto *mutableAttr = const_cast(attr); + auto *nominal = evaluateOrDefault( + ctx.evaluator, CustomAttrNominalRequest{mutableAttr, decl}, nullptr); + + if (!nominal) + continue; + + auto *typeWrapper = nominal->getAttrs().getAttribute(); + if (typeWrapper && typeWrapper->isValid()) + typeWrappers.push_back(nominal); + } + + if (typeWrappers.empty()) + return nullptr; + + if (typeWrappers.size() != 1) { + ctx.Diags.diagnose(decl, diag::cannot_use_multiple_type_wrappers); + return nullptr; + } + + return typeWrappers.front(); +} From eb51a115287fef61bc52bdd5a34efe6165dda2d3 Mon Sep 17 00:00:00 2001 From: Pavel Yaskevich Date: Mon, 1 Aug 2022 10:09:04 -0700 Subject: [PATCH 03/27] [AST] Add a way to obtain a type of type wrapper --- include/swift/AST/TypeCheckRequests.h | 21 ++++++++++ include/swift/AST/TypeCheckerTypeIDZone.def | 3 ++ lib/AST/TypeCheckRequests.cpp | 4 ++ lib/Sema/TypeCheckType.cpp | 3 +- lib/Sema/TypeCheckTypeWrapper.cpp | 46 +++++++++++++++++---- 5 files changed, 68 insertions(+), 9 deletions(-) diff --git a/include/swift/AST/TypeCheckRequests.h b/include/swift/AST/TypeCheckRequests.h index 81b68bee7245c..c6c032db571ee 100644 --- a/include/swift/AST/TypeCheckRequests.h +++ b/include/swift/AST/TypeCheckRequests.h @@ -3364,6 +3364,10 @@ enum class CustomAttrTypeKind { /// unbound generic types. PropertyWrapper, + /// Just like property wrappers, type wrappers are represented + /// as custom type attributes and allow unbound generic types. + TypeWrapper, + /// Global actors are represented as custom type attributes. They don't /// have any particularly interesting semantics. GlobalActor, @@ -3515,6 +3519,23 @@ class GetTypeWrapper bool isCached() const { return true; } }; +/// Return a type of the type wrapper (if any) associated with the given +/// declaration. +class GetTypeWrapperType + : public SimpleRequest { +public: + using SimpleRequest::SimpleRequest; + +private: + friend SimpleRequest; + + Type evaluate(Evaluator &evaluator, NominalTypeDecl *) const; + +public: + bool isCached() const { return true; } +}; + void simple_display(llvm::raw_ostream &out, ASTNode node); void simple_display(llvm::raw_ostream &out, Type value); void simple_display(llvm::raw_ostream &out, const TypeRepr *TyR); diff --git a/include/swift/AST/TypeCheckerTypeIDZone.def b/include/swift/AST/TypeCheckerTypeIDZone.def index 932ea201c4ddc..ffdb8a9daf749 100644 --- a/include/swift/AST/TypeCheckerTypeIDZone.def +++ b/include/swift/AST/TypeCheckerTypeIDZone.def @@ -402,3 +402,6 @@ SWIFT_REQUEST(TypeChecker, GetSourceFileAsyncNode, SWIFT_REQUEST(TypeChecker, GetTypeWrapper, NominalTypeDecl *(NominalTypeDecl *), Cached, NoLocationInfo) +SWIFT_REQUEST(TypeChecker, GetTypeWrapperType, + Type(NominalTypeDecl *), + Cached, NoLocationInfo) diff --git a/lib/AST/TypeCheckRequests.cpp b/lib/AST/TypeCheckRequests.cpp index c4fc3c22e1324..72daff4ba6d82 100644 --- a/lib/AST/TypeCheckRequests.cpp +++ b/lib/AST/TypeCheckRequests.cpp @@ -1531,6 +1531,10 @@ void swift::simple_display(llvm::raw_ostream &out, CustomAttrTypeKind value) { out << "property-wrapper"; return; + case CustomAttrTypeKind::TypeWrapper: + out << "type-wrapper"; + return; + case CustomAttrTypeKind::GlobalActor: out << "global-actor"; return; diff --git a/lib/Sema/TypeCheckType.cpp b/lib/Sema/TypeCheckType.cpp index eed633a73d52e..cf3295c866ca2 100644 --- a/lib/Sema/TypeCheckType.cpp +++ b/lib/Sema/TypeCheckType.cpp @@ -4706,7 +4706,8 @@ Type CustomAttrTypeRequest::evaluate(Evaluator &eval, CustomAttr *attr, OpenUnboundGenericTypeFn unboundTyOpener = nullptr; // Property delegates allow their type to be an unbound generic. - if (typeKind == CustomAttrTypeKind::PropertyWrapper) { + if (typeKind == CustomAttrTypeKind::PropertyWrapper || + typeKind == CustomAttrTypeKind::TypeWrapper) { unboundTyOpener = [](auto unboundTy) { // FIXME: Don't let unbound generic types // escape type resolution. For now, just diff --git a/lib/Sema/TypeCheckTypeWrapper.cpp b/lib/Sema/TypeCheckTypeWrapper.cpp index 374d97ff1cec2..434b22515da22 100644 --- a/lib/Sema/TypeCheckTypeWrapper.cpp +++ b/lib/Sema/TypeCheckTypeWrapper.cpp @@ -29,14 +29,11 @@ NominalTypeDecl *NominalTypeDecl::getTypeWrapper() const { GetTypeWrapper{mutableSelf}, nullptr); } -NominalTypeDecl *GetTypeWrapper::evaluate(Evaluator &evaluator, - NominalTypeDecl *decl) const { +static void getTypeWrappers( + NominalTypeDecl *decl, + SmallVectorImpl> &typeWrappers) { auto &ctx = decl->getASTContext(); - // Note that we don't actually care whether there are duplicates, - // using the same type wrapper multiple times is still an error. - SmallVector typeWrappers; - for (auto *attr : decl->getAttrs().getAttributes()) { auto *mutableAttr = const_cast(attr); auto *nominal = evaluateOrDefault( @@ -47,8 +44,19 @@ NominalTypeDecl *GetTypeWrapper::evaluate(Evaluator &evaluator, auto *typeWrapper = nominal->getAttrs().getAttribute(); if (typeWrapper && typeWrapper->isValid()) - typeWrappers.push_back(nominal); + typeWrappers.push_back({mutableAttr, nominal}); } +} + +NominalTypeDecl *GetTypeWrapper::evaluate(Evaluator &evaluator, + NominalTypeDecl *decl) const { + auto &ctx = decl->getASTContext(); + + // Note that we don't actually care whether there are duplicates, + // using the same type wrapper multiple times is still an error. + SmallVector, 2> typeWrappers; + + getTypeWrappers(decl, typeWrappers); if (typeWrappers.empty()) return nullptr; @@ -58,5 +66,27 @@ NominalTypeDecl *GetTypeWrapper::evaluate(Evaluator &evaluator, return nullptr; } - return typeWrappers.front(); + return typeWrappers.front().second; +} + +Type GetTypeWrapperType::evaluate(Evaluator &evaluator, + NominalTypeDecl *decl) const { + SmallVector, 2> typeWrappers; + + getTypeWrappers(decl, typeWrappers); + + if (typeWrappers.size() != 1) + return Type(); + + auto *typeWrapperAttr = typeWrappers.front().first; + auto type = evaluateOrDefault( + evaluator, + CustomAttrTypeRequest{typeWrapperAttr, decl->getDeclContext(), + CustomAttrTypeKind::TypeWrapper}, + Type()); + + if (!type || type->hasError()) { + return ErrorType::get(decl->getASTContext()); + } + return type; } From 029d6d7f8703370ce8f91ad8573c3af507bd4942 Mon Sep 17 00:00:00 2001 From: Pavel Yaskevich Date: Tue, 26 Jul 2022 10:48:45 -0700 Subject: [PATCH 04/27] [AST] TypeWrappers: Add a way to inject `$Storage` into a type `$Storage` type is going to used as a type of `$_storage` variable and contains all of the stored properties of the type it's attached to. --- include/swift/AST/KnownIdentifiers.def | 3 +++ include/swift/AST/TypeCheckRequests.h | 18 ++++++++++++++++ include/swift/AST/TypeCheckerTypeIDZone.def | 3 +++ lib/Sema/TypeCheckTypeWrapper.cpp | 23 +++++++++++++++++++++ 4 files changed, 47 insertions(+) diff --git a/include/swift/AST/KnownIdentifiers.def b/include/swift/AST/KnownIdentifiers.def index c099657ff9a86..a7380114984bb 100644 --- a/include/swift/AST/KnownIdentifiers.def +++ b/include/swift/AST/KnownIdentifiers.def @@ -307,6 +307,9 @@ IDENTIFIER(decodeNextArgument) IDENTIFIER(SerializationRequirement) IDENTIFIER_WITH_NAME(builderSelf, "$builderSelf") +// Type wrappers +IDENTIFIER_WITH_NAME(TypeWrapperStorage, "$Storage") + #undef IDENTIFIER #undef IDENTIFIER_ #undef IDENTIFIER_WITH_NAME diff --git a/include/swift/AST/TypeCheckRequests.h b/include/swift/AST/TypeCheckRequests.h index c6c032db571ee..8913ffb205761 100644 --- a/include/swift/AST/TypeCheckRequests.h +++ b/include/swift/AST/TypeCheckRequests.h @@ -3536,6 +3536,24 @@ class GetTypeWrapperType bool isCached() const { return true; } }; +/// Inject or get `$Storage` type which has all of the stored properties +/// of the given type with a type wrapper. +class GetTypeWrapperStorage + : public SimpleRequest { +public: + using SimpleRequest::SimpleRequest; + +private: + friend SimpleRequest; + + NominalTypeDecl *evaluate(Evaluator &evaluator, NominalTypeDecl *) const; + +public: + bool isCached() const { return true; } +}; + void simple_display(llvm::raw_ostream &out, ASTNode node); void simple_display(llvm::raw_ostream &out, Type value); void simple_display(llvm::raw_ostream &out, const TypeRepr *TyR); diff --git a/include/swift/AST/TypeCheckerTypeIDZone.def b/include/swift/AST/TypeCheckerTypeIDZone.def index ffdb8a9daf749..76e42012a390f 100644 --- a/include/swift/AST/TypeCheckerTypeIDZone.def +++ b/include/swift/AST/TypeCheckerTypeIDZone.def @@ -405,3 +405,6 @@ SWIFT_REQUEST(TypeChecker, GetTypeWrapper, SWIFT_REQUEST(TypeChecker, GetTypeWrapperType, Type(NominalTypeDecl *), Cached, NoLocationInfo) +SWIFT_REQUEST(TypeChecker, GetTypeWrapperStorage, + NominalTypeDecl *(NominalTypeDecl *), + Cached, NoLocationInfo) diff --git a/lib/Sema/TypeCheckTypeWrapper.cpp b/lib/Sema/TypeCheckTypeWrapper.cpp index 434b22515da22..3376fb9737626 100644 --- a/lib/Sema/TypeCheckTypeWrapper.cpp +++ b/lib/Sema/TypeCheckTypeWrapper.cpp @@ -90,3 +90,26 @@ Type GetTypeWrapperType::evaluate(Evaluator &evaluator, } return type; } + +NominalTypeDecl * +GetTypeWrapperStorage::evaluate(Evaluator &evaluator, + NominalTypeDecl *parent) const { + if (!parent->hasTypeWrapper()) + return nullptr; + + auto &ctx = parent->getASTContext(); + + auto *storage = + new (ctx) StructDecl(/*StructLoc=*/SourceLoc(), ctx.Id_TypeWrapperStorage, + /*NameLoc=*/SourceLoc(), + /*Inheritted=*/{}, + /*GenericParams=*/nullptr, parent); + + storage->setImplicit(); + storage->setSynthesized(); + storage->copyFormalAccessFrom(parent, /*sourceIsParentContext=*/true); + + parent->addMember(storage); + + return storage; +} From ccd7e4f0225500394e14b444c70c1a93a6a42f42 Mon Sep 17 00:00:00 2001 From: Pavel Yaskevich Date: Tue, 26 Jul 2022 12:52:12 -0700 Subject: [PATCH 05/27] [AST] TypeWrappers: Add a request to inject or get `$_storage` property This is the property that would get used to route stored property accesses through a type wrapper. --- include/swift/AST/KnownIdentifiers.def | 1 + include/swift/AST/TypeCheckRequests.h | 17 +++++++ include/swift/AST/TypeCheckerTypeIDZone.def | 3 ++ lib/Sema/TypeCheckTypeWrapper.cpp | 55 +++++++++++++++++++++ 4 files changed, 76 insertions(+) diff --git a/include/swift/AST/KnownIdentifiers.def b/include/swift/AST/KnownIdentifiers.def index a7380114984bb..7526690772b5a 100644 --- a/include/swift/AST/KnownIdentifiers.def +++ b/include/swift/AST/KnownIdentifiers.def @@ -309,6 +309,7 @@ IDENTIFIER_WITH_NAME(builderSelf, "$builderSelf") // Type wrappers IDENTIFIER_WITH_NAME(TypeWrapperStorage, "$Storage") +IDENTIFIER_WITH_NAME(TypeWrapperProperty, "$_storage") #undef IDENTIFIER #undef IDENTIFIER_ diff --git a/include/swift/AST/TypeCheckRequests.h b/include/swift/AST/TypeCheckRequests.h index 8913ffb205761..abf7a64d4bc25 100644 --- a/include/swift/AST/TypeCheckRequests.h +++ b/include/swift/AST/TypeCheckRequests.h @@ -3554,6 +3554,23 @@ class GetTypeWrapperStorage bool isCached() const { return true; } }; +/// Inject or get `$_storage` property which is used to route accesses through +/// to all stored properties of a type that has a type wrapper. +class GetTypeWrapperProperty + : public SimpleRequest { +public: + using SimpleRequest::SimpleRequest; + +private: + friend SimpleRequest; + + VarDecl *evaluate(Evaluator &evaluator, NominalTypeDecl *) const; + +public: + bool isCached() const { return true; } +}; + void simple_display(llvm::raw_ostream &out, ASTNode node); void simple_display(llvm::raw_ostream &out, Type value); void simple_display(llvm::raw_ostream &out, const TypeRepr *TyR); diff --git a/include/swift/AST/TypeCheckerTypeIDZone.def b/include/swift/AST/TypeCheckerTypeIDZone.def index 76e42012a390f..e25c1c262a4dc 100644 --- a/include/swift/AST/TypeCheckerTypeIDZone.def +++ b/include/swift/AST/TypeCheckerTypeIDZone.def @@ -408,3 +408,6 @@ SWIFT_REQUEST(TypeChecker, GetTypeWrapperType, SWIFT_REQUEST(TypeChecker, GetTypeWrapperStorage, NominalTypeDecl *(NominalTypeDecl *), Cached, NoLocationInfo) +SWIFT_REQUEST(TypeChecker, GetTypeWrapperProperty, + VarDecl *(NominalTypeDecl *), + Cached, NoLocationInfo) diff --git a/lib/Sema/TypeCheckTypeWrapper.cpp b/lib/Sema/TypeCheckTypeWrapper.cpp index 3376fb9737626..417263ff4bb47 100644 --- a/lib/Sema/TypeCheckTypeWrapper.cpp +++ b/lib/Sema/TypeCheckTypeWrapper.cpp @@ -17,12 +17,41 @@ #include "swift/AST/ASTContext.h" #include "swift/AST/Decl.h" #include "swift/AST/NameLookupRequests.h" +#include "swift/AST/Pattern.h" #include "swift/AST/TypeCheckRequests.h" #include "swift/Basic/LLVM.h" #include "swift/Basic/SourceLoc.h" using namespace swift; +/// Create a property declaration and inject it into the given type. +static VarDecl *injectProperty(NominalTypeDecl *parent, Identifier name, + Type type, VarDecl::Introducer introducer, + Expr *initializer = nullptr) { + auto &ctx = parent->getASTContext(); + + auto *var = new (ctx) VarDecl(/*isStatic=*/false, introducer, + /*nameLoc=*/SourceLoc(), name, parent); + + var->setImplicit(); + var->setSynthesized(); + var->copyFormalAccessFrom(parent, /*sourceIsParentContext=*/true); + var->setInterfaceType(type); + + Pattern *pattern = NamedPattern::createImplicit(ctx, var); + pattern->setType(type); + + pattern = TypedPattern::createImplicit(ctx, pattern, type); + + auto *PBD = PatternBindingDecl::createImplicit(ctx, StaticSpellingKind::None, + pattern, initializer, parent); + + parent->addMember(PBD); + parent->addMember(var); + + return var; +} + NominalTypeDecl *NominalTypeDecl::getTypeWrapper() const { auto *mutableSelf = const_cast(this); return evaluateOrDefault(getASTContext().evaluator, @@ -113,3 +142,29 @@ GetTypeWrapperStorage::evaluate(Evaluator &evaluator, return storage; } + +VarDecl * +GetTypeWrapperProperty::evaluate(Evaluator &evaluator, + NominalTypeDecl *parent) const { + auto &ctx = parent->getASTContext(); + + auto *typeWrapper = parent->getTypeWrapper(); + if (!typeWrapper) + return nullptr; + + auto *storage = + evaluateOrDefault(ctx.evaluator, GetTypeWrapperStorage{parent}, nullptr); + + auto *typeWrapperType = + evaluateOrDefault(ctx.evaluator, GetTypeWrapperType{parent}, Type()) + ->castTo(); + assert(typeWrapperType); + + // $_storage: Wrapper<$Storage> + auto propertyTy = BoundGenericType::get( + typeWrapper, /*Parent=*/typeWrapperType->getParent(), + /*genericArgs=*/{storage->getInterfaceType()->getMetatypeInstanceType()}); + + return injectProperty(parent, ctx.Id_TypeWrapperProperty, + propertyTy, VarDecl::Introducer::Var); +} From 43feefd581894fc370d4589da91d786a488893b1 Mon Sep 17 00:00:00 2001 From: Pavel Yaskevich Date: Wed, 27 Jul 2022 00:09:20 -0700 Subject: [PATCH 06/27] [AST] TypeWrappers: Add a request to create and get "mirror" type wrapper property Given a stored property associated with a type wrapped type, produce a property that mirrors it in the type wrapper context. --- include/swift/AST/TypeCheckRequests.h | 17 ++++++++++++++++ include/swift/AST/TypeCheckerTypeIDZone.def | 3 +++ lib/Sema/TypeCheckTypeWrapper.cpp | 22 +++++++++++++++++++++ 3 files changed, 42 insertions(+) diff --git a/include/swift/AST/TypeCheckRequests.h b/include/swift/AST/TypeCheckRequests.h index abf7a64d4bc25..b49881ac26d53 100644 --- a/include/swift/AST/TypeCheckRequests.h +++ b/include/swift/AST/TypeCheckRequests.h @@ -3571,6 +3571,23 @@ class GetTypeWrapperProperty bool isCached() const { return true; } }; +/// Given a stored property associated with a type wrapped type, +/// produce a property that mirrors it in the type wrapper context. +class GetTypeWrapperStorageForProperty + : public SimpleRequest { +public: + using SimpleRequest::SimpleRequest; + +private: + friend SimpleRequest; + + VarDecl *evaluate(Evaluator &evaluator, VarDecl *) const; + +public: + bool isCached() const { return true; } +}; + void simple_display(llvm::raw_ostream &out, ASTNode node); void simple_display(llvm::raw_ostream &out, Type value); void simple_display(llvm::raw_ostream &out, const TypeRepr *TyR); diff --git a/include/swift/AST/TypeCheckerTypeIDZone.def b/include/swift/AST/TypeCheckerTypeIDZone.def index e25c1c262a4dc..add1bbd9b6b73 100644 --- a/include/swift/AST/TypeCheckerTypeIDZone.def +++ b/include/swift/AST/TypeCheckerTypeIDZone.def @@ -411,3 +411,6 @@ SWIFT_REQUEST(TypeChecker, GetTypeWrapperStorage, SWIFT_REQUEST(TypeChecker, GetTypeWrapperProperty, VarDecl *(NominalTypeDecl *), Cached, NoLocationInfo) +SWIFT_REQUEST(TypeChecker, GetTypeWrapperStorageForProperty, + VarDecl *(VarDecl *), + Cached, NoLocationInfo) diff --git a/lib/Sema/TypeCheckTypeWrapper.cpp b/lib/Sema/TypeCheckTypeWrapper.cpp index 417263ff4bb47..e6b700c300786 100644 --- a/lib/Sema/TypeCheckTypeWrapper.cpp +++ b/lib/Sema/TypeCheckTypeWrapper.cpp @@ -168,3 +168,25 @@ GetTypeWrapperProperty::evaluate(Evaluator &evaluator, return injectProperty(parent, ctx.Id_TypeWrapperProperty, propertyTy, VarDecl::Introducer::Var); } + +VarDecl *GetTypeWrapperStorageForProperty::evaluate(Evaluator &evaluator, + VarDecl *property) const { + auto *wrappedType = property->getDeclContext()->getSelfNominalTypeDecl(); + if (!(wrappedType && wrappedType->hasTypeWrapper())) + return nullptr; + + // Type wrappers support only stored `var`s. + if (property->isStatic() || property->isLet() || !property->hasStorage()) + return nullptr; + + auto &ctx = wrappedType->getASTContext(); + + auto *storage = evaluateOrDefault( + ctx.evaluator, GetTypeWrapperStorage{wrappedType}, nullptr); + assert(storage); + + return injectProperty( + storage, property->getName(), + storage->mapTypeIntoContext(property->getValueInterfaceType()), + property->getIntroducer()); +} From 3dc441b3d3435028ceecba8c573bc7045d331390 Mon Sep 17 00:00:00 2001 From: Pavel Yaskevich Date: Wed, 27 Jul 2022 14:16:29 -0700 Subject: [PATCH 07/27] [TypeChecker] Synthesize getters for stored properties of a type wrapped type A getter routes accesses through a subscript of `$_storage` variable injected into a wrapped type. --- include/swift/AST/Decl.h | 6 +- include/swift/AST/KnownIdentifiers.def | 1 + include/swift/AST/TypeCheckRequests.h | 17 ++++ include/swift/AST/TypeCheckerTypeIDZone.def | 3 + lib/Sema/TypeCheckStorage.cpp | 15 ++++ lib/Sema/TypeCheckTypeWrapper.cpp | 88 +++++++++++++++++++-- 6 files changed, 124 insertions(+), 6 deletions(-) diff --git a/include/swift/AST/Decl.h b/include/swift/AST/Decl.h index 1cebb75affb6f..05f3557c9af91 100644 --- a/include/swift/AST/Decl.h +++ b/include/swift/AST/Decl.h @@ -5555,7 +5555,11 @@ class VarDecl : public AbstractStorageDecl { /// Return true if this property either has storage or has an attached property /// wrapper that has storage. bool hasStorageOrWrapsStorage() const; - + + /// Whether this property belongs to a type wrapped type and has + /// all access to it routed through a type wrapper. + bool isAccessedViaTypeWrapper() const; + /// Visit all auxiliary declarations to this VarDecl. /// /// An auxiliary declaration is a declaration synthesized by the compiler to support diff --git a/include/swift/AST/KnownIdentifiers.def b/include/swift/AST/KnownIdentifiers.def index 7526690772b5a..3d9bb1ce996b9 100644 --- a/include/swift/AST/KnownIdentifiers.def +++ b/include/swift/AST/KnownIdentifiers.def @@ -310,6 +310,7 @@ IDENTIFIER_WITH_NAME(builderSelf, "$builderSelf") // Type wrappers IDENTIFIER_WITH_NAME(TypeWrapperStorage, "$Storage") IDENTIFIER_WITH_NAME(TypeWrapperProperty, "$_storage") +IDENTIFIER(storageKeyPath) #undef IDENTIFIER #undef IDENTIFIER_ diff --git a/include/swift/AST/TypeCheckRequests.h b/include/swift/AST/TypeCheckRequests.h index b49881ac26d53..178c978e6769e 100644 --- a/include/swift/AST/TypeCheckRequests.h +++ b/include/swift/AST/TypeCheckRequests.h @@ -3588,6 +3588,23 @@ class GetTypeWrapperStorageForProperty bool isCached() const { return true; } }; +/// Synthesize the body of a getter for a stored property that belongs to +/// a type wrapped type. +class SynthesizeTypeWrappedPropertyGetterBody + : public SimpleRequest { +public: + using SimpleRequest::SimpleRequest; + +private: + friend SimpleRequest; + + BraceStmt *evaluate(Evaluator &evaluator, AccessorDecl *) const; + +public: + bool isCached() const { return true; } +}; + void simple_display(llvm::raw_ostream &out, ASTNode node); void simple_display(llvm::raw_ostream &out, Type value); void simple_display(llvm::raw_ostream &out, const TypeRepr *TyR); diff --git a/include/swift/AST/TypeCheckerTypeIDZone.def b/include/swift/AST/TypeCheckerTypeIDZone.def index add1bbd9b6b73..7e23b0580d1c2 100644 --- a/include/swift/AST/TypeCheckerTypeIDZone.def +++ b/include/swift/AST/TypeCheckerTypeIDZone.def @@ -414,3 +414,6 @@ SWIFT_REQUEST(TypeChecker, GetTypeWrapperProperty, SWIFT_REQUEST(TypeChecker, GetTypeWrapperStorageForProperty, VarDecl *(VarDecl *), Cached, NoLocationInfo) +SWIFT_REQUEST(TypeChecker, SynthesizeTypeWrappedPropertyGetterBody, + BraceStmt *(AccessorDecl *), + Cached, NoLocationInfo) diff --git a/lib/Sema/TypeCheckStorage.cpp b/lib/Sema/TypeCheckStorage.cpp index 57fe732879b32..e047e2d097cec 100644 --- a/lib/Sema/TypeCheckStorage.cpp +++ b/lib/Sema/TypeCheckStorage.cpp @@ -1498,12 +1498,27 @@ synthesizeInvalidAccessor(AccessorDecl *accessor, ASTContext &ctx) { return { BraceStmt::create(ctx, loc, ArrayRef(), loc, true), true }; } +/// Synthesize the body of a getter for a stored property that belongs to +/// a type wrapped type. +static std::pair +synthesizeTypeWrappedPropertyGetterBody(AccessorDecl *getter, ASTContext &ctx) { + auto *body = evaluateOrDefault( + ctx.evaluator, SynthesizeTypeWrappedPropertyGetterBody{getter}, nullptr); + return body ? std::make_pair(body, /*isTypeChecked=*/false) + : synthesizeInvalidAccessor(getter, ctx); +} + static std::pair synthesizeGetterBody(AccessorDecl *getter, ASTContext &ctx) { auto storage = getter->getStorage(); // Synthesize the getter for a lazy property or property wrapper. if (auto var = dyn_cast(storage)) { + // If the type this stored property belongs to has a type wrapper + // we need to route getter/setter through the wrapper. + if (var->isAccessedViaTypeWrapper()) + return synthesizeTypeWrappedPropertyGetterBody(getter, ctx); + if (var->getAttrs().hasAttribute()) { auto *storage = var->getLazyStorageProperty(); return synthesizeLazyGetterBody(getter, var, storage, ctx); diff --git a/lib/Sema/TypeCheckTypeWrapper.cpp b/lib/Sema/TypeCheckTypeWrapper.cpp index e6b700c300786..c8c6b523fd5e8 100644 --- a/lib/Sema/TypeCheckTypeWrapper.cpp +++ b/lib/Sema/TypeCheckTypeWrapper.cpp @@ -18,6 +18,7 @@ #include "swift/AST/Decl.h" #include "swift/AST/NameLookupRequests.h" #include "swift/AST/Pattern.h" +#include "swift/AST/Stmt.h" #include "swift/AST/TypeCheckRequests.h" #include "swift/Basic/LLVM.h" #include "swift/Basic/SourceLoc.h" @@ -52,6 +53,23 @@ static VarDecl *injectProperty(NominalTypeDecl *parent, Identifier name, return var; } +bool VarDecl::isAccessedViaTypeWrapper() const { + auto *parent = getDeclContext()->getSelfNominalTypeDecl(); + if (!(parent && parent->hasTypeWrapper())) + return false; + + if (isStatic() || isLet() || !hasStorage()) + return false; + + if (getAttrs().hasAttribute()) + return false; + + if (hasAttachedPropertyWrapper()) + return false; + + return true; +} + NominalTypeDecl *NominalTypeDecl::getTypeWrapper() const { auto *mutableSelf = const_cast(this); return evaluateOrDefault(getASTContext().evaluator, @@ -176,7 +194,7 @@ VarDecl *GetTypeWrapperStorageForProperty::evaluate(Evaluator &evaluator, return nullptr; // Type wrappers support only stored `var`s. - if (property->isStatic() || property->isLet() || !property->hasStorage()) + if (!property->isAccessedViaTypeWrapper()) return nullptr; auto &ctx = wrappedType->getASTContext(); @@ -185,8 +203,68 @@ VarDecl *GetTypeWrapperStorageForProperty::evaluate(Evaluator &evaluator, ctx.evaluator, GetTypeWrapperStorage{wrappedType}, nullptr); assert(storage); - return injectProperty( - storage, property->getName(), - storage->mapTypeIntoContext(property->getValueInterfaceType()), - property->getIntroducer()); + return injectProperty(storage, property->getName(), + property->getValueInterfaceType(), + property->getIntroducer()); +} + +/// Given the property create a subscript to reach its type wrapper storage: +/// `$_storage[storageKeyPath: \$Storage.]`. +static SubscriptExpr *subscriptTypeWrappedProperty(VarDecl *var, + AccessorDecl *useDC) { + auto &ctx = useDC->getASTContext(); + auto *parent = var->getDeclContext()->getSelfNominalTypeDecl(); + + if (!(parent && parent->hasTypeWrapper())) + return nullptr; + + auto *typeWrapperVar = + evaluateOrDefault(ctx.evaluator, GetTypeWrapperProperty{parent}, nullptr); + auto *storageVar = evaluateOrDefault( + ctx.evaluator, GetTypeWrapperStorageForProperty{var}, nullptr); + + assert(typeWrapperVar); + assert(storageVar); + + // \$Storage. + auto *argExpr = KeyPathExpr::createImplicit( + ctx, /*backslashLoc=*/SourceLoc(), + {KeyPathExpr::Component::forProperty( + {storageVar}, + useDC->mapTypeIntoContext(storageVar->getInterfaceType()), + /*Loc=*/SourceLoc())}, + /*endLoc=*/SourceLoc()); + + auto *subscriptBaseExpr = UnresolvedDotExpr::createImplicit( + ctx, + new (ctx) DeclRefExpr({useDC->getImplicitSelfDecl()}, + /*Loc=*/DeclNameLoc(), /*Implicit=*/true), + typeWrapperVar->getName()); + + // $_storage[storageKeyPath: \$Storage.] + return SubscriptExpr::create( + ctx, subscriptBaseExpr, + ArgumentList::forImplicitSingle(ctx, ctx.Id_storageKeyPath, argExpr), + ConcreteDeclRef(), /*implicit=*/true); +} + +BraceStmt * +SynthesizeTypeWrappedPropertyGetterBody::evaluate(Evaluator &evaluator, + AccessorDecl *getter) const { + assert(getter->isGetter()); + + auto &ctx = getter->getASTContext(); + + auto *var = dyn_cast(getter->getStorage()); + if (!var) + return nullptr; + + auto *subscript = subscriptTypeWrappedProperty(var, getter); + if (!subscript) + return nullptr; + + ASTNode body = new (ctx) ReturnStmt(SourceLoc(), subscript, + /*isImplicit=*/true); + return BraceStmt::create(ctx, /*lbloc=*/var->getLoc(), body, + /*rbloc=*/var->getLoc(), /*implicit=*/true); } From 1e0976b213ec07ffaca289d8b0f256e25d07344c Mon Sep 17 00:00:00 2001 From: Pavel Yaskevich Date: Thu, 28 Jul 2022 09:53:33 -0700 Subject: [PATCH 08/27] [TypeChecker] Synthesize setters for stored properties of a type wrapped type A setter routes assignment through a subscript of `$_storage` variable injected into a wrapped type - `$_storage[storageKeyPath: \$Storage.] = newValue` --- include/swift/AST/TypeCheckRequests.h | 17 +++++++++++++ include/swift/AST/TypeCheckerTypeIDZone.def | 3 +++ lib/Sema/TypeCheckStorage.cpp | 13 ++++++++++ lib/Sema/TypeCheckTypeWrapper.cpp | 28 +++++++++++++++++++++ 4 files changed, 61 insertions(+) diff --git a/include/swift/AST/TypeCheckRequests.h b/include/swift/AST/TypeCheckRequests.h index 178c978e6769e..157599f760da6 100644 --- a/include/swift/AST/TypeCheckRequests.h +++ b/include/swift/AST/TypeCheckRequests.h @@ -3605,6 +3605,23 @@ class SynthesizeTypeWrappedPropertyGetterBody bool isCached() const { return true; } }; +/// Synthesize the body of a setter for a stored property that belongs to +/// a type wrapped type. +class SynthesizeTypeWrappedPropertySetterBody + : public SimpleRequest { +public: + using SimpleRequest::SimpleRequest; + +private: + friend SimpleRequest; + + BraceStmt *evaluate(Evaluator &evaluator, AccessorDecl *) const; + +public: + bool isCached() const { return true; } +}; + void simple_display(llvm::raw_ostream &out, ASTNode node); void simple_display(llvm::raw_ostream &out, Type value); void simple_display(llvm::raw_ostream &out, const TypeRepr *TyR); diff --git a/include/swift/AST/TypeCheckerTypeIDZone.def b/include/swift/AST/TypeCheckerTypeIDZone.def index 7e23b0580d1c2..48d6cc8f93ef2 100644 --- a/include/swift/AST/TypeCheckerTypeIDZone.def +++ b/include/swift/AST/TypeCheckerTypeIDZone.def @@ -417,3 +417,6 @@ SWIFT_REQUEST(TypeChecker, GetTypeWrapperStorageForProperty, SWIFT_REQUEST(TypeChecker, SynthesizeTypeWrappedPropertyGetterBody, BraceStmt *(AccessorDecl *), Cached, NoLocationInfo) +SWIFT_REQUEST(TypeChecker, SynthesizeTypeWrappedPropertySetterBody, + BraceStmt *(AccessorDecl *), + Cached, NoLocationInfo) diff --git a/lib/Sema/TypeCheckStorage.cpp b/lib/Sema/TypeCheckStorage.cpp index e047e2d097cec..89afc0884a480 100644 --- a/lib/Sema/TypeCheckStorage.cpp +++ b/lib/Sema/TypeCheckStorage.cpp @@ -1508,6 +1508,14 @@ synthesizeTypeWrappedPropertyGetterBody(AccessorDecl *getter, ASTContext &ctx) { : synthesizeInvalidAccessor(getter, ctx); } +static std::pair +synthesizeTypeWrappedPropertySetterBody(AccessorDecl *getter, ASTContext &ctx) { + auto *body = evaluateOrDefault( + ctx.evaluator, SynthesizeTypeWrappedPropertySetterBody{getter}, nullptr); + return body ? std::make_pair(body, /*isTypeChecked=*/false) + : synthesizeInvalidAccessor(getter, ctx); +} + static std::pair synthesizeGetterBody(AccessorDecl *getter, ASTContext &ctx) { auto storage = getter->getStorage(); @@ -1755,6 +1763,11 @@ synthesizeSetterBody(AccessorDecl *setter, ASTContext &ctx) { // Synthesize the setter for a lazy property or property wrapper. if (auto var = dyn_cast(storage)) { + // If the type this stored property belongs to has a type wrapper + // we need to route getter/setter through the wrapper. + if (var->isAccessedViaTypeWrapper()) + return synthesizeTypeWrappedPropertySetterBody(setter, ctx); + if (var->getAttrs().hasAttribute()) { // Lazy property setters write to the underlying storage. if (var->hasObservers()) { diff --git a/lib/Sema/TypeCheckTypeWrapper.cpp b/lib/Sema/TypeCheckTypeWrapper.cpp index c8c6b523fd5e8..4e00d340e9218 100644 --- a/lib/Sema/TypeCheckTypeWrapper.cpp +++ b/lib/Sema/TypeCheckTypeWrapper.cpp @@ -268,3 +268,31 @@ SynthesizeTypeWrappedPropertyGetterBody::evaluate(Evaluator &evaluator, return BraceStmt::create(ctx, /*lbloc=*/var->getLoc(), body, /*rbloc=*/var->getLoc(), /*implicit=*/true); } + +BraceStmt * +SynthesizeTypeWrappedPropertySetterBody::evaluate(Evaluator &evaluator, + AccessorDecl *setter) const { + assert(setter->isSetter()); + + auto &ctx = setter->getASTContext(); + + auto *var = dyn_cast(setter->getStorage()); + if (!var) + return nullptr; + + auto *subscript = subscriptTypeWrappedProperty(var, setter); + if (!subscript) + return nullptr; + + VarDecl *newValueParam = setter->getParameters()->get(0); + + auto *assignment = new (ctx) AssignExpr( + subscript, /*EqualLoc=*/SourceLoc(), + new (ctx) DeclRefExpr(newValueParam, DeclNameLoc(), /*IsImplicit=*/true), + /*Implicit=*/true); + + ASTNode body = new (ctx) ReturnStmt(SourceLoc(), assignment, + /*isImplicit=*/true); + return BraceStmt::create(ctx, /*lbloc=*/var->getLoc(), body, + /*rbloc=*/var->getLoc(), /*implicit=*/true); +} From 0711d774b70a061e945253008c11f6116b70b3ed Mon Sep 17 00:00:00 2001 From: Pavel Yaskevich Date: Thu, 28 Jul 2022 10:12:12 -0700 Subject: [PATCH 09/27] [Sema] Don't attempt init synthesis for type wrapped types Compiler cannot synthesize regular memberwise or default initializers for type wrapped types because stored properties of such a type cannot be accessed directly. A special initializer would be synthesized instead, it is going to initialize `$_storage` variable and handle default initialization of stored properties. --- lib/Sema/CodeSynthesis.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lib/Sema/CodeSynthesis.cpp b/lib/Sema/CodeSynthesis.cpp index e3b801b85af4c..6ac9b53f4644e 100644 --- a/lib/Sema/CodeSynthesis.cpp +++ b/lib/Sema/CodeSynthesis.cpp @@ -1113,6 +1113,10 @@ static bool shouldAttemptInitializerSynthesis(const NominalTypeDecl *decl) { if (decl->isInvalid()) return false; + // Don't attempt if the decl has a type wrapper. + if (decl->hasTypeWrapper()) + return false; + return true; } From d50dec4125b6b4b2022cf4b4128789d43b2541df Mon Sep 17 00:00:00 2001 From: Pavel Yaskevich Date: Thu, 28 Jul 2022 22:20:41 -0700 Subject: [PATCH 10/27] [AST] TypeWrapper: Turn `VarDecl::isAccessibleViaTypeWrapper()` into a cached request --- include/swift/AST/TypeCheckRequests.h | 17 ++++++ include/swift/AST/TypeCheckerTypeIDZone.def | 3 + lib/Sema/TypeCheckTypeWrapper.cpp | 61 ++++++++++++++++----- 3 files changed, 67 insertions(+), 14 deletions(-) diff --git a/include/swift/AST/TypeCheckRequests.h b/include/swift/AST/TypeCheckRequests.h index 157599f760da6..dcbf2b4c3984c 100644 --- a/include/swift/AST/TypeCheckRequests.h +++ b/include/swift/AST/TypeCheckRequests.h @@ -3622,6 +3622,23 @@ class SynthesizeTypeWrappedPropertySetterBody bool isCached() const { return true; } }; +/// Inject or get `$Storage` type which has all of the stored properties +/// of the given type with a type wrapper. +class IsPropertyAccessedViaTypeWrapper + : public SimpleRequest { +public: + using SimpleRequest::SimpleRequest; + +private: + friend SimpleRequest; + + bool evaluate(Evaluator &evaluator, VarDecl *) const; + +public: + bool isCached() const { return true; } +}; + void simple_display(llvm::raw_ostream &out, ASTNode node); void simple_display(llvm::raw_ostream &out, Type value); void simple_display(llvm::raw_ostream &out, const TypeRepr *TyR); diff --git a/include/swift/AST/TypeCheckerTypeIDZone.def b/include/swift/AST/TypeCheckerTypeIDZone.def index 48d6cc8f93ef2..8e61ec3c8066f 100644 --- a/include/swift/AST/TypeCheckerTypeIDZone.def +++ b/include/swift/AST/TypeCheckerTypeIDZone.def @@ -420,3 +420,6 @@ SWIFT_REQUEST(TypeChecker, SynthesizeTypeWrappedPropertyGetterBody, SWIFT_REQUEST(TypeChecker, SynthesizeTypeWrappedPropertySetterBody, BraceStmt *(AccessorDecl *), Cached, NoLocationInfo) +SWIFT_REQUEST(TypeChecker, IsPropertyAccessedViaTypeWrapper, + bool(VarDecl *), + Cached, NoLocationInfo) diff --git a/lib/Sema/TypeCheckTypeWrapper.cpp b/lib/Sema/TypeCheckTypeWrapper.cpp index 4e00d340e9218..485a340c5c6af 100644 --- a/lib/Sema/TypeCheckTypeWrapper.cpp +++ b/lib/Sema/TypeCheckTypeWrapper.cpp @@ -54,20 +54,10 @@ static VarDecl *injectProperty(NominalTypeDecl *parent, Identifier name, } bool VarDecl::isAccessedViaTypeWrapper() const { - auto *parent = getDeclContext()->getSelfNominalTypeDecl(); - if (!(parent && parent->hasTypeWrapper())) - return false; - - if (isStatic() || isLet() || !hasStorage()) - return false; - - if (getAttrs().hasAttribute()) - return false; - - if (hasAttachedPropertyWrapper()) - return false; - - return true; + auto *mutableSelf = const_cast(this); + return evaluateOrDefault(getASTContext().evaluator, + IsPropertyAccessedViaTypeWrapper{mutableSelf}, + false); } NominalTypeDecl *NominalTypeDecl::getTypeWrapper() const { @@ -296,3 +286,46 @@ SynthesizeTypeWrappedPropertySetterBody::evaluate(Evaluator &evaluator, return BraceStmt::create(ctx, /*lbloc=*/var->getLoc(), body, /*rbloc=*/var->getLoc(), /*implicit=*/true); } + +bool IsPropertyAccessedViaTypeWrapper::evaluate(Evaluator &evaluator, + VarDecl *property) const { + auto *parent = property->getDeclContext()->getSelfNominalTypeDecl(); + if (!(parent && parent->hasTypeWrapper())) + return false; + + // Don't attempt to wrap the `$_storage` property. + if (property->getName() == property->getASTContext().Id_TypeWrapperProperty) + return false; + + if (property->isStatic() || property->isLet()) + return false; + + // `lazy` properties are not wrapped. + if (property->getAttrs().hasAttribute()) + return false; + + // properties with attached property wrappers are not yet supported. + if (property->hasAttachedPropertyWrapper()) + return false; + + // Check whether this is a computed property. + { + auto declaresAccessor = [&](ArrayRef kinds) -> bool { + return llvm::any_of(kinds, [&](const AccessorKind &kind) { + return bool(property->getParsedAccessor(kind)); + }); + }; + + // property has a getter. + if (declaresAccessor( + {AccessorKind::Get, AccessorKind::Read, AccessorKind::Address})) + return false; + + // property has a setter. + if (declaresAccessor({AccessorKind::Set, AccessorKind::Modify, + AccessorKind::MutableAddress})) + return false; + } + + return true; +} From 39f5b69bce53c39a4cbf13ecda17f402621a74b0 Mon Sep 17 00:00:00 2001 From: Pavel Yaskevich Date: Fri, 29 Jul 2022 09:31:51 -0700 Subject: [PATCH 11/27] [TypeChecker] Synthesize initializer for a type wrapped declaration Synthesize an `init` declaration which is going to initialize type wrapper instance property `$_storage` via user provided values for all stored properties. --- include/swift/AST/Decl.h | 4 + include/swift/AST/KnownIdentifiers.def | 1 + include/swift/AST/TypeCheckRequests.h | 16 ++++ include/swift/AST/TypeCheckerTypeIDZone.def | 3 + lib/Sema/CodeSynthesis.cpp | 99 +++++++++++++++++++++ lib/Sema/TypeCheckAttr.cpp | 2 +- lib/Sema/TypeCheckTypeWrapper.cpp | 7 ++ 7 files changed, 131 insertions(+), 1 deletion(-) diff --git a/include/swift/AST/Decl.h b/include/swift/AST/Decl.h index 05f3557c9af91..9e8ed8aa87801 100644 --- a/include/swift/AST/Decl.h +++ b/include/swift/AST/Decl.h @@ -3760,6 +3760,10 @@ class NominalTypeDecl : public GenericTypeDecl, public IterableDeclContext { /// Return a type wrapper (if any) associated with this type. NominalTypeDecl *getTypeWrapper() const; + /// Get an initializer that could be used to instantiate a + /// type wrapped type. + ConstructorDecl *getTypeWrapperInitializer() const; + // Implement isa/cast/dyncast/etc. static bool classof(const Decl *D) { return D->getKind() >= DeclKind::First_NominalTypeDecl && diff --git a/include/swift/AST/KnownIdentifiers.def b/include/swift/AST/KnownIdentifiers.def index 3d9bb1ce996b9..787b2d2ca836c 100644 --- a/include/swift/AST/KnownIdentifiers.def +++ b/include/swift/AST/KnownIdentifiers.def @@ -311,6 +311,7 @@ IDENTIFIER_WITH_NAME(builderSelf, "$builderSelf") IDENTIFIER_WITH_NAME(TypeWrapperStorage, "$Storage") IDENTIFIER_WITH_NAME(TypeWrapperProperty, "$_storage") IDENTIFIER(storageKeyPath) +IDENTIFIER(memberwise) #undef IDENTIFIER #undef IDENTIFIER_ diff --git a/include/swift/AST/TypeCheckRequests.h b/include/swift/AST/TypeCheckRequests.h index dcbf2b4c3984c..d7635ed44c52a 100644 --- a/include/swift/AST/TypeCheckRequests.h +++ b/include/swift/AST/TypeCheckRequests.h @@ -3639,6 +3639,22 @@ class IsPropertyAccessedViaTypeWrapper bool isCached() const { return true; } }; +class SynthesizeTypeWrapperInitializer + : public SimpleRequest { +public: + using SimpleRequest::SimpleRequest; + +private: + friend SimpleRequest; + + ConstructorDecl *evaluate(Evaluator &evaluator, NominalTypeDecl *) const; + +public: + bool isCached() const { return true; } +}; + void simple_display(llvm::raw_ostream &out, ASTNode node); void simple_display(llvm::raw_ostream &out, Type value); void simple_display(llvm::raw_ostream &out, const TypeRepr *TyR); diff --git a/include/swift/AST/TypeCheckerTypeIDZone.def b/include/swift/AST/TypeCheckerTypeIDZone.def index 8e61ec3c8066f..933c911826e0f 100644 --- a/include/swift/AST/TypeCheckerTypeIDZone.def +++ b/include/swift/AST/TypeCheckerTypeIDZone.def @@ -423,3 +423,6 @@ SWIFT_REQUEST(TypeChecker, SynthesizeTypeWrappedPropertySetterBody, SWIFT_REQUEST(TypeChecker, IsPropertyAccessedViaTypeWrapper, bool(VarDecl *), Cached, NoLocationInfo) +SWIFT_REQUEST(TypeChecker, SynthesizeTypeWrapperInitializer, + ConstructorDecl *(NominalTypeDecl *), + Cached, NoLocationInfo) diff --git a/lib/Sema/CodeSynthesis.cpp b/lib/Sema/CodeSynthesis.cpp index 6ac9b53f4644e..bfdb9bd63fbf9 100644 --- a/lib/Sema/CodeSynthesis.cpp +++ b/lib/Sema/CodeSynthesis.cpp @@ -202,6 +202,9 @@ enum class ImplicitConstructorKind { /// the instance variables from a parameter of the same type and /// name. Memberwise, + /// The constructor of a type wrapped type which is going to + /// initialize underlying storage for all applicable properties. + TypeWrapper, }; /// Create an implicit struct or class constructor. @@ -318,6 +321,24 @@ static ConstructorDecl *createImplicitConstructor(NominalTypeDecl *decl, arg->setInterfaceType(systemTy); arg->setImplicit(); + params.push_back(arg); + } + } else if (ICK == ImplicitConstructorKind::TypeWrapper) { + // Access to the initializer should match that of its parent type. + accessLevel = decl->getEffectiveAccess(); + + for (auto *member : decl->getMembers()) { + auto *var = dyn_cast(member); + if (!(var && var->isAccessedViaTypeWrapper())) + continue; + + auto *arg = new (ctx) ParamDecl(SourceLoc(), Loc, var->getName(), Loc, + var->getName(), decl); + + arg->setSpecifier(ParamSpecifier::Default); + arg->setInterfaceType(var->getValueInterfaceType()); + arg->setImplicit(); + params.push_back(arg); } } @@ -1126,6 +1147,11 @@ void TypeChecker::addImplicitConstructors(NominalTypeDecl *decl) { return; if (!shouldAttemptInitializerSynthesis(decl)) { + // If declaration is type wrapped, synthesize a + // special initializer that would instantiate storage. + if (decl->hasTypeWrapper()) + (void)decl->getTypeWrapperInitializer(); + decl->setAddedImplicitInitializers(); return; } @@ -1445,3 +1471,76 @@ void swift::addNonIsolatedToSynthesized( ASTContext &ctx = nominal->getASTContext(); value->getAttrs().add(new (ctx) NonisolatedAttr(/*isImplicit=*/true)); } + +static std::pair +synthesizeTypeWrapperInitializerBody(AbstractFunctionDecl *fn, void *context) { + auto *ctor = cast(fn); + auto &ctx = ctor->getASTContext(); + auto *parent = ctor->getDeclContext()->getSelfNominalTypeDecl(); + + // self.$_storage = .init(memberwise: $Storage(...)) + auto *storageType = + evaluateOrDefault(ctx.evaluator, GetTypeWrapperStorage{parent}, nullptr); + assert(storageType); + + auto *typeWrapperVar = + evaluateOrDefault(ctx.evaluator, GetTypeWrapperProperty{parent}, nullptr); + assert(typeWrapperVar); + + auto *storageVarRef = UnresolvedDotExpr::createImplicit( + ctx, + new (ctx) DeclRefExpr({ctor->getImplicitSelfDecl()}, + /*Loc=*/DeclNameLoc(), /*Implicit=*/true), + typeWrapperVar->getName()); + + SmallVector arguments; + { + for (auto *param : *ctor->getParameters()) { + arguments.push_back({/*labelLoc=*/SourceLoc(), param->getName(), + new (ctx) DeclRefExpr(param, /*Loc=*/DeclNameLoc(), + /*Implicit=*/true)}); + } + } + + auto *storageInit = CallExpr::createImplicit( + ctx, + TypeExpr::createImplicitForDecl( + /*Loc=*/DeclNameLoc(), storageType, ctor, + ctor->mapTypeIntoContext(storageType->getInterfaceType())), + ArgumentList::createImplicit(ctx, arguments)); + + auto *initRef = new (ctx) UnresolvedMemberExpr( + /*dotLoc=*/SourceLoc(), /*declNameLoc=*/DeclNameLoc(), + DeclNameRef::createConstructor(), /*implicit=*/true); + { initRef->setFunctionRefKind(FunctionRefKind::DoubleApply); } + + // .init($Storage(...)) + Expr *typeWrapperInit = CallExpr::createImplicit( + ctx, initRef, + ArgumentList::forImplicitSingle(ctx, ctx.Id_memberwise, storageInit)); + + auto *assignment = new (ctx) + AssignExpr(storageVarRef, /*EqualLoc=*/SourceLoc(), typeWrapperInit, + /*Implicit=*/true); + + return {BraceStmt::create(ctx, /*lbloc=*/ctor->getLoc(), + /*body=*/{assignment}, + /*rbloc=*/ctor->getLoc(), /*implicit=*/true), + /*isTypeChecked=*/false}; +} + +ConstructorDecl * +SynthesizeTypeWrapperInitializer::evaluate(Evaluator &evaluator, + NominalTypeDecl *wrappedType) const { + if (!wrappedType->hasTypeWrapper()) + return nullptr; + + // Create the implicit memberwise constructor. + auto &ctx = wrappedType->getASTContext(); + auto ctor = createImplicitConstructor( + wrappedType, ImplicitConstructorKind::TypeWrapper, ctx); + wrappedType->addMember(ctor); + + ctor->setBodySynthesizer(synthesizeTypeWrapperInitializerBody); + return ctor; +} diff --git a/lib/Sema/TypeCheckAttr.cpp b/lib/Sema/TypeCheckAttr.cpp index 281362d4c599c..e6af45d283550 100644 --- a/lib/Sema/TypeCheckAttr.cpp +++ b/lib/Sema/TypeCheckAttr.cpp @@ -3634,7 +3634,7 @@ void AttributeChecker::visitTypeWrapperAttr(TypeWrapperAttr *attr) { // `init(memberwise:)` { DeclName initName(ctx, DeclBaseName::createConstructor(), - ArrayRef(ctx.getIdentifier("memberwise"))); + ArrayRef(ctx.Id_memberwise)); SmallVector inits; if (findMembersOrDiagnose(initName, inits, diff --git a/lib/Sema/TypeCheckTypeWrapper.cpp b/lib/Sema/TypeCheckTypeWrapper.cpp index 485a340c5c6af..7d2a42598085b 100644 --- a/lib/Sema/TypeCheckTypeWrapper.cpp +++ b/lib/Sema/TypeCheckTypeWrapper.cpp @@ -128,6 +128,13 @@ Type GetTypeWrapperType::evaluate(Evaluator &evaluator, return type; } +ConstructorDecl *NominalTypeDecl::getTypeWrapperInitializer() const { + auto *mutableSelf = const_cast(this); + return evaluateOrDefault(getASTContext().evaluator, + SynthesizeTypeWrapperInitializer{mutableSelf}, + nullptr); +} + NominalTypeDecl * GetTypeWrapperStorage::evaluate(Evaluator &evaluator, NominalTypeDecl *parent) const { From a672db9cb736cd58d588a277575b8214bf08a1ee Mon Sep 17 00:00:00 2001 From: Pavel Yaskevich Date: Fri, 29 Jul 2022 11:05:37 -0700 Subject: [PATCH 12/27] [Sema] TypeWrappers: Make sure that type wrapped properties are always "computed" --- lib/Sema/TypeCheckStorage.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/Sema/TypeCheckStorage.cpp b/lib/Sema/TypeCheckStorage.cpp index 89afc0884a480..2b22fbdee0963 100644 --- a/lib/Sema/TypeCheckStorage.cpp +++ b/lib/Sema/TypeCheckStorage.cpp @@ -3277,6 +3277,8 @@ static void finishStorageImplInfo(AbstractStorageDecl *storage, finishNSManagedImplInfo(var, info); } else if (var->hasAttachedPropertyWrapper()) { finishPropertyWrapperImplInfo(var, info); + } else if (var->isAccessedViaTypeWrapper()) { + info = StorageImplInfo::getMutableComputed(); } } From b3ed4d32ff0d5fc9eaf306edc42183edab6cd5d6 Mon Sep 17 00:00:00 2001 From: Pavel Yaskevich Date: Fri, 29 Jul 2022 11:33:30 -0700 Subject: [PATCH 13/27] [AST] Make it possible to access type wrapper storage of a var This is important because we need to force existance of the underlying storage at the right moment. --- include/swift/AST/Decl.h | 7 +++++++ lib/Sema/TypeCheckStorage.cpp | 6 +++++- lib/Sema/TypeCheckTypeWrapper.cpp | 10 ++++++++-- 3 files changed, 20 insertions(+), 3 deletions(-) diff --git a/include/swift/AST/Decl.h b/include/swift/AST/Decl.h index 9e8ed8aa87801..d2cec186ff43e 100644 --- a/include/swift/AST/Decl.h +++ b/include/swift/AST/Decl.h @@ -5564,6 +5564,13 @@ class VarDecl : public AbstractStorageDecl { /// all access to it routed through a type wrapper. bool isAccessedViaTypeWrapper() const; + /// For type wrapped properties (see \c isAccessedViaTypeWrapper) + /// all access is routed through a type wrapper. + /// + /// \returns an underlying type wrapper property which is a + /// storage endpoint for all access to this property. + VarDecl *getUnderlyingTypeWrapperStorage() const; + /// Visit all auxiliary declarations to this VarDecl. /// /// An auxiliary declaration is a declaration synthesized by the compiler to support diff --git a/lib/Sema/TypeCheckStorage.cpp b/lib/Sema/TypeCheckStorage.cpp index 2b22fbdee0963..0247ffdc02c88 100644 --- a/lib/Sema/TypeCheckStorage.cpp +++ b/lib/Sema/TypeCheckStorage.cpp @@ -104,7 +104,7 @@ static bool hasStoredProperties(NominalTypeDecl *decl) { static void computeLoweredStoredProperties(NominalTypeDecl *decl) { // Just walk over the members of the type, forcing backing storage - // for lazy properties and property wrappers to be synthesized. + // for lazy properties, property and type wrappers to be synthesized. for (auto *member : decl->getMembers()) { auto *var = dyn_cast(member); if (!var || var->isStatic()) @@ -117,6 +117,10 @@ static void computeLoweredStoredProperties(NominalTypeDecl *decl) { (void) var->getPropertyWrapperAuxiliaryVariables(); (void) var->getPropertyWrapperInitializerInfo(); } + + if (var->isAccessedViaTypeWrapper()) { + (void)var->getUnderlyingTypeWrapperStorage(); + } } // If this is an actor, check conformance to the Actor protocol to diff --git a/lib/Sema/TypeCheckTypeWrapper.cpp b/lib/Sema/TypeCheckTypeWrapper.cpp index 7d2a42598085b..15c628014fc1c 100644 --- a/lib/Sema/TypeCheckTypeWrapper.cpp +++ b/lib/Sema/TypeCheckTypeWrapper.cpp @@ -60,6 +60,13 @@ bool VarDecl::isAccessedViaTypeWrapper() const { false); } +VarDecl *VarDecl::getUnderlyingTypeWrapperStorage() const { + auto *mutableSelf = const_cast(this); + return evaluateOrDefault(getASTContext().evaluator, + GetTypeWrapperStorageForProperty{mutableSelf}, + nullptr); +} + NominalTypeDecl *NominalTypeDecl::getTypeWrapper() const { auto *mutableSelf = const_cast(this); return evaluateOrDefault(getASTContext().evaluator, @@ -217,8 +224,7 @@ static SubscriptExpr *subscriptTypeWrappedProperty(VarDecl *var, auto *typeWrapperVar = evaluateOrDefault(ctx.evaluator, GetTypeWrapperProperty{parent}, nullptr); - auto *storageVar = evaluateOrDefault( - ctx.evaluator, GetTypeWrapperStorageForProperty{var}, nullptr); + auto *storageVar = var->getUnderlyingTypeWrapperStorage(); assert(typeWrapperVar); assert(storageVar); From d341c4128c1a97c9695abb8f53788ea04ae165de Mon Sep 17 00:00:00 2001 From: Pavel Yaskevich Date: Fri, 29 Jul 2022 11:47:44 -0700 Subject: [PATCH 14/27] [AST] Make it possible to access type wrapper property (storage) This is important because we need to force existance of the underlying storage at the right moment. --- include/swift/AST/Decl.h | 5 +++++ lib/Sema/TypeCheckStorage.cpp | 5 +++++ lib/Sema/TypeCheckTypeWrapper.cpp | 9 +++++++-- 3 files changed, 17 insertions(+), 2 deletions(-) diff --git a/include/swift/AST/Decl.h b/include/swift/AST/Decl.h index d2cec186ff43e..984bd8d602242 100644 --- a/include/swift/AST/Decl.h +++ b/include/swift/AST/Decl.h @@ -3760,6 +3760,11 @@ class NominalTypeDecl : public GenericTypeDecl, public IterableDeclContext { /// Return a type wrapper (if any) associated with this type. NominalTypeDecl *getTypeWrapper() const; + /// If this declaration has a type wrapper return a property that + /// is used for all type wrapper related operations (mainly for + /// applicable property access routing). + VarDecl *getTypeWrapperProperty() const; + /// Get an initializer that could be used to instantiate a /// type wrapped type. ConstructorDecl *getTypeWrapperInitializer() const; diff --git a/lib/Sema/TypeCheckStorage.cpp b/lib/Sema/TypeCheckStorage.cpp index 0247ffdc02c88..710afc6b29905 100644 --- a/lib/Sema/TypeCheckStorage.cpp +++ b/lib/Sema/TypeCheckStorage.cpp @@ -103,6 +103,11 @@ static bool hasStoredProperties(NominalTypeDecl *decl) { } static void computeLoweredStoredProperties(NominalTypeDecl *decl) { + // If declaration has a type wrapper, make sure that + // `$_storage` property is synthesized. + if (decl->hasTypeWrapper()) + (void)decl->getTypeWrapperProperty(); + // Just walk over the members of the type, forcing backing storage // for lazy properties, property and type wrappers to be synthesized. for (auto *member : decl->getMembers()) { diff --git a/lib/Sema/TypeCheckTypeWrapper.cpp b/lib/Sema/TypeCheckTypeWrapper.cpp index 15c628014fc1c..bcd356563d498 100644 --- a/lib/Sema/TypeCheckTypeWrapper.cpp +++ b/lib/Sema/TypeCheckTypeWrapper.cpp @@ -135,6 +135,12 @@ Type GetTypeWrapperType::evaluate(Evaluator &evaluator, return type; } +VarDecl *NominalTypeDecl::getTypeWrapperProperty() const { + auto *mutableSelf = const_cast(this); + return evaluateOrDefault(getASTContext().evaluator, + GetTypeWrapperProperty{mutableSelf}, nullptr); +} + ConstructorDecl *NominalTypeDecl::getTypeWrapperInitializer() const { auto *mutableSelf = const_cast(this); return evaluateOrDefault(getASTContext().evaluator, @@ -222,8 +228,7 @@ static SubscriptExpr *subscriptTypeWrappedProperty(VarDecl *var, if (!(parent && parent->hasTypeWrapper())) return nullptr; - auto *typeWrapperVar = - evaluateOrDefault(ctx.evaluator, GetTypeWrapperProperty{parent}, nullptr); + auto *typeWrapperVar = parent->getTypeWrapperProperty(); auto *storageVar = var->getUnderlyingTypeWrapperStorage(); assert(typeWrapperVar); From 047f51f16272bae2d5506c77a36b7aaaa42ba5c2 Mon Sep 17 00:00:00 2001 From: Pavel Yaskevich Date: Fri, 29 Jul 2022 12:00:50 -0700 Subject: [PATCH 15/27] [Sema] TypeWrappers: Make sure that synthesized accessors for wrapped vars are not transparent Type wrapped variables loose their storage and are accessed through `$Storage` and synthesized accessors cannot be transparent. --- lib/Sema/TypeCheckStorage.cpp | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/lib/Sema/TypeCheckStorage.cpp b/lib/Sema/TypeCheckStorage.cpp index 710afc6b29905..8b77518476c7c 100644 --- a/lib/Sema/TypeCheckStorage.cpp +++ b/lib/Sema/TypeCheckStorage.cpp @@ -2475,6 +2475,12 @@ IsAccessorTransparentRequest::evaluate(Evaluator &evaluator, switch (accessor->getAccessorKind()) { case AccessorKind::Get: + // Synthesized getter for a type wrapped variable is never transparent. + if (auto var = dyn_cast(storage)) { + if (var->isAccessedViaTypeWrapper()) + return false; + } + break; case AccessorKind::Set: @@ -2493,6 +2499,10 @@ IsAccessorTransparentRequest::evaluate(Evaluator &evaluator, PropertyWrapperSynthesizedPropertyKind::Projection)) { break; } + + // Synthesized setter for a type wrapped variable is never transparent. + if (var->isAccessedViaTypeWrapper()) + return false; } if (auto subscript = dyn_cast(storage)) { From 7d28a4fa78388e24f5850d9cf6dab15457141d34 Mon Sep 17 00:00:00 2001 From: Pavel Yaskevich Date: Mon, 1 Aug 2022 11:09:28 -0700 Subject: [PATCH 16/27] [TypeChecker] NFC: Add more property wrapper tests --- .../Inputs/type_wrapper_defs.swift | 26 ++++++++ test/Interpreter/type_wrappers.swift | 39 +++++++++++ test/type/type_wrapper.swift | 65 +++++++++++++++++++ 3 files changed, 130 insertions(+) create mode 100644 test/Interpreter/Inputs/type_wrapper_defs.swift create mode 100644 test/Interpreter/type_wrappers.swift diff --git a/test/Interpreter/Inputs/type_wrapper_defs.swift b/test/Interpreter/Inputs/type_wrapper_defs.swift new file mode 100644 index 0000000000000..3fef5fcbf547b --- /dev/null +++ b/test/Interpreter/Inputs/type_wrapper_defs.swift @@ -0,0 +1,26 @@ +@typeWrapper +public struct Wrapper { + var underlying: S + + public init(memberwise: S) { + print("Wrapper.init(\(memberwise))") + self.underlying = memberwise + } + + public subscript(storageKeyPath path: WritableKeyPath) -> V { + get { + print("in getter") + return underlying[keyPath: path] + } + set { + print("in setter => \(newValue)") + underlying[keyPath: path] = newValue + } + } +} + +@Wrapper +public class Person { + public var name: String + public var projects: [T] +} diff --git a/test/Interpreter/type_wrappers.swift b/test/Interpreter/type_wrappers.swift new file mode 100644 index 0000000000000..d1199227e989d --- /dev/null +++ b/test/Interpreter/type_wrappers.swift @@ -0,0 +1,39 @@ +// RUN: %empty-directory(%t) +// RUN: %target-build-swift -parse-as-library -emit-library -emit-module-path %t/type_wrapper_defs.swiftmodule -module-name type_wrapper_defs %S/Inputs/type_wrapper_defs.swift -o %t/%target-library-name(type_wrapper_defs) +// RUN: %target-build-swift -ltype_wrapper_defs -module-name main -I %t -L %t %s -o %t/a.out +// RUN: %target-run %t/a.out | %FileCheck %s + +import type_wrapper_defs + +var p: Person = .init(name: "P", projects: ["A", "B"]) +// CHECK: Wrapper.init($Storage(name: "P", projects: ["A", "B"])) + +print(p.name) +// CHECK: in getter +// CHECK-NEXT: P +print(p.projects) +// CHECK: in getter +// CHECK-NEXT: ["A", "B"] + +p.name = "OtherP" +// CHECK: in setter => OtherP +p.projects.append("C") +// CHECK: in getter +// CHECK-NEXT: in setter => ["A", "B", "C"] + + +func addProjects(p: inout Person, _ newProjects: [T]) { + p.projects.append(contentsOf: newProjects) +} + +addProjects(p: &p, ["D"]) +// CHECK: in getter +// CHECK: in setter => ["A", "B", "C", "D"] + +print(p.name) +// CHECK: in getter +// CHECK-NEXT: OtherP + +print(p.projects) +// CHECK: in getter +// CHECK-NEXT: ["A", "B", "C", "D"] diff --git a/test/type/type_wrapper.swift b/test/type/type_wrapper.swift index d09a64a366ded..85a36fb7a2be9 100644 --- a/test/type/type_wrapper.swift +++ b/test/type/type_wrapper.swift @@ -82,3 +82,68 @@ struct InaccessibleOrInvalidSubscripts { get { true } } } + +@typeWrapper +struct NoopWrapper { + init(memberwise: S) {} + + subscript(storageKeyPath path: KeyPath) -> V { + get { fatalError() } + set { } + } +} + +@NoopWrapper +struct A { + var a: String + var b: Int +} + +@NoopWrapper +class GenericA { + var data: [K: V] +} + +@NoopWrapper // expected-error {{type wrapper attribute 'NoopWrapper' can only be applied to a class, struct}} +protocol P { +} + +@NoopWrapper // expected-error {{type wrapper attribute 'NoopWrapper' can only be applied to a class, struct}} +enum E { + var x: Int { get { 42 } } +} + +func testWrappedTypeAccessChecking() { + let a = A(a: "", b: 42) // synthesized init + let b = GenericA(data: ["ultimate question": 42]) // generic synthesized init + + _ = a.a // Ok + _ = b.data // Ok +} + +struct Parent { + @typeWrapper + struct Wrapper { + init(memberwise: S) {} + + subscript(storageKeyPath path: KeyPath) -> V { + get { fatalError() } + set { } + } + } +} + +func testLocalWithNestedWrapper() { + @Parent.Wrapper + class WithNestedWrapper { + var test: [T] + + var computed: String { + get { "" } + } + } + + let t = WithNestedWrapper(test: [1, 2]) // synthesized init + _ = t.test // Ok + _ = t.computed // Ok +} From a3b54308d2718ced9289b808bfa2bc4d2f66cdc2 Mon Sep 17 00:00:00 2001 From: Pavel Yaskevich Date: Mon, 1 Aug 2022 12:10:38 -0700 Subject: [PATCH 17/27] [Frontend] Mark 'Type Wrappers' as experimental feature that has to be enabled --- include/swift/AST/DiagnosticsSema.def | 3 +++ include/swift/Basic/Features.def | 5 +++++ lib/AST/ASTPrinter.cpp | 4 ++++ lib/Sema/TypeCheckAttr.cpp | 6 ++++++ test/Interpreter/type_wrappers.swift | 2 +- test/type/type_wrapper.swift | 2 +- 6 files changed, 20 insertions(+), 2 deletions(-) diff --git a/include/swift/AST/DiagnosticsSema.def b/include/swift/AST/DiagnosticsSema.def index ffead969c6238..c9cb5ab58258c 100644 --- a/include/swift/AST/DiagnosticsSema.def +++ b/include/swift/AST/DiagnosticsSema.def @@ -6480,6 +6480,9 @@ ERROR(move_expression_not_passed_lvalue,none, // MARK: Type Wrappers //------------------------------------------------------------------------------ +ERROR(type_wrappers_are_experimental,none, + "type wrappers are an experimental feature", ()) + ERROR(type_wrapper_attribute_not_allowed_here,none, "type wrapper attribute %0 can only be applied to a class, struct", (Identifier)) diff --git a/include/swift/Basic/Features.def b/include/swift/Basic/Features.def index e8c1bba7e5f45..f19e543334edd 100644 --- a/include/swift/Basic/Features.def +++ b/include/swift/Basic/Features.def @@ -122,6 +122,11 @@ EXPERIMENTAL_FEATURE(SendableCompletionHandlers) /// Enables opaque type erasure without also enabling implict dynamic EXPERIMENTAL_FEATURE(OpaqueTypeErasure) +/// Whether to enable experimental @typeWrapper feature which allows to +/// declare a type that controls access to all stored properties of the +/// wrapped type. +EXPERIMENTAL_FEATURE(TypeWrappers) + #undef EXPERIMENTAL_FEATURE #undef UPCOMING_FEATURE #undef SUPPRESSIBLE_LANGUAGE_FEATURE diff --git a/lib/AST/ASTPrinter.cpp b/lib/AST/ASTPrinter.cpp index 150df1018aa62..55e769b025591 100644 --- a/lib/AST/ASTPrinter.cpp +++ b/lib/AST/ASTPrinter.cpp @@ -2935,6 +2935,10 @@ static bool usesFeatureSpecializeAttributeWithAvailability(Decl *decl) { return false; } +static bool usesFeatureTypeWrappers(Decl *decl) { + return decl->getAttrs().hasAttribute(); +} + static void suppressingFeatureSpecializeAttributeWithAvailability( PrintOptions &options, llvm::function_ref action) { diff --git a/lib/Sema/TypeCheckAttr.cpp b/lib/Sema/TypeCheckAttr.cpp index e6af45d283550..0476ce49dafcd 100644 --- a/lib/Sema/TypeCheckAttr.cpp +++ b/lib/Sema/TypeCheckAttr.cpp @@ -3587,6 +3587,12 @@ void AttributeChecker::visitPropertyWrapperAttr(PropertyWrapperAttr *attr) { } void AttributeChecker::visitTypeWrapperAttr(TypeWrapperAttr *attr) { + if (!Ctx.LangOpts.hasFeature(Feature::TypeWrappers)) { + diagnose(attr->getLocation(), diag::type_wrappers_are_experimental); + attr->setInvalid(); + return; + } + auto nominal = dyn_cast(D); if (!nominal) return; diff --git a/test/Interpreter/type_wrappers.swift b/test/Interpreter/type_wrappers.swift index d1199227e989d..2b8cbf68e82f4 100644 --- a/test/Interpreter/type_wrappers.swift +++ b/test/Interpreter/type_wrappers.swift @@ -1,5 +1,5 @@ // RUN: %empty-directory(%t) -// RUN: %target-build-swift -parse-as-library -emit-library -emit-module-path %t/type_wrapper_defs.swiftmodule -module-name type_wrapper_defs %S/Inputs/type_wrapper_defs.swift -o %t/%target-library-name(type_wrapper_defs) +// RUN: %target-build-swift -enable-experimental-feature TypeWrappers -parse-as-library -emit-library -emit-module-path %t/type_wrapper_defs.swiftmodule -module-name type_wrapper_defs %S/Inputs/type_wrapper_defs.swift -o %t/%target-library-name(type_wrapper_defs) // RUN: %target-build-swift -ltype_wrapper_defs -module-name main -I %t -L %t %s -o %t/a.out // RUN: %target-run %t/a.out | %FileCheck %s diff --git a/test/type/type_wrapper.swift b/test/type/type_wrapper.swift index 85a36fb7a2be9..150e9d900c7c9 100644 --- a/test/type/type_wrapper.swift +++ b/test/type/type_wrapper.swift @@ -1,4 +1,4 @@ -// RUN: %target-typecheck-verify-swift +// RUN: %target-typecheck-verify-swift -enable-experimental-feature TypeWrappers @typeWrapper struct ConcreteTypeWrapper { // expected-error {{type wrapper has to declare a single generic parameter for underlying storage type}} From 39b15662402733c6f37f69ebc999c3e7788e5b33 Mon Sep 17 00:00:00 2001 From: Pavel Yaskevich Date: Mon, 1 Aug 2022 16:38:49 -0700 Subject: [PATCH 18/27] [Sema] TypeWrappers: convert variable init expr into initializer default All of the stored properties are wrapped which means that their initializers are subsummed and moved to the synthesized `init` as default arguments. --- lib/Sema/CodeSynthesis.cpp | 24 ++++++++++ .../Inputs/type_wrapper_defs.swift | 6 +++ test/Interpreter/type_wrappers.swift | 45 +++++++++++++++++++ test/type/type_wrapper.swift | 15 +++++++ 4 files changed, 90 insertions(+) diff --git a/lib/Sema/CodeSynthesis.cpp b/lib/Sema/CodeSynthesis.cpp index bfdb9bd63fbf9..bdf8fd48330e3 100644 --- a/lib/Sema/CodeSynthesis.cpp +++ b/lib/Sema/CodeSynthesis.cpp @@ -188,6 +188,28 @@ static void maybeAddMemberwiseDefaultArg(ParamDecl *arg, VarDecl *var, arg->setDefaultArgumentKind(DefaultArgumentKind::StoredProperty); } +static void maybeAddTypeWrapperDefaultArg(ParamDecl *arg, VarDecl *var, + ASTContext &ctx) { + assert(var->isAccessedViaTypeWrapper()); + + if (!var->getParentPattern()->getSingleVar()) + return; + + auto *PBD = var->getParentPatternBinding(); + + auto *initExpr = PBD->getInit(/*index=*/0); + if (!initExpr) + return; + + // Type wrapper variables are never initialized directly, + // initialization expression (if any) becomes an default + // argument of the initializer synthesized by the type wrapper. + PBD->setInitializerSubsumed(/*index=*/0); + + arg->setDefaultExpr(initExpr, /*isTypeChecked=*/false); + arg->setDefaultArgumentKind(DefaultArgumentKind::Normal); +} + /// Describes the kind of implicit constructor that will be /// generated. enum class ImplicitConstructorKind { @@ -339,6 +361,8 @@ static ConstructorDecl *createImplicitConstructor(NominalTypeDecl *decl, arg->setInterfaceType(var->getValueInterfaceType()); arg->setImplicit(); + maybeAddTypeWrapperDefaultArg(arg, var, ctx); + params.push_back(arg); } } diff --git a/test/Interpreter/Inputs/type_wrapper_defs.swift b/test/Interpreter/Inputs/type_wrapper_defs.swift index 3fef5fcbf547b..783e76f6dfac9 100644 --- a/test/Interpreter/Inputs/type_wrapper_defs.swift +++ b/test/Interpreter/Inputs/type_wrapper_defs.swift @@ -24,3 +24,9 @@ public class Person { public var name: String public var projects: [T] } + +@Wrapper +public struct PersonWithDefaults { + public var name: String = "" + public var age: Int = 99 +} diff --git a/test/Interpreter/type_wrappers.swift b/test/Interpreter/type_wrappers.swift index 2b8cbf68e82f4..f3ddf6e05da1c 100644 --- a/test/Interpreter/type_wrappers.swift +++ b/test/Interpreter/type_wrappers.swift @@ -37,3 +37,48 @@ print(p.name) print(p.projects) // CHECK: in getter // CHECK-NEXT: ["A", "B", "C", "D"] + +var pDefaults = PersonWithDefaults() +// CHECK: Wrapper.init($Storage(name: "", age: 99)) + +print(pDefaults.name) +// CHECK: in getter +// CHECK: + +print(pDefaults.age) +// CHECK: in getter +// CHECK: 99 + +pDefaults.name = "Actual Name" +// CHECK-NEXT: in setter => Actual Name + +pDefaults.age = 0 +// CHECK-NEXT: in setter => 0 + +print(pDefaults.name) +// CHECK: in getter +// CHECK: Actual Name + +print(pDefaults.age) +// CHECK: in getter +// CHECK: 0 + +let pDefaultsAge = PersonWithDefaults(name: "Actual Name") + +print(pDefaultsAge.name) +// CHECK: in getter +// CHECK: Actual Name + +print(pDefaultsAge.age) +// CHECK: in getter +// CHECK: 99 + +let pDefaultsName = PersonWithDefaults(age: 31337) + +print(pDefaultsName.name) +// CHECK: in getter +// CHECK: + +print(pDefaultsName.age) +// CHECK: in getter +// CHECK: 31337 diff --git a/test/type/type_wrapper.swift b/test/type/type_wrapper.swift index 150e9d900c7c9..cd3dc82120959 100644 --- a/test/type/type_wrapper.swift +++ b/test/type/type_wrapper.swift @@ -147,3 +147,18 @@ func testLocalWithNestedWrapper() { _ = t.test // Ok _ = t.computed // Ok } + +func testTypeWrapperWithDefaults() { + @NoopWrapper + struct A { + var question: String = "Ultimate Question" + var answer: Int = 42 + } + + let a = A() + _ = a.question + _ = a.answer + + _ = A(question: "") + _ = A(answer: 0) +} From ecc363d3f78ed9f9fdfc81120f5ed7b17cc92def Mon Sep 17 00:00:00 2001 From: Pavel Yaskevich Date: Wed, 10 Aug 2022 11:00:40 -0700 Subject: [PATCH 19/27] [Sema] TypeWrappers: Allow wrapping stored properties with attached property wrappers Type wrapper doesn't wrap anything expect to a private backing storage property. This means that type wrapper is applied first and subsequent `.wrappedValue` and/or `.projectedValue` is referenced for the returned property wrapper type. --- lib/Sema/TypeCheckStorage.cpp | 8 ++++++-- lib/Sema/TypeCheckTypeWrapper.cpp | 24 +++++++++++++++++++++--- 2 files changed, 27 insertions(+), 5 deletions(-) diff --git a/lib/Sema/TypeCheckStorage.cpp b/lib/Sema/TypeCheckStorage.cpp index 8b77518476c7c..b2a23fa501f45 100644 --- a/lib/Sema/TypeCheckStorage.cpp +++ b/lib/Sema/TypeCheckStorage.cpp @@ -923,7 +923,9 @@ static Expr *buildStorageReference(AccessorDecl *accessor, underlyingVars.push_back({ wrappedValue, isWrapperRefLValue }); } } - semantics = AccessSemantics::DirectToStorage; + semantics = backing->isAccessedViaTypeWrapper() + ? AccessSemantics::DirectToImplementation + : AccessSemantics::DirectToStorage; selfAccessKind = SelfAccessorKind::Peer; break; } @@ -952,7 +954,9 @@ static Expr *buildStorageReference(AccessorDecl *accessor, { var->getAttachedPropertyWrapperTypeInfo(0).projectedValueVar, isLValue }); } - semantics = AccessSemantics::DirectToStorage; + semantics = backing->isAccessedViaTypeWrapper() + ? AccessSemantics::DirectToImplementation + : AccessSemantics::DirectToStorage; selfAccessKind = SelfAccessorKind::Peer; break; } diff --git a/lib/Sema/TypeCheckTypeWrapper.cpp b/lib/Sema/TypeCheckTypeWrapper.cpp index bcd356563d498..2e08c13701601 100644 --- a/lib/Sema/TypeCheckTypeWrapper.cpp +++ b/lib/Sema/TypeCheckTypeWrapper.cpp @@ -322,9 +322,27 @@ bool IsPropertyAccessedViaTypeWrapper::evaluate(Evaluator &evaluator, if (property->getAttrs().hasAttribute()) return false; - // properties with attached property wrappers are not yet supported. - if (property->hasAttachedPropertyWrapper()) - return false; + // Properties with attached property wrappers are not considered + // accessible via type wrapper directly, only their backing storage is. + { + // Wrapped property itself `` + if (property->hasAttachedPropertyWrapper()) + return false; + + // Projection - `$` + if (property->getOriginalWrappedProperty( + PropertyWrapperSynthesizedPropertyKind::Projection)) + return false; + + // Backing storage (or wrapper property) - `_`. + // + // This is the only thing that wrapper needs to handle because + // all access to the wrapped variable and it's projection + // is routed through it. + if (property->getOriginalWrappedProperty( + PropertyWrapperSynthesizedPropertyKind::Backing)) + return true; + } // Check whether this is a computed property. { From 20a52d2f97c3f871b9c41327d1cd2cb495da220d Mon Sep 17 00:00:00 2001 From: Pavel Yaskevich Date: Wed, 10 Aug 2022 13:46:32 -0700 Subject: [PATCH 20/27] [Sema] TypeWrappers/NFC: Move initializer synthesis into a request --- include/swift/AST/TypeCheckRequests.h | 16 ++++++ include/swift/AST/TypeCheckerTypeIDZone.def | 3 + lib/Sema/CodeSynthesis.cpp | 64 ++------------------- lib/Sema/TypeCheckTypeWrapper.cpp | 55 ++++++++++++++++++ 4 files changed, 80 insertions(+), 58 deletions(-) diff --git a/include/swift/AST/TypeCheckRequests.h b/include/swift/AST/TypeCheckRequests.h index d7635ed44c52a..9a038cd292b4e 100644 --- a/include/swift/AST/TypeCheckRequests.h +++ b/include/swift/AST/TypeCheckRequests.h @@ -3655,6 +3655,22 @@ class SynthesizeTypeWrapperInitializer bool isCached() const { return true; } }; +class SynthesizeTypeWrapperInitializerBody + : public SimpleRequest { +public: + using SimpleRequest::SimpleRequest; + +private: + friend SimpleRequest; + + BraceStmt *evaluate(Evaluator &evaluator, ConstructorDecl *) const; + +public: + bool isCached() const { return true; } +}; + void simple_display(llvm::raw_ostream &out, ASTNode node); void simple_display(llvm::raw_ostream &out, Type value); void simple_display(llvm::raw_ostream &out, const TypeRepr *TyR); diff --git a/include/swift/AST/TypeCheckerTypeIDZone.def b/include/swift/AST/TypeCheckerTypeIDZone.def index 933c911826e0f..6308c13f30747 100644 --- a/include/swift/AST/TypeCheckerTypeIDZone.def +++ b/include/swift/AST/TypeCheckerTypeIDZone.def @@ -426,3 +426,6 @@ SWIFT_REQUEST(TypeChecker, IsPropertyAccessedViaTypeWrapper, SWIFT_REQUEST(TypeChecker, SynthesizeTypeWrapperInitializer, ConstructorDecl *(NominalTypeDecl *), Cached, NoLocationInfo) +SWIFT_REQUEST(TypeChecker, SynthesizeTypeWrapperInitializerBody, + BraceStmt *(ConstructorDecl *), + Cached, NoLocationInfo) diff --git a/lib/Sema/CodeSynthesis.cpp b/lib/Sema/CodeSynthesis.cpp index bdf8fd48330e3..acc0f53a8ac1e 100644 --- a/lib/Sema/CodeSynthesis.cpp +++ b/lib/Sema/CodeSynthesis.cpp @@ -1496,63 +1496,6 @@ void swift::addNonIsolatedToSynthesized( value->getAttrs().add(new (ctx) NonisolatedAttr(/*isImplicit=*/true)); } -static std::pair -synthesizeTypeWrapperInitializerBody(AbstractFunctionDecl *fn, void *context) { - auto *ctor = cast(fn); - auto &ctx = ctor->getASTContext(); - auto *parent = ctor->getDeclContext()->getSelfNominalTypeDecl(); - - // self.$_storage = .init(memberwise: $Storage(...)) - auto *storageType = - evaluateOrDefault(ctx.evaluator, GetTypeWrapperStorage{parent}, nullptr); - assert(storageType); - - auto *typeWrapperVar = - evaluateOrDefault(ctx.evaluator, GetTypeWrapperProperty{parent}, nullptr); - assert(typeWrapperVar); - - auto *storageVarRef = UnresolvedDotExpr::createImplicit( - ctx, - new (ctx) DeclRefExpr({ctor->getImplicitSelfDecl()}, - /*Loc=*/DeclNameLoc(), /*Implicit=*/true), - typeWrapperVar->getName()); - - SmallVector arguments; - { - for (auto *param : *ctor->getParameters()) { - arguments.push_back({/*labelLoc=*/SourceLoc(), param->getName(), - new (ctx) DeclRefExpr(param, /*Loc=*/DeclNameLoc(), - /*Implicit=*/true)}); - } - } - - auto *storageInit = CallExpr::createImplicit( - ctx, - TypeExpr::createImplicitForDecl( - /*Loc=*/DeclNameLoc(), storageType, ctor, - ctor->mapTypeIntoContext(storageType->getInterfaceType())), - ArgumentList::createImplicit(ctx, arguments)); - - auto *initRef = new (ctx) UnresolvedMemberExpr( - /*dotLoc=*/SourceLoc(), /*declNameLoc=*/DeclNameLoc(), - DeclNameRef::createConstructor(), /*implicit=*/true); - { initRef->setFunctionRefKind(FunctionRefKind::DoubleApply); } - - // .init($Storage(...)) - Expr *typeWrapperInit = CallExpr::createImplicit( - ctx, initRef, - ArgumentList::forImplicitSingle(ctx, ctx.Id_memberwise, storageInit)); - - auto *assignment = new (ctx) - AssignExpr(storageVarRef, /*EqualLoc=*/SourceLoc(), typeWrapperInit, - /*Implicit=*/true); - - return {BraceStmt::create(ctx, /*lbloc=*/ctor->getLoc(), - /*body=*/{assignment}, - /*rbloc=*/ctor->getLoc(), /*implicit=*/true), - /*isTypeChecked=*/false}; -} - ConstructorDecl * SynthesizeTypeWrapperInitializer::evaluate(Evaluator &evaluator, NominalTypeDecl *wrappedType) const { @@ -1565,6 +1508,11 @@ SynthesizeTypeWrapperInitializer::evaluate(Evaluator &evaluator, wrappedType, ImplicitConstructorKind::TypeWrapper, ctx); wrappedType->addMember(ctor); - ctor->setBodySynthesizer(synthesizeTypeWrapperInitializerBody); + auto *body = evaluateOrDefault( + evaluator, SynthesizeTypeWrapperInitializerBody{ctor}, nullptr); + if (!body) + return nullptr; + + ctor->setBody(body, AbstractFunctionDecl::BodyKind::Parsed); return ctor; } diff --git a/lib/Sema/TypeCheckTypeWrapper.cpp b/lib/Sema/TypeCheckTypeWrapper.cpp index 2e08c13701601..2110cb5ce84df 100644 --- a/lib/Sema/TypeCheckTypeWrapper.cpp +++ b/lib/Sema/TypeCheckTypeWrapper.cpp @@ -365,3 +365,58 @@ bool IsPropertyAccessedViaTypeWrapper::evaluate(Evaluator &evaluator, return true; } + +BraceStmt * +SynthesizeTypeWrapperInitializerBody::evaluate(Evaluator &evaluator, + ConstructorDecl *ctor) const { + auto &ctx = ctor->getASTContext(); + auto *parent = ctor->getDeclContext()->getSelfNominalTypeDecl(); + + // self.$_storage = .init(memberwise: $Storage(...)) + auto *storageType = + evaluateOrDefault(ctx.evaluator, GetTypeWrapperStorage{parent}, nullptr); + assert(storageType); + + auto *typeWrapperVar = parent->getTypeWrapperProperty(); + assert(typeWrapperVar); + + auto *storageVarRef = UnresolvedDotExpr::createImplicit( + ctx, + new (ctx) DeclRefExpr({ctor->getImplicitSelfDecl()}, + /*Loc=*/DeclNameLoc(), /*Implicit=*/true), + typeWrapperVar->getName()); + + SmallVector arguments; + { + for (auto *param : *ctor->getParameters()) { + arguments.push_back({/*labelLoc=*/SourceLoc(), param->getName(), + new (ctx) DeclRefExpr(param, /*Loc=*/DeclNameLoc(), + /*Implicit=*/true)}); + } + } + + auto *storageInit = CallExpr::createImplicit( + ctx, + TypeExpr::createImplicitForDecl( + /*Loc=*/DeclNameLoc(), storageType, ctor, + ctor->mapTypeIntoContext(storageType->getInterfaceType())), + ArgumentList::createImplicit(ctx, arguments)); + + auto *initRef = new (ctx) UnresolvedMemberExpr( + /*dotLoc=*/SourceLoc(), /*declNameLoc=*/DeclNameLoc(), + DeclNameRef::createConstructor(), /*implicit=*/true); + { initRef->setFunctionRefKind(FunctionRefKind::DoubleApply); } + + // .init($Storage(...)) + Expr *typeWrapperInit = CallExpr::createImplicit( + ctx, initRef, + ArgumentList::forImplicitSingle(ctx, ctx.Id_memberwise, storageInit)); + + auto *assignment = new (ctx) + AssignExpr(storageVarRef, /*EqualLoc=*/SourceLoc(), typeWrapperInit, + /*Implicit=*/true); + + return BraceStmt::create(ctx, /*lbloc=*/ctor->getLoc(), + /*body=*/{assignment}, + /*rbloc=*/ctor->getLoc(), /*implicit=*/true); +} From bf2519aed97dde80643138d8faf7e1401ab5b53c Mon Sep 17 00:00:00 2001 From: Pavel Yaskevich Date: Thu, 11 Aug 2022 09:56:15 -0700 Subject: [PATCH 21/27] [Sema] Allow implicit parameter decls to have property wrapper attributes --- lib/AST/Decl.cpp | 3 ++- lib/Sema/TypeCheckStorage.cpp | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/lib/AST/Decl.cpp b/lib/AST/Decl.cpp index 4f409449baa93..288c35c9ccd5e 100644 --- a/lib/AST/Decl.cpp +++ b/lib/AST/Decl.cpp @@ -6733,7 +6733,8 @@ bool VarDecl::hasStorageOrWrapsStorage() const { } void VarDecl::visitAuxiliaryDecls(llvm::function_ref visit) const { - if (getDeclContext()->isTypeContext() || isImplicit()) + if (getDeclContext()->isTypeContext() || + (isImplicit() && !isa(this))) return; if (getAttrs().hasAttribute()) { diff --git a/lib/Sema/TypeCheckStorage.cpp b/lib/Sema/TypeCheckStorage.cpp index b2a23fa501f45..ee5e68e8f2841 100644 --- a/lib/Sema/TypeCheckStorage.cpp +++ b/lib/Sema/TypeCheckStorage.cpp @@ -2981,7 +2981,8 @@ PropertyWrapperAuxiliaryVariablesRequest::evaluate(Evaluator &evaluator, PropertyWrapperInitializerInfo PropertyWrapperInitializerInfoRequest::evaluate(Evaluator &evaluator, VarDecl *var) const { - if (!var->hasAttachedPropertyWrapper() || var->isImplicit()) + if (!var->hasAttachedPropertyWrapper() || + (var->isImplicit() && !isa(var))) return PropertyWrapperInitializerInfo(); auto wrapperInfo = var->getAttachedPropertyWrapperTypeInfo(0); From 0500f35537e7fced047814ab681a0fe9d2f6ad24 Mon Sep 17 00:00:00 2001 From: Pavel Yaskevich Date: Fri, 12 Aug 2022 14:09:19 -0700 Subject: [PATCH 22/27] [Sema] TypeWrappers: Synthesize init parameters for property wrapped members Since properties with property wrappers are not supported, default init synthesis needs to handle them as well by using correct interface type depending on outer wrapper capabilities and setting correct default expression. --- lib/Sema/CodeSynthesis.cpp | 77 ++++- lib/Sema/TypeCheckTypeWrapper.cpp | 14 +- .../Inputs/type_wrapper_defs.swift | 91 ++++++ test/Interpreter/type_wrappers.swift | 264 ++++++++++++++++++ test/type/type_wrapper.swift | 170 +++++++++++ 5 files changed, 606 insertions(+), 10 deletions(-) diff --git a/lib/Sema/CodeSynthesis.cpp b/lib/Sema/CodeSynthesis.cpp index acc0f53a8ac1e..b48432a0dc568 100644 --- a/lib/Sema/CodeSynthesis.cpp +++ b/lib/Sema/CodeSynthesis.cpp @@ -190,23 +190,44 @@ static void maybeAddMemberwiseDefaultArg(ParamDecl *arg, VarDecl *var, static void maybeAddTypeWrapperDefaultArg(ParamDecl *arg, VarDecl *var, ASTContext &ctx) { - assert(var->isAccessedViaTypeWrapper()); + assert(var->isAccessedViaTypeWrapper() || var->hasAttachedPropertyWrapper()); - if (!var->getParentPattern()->getSingleVar()) + if (!(var->getParentPattern() && var->getParentPattern()->getSingleVar())) return; auto *PBD = var->getParentPatternBinding(); - auto *initExpr = PBD->getInit(/*index=*/0); + Expr *initExpr = nullptr; + + if (var->hasAttachedPropertyWrapper()) { + auto initInfo = var->getPropertyWrapperInitializerInfo(); + + if (initInfo.hasInitFromWrappedValue()) { + initExpr = + initInfo.getWrappedValuePlaceholder()->getOriginalWrappedValue(); + } + } else { + initExpr = PBD->getInit(/*index=*/0); + } + if (!initExpr) return; // Type wrapper variables are never initialized directly, // initialization expression (if any) becomes an default // argument of the initializer synthesized by the type wrapper. - PBD->setInitializerSubsumed(/*index=*/0); + { + // Since type wrapper is applied to backing property, that's + // the the initializer it subsumes. + if (var->hasAttachedPropertyWrapper()) { + auto *backingVar = var->getPropertyWrapperBackingProperty(); + PBD = backingVar->getParentPatternBinding(); + } - arg->setDefaultExpr(initExpr, /*isTypeChecked=*/false); + PBD->setInitializerSubsumed(/*index=*/0); + } + + arg->setDefaultExpr(initExpr, PBD->isInitializerChecked(/*index=*/0)); arg->setDefaultArgumentKind(DefaultArgumentKind::Normal); } @@ -354,11 +375,51 @@ static ConstructorDecl *createImplicitConstructor(NominalTypeDecl *decl, if (!(var && var->isAccessedViaTypeWrapper())) continue; - auto *arg = new (ctx) ParamDecl(SourceLoc(), Loc, var->getName(), Loc, - var->getName(), decl); + Identifier argName = var->getName(); + Identifier paramName = argName; + + auto paramInterfaceType = var->getValueInterfaceType(); + DeclAttributes attrs; + + // If this is a backing storage of a property wrapped property + // let's use wrapped property as a parameter and synthesize + // appropriate property wrapper initialization upon assignment. + if (auto *wrappedVar = var->getOriginalWrappedProperty( + PropertyWrapperSynthesizedPropertyKind::Backing)) { + // If there is `init(wrappedValue:)` or default value for a wrapped + // property we should use wrapped type, otherwise let's proceed with + // wrapper type. + if (wrappedVar->isPropertyMemberwiseInitializedWithWrappedType()) { + var = wrappedVar; + // If parameter have to get wrapped type, let's re-map both argument + // and parameter name to match wrapped property and let property + // wrapper attributes generate wrapped value and projection variables. + argName = wrappedVar->getName(); + paramName = argName; + + paramInterfaceType = var->getPropertyWrapperInitValueInterfaceType(); + // The parameter needs to have all of the property wrapper + // attributes to generate projection and wrapper variables. + for (auto *attr : wrappedVar->getAttachedPropertyWrappers()) + attrs.add(attr); + } else { + // If parameter has to have wrapper type then argument type should + // match that of a wrapped property but parameter name stays the same + // since it represents the type of backing storage and could be passed + // to `$Storage` constructor directly. + argName = wrappedVar->getName(); + } + } + + if (!paramInterfaceType || paramInterfaceType->hasError()) + continue; + + auto *arg = + new (ctx) ParamDecl(SourceLoc(), Loc, argName, Loc, paramName, decl); + arg->getAttrs().add(attrs); arg->setSpecifier(ParamSpecifier::Default); - arg->setInterfaceType(var->getValueInterfaceType()); + arg->setInterfaceType(paramInterfaceType); arg->setImplicit(); maybeAddTypeWrapperDefaultArg(arg, var, ctx); diff --git a/lib/Sema/TypeCheckTypeWrapper.cpp b/lib/Sema/TypeCheckTypeWrapper.cpp index 2110cb5ce84df..cc5838f3e345c 100644 --- a/lib/Sema/TypeCheckTypeWrapper.cpp +++ b/lib/Sema/TypeCheckTypeWrapper.cpp @@ -389,8 +389,18 @@ SynthesizeTypeWrapperInitializerBody::evaluate(Evaluator &evaluator, SmallVector arguments; { for (auto *param : *ctor->getParameters()) { - arguments.push_back({/*labelLoc=*/SourceLoc(), param->getName(), - new (ctx) DeclRefExpr(param, /*Loc=*/DeclNameLoc(), + VarDecl *arg = param; + + // type wrappers wrap only backing storage of a wrapped + // property, so in this case we need to pass `_` to + // `$Storage` constructor. + if (param->hasAttachedPropertyWrapper()) { + arg = param->getPropertyWrapperBackingProperty(); + (void)param->getPropertyWrapperBackingPropertyType(); + } + + arguments.push_back({/*labelLoc=*/SourceLoc(), arg->getName(), + new (ctx) DeclRefExpr(arg, /*Loc=*/DeclNameLoc(), /*Implicit=*/true)}); } } diff --git a/test/Interpreter/Inputs/type_wrapper_defs.swift b/test/Interpreter/Inputs/type_wrapper_defs.swift index 783e76f6dfac9..99dece208c0d9 100644 --- a/test/Interpreter/Inputs/type_wrapper_defs.swift +++ b/test/Interpreter/Inputs/type_wrapper_defs.swift @@ -19,6 +19,64 @@ public struct Wrapper { } } +@propertyWrapper +public struct PropWrapper { + public var value: Value + + public init(wrappedValue: Value) { + self.value = wrappedValue + } + + public var projectedValue: Self { return self } + + public var wrappedValue: Value { + get { + self.value + } + set { + self.value = newValue + } + } +} + +@propertyWrapper +public struct PropWrapperWithoutInit { + public var value: Value + + public init(value: Value) { + self.value = value + } + + public var projectedValue: Self { return self } + + public var wrappedValue: Value { + get { + self.value + } + set { + self.value = newValue + } + } +} + +@propertyWrapper +public struct PropWrapperWithoutProjection { + public var value: Value + + public init(wrappedValue: Value) { + self.value = wrappedValue + } + + public var wrappedValue: Value { + get { + self.value + } + set { + self.value = newValue + } + } +} + @Wrapper public class Person { public var name: String @@ -30,3 +88,36 @@ public struct PersonWithDefaults { public var name: String = "" public var age: Int = 99 } + +@Wrapper +public struct PropWrapperTest { + @PropWrapper public var test: Int +} + +@Wrapper +public struct DefaultedPropWrapperTest { + @PropWrapper public var test: Int = 0 +} + +@Wrapper +public struct DefaultedPropWrapperWithArgTest { + @PropWrapper(wrappedValue: 3) public var test: Int +} + +@Wrapper +public struct PropWrapperNoInitTest { + @PropWrapperWithoutInit public var a: Int + @PropWrapperWithoutInit(value: "b") public var b: String +} + +@Wrapper +public struct PropWrapperNoProjectionTest { + @PropWrapperWithoutProjection public var a: Int = 0 + @PropWrapperWithoutProjection(wrappedValue: PropWrapper(wrappedValue: "b")) @PropWrapper public var b: String +} + +@Wrapper +public struct ComplexPropWrapperTest { + @PropWrapper public var a: [String] = ["a"] + @PropWrapperWithoutInit(value: PropWrapper(wrappedValue: [1, 2, 3])) @PropWrapper public var b: [Int] +} diff --git a/test/Interpreter/type_wrappers.swift b/test/Interpreter/type_wrappers.swift index f3ddf6e05da1c..f2b8b796bba71 100644 --- a/test/Interpreter/type_wrappers.swift +++ b/test/Interpreter/type_wrappers.swift @@ -82,3 +82,267 @@ print(pDefaultsName.name) print(pDefaultsName.age) // CHECK: in getter // CHECK: 31337 + +func testPropertyWrappers() { + var wrapped1 = PropWrapperTest(test: 42) + // CHECK: Wrapper.init($Storage(_test: type_wrapper_defs.PropWrapper(value: 42))) + do { + print(wrapped1.test) + // CHECK: in getter + // CHECK-NEXT: 42 + + wrapped1.test = 0 + // CHECK: in getter + // CHECK-NEXT: in setter => PropWrapper(value: 0) + + print(wrapped1.test) + // CHECK: in getter + // CHECK-NEXT: 0 + } + + var wrapped2 = DefaultedPropWrapperTest() + // CHECK: Wrapper.init($Storage(_test: type_wrapper_defs.PropWrapper(value: 0))) + do { + print(wrapped2.test) + // CHECK: in getter + // CHECK-NEXT: 0 + + wrapped2.test = 42 + // CHECK: in getter + // CHECK-NEXT: in setter => PropWrapper(value: 42) + + print(wrapped2.test) + // CHECK: in getter + // CHECK-NEXT: 42 + } + + var wrapped3 = DefaultedPropWrapperTest(test: 1) + // CHECK: Wrapper.init($Storage(_test: type_wrapper_defs.PropWrapper(value: 1))) + do { + print(wrapped3.test) + // CHECK: in getter + // CHECK-NEXT: 1 + + wrapped3.test = 31337 + // CHECK: in getter + // CHECK-NEXT: in setter => PropWrapper(value: 31337) + + print(wrapped3.test) + // CHECK: in getter + // CHECK-NEXT: 31337 + } + + var wrapped4 = DefaultedPropWrapperWithArgTest() + // CHECK: Wrapper.init($Storage(_test: type_wrapper_defs.PropWrapper(value: 3))) + do { + print(wrapped4.test) + // CHECK: in getter + // CHECK-NEXT: 3 + + wrapped4.test = 0 + // CHECK: in getter + // CHECK-NEXT: in setter => PropWrapper(value: 0) + + print(wrapped4.test) + // CHECK: in getter + // CHECK-NEXT: 0 + } + + var wrapped5 = PropWrapperNoInitTest(a: PropWrapperWithoutInit(value: 1)) + // CHECK: Wrapper.init($Storage(_a: type_wrapper_defs.PropWrapperWithoutInit(value: 1), _b: type_wrapper_defs.PropWrapperWithoutInit(value: "b"))) + do { + print(wrapped5.a) + // CHECK: in getter + // CHECK-NEXT: 1 + + print(wrapped5.b) + // CHECK: in getter + // CHECK-NEXT: b + + wrapped5.a = 42 + // CHECK: in getter + // CHECK-NEXT: in setter => PropWrapperWithoutInit(value: 42) + + wrapped5.b = "not b" + // CHECK: in getter + // CHECK-NEXT: in setter => PropWrapperWithoutInit(value: "not b") + + print(wrapped5.a) + // CHECK: in getter + // CHECK-NEXT: 42 + + print(wrapped5.b) + // CHECK: in getter + // CHECK-NEXT: not b + } + + var wrapped6 = PropWrapperNoInitTest(a: PropWrapperWithoutInit(value: 1), b: PropWrapperWithoutInit(value: "hello")) + // CHECK: Wrapper.init($Storage(_a: type_wrapper_defs.PropWrapperWithoutInit(value: 1), _b: type_wrapper_defs.PropWrapperWithoutInit(value: "hello"))) + do { + print(wrapped6.a) + // CHECK: in getter + // CHECK-NEXT: 1 + + print(wrapped6.b) + // CHECK: in getter + // CHECK-NEXT: hello + + wrapped6.a = 42 + // CHECK: in getter + // CHECK-NEXT: in setter => PropWrapperWithoutInit(value: 42) + + wrapped6.b = "b" + // CHECK: in getter + // CHECK-NEXT: in setter => PropWrapperWithoutInit(value: "b") + + print(wrapped6.a) + // CHECK: in getter + // CHECK-NEXT: 42 + + print(wrapped6.b) + // CHECK: in getter + // CHECK-NEXT: b + } + + var wrapped7 = ComplexPropWrapperTest() + // CHECK: Wrapper.init($Storage(_a: type_wrapper_defs.PropWrapper>(value: ["a"]), _b: type_wrapper_defs.PropWrapperWithoutInit>>(value: type_wrapper_defs.PropWrapper>(value: [1, 2, 3])))) + do { + print(wrapped7.a) + // CHECK: in getter + // CHECK-NEXT: ["a"] + + print(wrapped7.b) + // CHECK: in getter + // CHECK-NEXT: [1, 2, 3] + + wrapped7.a = ["a", "b", "c"] + // CHECK: in getter + // CHECK-NEXT: in setter => PropWrapper>(value: ["a", "b", "c"]) + + print(wrapped7.a) + // CHECK: in getter + // CHECK-NEXT: ["a", "b", "c"] + + wrapped7.b = [0] + // CHECK: in getter + // CHECK-NEXT: in setter => PropWrapperWithoutInit>>(value: type_wrapper_defs.PropWrapper>(value: [0])) + + print(wrapped7.b) + // CHECK: in getter + // CHECK-NEXT: [0] + } + + var wrapped8 = ComplexPropWrapperTest(a: ["a", "b"]) + // CHECK: Wrapper.init($Storage(_a: type_wrapper_defs.PropWrapper>(value: ["a", "b"]), _b: type_wrapper_defs.PropWrapperWithoutInit>>(value: type_wrapper_defs.PropWrapper>(value: [1, 2, 3])))) + do { + print(wrapped8.a) + // CHECK: in getter + // CHECK-NEXT: ["a", "b"] + + print(wrapped8.b) + // CHECK: in getter + // CHECK-NEXT: [1, 2, 3] + + wrapped8.a = ["a", "b", "c"] + // CHECK: in getter + // CHECK-NEXT: in setter => PropWrapper>(value: ["a", "b", "c"]) + + print(wrapped8.a) + // CHECK: in getter + // CHECK-NEXT: ["a", "b", "c"] + + wrapped8.b = [0] + // CHECK: in getter + // CHECK-NEXT: in setter => PropWrapperWithoutInit>>(value: type_wrapper_defs.PropWrapper>(value: [0])) + + print(wrapped8.b) + // CHECK: in getter + // CHECK-NEXT: [0] + } + + var wrapped9 = ComplexPropWrapperTest(b: PropWrapperWithoutInit(value: PropWrapper(wrappedValue: [0]))) + // CHECK: Wrapper.init($Storage(_a: type_wrapper_defs.PropWrapper>(value: ["a"]), _b: type_wrapper_defs.PropWrapperWithoutInit>>(value: type_wrapper_defs.PropWrapper>(value: [0])))) + do { + print(wrapped9.a) + // CHECK: in getter + // CHECK-NEXT: ["a"] + + print(wrapped9.b) + // CHECK: in getter + // CHECK-NEXT: [0] + + wrapped9.a = ["a", "b", "c"] + // CHECK: in getter + // CHECK-NEXT: in setter => PropWrapper>(value: ["a", "b", "c"]) + + print(wrapped9.a) + // CHECK: in getter + // CHECK-NEXT: ["a", "b", "c"] + + wrapped9.b = [1, 2, 3] + // CHECK: in getter + // CHECK-NEXT: in setter => PropWrapperWithoutInit>>(value: type_wrapper_defs.PropWrapper>(value: [1, 2, 3])) + + print(wrapped9.b) + // CHECK: in getter + // CHECK-NEXT: [1, 2, 3] + } + + var wrapped10 = ComplexPropWrapperTest(a: [], b: PropWrapperWithoutInit(value: PropWrapper(wrappedValue: [0]))) + // CHECK: Wrapper.init($Storage(_a: type_wrapper_defs.PropWrapper>(value: []), _b: type_wrapper_defs.PropWrapperWithoutInit>>(value: type_wrapper_defs.PropWrapper>(value: [0])))) + do { + print(wrapped10.a) + // CHECK: in getter + // CHECK-NEXT: [] + + print(wrapped10.b) + // CHECK: in getter + // CHECK-NEXT: [0] + + wrapped10.a = ["a", "b", "c"] + // CHECK: in getter + // CHECK-NEXT: in setter => PropWrapper>(value: ["a", "b", "c"]) + + print(wrapped10.a) + // CHECK: in getter + // CHECK-NEXT: ["a", "b", "c"] + + wrapped10.b = [1, 2, 3] + // CHECK: in getter + // CHECK-NEXT: in setter => PropWrapperWithoutInit>>(value: type_wrapper_defs.PropWrapper>(value: [1, 2, 3])) + + print(wrapped10.b) + // CHECK: in getter + // CHECK-NEXT: [1, 2, 3] + } + + var wrapped11 = PropWrapperNoProjectionTest() + // CHECK: Wrapper.init($Storage(_a: type_wrapper_defs.PropWrapperWithoutProjection(value: 0), _b: type_wrapper_defs.PropWrapperWithoutProjection>(value: type_wrapper_defs.PropWrapper(value: "b")))) + do { + print(wrapped11.a) + // CHECK: in getter + // CHECK-NEXT: 0 + + print(wrapped11.b) + // CHECK: in getter + // CHECK-NEXT: b + + wrapped11.a = 42 + // CHECK: in getter + // CHECK-NEXT: in setter => PropWrapperWithoutProjection(value: 42) + + wrapped11.b = "not b" + // CHECK: in getter + // CHECK-NEXT: in setter => PropWrapperWithoutProjection>(value: type_wrapper_defs.PropWrapper(value: "not b")) + + print(wrapped11.a) + // CHECK: in getter + // CHECK-NEXT: 42 + + print(wrapped11.b) + // CHECK: in getter + // CHECK-NEXT: not b + } +} + +testPropertyWrappers() diff --git a/test/type/type_wrapper.swift b/test/type/type_wrapper.swift index cd3dc82120959..d906aeb54363b 100644 --- a/test/type/type_wrapper.swift +++ b/test/type/type_wrapper.swift @@ -162,3 +162,173 @@ func testTypeWrapperWithDefaults() { _ = A(question: "") _ = A(answer: 0) } + +/// Properties with property wrappers + +@propertyWrapper +struct Wrapper { + public var value: Value + + init(wrappedValue: Value) { + self.value = wrappedValue + } + + var projectedValue: Self { return self } + + var wrappedValue: Value { + get { + self.value + } + set { + self.value = newValue + } + } +} + +@propertyWrapper +struct WrapperWithoutInit { + public var value: Value + + var projectedValue: Self { return self } + + var wrappedValue: Value { + get { + self.value + } + set { + self.value = newValue + } + } +} + +@propertyWrapper +struct WrapperWithoutProjection { + public var value: Value + + init(wrappedValue: Value) { + self.value = wrappedValue + } + + var wrappedValue: Value { + get { + self.value + } + set { + self.value = newValue + } + } +} + +func propertyWrapperTests() { + @NoopWrapper + struct WrapperTest { + @Wrapper var test: Int + } + + _ = WrapperTest(test: 42) // Ok + + @NoopWrapper + struct DefaultedWrapperTest { + @Wrapper var test: Int = 42 + } + + _ = DefaultedWrapperTest() // Ok + _ = DefaultedWrapperTest(test: 42) // Ok + + @NoopWrapper + struct NoInitTest { + @WrapperWithoutInit var test: Int + } + + _ = NoInitTest(test: WrapperWithoutInit(value: 42)) // Ok + + @NoopWrapper + struct DefaultedNoInitTest { + @WrapperWithoutInit(value: 42) var test: Int + } + + _ = DefaultedNoInitTest() // Ok + _ = DefaultedNoInitTest(test: WrapperWithoutInit(value: 0)) // Ok + + @NoopWrapper + struct NoProjection { + @WrapperWithoutProjection var test: Int // expected-note {{'test' declared here}} + } + + let noProj = NoProjection(test: 42) // Ok + _ = noProj.test // Ok + _ = noProj.$test // expected-error {{value of type 'NoProjection' has no member '$test'}} + + @NoopWrapper + struct NoInitComposition1 { + @Wrapper @WrapperWithoutInit var test: Int + } + + _ = NoInitComposition1(test: Wrapper(wrappedValue: WrapperWithoutInit(value: 42))) // Ok + + @NoopWrapper + struct NoInitComposition2 { + @WrapperWithoutInit @Wrapper var test: Int + } + + _ = NoInitComposition2(test: WrapperWithoutInit(value: Wrapper(wrappedValue: 42))) // Ok + + @NoopWrapper + struct NoInitCompositionWithDefault { + @Wrapper(wrappedValue: WrapperWithoutInit(value: 42)) @WrapperWithoutInit var test: Int + } + + _ = NoInitCompositionWithDefault() // Ok + _ = NoInitCompositionWithDefault(test: Wrapper(wrappedValue: WrapperWithoutInit(value: 0))) // Ok + + @NoopWrapper + struct Complex1 { + @Wrapper var a: Int = 42 + var b: String = "" + } + + _ = Complex1() + _ = Complex1(b: "hello") // Ok + _ = Complex1(a: 0) // Ok + _ = Complex1(a: 0, b: "") // Ok + + @NoopWrapper + struct Complex2 { + @Wrapper var a: Int = 42 + @WrapperWithoutInit @Wrapper var b: String + } + + _ = Complex2(b: WrapperWithoutInit(value: Wrapper(wrappedValue: "hello"))) // Ok + _ = Complex2(a: 0, b: WrapperWithoutInit(value: Wrapper(wrappedValue: "hello"))) // Ok + _ = Complex2(b: WrapperWithoutInit(value: Wrapper(wrappedValue: "wrong")), a: 0) // expected-error {{argument 'a' must precede argument 'b'}} + + @NoopWrapper + struct Complex3 { + @Wrapper var a: Int = 42 + var b: String = "" + @WrapperWithoutProjection var c: [Int] = [] + } + + _ = Complex3() + _ = Complex3(b: "hello") // Ok + _ = Complex3(c: [1, 2, 3]) // Ok + _ = Complex3(a: 0) // Ok + _ = Complex3(a: 0, b: "") // Ok + _ = Complex3(a: 0, b: "", c: [1, 2, 3]) // Ok + + @NoopWrapper + struct Invalid { + @Wrapper(wrappedValue: "") var a: Int + // expected-error@-1 {{cannot convert value of type 'String' to expected argument type 'Int'}} + + @Wrapper var b: Int = "" + // expected-error@-1 {{cannot convert value of type 'String' to specified type 'Int'}} + + @Wrapper(wrappedValue: 0) var c: Int = 1 + // expected-error@-1 {{extra argument 'wrappedValue' in call}} + + @Wrapper(other: "") var d: Float + // expected-error@-1 {{incorrect argument label in call (have 'other:', expected 'wrappedValue:')}} + // expected-error@-2 {{cannot convert value of type 'String' to expected argument type 'Float'}} + } +} From 8c0f6e15cd352c1a74165ca6bbc75e8f2d72b1e7 Mon Sep 17 00:00:00 2001 From: Pavel Yaskevich Date: Thu, 18 Aug 2022 12:51:56 -0700 Subject: [PATCH 23/27] [TypeWrappers] NFC: Fix type wrapper executable test on Linux --- test/Interpreter/type_wrappers.swift | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/test/Interpreter/type_wrappers.swift b/test/Interpreter/type_wrappers.swift index f2b8b796bba71..d6bcc9f4665f9 100644 --- a/test/Interpreter/type_wrappers.swift +++ b/test/Interpreter/type_wrappers.swift @@ -1,7 +1,10 @@ // RUN: %empty-directory(%t) // RUN: %target-build-swift -enable-experimental-feature TypeWrappers -parse-as-library -emit-library -emit-module-path %t/type_wrapper_defs.swiftmodule -module-name type_wrapper_defs %S/Inputs/type_wrapper_defs.swift -o %t/%target-library-name(type_wrapper_defs) -// RUN: %target-build-swift -ltype_wrapper_defs -module-name main -I %t -L %t %s -o %t/a.out -// RUN: %target-run %t/a.out | %FileCheck %s +// RUN: %target-build-swift -ltype_wrapper_defs -module-name main -I %t -L %t %s -o %t/main %target-rpath(%t) +// RUN: %target-codesign %t/main +// RUN: %target-run %t/main %t/%target-library-name(type_wrapper_defs) | %FileCheck %s + +// REQUIRES: executable_test import type_wrapper_defs From 5a73b48599081ac13f47e02809a578090d18843e Mon Sep 17 00:00:00 2001 From: Pavel Yaskevich Date: Wed, 24 Aug 2022 09:22:50 -0700 Subject: [PATCH 24/27] [Sema] CodeSynthesis: Extract logic to synthesize a parameter for memberwise `init` --- lib/Sema/CodeSynthesis.cpp | 135 +++++++++++++++++++------------------ 1 file changed, 70 insertions(+), 65 deletions(-) diff --git a/lib/Sema/CodeSynthesis.cpp b/lib/Sema/CodeSynthesis.cpp index b48432a0dc568..ce854186e131a 100644 --- a/lib/Sema/CodeSynthesis.cpp +++ b/lib/Sema/CodeSynthesis.cpp @@ -128,7 +128,7 @@ ArgumentList *swift::buildForwardingArgumentList(ArrayRef params, } static void maybeAddMemberwiseDefaultArg(ParamDecl *arg, VarDecl *var, - unsigned paramSize, ASTContext &ctx) { + ASTContext &ctx) { // First and foremost, if this is a constant don't bother. if (var->isLet()) return; @@ -250,6 +250,74 @@ enum class ImplicitConstructorKind { TypeWrapper, }; +static ParamDecl *createMemberwiseInitParameter(DeclContext *DC, + SourceLoc paramLoc, + VarDecl *var) { + auto &ctx = var->getASTContext(); + auto varInterfaceType = var->getValueInterfaceType(); + bool isAutoClosure = false; + + if (var->getAttrs().hasAttribute()) { + // If var is a lazy property, its value is provided for the underlying + // storage. We thus take an optional of the property's type. We only + // need to do this because the implicit initializer is added before all + // the properties are type checked. Perhaps init() synth should be + // moved later. + varInterfaceType = OptionalType::get(varInterfaceType); + } else if (Type backingPropertyType = + var->getPropertyWrapperBackingPropertyType()) { + // For a property that has a wrapper, writing the initializer + // with an '=' implies that the memberwise initializer should also + // accept a value of the original property type. Otherwise, the + // memberwise initializer will be in terms of the backing storage + // type. + if (var->isPropertyMemberwiseInitializedWithWrappedType()) { + varInterfaceType = var->getPropertyWrapperInitValueInterfaceType(); + + auto initInfo = var->getPropertyWrapperInitializerInfo(); + isAutoClosure = initInfo.getWrappedValuePlaceholder()->isAutoClosure(); + } else { + varInterfaceType = backingPropertyType; + } + } + + Type resultBuilderType = var->getResultBuilderType(); + if (resultBuilderType) { + // If the variable's type is structurally a function type, use that + // type. Otherwise, form a non-escaping function type for the function + // parameter. + bool isStructuralFunctionType = + varInterfaceType->lookThroughAllOptionalTypes()->is(); + if (!isStructuralFunctionType) { + auto extInfo = ASTExtInfoBuilder().withNoEscape().build(); + varInterfaceType = FunctionType::get({}, varInterfaceType, extInfo); + } + } + + // Create the parameter. + auto *arg = new (ctx) ParamDecl(SourceLoc(), paramLoc, var->getName(), + paramLoc, var->getName(), DC); + arg->setSpecifier(ParamSpecifier::Default); + arg->setInterfaceType(varInterfaceType); + arg->setImplicit(); + arg->setAutoClosure(isAutoClosure); + + // Don't allow the parameter to accept temporary pointer conversions. + arg->setNonEphemeralIfPossible(); + + // Attach a result builder attribute if needed. + if (resultBuilderType) { + auto typeExpr = TypeExpr::createImplicit(resultBuilderType, ctx); + auto attr = + CustomAttr::create(ctx, SourceLoc(), typeExpr, /*implicit=*/true); + arg->getAttrs().add(attr); + } + + maybeAddMemberwiseDefaultArg(arg, var, ctx); + + return arg; +} + /// Create an implicit struct or class constructor. /// /// \param decl The struct or class for which a constructor will be created. @@ -281,70 +349,7 @@ static ConstructorDecl *createImplicitConstructor(NominalTypeDecl *decl, accessLevel = std::min(accessLevel, var->getFormalAccess()); - auto varInterfaceType = var->getValueInterfaceType(); - bool isAutoClosure = false; - - if (var->getAttrs().hasAttribute()) { - // If var is a lazy property, its value is provided for the underlying - // storage. We thus take an optional of the property's type. We only - // need to do this because the implicit initializer is added before all - // the properties are type checked. Perhaps init() synth should be - // moved later. - varInterfaceType = OptionalType::get(varInterfaceType); - } else if (Type backingPropertyType = - var->getPropertyWrapperBackingPropertyType()) { - // For a property that has a wrapper, writing the initializer - // with an '=' implies that the memberwise initializer should also - // accept a value of the original property type. Otherwise, the - // memberwise initializer will be in terms of the backing storage - // type. - if (var->isPropertyMemberwiseInitializedWithWrappedType()) { - varInterfaceType = var->getPropertyWrapperInitValueInterfaceType(); - - auto initInfo = var->getPropertyWrapperInitializerInfo(); - isAutoClosure = initInfo.getWrappedValuePlaceholder()->isAutoClosure(); - } else { - varInterfaceType = backingPropertyType; - } - } - - Type resultBuilderType= var->getResultBuilderType(); - if (resultBuilderType) { - // If the variable's type is structurally a function type, use that - // type. Otherwise, form a non-escaping function type for the function - // parameter. - bool isStructuralFunctionType = - varInterfaceType->lookThroughAllOptionalTypes() - ->is(); - if (!isStructuralFunctionType) { - auto extInfo = ASTExtInfoBuilder().withNoEscape().build(); - varInterfaceType = FunctionType::get({ }, varInterfaceType, extInfo); - } - } - - // Create the parameter. - auto *arg = new (ctx) - ParamDecl(SourceLoc(), Loc, - var->getName(), Loc, var->getName(), decl); - arg->setSpecifier(ParamSpecifier::Default); - arg->setInterfaceType(varInterfaceType); - arg->setImplicit(); - arg->setAutoClosure(isAutoClosure); - - // Don't allow the parameter to accept temporary pointer conversions. - arg->setNonEphemeralIfPossible(); - - // Attach a result builder attribute if needed. - if (resultBuilderType) { - auto typeExpr = TypeExpr::createImplicit(resultBuilderType, ctx); - auto attr = CustomAttr::create( - ctx, SourceLoc(), typeExpr, /*implicit=*/true); - arg->getAttrs().add(attr); - } - - maybeAddMemberwiseDefaultArg(arg, var, params.size(), ctx); - - params.push_back(arg); + params.push_back(createMemberwiseInitParameter(decl, Loc, var)); } } else if (ICK == ImplicitConstructorKind::DefaultDistributedActor) { auto classDecl = dyn_cast(decl); From 347e85ddc421a6a0908b5585b9a7702b3d6cf931 Mon Sep 17 00:00:00 2001 From: Pavel Yaskevich Date: Wed, 24 Aug 2022 14:18:32 -0700 Subject: [PATCH 25/27] [Sema] TypeWrappers: Add unamanged stored properties to the synthesized memberwise `init` If a stored property would be in a default memberwise initializer it would be added to the `init` synthesized for a type wrapped type as well i.e. a `let` property without a default. --- lib/Sema/CodeSynthesis.cpp | 20 ++++++- lib/Sema/TypeCheckTypeWrapper.cpp | 53 +++++++++++++++---- .../Inputs/type_wrapper_defs.swift | 15 ++++++ test/Interpreter/type_wrappers.swift | 26 +++++++++ test/type/type_wrapper.swift | 46 ++++++++++++++++ 5 files changed, 148 insertions(+), 12 deletions(-) diff --git a/lib/Sema/CodeSynthesis.cpp b/lib/Sema/CodeSynthesis.cpp index ce854186e131a..c44d4bd2f6300 100644 --- a/lib/Sema/CodeSynthesis.cpp +++ b/lib/Sema/CodeSynthesis.cpp @@ -377,9 +377,27 @@ static ConstructorDecl *createImplicitConstructor(NominalTypeDecl *decl, for (auto *member : decl->getMembers()) { auto *var = dyn_cast(member); - if (!(var && var->isAccessedViaTypeWrapper())) + if (!var) continue; + if (!var->isAccessedViaTypeWrapper()) { + // $_storage itself. + if (var->getName() == ctx.Id_TypeWrapperProperty) + continue; + + // Computed properties are not included. + if (!var->hasStorage()) + continue; + + // If this is a memberwise initializeable property include + // it into the type wrapper initializer otherwise the instance + // of type wrapped type wouldn't be completely initialized. + if (var->isMemberwiseInitialized(/*preferDeclaredProperties=*/true)) + params.push_back(createMemberwiseInitParameter(decl, Loc, var)); + + continue; + } + Identifier argName = var->getName(); Identifier paramName = argName; diff --git a/lib/Sema/TypeCheckTypeWrapper.cpp b/lib/Sema/TypeCheckTypeWrapper.cpp index cc5838f3e345c..1d1a06501e835 100644 --- a/lib/Sema/TypeCheckTypeWrapper.cpp +++ b/lib/Sema/TypeCheckTypeWrapper.cpp @@ -319,7 +319,8 @@ bool IsPropertyAccessedViaTypeWrapper::evaluate(Evaluator &evaluator, return false; // `lazy` properties are not wrapped. - if (property->getAttrs().hasAttribute()) + if (property->getAttrs().hasAttribute() || + property->isLazyStorageProperty()) return false; // Properties with attached property wrappers are not considered @@ -386,11 +387,42 @@ SynthesizeTypeWrapperInitializerBody::evaluate(Evaluator &evaluator, /*Loc=*/DeclNameLoc(), /*Implicit=*/true), typeWrapperVar->getName()); - SmallVector arguments; + // Check whether given parameter requires a direct assignment to + // intialize the property. + // + // If `$Storage` doesn't have a member that corresponds + // to the current parameter it means that this is a property + // that not managed by the type wrapper which has to be + // initialized by direct assignment: `self. = ` + auto useDirectAssignment = [&](ParamDecl *param) { + // Properties with property wrappers are always managed by the type wrapper + if (param->hasAttachedPropertyWrapper()) + return false; + return storageType->lookupDirect(param->getName()).empty(); + }; + + SmallVector body; + + SmallVector initArgs; { for (auto *param : *ctor->getParameters()) { VarDecl *arg = param; + if (useDirectAssignment(param)) { + auto *propRef = UnresolvedDotExpr::createImplicit( + ctx, + new (ctx) DeclRefExpr({ctor->getImplicitSelfDecl()}, + /*Loc=*/DeclNameLoc(), /*Implicit=*/true), + arg->getName()); + + body.push_back(new (ctx) AssignExpr( + propRef, /*EqualLoc=*/SourceLoc(), + new (ctx) DeclRefExpr({arg}, /*DeclNameLoc=*/DeclNameLoc(), + /*Implicit=*/true), + /*Implicit=*/true)); + continue; + } + // type wrappers wrap only backing storage of a wrapped // property, so in this case we need to pass `_` to // `$Storage` constructor. @@ -399,9 +431,9 @@ SynthesizeTypeWrapperInitializerBody::evaluate(Evaluator &evaluator, (void)param->getPropertyWrapperBackingPropertyType(); } - arguments.push_back({/*labelLoc=*/SourceLoc(), arg->getName(), - new (ctx) DeclRefExpr(arg, /*Loc=*/DeclNameLoc(), - /*Implicit=*/true)}); + initArgs.push_back({/*labelLoc=*/SourceLoc(), arg->getName(), + new (ctx) DeclRefExpr(arg, /*Loc=*/DeclNameLoc(), + /*Implicit=*/true)}); } } @@ -410,7 +442,7 @@ SynthesizeTypeWrapperInitializerBody::evaluate(Evaluator &evaluator, TypeExpr::createImplicitForDecl( /*Loc=*/DeclNameLoc(), storageType, ctor, ctor->mapTypeIntoContext(storageType->getInterfaceType())), - ArgumentList::createImplicit(ctx, arguments)); + ArgumentList::createImplicit(ctx, initArgs)); auto *initRef = new (ctx) UnresolvedMemberExpr( /*dotLoc=*/SourceLoc(), /*declNameLoc=*/DeclNameLoc(), @@ -422,11 +454,10 @@ SynthesizeTypeWrapperInitializerBody::evaluate(Evaluator &evaluator, ctx, initRef, ArgumentList::forImplicitSingle(ctx, ctx.Id_memberwise, storageInit)); - auto *assignment = new (ctx) - AssignExpr(storageVarRef, /*EqualLoc=*/SourceLoc(), typeWrapperInit, - /*Implicit=*/true); + body.push_back(new (ctx) AssignExpr(storageVarRef, /*EqualLoc=*/SourceLoc(), + typeWrapperInit, + /*Implicit=*/true)); - return BraceStmt::create(ctx, /*lbloc=*/ctor->getLoc(), - /*body=*/{assignment}, + return BraceStmt::create(ctx, /*lbloc=*/ctor->getLoc(), body, /*rbloc=*/ctor->getLoc(), /*implicit=*/true); } diff --git a/test/Interpreter/Inputs/type_wrapper_defs.swift b/test/Interpreter/Inputs/type_wrapper_defs.swift index 99dece208c0d9..a9ab5031582c7 100644 --- a/test/Interpreter/Inputs/type_wrapper_defs.swift +++ b/test/Interpreter/Inputs/type_wrapper_defs.swift @@ -121,3 +121,18 @@ public struct ComplexPropWrapperTest { @PropWrapper public var a: [String] = ["a"] @PropWrapperWithoutInit(value: PropWrapper(wrappedValue: [1, 2, 3])) @PropWrapper public var b: [Int] } + +@Wrapper +public struct PersonWithUnmanagedTest { + public let name: String + + public lazy var age: Int = { + 30 + }() + + public var placeOfBirth: String { + get { "Earth" } + } + + @PropWrapper public var favoredColor: String = "red" +} diff --git a/test/Interpreter/type_wrappers.swift b/test/Interpreter/type_wrappers.swift index d6bcc9f4665f9..304678f506776 100644 --- a/test/Interpreter/type_wrappers.swift +++ b/test/Interpreter/type_wrappers.swift @@ -349,3 +349,29 @@ func testPropertyWrappers() { } testPropertyWrappers() + +do { + var person = PersonWithUnmanagedTest(name: "Arthur Dent") + // CHECK: Wrapper.init($Storage(_favoredColor: type_wrapper_defs.PropWrapper(value: "red"))) + + print(person.name) + // CHECK: Arthur Dent + + print(person.age) + // CHECK: 30 + + print(person.placeOfBirth) + // CHECK: Earth + + print(person.favoredColor) + // CHECK: in getter + // CHECK-NEXT: red + + person.favoredColor = "yellow" + // CHECK: in getter + // CHECK-NEXT: in setter => PropWrapper(value: "yellow") + + print(person.favoredColor) + // CHECK: in getter + // CHECK-NEXT: yellow +} diff --git a/test/type/type_wrapper.swift b/test/type/type_wrapper.swift index d906aeb54363b..887362c1da55d 100644 --- a/test/type/type_wrapper.swift +++ b/test/type/type_wrapper.swift @@ -332,3 +332,49 @@ func propertyWrapperTests() { // expected-error@-2 {{cannot convert value of type 'String' to expected argument type 'Float'}} } } + +func testDeclarationsWithUnmanagedProperties() { + @NoopWrapper + struct WithLet { // expected-note {{'init(name:age:)' declared here}} + let name: String + var age: Int + } + _ = WithLet(age: 0) // expected-error {{missing argument for parameter 'name' in call}} + _ = WithLet(name: "", age: 0) // Ok + + @NoopWrapper + struct WithDefaultedLet { + let name: String = "Arthur Dent" + var age: Int + } + + _ = WithDefaultedLet(age: 32) // Ok + _ = WithDefaultedLet(name: "", age: 0) // expected-error {{extra argument 'name' in call}} + + @NoopWrapper + struct WithLazy { + lazy var name: String = { + "Arthur Dent" + }() + + var age: Int = 30 + } + + _ = WithLazy() // Ok + _ = WithLazy(name: "") // expected-error {{extra argument 'name' in call}} + _ = WithLazy(name: "", age: 0) // expected-error {{extra argument 'name' in call}} + _ = WithLazy(age: 0) // Ok + + @NoopWrapper + struct OnlyLazyLetAndComputed { + let name: String + lazy var age: Int = { + 30 + }() + var planet: String { + get { "Earth" } + } + } + + _ = OnlyLazyLetAndComputed(name: "Arthur Dent") // Ok +} From 3aa2bb958ac57066c54913ca4f82d1355d52e6cc Mon Sep 17 00:00:00 2001 From: Pavel Yaskevich Date: Fri, 26 Aug 2022 00:06:14 -0700 Subject: [PATCH 26/27] [Sema] TypeWrappers: Mark `$Storage` struct as `internal` --- lib/Sema/TypeCheckTypeWrapper.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/Sema/TypeCheckTypeWrapper.cpp b/lib/Sema/TypeCheckTypeWrapper.cpp index 1d1a06501e835..32f546e5fd1e4 100644 --- a/lib/Sema/TypeCheckTypeWrapper.cpp +++ b/lib/Sema/TypeCheckTypeWrapper.cpp @@ -164,7 +164,7 @@ GetTypeWrapperStorage::evaluate(Evaluator &evaluator, storage->setImplicit(); storage->setSynthesized(); - storage->copyFormalAccessFrom(parent, /*sourceIsParentContext=*/true); + storage->setAccess(AccessLevel::Internal); parent->addMember(storage); From 491defb682e3b125e47223a15299383fbe59f86d Mon Sep 17 00:00:00 2001 From: Pavel Yaskevich Date: Fri, 26 Aug 2022 12:25:08 -0700 Subject: [PATCH 27/27] [Sema] TypeWrappers: Mark `$_storage` as `private` and all `$Storage` properties as `internal` --- lib/Sema/TypeCheckTypeWrapper.cpp | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/lib/Sema/TypeCheckTypeWrapper.cpp b/lib/Sema/TypeCheckTypeWrapper.cpp index 32f546e5fd1e4..2c0a1d11da56e 100644 --- a/lib/Sema/TypeCheckTypeWrapper.cpp +++ b/lib/Sema/TypeCheckTypeWrapper.cpp @@ -28,6 +28,7 @@ using namespace swift; /// Create a property declaration and inject it into the given type. static VarDecl *injectProperty(NominalTypeDecl *parent, Identifier name, Type type, VarDecl::Introducer introducer, + AccessLevel accessLevel, Expr *initializer = nullptr) { auto &ctx = parent->getASTContext(); @@ -36,7 +37,7 @@ static VarDecl *injectProperty(NominalTypeDecl *parent, Identifier name, var->setImplicit(); var->setSynthesized(); - var->copyFormalAccessFrom(parent, /*sourceIsParentContext=*/true); + var->setAccess(accessLevel); var->setInterfaceType(type); Pattern *pattern = NamedPattern::createImplicit(ctx, var); @@ -193,8 +194,8 @@ GetTypeWrapperProperty::evaluate(Evaluator &evaluator, typeWrapper, /*Parent=*/typeWrapperType->getParent(), /*genericArgs=*/{storage->getInterfaceType()->getMetatypeInstanceType()}); - return injectProperty(parent, ctx.Id_TypeWrapperProperty, - propertyTy, VarDecl::Introducer::Var); + return injectProperty(parent, ctx.Id_TypeWrapperProperty, propertyTy, + VarDecl::Introducer::Var, AccessLevel::Private); } VarDecl *GetTypeWrapperStorageForProperty::evaluate(Evaluator &evaluator, @@ -215,7 +216,7 @@ VarDecl *GetTypeWrapperStorageForProperty::evaluate(Evaluator &evaluator, return injectProperty(storage, property->getName(), property->getValueInterfaceType(), - property->getIntroducer()); + property->getIntroducer(), AccessLevel::Internal); } /// Given the property create a subscript to reach its type wrapper storage: