From f9c716df87c0f4809250bc7e690522a9668612cf Mon Sep 17 00:00:00 2001 From: Doug Gregor Date: Sat, 30 Mar 2019 22:41:11 -0700 Subject: [PATCH 01/31] Add support for the @propertyDelegate attribute. Add the @propertyDelegate attribute, enforcing all of the semantic requirements that are placed on property delegate types. --- include/swift/AST/ASTTypeIDZone.def | 2 +- include/swift/AST/ASTTypeIDs.h | 2 +- include/swift/AST/Attr.def | 3 + include/swift/AST/Decl.h | 4 + include/swift/AST/DiagnosticsSema.def | 20 ++ include/swift/AST/TypeCheckRequests.h | 26 ++ include/swift/AST/TypeCheckerTypeIDZone.def | 1 + lib/AST/Decl.cpp | 9 + lib/AST/TypeCheckRequests.cpp | 31 +++ lib/Sema/CMakeLists.txt | 1 + lib/Sema/TypeCheckAttr.cpp | 13 + lib/Sema/TypeCheckDeclOverride.cpp | 1 + lib/Sema/TypeCheckPropertyDelegate.cpp | 279 ++++++++++++++++++++ lib/Sema/TypeCheckRequestFunctions.cpp | 1 + test/IDE/complete_decl_attribute.swift | 4 + 15 files changed, 395 insertions(+), 2 deletions(-) create mode 100644 lib/Sema/TypeCheckPropertyDelegate.cpp diff --git a/include/swift/AST/ASTTypeIDZone.def b/include/swift/AST/ASTTypeIDZone.def index 3a19b31a5df5c..de32152ae3291 100644 --- a/include/swift/AST/ASTTypeIDZone.def +++ b/include/swift/AST/ASTTypeIDZone.def @@ -16,4 +16,4 @@ //===----------------------------------------------------------------------===// SWIFT_TYPEID_NAMED(NominalTypeDecl *, NominalTypeDecl) SWIFT_TYPEID_NAMED(VarDecl *, VarDecl) -SWIFT_TYPEID(PropertyBehaviorTypeInfo) +SWIFT_TYPEID(PropertyDelegateTypeInfo) diff --git a/include/swift/AST/ASTTypeIDs.h b/include/swift/AST/ASTTypeIDs.h index e13342c72b3f5..6abcab370db90 100644 --- a/include/swift/AST/ASTTypeIDs.h +++ b/include/swift/AST/ASTTypeIDs.h @@ -21,7 +21,7 @@ namespace swift { class NominalTypeDecl; -struct PropertyBehaviorTypeInfo; +struct PropertyDelegateTypeInfo; class VarDecl; #define SWIFT_AST_TYPEID_ZONE 1 diff --git a/include/swift/AST/Attr.def b/include/swift/AST/Attr.def index 6198a6243ac33..e347378fb9bd1 100644 --- a/include/swift/AST/Attr.def +++ b/include/swift/AST/Attr.def @@ -399,6 +399,9 @@ SIMPLE_DECL_ATTR(_implementationOnly, ImplementationOnly, DECL_ATTR(_custom, Custom, OnAnyDecl | UserInaccessible, 85) +SIMPLE_DECL_ATTR(propertyDelegate, PropertyDelegate, + OnStruct | OnClass | OnEnum, + 86) #undef TYPE_ATTR #undef DECL_ATTR_ALIAS diff --git a/include/swift/AST/Decl.h b/include/swift/AST/Decl.h index 2b4d5ebaa0ba9..4101e3938c408 100644 --- a/include/swift/AST/Decl.h +++ b/include/swift/AST/Decl.h @@ -78,6 +78,7 @@ namespace swift { class ParameterTypeFlags; class Pattern; struct PrintOptions; + struct PropertyDelegateTypeInfo; class ProtocolDecl; class ProtocolType; struct RawComment; @@ -3358,6 +3359,9 @@ class NominalTypeDecl : public GenericTypeDecl, public IterableDeclContext { /// Is this a key path type? Optional getKeyPathTypeKind() const; + /// Retrieve information about this type as a property delegate. + PropertyDelegateTypeInfo getPropertyDelegateTypeInfo() const; + private: /// Predicate used to filter StoredPropertyRange. struct ToStoredProperty { diff --git a/include/swift/AST/DiagnosticsSema.def b/include/swift/AST/DiagnosticsSema.def index 80c343b72cbb2..e042f33e8fd9e 100644 --- a/include/swift/AST/DiagnosticsSema.def +++ b/include/swift/AST/DiagnosticsSema.def @@ -4312,6 +4312,26 @@ WARNING(hashvalue_implementation,none, "conform type %0 to 'Hashable' by implementing 'hash(into:)' instead", (Type)) +//------------------------------------------------------------------------------ +// MARK: property delegate diagnostics +//------------------------------------------------------------------------------ +ERROR(property_delegate_no_value_property, none, + "property delegate type %0 does not contain a non-static property " + "named 'value'", (Type)) +ERROR(property_delegate_ambiguous_value_property, none, + "property delegate type %0 has multiple non-static properties " + "named 'value'", (Type)) +ERROR(property_delegate_wrong_initial_value_init, none, + "'init(initialValue:)' parameter type (%0) must be the same as its " + "'value' property type (%1) or an @autoclosure thereof", (Type, Type)) +ERROR(property_delegate_ambiguous_initial_value_init, none, + "property delegate type %0 has multiple initial-value initializers", (Type)) +ERROR(property_delegate_type_requirement_not_accessible,none, + "%select{private|fileprivate|internal|public|open}0 %1 %2 cannot have " + "more restrictive access than its enclosing property delegate type %3 " + "(which is %select{private|fileprivate|internal|public|open}4)", + (AccessLevel, DescriptiveDeclKind, DeclName, Type, AccessLevel)) + #ifndef DIAG_NO_UNDEF # if defined(DIAG) # undef DIAG diff --git a/include/swift/AST/TypeCheckRequests.h b/include/swift/AST/TypeCheckRequests.h index 0f4af863cadc3..e15d5f491899d 100644 --- a/include/swift/AST/TypeCheckRequests.h +++ b/include/swift/AST/TypeCheckRequests.h @@ -16,6 +16,7 @@ #ifndef SWIFT_TYPE_CHECK_REQUESTS_H #define SWIFT_TYPE_CHECK_REQUESTS_H +#include "swift/AST/ASTTypeIDs.h" #include "swift/AST/Type.h" #include "swift/AST/Evaluator.h" #include "swift/AST/SimpleRequest.h" @@ -415,6 +416,31 @@ class DefaultTypeRequest Type &getCache() const; }; +/// Retrieve information about a property delegate type. +class PropertyDelegateTypeInfoRequest + : public SimpleRequest { +public: + using SimpleRequest::SimpleRequest; + +private: + friend SimpleRequest; + + // Evaluation. + llvm::Expected + evaluate(Evaluator &eval, NominalTypeDecl *nominal) const; + +public: + // Caching + bool isCached() const; + + // Cycle handling + void diagnoseCycle(DiagnosticEngine &diags) const; + void noteCycleStep(DiagnosticEngine &diags) const; +}; + /// The zone number for the type checker. #define SWIFT_TYPE_CHECKER_REQUESTS_TYPEID_ZONE 10 diff --git a/include/swift/AST/TypeCheckerTypeIDZone.def b/include/swift/AST/TypeCheckerTypeIDZone.def index 3067be876d875..62e6e58403db5 100644 --- a/include/swift/AST/TypeCheckerTypeIDZone.def +++ b/include/swift/AST/TypeCheckerTypeIDZone.def @@ -25,3 +25,4 @@ SWIFT_TYPEID(RequirementRequest) SWIFT_TYPEID(USRGenerationRequest) SWIFT_TYPEID(DefaultTypeRequest) SWIFT_TYPEID(MangleLocalTypeDeclRequest) +SWIFT_TYPEID(PropertyDelegateTypeInfoRequest) diff --git a/lib/AST/Decl.cpp b/lib/AST/Decl.cpp index 9430240ad8dd5..4b8e28d9f9d0e 100644 --- a/lib/AST/Decl.cpp +++ b/lib/AST/Decl.cpp @@ -35,6 +35,7 @@ #include "swift/AST/NameLookupRequests.h" #include "swift/AST/ParameterList.h" #include "swift/AST/Pattern.h" +#include "swift/AST/PropertyDelegates.h" #include "swift/AST/ProtocolConformance.h" #include "swift/AST/ResilienceExpansion.h" #include "swift/AST/Stmt.h" @@ -3300,6 +3301,14 @@ Optional NominalTypeDecl::getKeyPathTypeKind() const { return None; } +PropertyDelegateTypeInfo NominalTypeDecl::getPropertyDelegateTypeInfo() const { + ASTContext &ctx = getASTContext(); + auto mutableThis = const_cast(this); + return evaluateOrDefault(ctx.evaluator, + PropertyDelegateTypeInfoRequest{mutableThis}, + PropertyDelegateTypeInfo()); +} + GenericTypeDecl::GenericTypeDecl(DeclKind K, DeclContext *DC, Identifier name, SourceLoc nameLoc, MutableArrayRef inherited, diff --git a/lib/AST/TypeCheckRequests.cpp b/lib/AST/TypeCheckRequests.cpp index 4f77e07349d6e..5c95bcaaf29d5 100644 --- a/lib/AST/TypeCheckRequests.cpp +++ b/lib/AST/TypeCheckRequests.cpp @@ -14,6 +14,7 @@ #include "swift/AST/Decl.h" #include "swift/AST/DiagnosticsCommon.h" #include "swift/AST/Module.h" +#include "swift/AST/PropertyDelegates.h" #include "swift/AST/TypeLoc.h" #include "swift/AST/TypeRepr.h" #include "swift/AST/Types.h" @@ -546,3 +547,33 @@ bool DefaultTypeRequest::getPerformLocalLookup(const KnownProtocolKind knownProt default: return false; } } + +bool PropertyDelegateTypeInfoRequest::isCached() const { + auto nominal = std::get<0>(getStorage()); + return nominal->getAttrs().hasAttribute();; +} + +void PropertyDelegateTypeInfoRequest::diagnoseCycle( + DiagnosticEngine &diags) const { + std::get<0>(getStorage())->diagnose(diag::circular_reference); +} + +void PropertyDelegateTypeInfoRequest::noteCycleStep( + DiagnosticEngine &diags) const { + std::get<0>(getStorage())->diagnose(diag::circular_reference_through); +} + +void swift::simple_display( + llvm::raw_ostream &out, const PropertyDelegateTypeInfo &propertyDelegate) { + out << "{ "; + if (propertyDelegate.valueVar) + out << propertyDelegate.valueVar->printRef(); + else + out << "null"; + out << ", "; + if (propertyDelegate.initialValueInit) + out << propertyDelegate.initialValueInit->printRef(); + else + out << "null"; + out << " }"; +} diff --git a/lib/Sema/CMakeLists.txt b/lib/Sema/CMakeLists.txt index 15c1cea16b6cc..dec2f5aae1b29 100644 --- a/lib/Sema/CMakeLists.txt +++ b/lib/Sema/CMakeLists.txt @@ -51,6 +51,7 @@ add_swift_host_library(swiftSema STATIC TypeCheckGeneric.cpp TypeCheckNameLookup.cpp TypeCheckPattern.cpp + TypeCheckPropertyDelegate.cpp TypeCheckProtocol.cpp TypeCheckProtocolInference.cpp TypeCheckREPL.cpp diff --git a/lib/Sema/TypeCheckAttr.cpp b/lib/Sema/TypeCheckAttr.cpp index 2fd70c8e1c111..0f5878c5db2e8 100644 --- a/lib/Sema/TypeCheckAttr.cpp +++ b/lib/Sema/TypeCheckAttr.cpp @@ -25,6 +25,7 @@ #include "swift/AST/NameLookup.h" #include "swift/AST/NameLookupRequests.h" #include "swift/AST/ParameterList.h" +#include "swift/AST/PropertyDelegates.h" #include "swift/AST/TypeCheckRequests.h" #include "swift/AST/Types.h" #include "swift/Parse/Lexer.h" @@ -127,6 +128,7 @@ class AttributeEarlyChecker : public AttributeVisitor { IGNORED_ATTR(DynamicReplacement) IGNORED_ATTR(PrivateImport) IGNORED_ATTR(Custom) + IGNORED_ATTR(PropertyDelegate) #undef IGNORED_ATTR void visitAlignmentAttr(AlignmentAttr *attr) { @@ -836,6 +838,7 @@ class AttributeChecker : public AttributeVisitor { void visitNonOverrideAttr(NonOverrideAttr *attr); void visitCustomAttr(CustomAttr *attr); + void visitPropertyDelegateAttr(PropertyDelegateAttr *attr); }; } // end anonymous namespace @@ -2491,12 +2494,22 @@ void AttributeChecker::visitCustomAttr(CustomAttr *attr) { attr->setInvalid(); } + void TypeChecker::checkParameterAttributes(ParameterList *params) { for (auto param: *params) { checkDeclAttributes(param); } } +void AttributeChecker::visitPropertyDelegateAttr(PropertyDelegateAttr *attr) { + auto nominal = dyn_cast(D); + if (!nominal) + return; + + // Force checking of the property delegate type. + (void)nominal->getPropertyDelegateTypeInfo(); +} + void TypeChecker::checkDeclAttributes(Decl *D) { AttributeChecker Checker(*this, D); diff --git a/lib/Sema/TypeCheckDeclOverride.cpp b/lib/Sema/TypeCheckDeclOverride.cpp index 184fd4f57f605..a39509187cf5c 100644 --- a/lib/Sema/TypeCheckDeclOverride.cpp +++ b/lib/Sema/TypeCheckDeclOverride.cpp @@ -1325,6 +1325,7 @@ namespace { UNINTERESTING_ATTR(HasInitialValue) UNINTERESTING_ATTR(ImplementationOnly) UNINTERESTING_ATTR(Custom) + UNINTERESTING_ATTR(PropertyDelegate) #undef UNINTERESTING_ATTR void visitAvailableAttr(AvailableAttr *attr) { diff --git a/lib/Sema/TypeCheckPropertyDelegate.cpp b/lib/Sema/TypeCheckPropertyDelegate.cpp new file mode 100644 index 0000000000000..985f5115dde02 --- /dev/null +++ b/lib/Sema/TypeCheckPropertyDelegate.cpp @@ -0,0 +1,279 @@ +//===--- TypeCheckPropertyDelegate.cpp - Property Delegates ---------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2014 - 2018 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 property delegates. +// +//===----------------------------------------------------------------------===// +#include "TypeChecker.h" +#include "TypeCheckType.h" +#include "swift/AST/ASTContext.h" +#include "swift/AST/Decl.h" +#include "swift/AST/DiagnosticsSema.h" +#include "swift/AST/LazyResolver.h" +#include "swift/AST/NameLookupRequests.h" +#include "swift/AST/PropertyDelegates.h" +#include "swift/AST/TypeCheckRequests.h" +using namespace swift; + +/// Find the "value" property in a property delegate to which access will +/// be delegated. +static VarDecl *findValueProperty(ASTContext &ctx, NominalTypeDecl *nominal) { + SmallVector vars; + { + SmallVector decls; + nominal->lookupQualified(nominal, ctx.Id_value, NL_QualifiedDefault, decls); + for (const auto &foundDecl : decls) { + auto foundVar = dyn_cast(foundDecl); + if (!foundVar || foundVar->isStatic() || + foundVar->getDeclContext() != nominal) + continue; + + vars.push_back(foundVar); + } + } + + // Diagnose missing or ambiguous "value" properties. + switch (vars.size()) { + case 0: + nominal->diagnose(diag::property_delegate_no_value_property, + nominal->getDeclaredType()); + return nullptr; + + case 1: + break; + + default: + nominal->diagnose(diag::property_delegate_ambiguous_value_property, + nominal->getDeclaredType()); + for (auto var : vars) { + var->diagnose(diag::kind_declname_declared_here, + var->getDescriptiveKind(), var->getFullName()); + } + return nullptr; + } + + // The 'value' property must be as accessible as the nominal type. + VarDecl *var = vars.front(); + if (var->getFormalAccess() < nominal->getFormalAccess()) { + var->diagnose(diag::property_delegate_type_requirement_not_accessible, + var->getFormalAccess(), var->getDescriptiveKind(), + var->getFullName(), nominal->getDeclaredType(), + nominal->getFormalAccess()); + return nullptr; + } + + return var; +} + +/// Determine whether we have a suitable init(initialValue:) within a property +/// delegate type. +static ConstructorDecl *findInitialValueInit(ASTContext &ctx, + NominalTypeDecl *nominal, + VarDecl *valueVar) { + SmallVector initialValueInitializers; + DeclName initName(ctx, DeclBaseName::createConstructor(), + {ctx.Id_initialValue}); + SmallVector decls; + nominal->lookupQualified(nominal, initName, NL_QualifiedDefault, decls); + for (const auto &decl : decls) { + auto init = dyn_cast(decl); + if (!init || init->getDeclContext() != nominal) + continue; + + initialValueInitializers.push_back(init); + } + + switch (initialValueInitializers.size()) { + case 0: + return nullptr; + + case 1: + break; + + default: + // Diagnose ambiguous init(initialValue:) initializers. + nominal->diagnose(diag::property_delegate_ambiguous_initial_value_init, + nominal->getDeclaredType()); + for (auto init : initialValueInitializers) { + init->diagnose(diag::kind_declname_declared_here, + init->getDescriptiveKind(), init->getFullName()); + } + return nullptr; + } + + // 'init(initialValue:)' must be as accessible as the nominal type. + auto init = initialValueInitializers.front(); + if (init->getFormalAccess() < nominal->getFormalAccess()) { + init->diagnose(diag::property_delegate_type_requirement_not_accessible, + init->getFormalAccess(), init->getDescriptiveKind(), + init->getFullName(), nominal->getDeclaredType(), + nominal->getFormalAccess()); + return nullptr; + } + + // Retrieve the type of the 'value' property. + if (!valueVar->hasInterfaceType()) + ctx.getLazyResolver()->resolveDeclSignature(valueVar); + Type valueVarType = valueVar->getValueInterfaceType(); + + // Retrieve the parameter type of the initializer. + if (!init->hasInterfaceType()) + ctx.getLazyResolver()->resolveDeclSignature(init); + Type paramType; + if (auto *curriedInitType = + init->getInterfaceType()->getAs()) { + if (auto *initType = + curriedInitType->getResult()->getAs()) { + if (initType->getParams().size() == 1) { + const auto ¶m = initType->getParams()[0]; + if (!param.isInOut() && !param.isVariadic()) { + paramType = param.getPlainType(); + if (param.isAutoClosure()) { + if (auto *fnType = paramType->getAs()) + paramType = fnType->getResult(); + } + } + } + } + } + + // The parameter type must be the same as the type of `valueVar` or an + // autoclosure thereof. + if (!paramType->isEqual(valueVarType)) { + init->diagnose(diag::property_delegate_wrong_initial_value_init, paramType, + valueVarType); + valueVar->diagnose(diag::decl_declared_here, valueVar->getFullName()); + return nullptr; + } + + // The initializer must not be failable. + if (init->getFailability() != OTK_None) { + init->diagnose(diag::property_delegate_failable_initial_value_init); + return nullptr; + } + + return init; +} + +llvm::Expected +PropertyDelegateTypeInfoRequest::evaluate( + Evaluator &eval, NominalTypeDecl *nominal) const { + // We must have the @propertyDelegate attribute to continue. + if (!nominal->getAttrs().hasAttribute()) { + return PropertyDelegateTypeInfo(); + } + + // Look for a non-static property named "value" in the property delegate + // type. + ASTContext &ctx = nominal->getASTContext(); + auto valueVar = findValueProperty(ctx, nominal); + if (!valueVar) + return PropertyDelegateTypeInfo(); + + PropertyDelegateTypeInfo result; + result.valueVar = valueVar; + result.initialValueInit = findInitialValueInit(ctx, nominal, valueVar); + + return result; +} + +llvm::Expected +AttachedPropertyDelegateRequest::evaluate(Evaluator &evaluator, + VarDecl *var) const { + ASTContext &ctx = var->getASTContext(); + auto dc = var->getDeclContext(); + for (auto attr : var->getAttrs().getAttributes()) { + auto mutableAttr = const_cast(attr); + // Figure out which nominal declaration this custom attribute refers to. + auto nominal = evaluateOrDefault( + ctx.evaluator, CustomAttrNominalRequest{mutableAttr, dc}, nullptr); + + // If we didn't find a nominal type with a @propertyDelegate attribute, + // skip this custom attribute. + if (!nominal || !nominal->getAttrs().hasAttribute()) + continue; + + // Check various restrictions on which properties can have delegates + // attached to them. + + // Local properties do not yet support delegates. + if (var->getDeclContext()->isLocalContext()) { + ctx.Diags.diagnose(attr->getLocation(), diag::property_delegate_local); + return nullptr; + } + + // Check that the variable is part of a single-variable pattern. + auto binding = var->getParentPatternBinding(); + if (!binding || binding->getSingleVar() != var) { + ctx.Diags.diagnose(attr->getLocation(), + diag::property_delegate_not_single_var); + return nullptr; + } + + // Check for conflicting attributes. + if (var->getAttrs().hasAttribute() || + var->getAttrs().hasAttribute() || + var->getAttrs().hasAttribute() || + (var->getAttrs().hasAttribute() && + var->getAttrs().getAttribute()->get() != + ReferenceOwnership::Strong)) { + int whichKind; + if (var->getAttrs().hasAttribute()) + whichKind = 0; + else if (var->getAttrs().hasAttribute()) + whichKind = 1; + else if (var->getAttrs().hasAttribute()) + whichKind = 2; + else { + auto attr = var->getAttrs().getAttribute(); + whichKind = 2 + static_cast(attr->get()); + } + var->diagnose(diag::property_with_delegate_conflict_attribute, + var->getFullName(), whichKind); + return nullptr; + } + + // A property with a delegate cannot be declared in a protocol, enum, or + // an extension. + if (isa(dc) || + (isa(dc) && var->isInstanceMember()) || + (isa(dc) && var->isInstanceMember())) { + int whichKind; + if (isa(dc)) + whichKind = 0; + else if (isa(dc)) + whichKind = 1; + else + whichKind = 2; + var->diagnose(diag::property_with_delegate_in_bad_context, + var->getFullName(), whichKind) + .highlight(attr->getRange()); + + return nullptr; + } + + // Properties with delegates must not override another property. + if (auto classDecl = dyn_cast(dc)) { + if (auto overrideAttr = var->getAttrs().getAttribute()) { + var->diagnose(diag::property_with_delegate_overrides, + var->getFullName()) + .highlight(attr->getRange()); + return nullptr; + } + } + + return mutableAttr; + } + + return nullptr; +} + diff --git a/lib/Sema/TypeCheckRequestFunctions.cpp b/lib/Sema/TypeCheckRequestFunctions.cpp index a7e24839b2b79..81cbb0d63a724 100644 --- a/lib/Sema/TypeCheckRequestFunctions.cpp +++ b/lib/Sema/TypeCheckRequestFunctions.cpp @@ -11,6 +11,7 @@ //===----------------------------------------------------------------------===// #include "TypeChecker.h" #include "TypeCheckType.h" +#include "swift/AST/PropertyDelegates.h" #include "swift/AST/TypeCheckRequests.h" #include "swift/AST/Decl.h" #include "swift/AST/ExistentialLayout.h" diff --git a/test/IDE/complete_decl_attribute.swift b/test/IDE/complete_decl_attribute.swift index 0782cff3ea0a6..2b5664e401c5d 100644 --- a/test/IDE/complete_decl_attribute.swift +++ b/test/IDE/complete_decl_attribute.swift @@ -66,6 +66,7 @@ class C {} // KEYWORD3-NEXT: Keyword/None: objcMembers[#Class Attribute#]; name=objcMembers{{$}} // KEYWORD3-NEXT: Keyword/None: NSApplicationMain[#Class Attribute#]; name=NSApplicationMain{{$}} // KEYWORD3-NEXT: Keyword/None: usableFromInline[#Class Attribute#]; name=usableFromInline +// KEYWORD3-NEXT: Keyword/None: propertyDelegate[#Class Attribute#]; name=propertyDelegate // KEYWORD3-NEXT: End completions @#^KEYWORD3_2^#IB @@ -80,6 +81,7 @@ enum E {} // KEYWORD4-NEXT: Keyword/None: dynamicCallable[#Enum Attribute#]; name=dynamicCallable // KEYWORD4-NEXT: Keyword/None: dynamicMemberLookup[#Enum Attribute#]; name=dynamicMemberLookup // KEYWORD4-NEXT: Keyword/None: usableFromInline[#Enum Attribute#]; name=usableFromInline +// KEYWORD4-NEXT: Keyword/None: propertyDelegate[#Enum Attribute#]; name=propertyDelegate // KEYWORD4-NEXT: End completions @@ -90,6 +92,7 @@ struct S{} // KEYWORD5-NEXT: Keyword/None: dynamicCallable[#Struct Attribute#]; name=dynamicCallable // KEYWORD5-NEXT: Keyword/None: dynamicMemberLookup[#Struct Attribute#]; name=dynamicMemberLookup // KEYWORD5-NEXT: Keyword/None: usableFromInline[#Struct Attribute#]; name=usableFromInline +// KEYWORD5-NEXT: Keyword/None: propertyDelegate[#Struct Attribute#]; name=propertyDelegate // KEYWORD5-NEXT: End completions @@ -117,4 +120,5 @@ struct S{} // KEYWORD_LAST-NEXT: Keyword/None: usableFromInline[#Declaration Attribute#]; name=usableFromInline{{$}} // KEYWORD_LAST-NEXT: Keyword/None: discardableResult[#Declaration Attribute#]; name=discardableResult // KEYWORD_LAST-NEXT: Keyword/None: GKInspectable[#Declaration Attribute#]; name=GKInspectable{{$}} +// KEYWORD_LAST-NEXT: Keyword/None: propertyDelegate[#Declaration Attribute#]; name=propertyDelegate // KEYWORD_LAST-NEXT: End completions From 96279c7044a40d68ec264a1fe6ae8a43d2c04435 Mon Sep 17 00:00:00 2001 From: Doug Gregor Date: Sun, 31 Mar 2019 20:57:27 -0700 Subject: [PATCH 02/31] Use custom attributes to apply property delegates to variables. A custom attribute can be resolved to a property delegate type. Allow this for property declarations (only), and diagnose the various places where properties cannot have property delegates. --- include/swift/AST/ASTTypeIDZone.def | 1 + include/swift/AST/ASTTypeIDs.h | 1 + include/swift/AST/Decl.h | 4 + include/swift/AST/DiagnosticsSema.def | 27 +++ include/swift/AST/TypeCheckRequests.h | 26 +++ include/swift/AST/TypeCheckerTypeIDZone.def | 1 + lib/AST/Decl.cpp | 8 + lib/AST/TypeCheckRequests.cpp | 15 ++ lib/Sema/TypeCheckAttr.cpp | 52 ++++++ lib/Sema/TypeCheckPropertyDelegate.cpp | 32 ++++ test/decl/var/property_delegates.swift | 175 ++++++++++++++++++++ 11 files changed, 342 insertions(+) create mode 100644 test/decl/var/property_delegates.swift diff --git a/include/swift/AST/ASTTypeIDZone.def b/include/swift/AST/ASTTypeIDZone.def index de32152ae3291..49864aee80876 100644 --- a/include/swift/AST/ASTTypeIDZone.def +++ b/include/swift/AST/ASTTypeIDZone.def @@ -17,3 +17,4 @@ SWIFT_TYPEID_NAMED(NominalTypeDecl *, NominalTypeDecl) SWIFT_TYPEID_NAMED(VarDecl *, VarDecl) SWIFT_TYPEID(PropertyDelegateTypeInfo) +SWIFT_TYPEID_NAMED(CustomAttr *, CustomAttr) diff --git a/include/swift/AST/ASTTypeIDs.h b/include/swift/AST/ASTTypeIDs.h index 6abcab370db90..232448dd525d9 100644 --- a/include/swift/AST/ASTTypeIDs.h +++ b/include/swift/AST/ASTTypeIDs.h @@ -20,6 +20,7 @@ #include "swift/Basic/TypeID.h" namespace swift { +class CustomAttr; class NominalTypeDecl; struct PropertyDelegateTypeInfo; class VarDecl; diff --git a/include/swift/AST/Decl.h b/include/swift/AST/Decl.h index 4101e3938c408..b0323d954816e 100644 --- a/include/swift/AST/Decl.h +++ b/include/swift/AST/Decl.h @@ -5021,6 +5021,10 @@ class VarDecl : public AbstractStorageDecl { Bits.VarDecl.IsREPLVar = IsREPLVar; } + /// Retrieve the custom attribute that attaches a property delegate to this + /// property. + CustomAttr *getAttachedPropertyDelegate() const; + /// Return the Objective-C runtime name for this property. Identifier getObjCPropertyName() const; diff --git a/include/swift/AST/DiagnosticsSema.def b/include/swift/AST/DiagnosticsSema.def index e042f33e8fd9e..d290b756e2447 100644 --- a/include/swift/AST/DiagnosticsSema.def +++ b/include/swift/AST/DiagnosticsSema.def @@ -4331,6 +4331,33 @@ ERROR(property_delegate_type_requirement_not_accessible,none, "more restrictive access than its enclosing property delegate type %3 " "(which is %select{private|fileprivate|internal|public|open}4)", (AccessLevel, DescriptiveDeclKind, DeclName, Type, AccessLevel)) +ERROR(property_delegate_reserved_name,none, + "property delegate type name must start with an uppercase letter", ()) + +ERROR(property_delegate_attribute_not_on_property, none, + "property delegate attribute %0 can only be applied to a property", + (DeclName)) + +ERROR(property_delegate_multiple,none, + "only one property delegate can be attached to a given property", ()) +NOTE(previous_property_delegate_here,none, + "previous property delegate specified here", ()) + +ERROR(property_delegate_local,none, + "property delegates are not yet supported on local properties", ()) +ERROR(property_with_delegate_conflict_attribute,none, + "property %0 with a delegate cannot also be " + "%select{lazy|@NSCopying|@NSManaged|weak|unowned|unmanaged}1", + (DeclName, int)) +ERROR(property_delegate_not_single_var, none, + "property delegate can only apply to a single variable", ()) +ERROR(property_with_delegate_in_bad_context,none, + "%select{|non-static |non-static }1property %0 declared inside " + "%select{a protocol|an extension|an enum}1 cannot have a delegate", + (DeclName, int)) +ERROR(property_with_delegate_overrides,none, + "property %0 with attached delegate cannot override another property", + (DeclName)) #ifndef DIAG_NO_UNDEF # if defined(DIAG) diff --git a/include/swift/AST/TypeCheckRequests.h b/include/swift/AST/TypeCheckRequests.h index e15d5f491899d..070a78484bc9a 100644 --- a/include/swift/AST/TypeCheckRequests.h +++ b/include/swift/AST/TypeCheckRequests.h @@ -441,6 +441,32 @@ class PropertyDelegateTypeInfoRequest void noteCycleStep(DiagnosticEngine &diags) const; }; +/// Request the nominal type declaration to which the given custom attribute +/// refers. +class AttachedPropertyDelegateRequest : + public SimpleRequest { +public: + using SimpleRequest::SimpleRequest; + +private: + friend SimpleRequest; + + // Evaluation. + llvm::Expected + evaluate(Evaluator &evaluator, VarDecl *) const; + +public: + // Caching + bool isCached() const; + + // Cycle handling + void diagnoseCycle(DiagnosticEngine &diags) const; + void noteCycleStep(DiagnosticEngine &diags) const; +}; + /// The zone number for the type checker. #define SWIFT_TYPE_CHECKER_REQUESTS_TYPEID_ZONE 10 diff --git a/include/swift/AST/TypeCheckerTypeIDZone.def b/include/swift/AST/TypeCheckerTypeIDZone.def index 62e6e58403db5..dbd58df408a67 100644 --- a/include/swift/AST/TypeCheckerTypeIDZone.def +++ b/include/swift/AST/TypeCheckerTypeIDZone.def @@ -26,3 +26,4 @@ SWIFT_TYPEID(USRGenerationRequest) SWIFT_TYPEID(DefaultTypeRequest) SWIFT_TYPEID(MangleLocalTypeDeclRequest) SWIFT_TYPEID(PropertyDelegateTypeInfoRequest) +SWIFT_TYPEID(AttachedPropertyDelegateRequest) diff --git a/lib/AST/Decl.cpp b/lib/AST/Decl.cpp index 4b8e28d9f9d0e..3b44aaff98d03 100644 --- a/lib/AST/Decl.cpp +++ b/lib/AST/Decl.cpp @@ -5200,6 +5200,14 @@ StaticSpellingKind AbstractStorageDecl::getCorrectStaticSpelling() const { return getCorrectStaticSpellingForDecl(this); } +CustomAttr *VarDecl::getAttachedPropertyDelegate() const { + auto &ctx = getASTContext(); + auto mutableThis = const_cast(this); + return evaluateOrDefault(ctx.evaluator, + AttachedPropertyDelegateRequest{mutableThis}, + nullptr); +} + Identifier VarDecl::getObjCPropertyName() const { if (auto attr = getAttrs().getAttribute()) { if (auto name = attr->getName()) diff --git a/lib/AST/TypeCheckRequests.cpp b/lib/AST/TypeCheckRequests.cpp index 5c95bcaaf29d5..766f8d81f5cf4 100644 --- a/lib/AST/TypeCheckRequests.cpp +++ b/lib/AST/TypeCheckRequests.cpp @@ -563,6 +563,21 @@ void PropertyDelegateTypeInfoRequest::noteCycleStep( std::get<0>(getStorage())->diagnose(diag::circular_reference_through); } +bool AttachedPropertyDelegateRequest::isCached() const { + auto var = std::get<0>(getStorage()); + return !var->getAttrs().isEmpty(); +} + +void AttachedPropertyDelegateRequest::diagnoseCycle( + DiagnosticEngine &diags) const { + std::get<0>(getStorage())->diagnose(diag::circular_reference); +} + +void AttachedPropertyDelegateRequest::noteCycleStep( + DiagnosticEngine &diags) const { + std::get<0>(getStorage())->diagnose(diag::circular_reference_through); +} + void swift::simple_display( llvm::raw_ostream &out, const PropertyDelegateTypeInfo &propertyDelegate) { out << "{ "; diff --git a/lib/Sema/TypeCheckAttr.cpp b/lib/Sema/TypeCheckAttr.cpp index 0f5878c5db2e8..c53e46c313aa6 100644 --- a/lib/Sema/TypeCheckAttr.cpp +++ b/lib/Sema/TypeCheckAttr.cpp @@ -30,6 +30,7 @@ #include "swift/AST/Types.h" #include "swift/Parse/Lexer.h" #include "swift/Sema/IDETypeChecking.h" +#include "clang/Basic/CharInfo.h" #include "llvm/Support/Debug.h" using namespace swift; @@ -718,6 +719,25 @@ void TypeChecker::checkDeclAttributesEarly(Decl *D) { } } +/// Determine whether the given string is an attribute name that is +/// reserved for the implementation. +static bool isReservedAttributeName(StringRef name) { + for (unsigned i : indices(name)) { + if (name[i] == '_') + continue; + + // First character is lowercase; reserved. + if (clang::isLowercase(name[i])) + return true; + + // Everything else is reserved. + return false; + } + + // All underscores is reserved. + return true; +} + namespace { class AttributeChecker : public AttributeVisitor { TypeChecker &TC; @@ -2488,6 +2508,33 @@ void AttributeChecker::visitCustomAttr(CustomAttr *attr) { return; } + // If the nominal type is a property delegate type, we can be delegating + // through a property. + if (nominal->getPropertyDelegateTypeInfo()) { + // Property delegates can only be applied to variables + if (!isa(D) || isa(D)) { + TC.diagnose(attr->getLocation(), + diag::property_delegate_attribute_not_on_property, + nominal->getFullName()); + attr->setInvalid(); + return; + } + + // If this attribute isn't the one that attached a property delegate to + // this property, complain. + auto var = cast(D); + if (auto attached = var->getAttachedPropertyDelegate()) { + if (attached != attr) { + TC.diagnose(attr->getLocation(), diag::property_delegate_multiple); + TC.diagnose(attached->getLocation(), + diag::previous_property_delegate_here); + return; + } + } + + return; + } + TC.diagnose(attr->getLocation(), diag::nominal_type_not_attribute, nominal->getDescriptiveKind(), nominal->getFullName()); nominal->diagnose(diag::decl_declared_here, nominal->getFullName()); @@ -2508,6 +2555,11 @@ void AttributeChecker::visitPropertyDelegateAttr(PropertyDelegateAttr *attr) { // Force checking of the property delegate type. (void)nominal->getPropertyDelegateTypeInfo(); + + // Make sure the name isn't reserved. + if (isReservedAttributeName(nominal->getName().str())) { + nominal->diagnose(diag::property_delegate_reserved_name); + } } void TypeChecker::checkDeclAttributes(Decl *D) { diff --git a/lib/Sema/TypeCheckPropertyDelegate.cpp b/lib/Sema/TypeCheckPropertyDelegate.cpp index 985f5115dde02..549704a5a7b77 100644 --- a/lib/Sema/TypeCheckPropertyDelegate.cpp +++ b/lib/Sema/TypeCheckPropertyDelegate.cpp @@ -277,3 +277,35 @@ AttachedPropertyDelegateRequest::evaluate(Evaluator &evaluator, return nullptr; } +llvm::Expected +AttachedPropertyDelegateTypeRequest::evaluate(Evaluator &evaluator, + VarDecl *var) const { + // Find the custom attribute for the attached property delegate. + llvm::Expected customAttrVal = + evaluator(AttachedPropertyDelegateRequest{var}); + if (!customAttrVal) + return customAttrVal.takeError(); + + // If there isn't an attached property delegate, we're done. + auto customAttr = *customAttrVal; + if (!customAttr) + return Type(); + + auto resolution = + TypeResolution::forContextual(var->getDeclContext()); + TypeResolutionOptions options(TypeResolverContext::PatternBindingDecl); + options |= TypeResolutionFlags::AllowUnboundGenerics; + + ASTContext &ctx = var->getASTContext(); + auto &tc = *static_cast(ctx.getLazyResolver()); + if (tc.validateType(customAttr->getTypeLoc(), resolution, options)) + return ErrorType::get(ctx); + + Type customAttrType = customAttr->getTypeLoc().getType(); + if (!customAttrType->getAnyNominal()) { + assert(ctx.Diags.hadAnyError()); + return ErrorType::get(ctx); + } + + return customAttrType; +} diff --git a/test/decl/var/property_delegates.swift b/test/decl/var/property_delegates.swift new file mode 100644 index 0000000000000..39e08980bfe4d --- /dev/null +++ b/test/decl/var/property_delegates.swift @@ -0,0 +1,175 @@ +// RUN: %target-typecheck-verify-swift -swift-version 5 + +// --------------------------------------------------------------------------- +// Property delegate type definitions +// --------------------------------------------------------------------------- +@propertyDelegate +struct Wrapper { + var value: T +} + +@propertyDelegate +struct WrapperWithInitialValue { + var value: T + + init(initialValue: T) { + self.value = initialValue + } +} + +@propertyDelegate +struct WrapperAcceptingAutoclosure { + private let fn: () -> T + + var value: T { + return fn() + } + + init(initialValue fn: @autoclosure @escaping () -> T) { + self.fn = fn + } +} + +@propertyDelegate +struct MissingValue { } +// expected-error@-1{{property delegate type 'MissingValue' does not contain a non-static property named 'value'}} + +@propertyDelegate +struct StaticValue { + static var value: Int = 17 +} +// expected-error@-3{{property delegate type 'StaticValue' does not contain a non-static property named 'value'}} + + +// expected-error@+1{{'@propertyDelegate' attribute cannot be applied to this declaration}} +@propertyDelegate +protocol CannotBeADelegate { + associatedtype Value + var value: Value { get set } +} + +@propertyDelegate +struct NonVisibleValueDelegate { + private var value: Value // expected-error{{private property 'value' cannot have more restrictive access than its enclosing property delegate type 'NonVisibleValueDelegate' (which is internal)}} +} + +@propertyDelegate +struct NonVisibleInitDelegate { + var value: Value + + private init(initialValue: Value) { // expected-error{{private initializer 'init(initialValue:)' cannot have more restrictive access than its enclosing property delegate type 'NonVisibleInitDelegate' (which is internal)}} + self.value = initialValue + } +} + +@propertyDelegate +struct InitialValueTypeMismatch { + var value: Value // expected-note{{'value' declared here}} + + init(initialValue: Value?) { // expected-error{{'init(initialValue:)' parameter type ('Value?') must be the same as its 'value' property type ('Value') or an @autoclosure thereof}} + self.value = initialValue! + } +} + +@propertyDelegate +struct MultipleInitialValues { // expected-error{{property delegate type 'MultipleInitialValues' has multiple initial-value initializers}} + var value: Value? = nil + + init(initialValue: Int) { // expected-note{{initializer 'init(initialValue:)' declared here}} + } + + init(initialValue: Double) { // expected-note{{initializer 'init(initialValue:)' declared here}} + } +} + +// --------------------------------------------------------------------------- +// Property delegate type definitions +// --------------------------------------------------------------------------- +@propertyDelegate +struct _lowercaseDelegate { // expected-error{{property delegate type name must start with an uppercase letter}} + var value: T +} + +@propertyDelegate +struct _UppercaseDelegate { + var value: T +} + +// --------------------------------------------------------------------------- +// Limitations on where property delegates can be used +// --------------------------------------------------------------------------- + +func testLocalContext() { + @WrapperWithInitialValue // expected-error{{property delegates are not yet supported on local properties}} + var x = 17 + x = 42 + _ = x +} + +enum SomeEnum { + case foo + + @Wrapper(value: 17) + var bar: Int // expected-error{{property 'bar' declared inside an enum cannot have a delegate}} + // expected-error@-1{{enums must not contain stored properties}} + + @Wrapper(value: 17) + static var x: Int = 17 // okay +} + +protocol SomeProtocol { + @Wrapper(value: 17) + var bar: Int // expected-error{{property 'bar' declared inside a protocol cannot have a delegate}} + // expected-error@-1{{property in protocol must have explicit { get } or { get set } specifier}} + + @Wrapper(value: 17) + static var x: Int // expected-error{{property 'x' declared inside a protocol cannot have a delegate}} + // expected-error@-1{{property in protocol must have explicit { get } or { get set } specifier}} +} + +struct HasDelegate { } + +extension HasDelegate { + @Wrapper(value: 17) + var inExt: Int // expected-error{{property 'inExt' declared inside an extension cannot have a delegate}} + // expected-error@-1{{extensions must not contain stored properties}} + + @Wrapper(value: 17) + static var x: Int = 17 // okay +} + +class ClassWithDelegates { + @Wrapper(value: 17) + var x: Int = 42 + + +} + +class Superclass { + var x: Int = 0 +} + +class SubclassWithDelegate: Superclass { + @Wrapper(value: 17) + override var x: Int { get { return 0 } set { } } // expected-error{{property 'x' with attached delegate cannot override another property}} +} + +class C { } + +struct BadCombinations { + @WrapperWithInitialValue + lazy var x: C = C() // expected-error{{property 'x' with a delegate cannot also be lazy}} + @Wrapper + weak var y: C? // expected-error{{property 'y' with a delegate cannot also be weak}} + @Wrapper + unowned var z: C // expected-error{{property 'z' with a delegate cannot also be unowned}} +} + +struct MultipleDelegates { + @Wrapper(value: 17) // expected-error{{only one property delegate can be attached to a given property}} + @WrapperWithInitialValue // expected-note{{previous property delegate specified here}} + var x: Int = 17 + + @WrapperWithInitialValue // expected-error 2{{property delegate can only apply to a single variable}} + var (y, z) = (1, 2) +} From 53b9e25502f807801ca15bc68f0e8ba185523be7 Mon Sep 17 00:00:00 2001 From: Doug Gregor Date: Sun, 31 Mar 2019 23:52:00 -0700 Subject: [PATCH 03/31] Implement initialization of properties with attached delegates. A property with an attached delegate can be initialized in one of two ways: * By directly specifying initialization arguments on the attribute, e.g., "@SomeDelegate(x: 17, y: 42) var a". * By initializing the original property itself, which goes through the delegate type's init(initialValue:) initializer, e.g., "@Lazy var x = 17". Implement support for both forms of initialization, including type inference and checking for inconsistencies (e.g., if the annotation on the property type doesn't match what the delegate type would provide). --- include/swift/AST/ASTTypeIDZone.def | 1 + include/swift/AST/ASTTypeIDs.h | 1 + include/swift/AST/Decl.h | 18 +++ include/swift/AST/DiagnosticsSema.def | 10 ++ include/swift/AST/TypeCheckRequests.h | 35 +++++ include/swift/AST/TypeCheckerTypeIDZone.def | 1 + lib/AST/Decl.cpp | 24 ++++ lib/AST/TypeCheckRequests.cpp | 29 ++++ lib/Sema/CSGen.cpp | 5 +- lib/Sema/TypeCheckConstraints.cpp | 143 +++++++++++++++++--- lib/Sema/TypeCheckDecl.cpp | 12 +- test/decl/var/property_delegates.swift | 39 +++++- 12 files changed, 290 insertions(+), 28 deletions(-) diff --git a/include/swift/AST/ASTTypeIDZone.def b/include/swift/AST/ASTTypeIDZone.def index 49864aee80876..9e2e956740704 100644 --- a/include/swift/AST/ASTTypeIDZone.def +++ b/include/swift/AST/ASTTypeIDZone.def @@ -16,5 +16,6 @@ //===----------------------------------------------------------------------===// SWIFT_TYPEID_NAMED(NominalTypeDecl *, NominalTypeDecl) SWIFT_TYPEID_NAMED(VarDecl *, VarDecl) +SWIFT_TYPEID(Type) SWIFT_TYPEID(PropertyDelegateTypeInfo) SWIFT_TYPEID_NAMED(CustomAttr *, CustomAttr) diff --git a/include/swift/AST/ASTTypeIDs.h b/include/swift/AST/ASTTypeIDs.h index 232448dd525d9..34336e2e18219 100644 --- a/include/swift/AST/ASTTypeIDs.h +++ b/include/swift/AST/ASTTypeIDs.h @@ -23,6 +23,7 @@ namespace swift { class CustomAttr; class NominalTypeDecl; struct PropertyDelegateTypeInfo; +class Type; class VarDecl; #define SWIFT_AST_TYPEID_ZONE 1 diff --git a/include/swift/AST/Decl.h b/include/swift/AST/Decl.h index b0323d954816e..8b6b39c07d0d3 100644 --- a/include/swift/AST/Decl.h +++ b/include/swift/AST/Decl.h @@ -1922,6 +1922,10 @@ class PatternBindingEntry { Pattern *getPattern() const { return PatternAndFlags.getPointer(); } void setPattern(Pattern *P) { PatternAndFlags.setPointer(P); } + + /// Whether the given pattern binding entry is initialized. + bool isInitialized() const; + Expr *getInit() const { if (PatternAndFlags.getInt().contains(Flags::Removed) || InitContextAndIsText.getInt()) @@ -2069,6 +2073,11 @@ class PatternBindingDecl final : public Decl, getMutablePatternList()[i].setInitStringRepresentation(str); } + /// Whether the given pattern entry is initialized. + bool isInitialized(unsigned i) const { + return getPatternList()[i].isInitialized(); + } + Expr *getInit(unsigned i) const { return getPatternList()[i].getInit(); } @@ -5025,6 +5034,15 @@ class VarDecl : public AbstractStorageDecl { /// property. CustomAttr *getAttachedPropertyDelegate() const; + /// Retrieve the type of the attached property delegate as a contextual + /// type. + /// + /// \returns a NULL type for properties without attached delegates, + /// an error type when the property delegate type itself is erroneous, + /// or the delegate type itself, which may involve unbound generic + /// types. + Type getAttachedPropertyDelegateType() const; + /// Return the Objective-C runtime name for this property. Identifier getObjCPropertyName() const; diff --git a/include/swift/AST/DiagnosticsSema.def b/include/swift/AST/DiagnosticsSema.def index d290b756e2447..20cfc90ef4a05 100644 --- a/include/swift/AST/DiagnosticsSema.def +++ b/include/swift/AST/DiagnosticsSema.def @@ -4359,6 +4359,16 @@ ERROR(property_with_delegate_overrides,none, "property %0 with attached delegate cannot override another property", (DeclName)) +ERROR(property_delegate_and_normal_init,none, + "property %0 with attached delegate cannot initialize both the " + "delegate type and the property", (DeclName)) +ERROR(property_delegate_init_without_initial_value, none, + "initializing property %0 with delegate %1 that lacks " + "an 'init(initialValue:)' initializer", (DeclName, Type)) +NOTE(property_delegate_direct_init,none, + "initialize the property delegate type directly with " + "'(...') on the attribute", ()) + #ifndef DIAG_NO_UNDEF # if defined(DIAG) # undef DIAG diff --git a/include/swift/AST/TypeCheckRequests.h b/include/swift/AST/TypeCheckRequests.h index 070a78484bc9a..f27191b92075d 100644 --- a/include/swift/AST/TypeCheckRequests.h +++ b/include/swift/AST/TypeCheckRequests.h @@ -21,6 +21,7 @@ #include "swift/AST/Evaluator.h" #include "swift/AST/SimpleRequest.h" #include "swift/AST/TypeResolutionStage.h" +#include "swift/Basic/AnyValue.h" #include "swift/Basic/Statistic.h" #include "llvm/ADT/Hashing.h" #include "llvm/ADT/STLExtras.h" @@ -467,6 +468,40 @@ class AttachedPropertyDelegateRequest : void noteCycleStep(DiagnosticEngine &diags) const; }; +/// Request the nominal type declaration to which the given custom attribute +/// refers. +class AttachedPropertyDelegateTypeRequest : + public SimpleRequest { +public: + using SimpleRequest::SimpleRequest; + +private: + friend SimpleRequest; + + // Evaluation. + llvm::Expected + evaluate(Evaluator &evaluator, VarDecl *var) const; + +public: + // Caching + bool isCached() const; + + // Cycle handling + void diagnoseCycle(DiagnosticEngine &diags) const; + void noteCycleStep(DiagnosticEngine &diags) const; +}; + +// Allow AnyValue to compare two Type values, even though Type doesn't +// support ==. +template<> +bool AnyValue::Holder::equals(const HolderBase &other) const; + +void simple_display(llvm::raw_ostream &out, const Type &type); + + /// The zone number for the type checker. #define SWIFT_TYPE_CHECKER_REQUESTS_TYPEID_ZONE 10 diff --git a/include/swift/AST/TypeCheckerTypeIDZone.def b/include/swift/AST/TypeCheckerTypeIDZone.def index dbd58df408a67..573bb0904dc32 100644 --- a/include/swift/AST/TypeCheckerTypeIDZone.def +++ b/include/swift/AST/TypeCheckerTypeIDZone.def @@ -27,3 +27,4 @@ SWIFT_TYPEID(DefaultTypeRequest) SWIFT_TYPEID(MangleLocalTypeDeclRequest) SWIFT_TYPEID(PropertyDelegateTypeInfoRequest) SWIFT_TYPEID(AttachedPropertyDelegateRequest) +SWIFT_TYPEID(AttachedPropertyDelegateTypeRequest) diff --git a/lib/AST/Decl.cpp b/lib/AST/Decl.cpp index 3b44aaff98d03..815c85152f81c 100644 --- a/lib/AST/Decl.cpp +++ b/lib/AST/Decl.cpp @@ -1340,6 +1340,22 @@ SourceRange PatternBindingEntry::getOrigInitRange() const { return Init ? Init->getSourceRange() : SourceRange(); } +bool PatternBindingEntry::isInitialized() const { + // Directly initialized. + if (getInit()) + return true; + + // Initialized via a property delegate. + if (auto var = getPattern()->getSingleVar()) { + if (auto customAttr = var->getAttachedPropertyDelegate()) { + if (customAttr->getArg() != nullptr) + return true; + } + } + + return false; +} + void PatternBindingEntry::setInit(Expr *E) { auto F = PatternAndFlags.getInt(); if (E) { @@ -5208,6 +5224,14 @@ CustomAttr *VarDecl::getAttachedPropertyDelegate() const { nullptr); } +Type VarDecl::getAttachedPropertyDelegateType() const { + auto &ctx = getASTContext(); + auto mutableThis = const_cast(this); + return evaluateOrDefault(ctx.evaluator, + AttachedPropertyDelegateTypeRequest{mutableThis}, + Type()); +} + Identifier VarDecl::getObjCPropertyName() const { if (auto attr = getAttrs().getAttribute()) { if (auto name = attr->getName()) diff --git a/lib/AST/TypeCheckRequests.cpp b/lib/AST/TypeCheckRequests.cpp index 766f8d81f5cf4..a375c8c3bb84d 100644 --- a/lib/AST/TypeCheckRequests.cpp +++ b/lib/AST/TypeCheckRequests.cpp @@ -578,6 +578,21 @@ void AttachedPropertyDelegateRequest::noteCycleStep( std::get<0>(getStorage())->diagnose(diag::circular_reference_through); } +bool AttachedPropertyDelegateTypeRequest::isCached() const { + auto var = std::get<0>(getStorage()); + return !var->getAttrs().isEmpty(); +} + +void AttachedPropertyDelegateTypeRequest::diagnoseCycle( + DiagnosticEngine &diags) const { + std::get<0>(getStorage())->diagnose(diag::circular_reference); +} + +void AttachedPropertyDelegateTypeRequest::noteCycleStep( + DiagnosticEngine &diags) const { + std::get<0>(getStorage())->diagnose(diag::circular_reference_through); +} + void swift::simple_display( llvm::raw_ostream &out, const PropertyDelegateTypeInfo &propertyDelegate) { out << "{ "; @@ -592,3 +607,17 @@ void swift::simple_display( out << "null"; out << " }"; } + +template<> +bool swift::AnyValue::Holder::equals(const HolderBase &other) const { + assert(typeID == other.typeID && "Caller should match type IDs"); + return value.getPointer() == + static_cast &>(other).value.getPointer(); +} + +void swift::simple_display(llvm::raw_ostream &out, const Type &type) { + if (type) + type.print(out); + else + out << "null"; +} diff --git a/lib/Sema/CSGen.cpp b/lib/Sema/CSGen.cpp index 09d817a10c1e9..6ce0ef68395a9 100644 --- a/lib/Sema/CSGen.cpp +++ b/lib/Sema/CSGen.cpp @@ -2100,9 +2100,12 @@ namespace { // will avoid exponential typecheck behavior in the case of // tuples, nested arrays, and dictionary literals. // + // FIXME: This should be handled in the solver, not here. + // // Otherwise, create a new type variable. auto ty = Type(); - if (!var->hasNonPatternBindingInit()) { + if (!var->hasNonPatternBindingInit() && + !var->getAttachedPropertyDelegate()) { if (auto boundExpr = locator.trySimplifyToExpr()) { if (!boundExpr->isSemanticallyInOutExpr()) ty = CS.getType(boundExpr)->getRValueType(); diff --git a/lib/Sema/TypeCheckConstraints.cpp b/lib/Sema/TypeCheckConstraints.cpp index 0e64ee0b787d0..b46d5d9b36ad8 100644 --- a/lib/Sema/TypeCheckConstraints.cpp +++ b/lib/Sema/TypeCheckConstraints.cpp @@ -27,8 +27,10 @@ #include "swift/AST/ExistentialLayout.h" #include "swift/AST/Initializer.h" #include "swift/AST/NameLookup.h" +#include "swift/AST/NameLookupRequests.h" #include "swift/AST/ParameterList.h" #include "swift/AST/PrettyStackTrace.h" +#include "swift/AST/PropertyDelegates.h" #include "swift/AST/ProtocolConformance.h" #include "swift/AST/SubstitutionMap.h" #include "swift/AST/TypeCheckerDebugConsumer.h" @@ -2458,21 +2460,42 @@ bool TypeChecker::typeCheckBinding(Pattern *&pattern, Expr *&initializer, /// Type checking listener for pattern binding initializers. class BindingListener : public ExprTypeCheckListener { + TypeChecker &tc; Pattern *&pattern; Expr *&initializer; /// The locator we're using. ConstraintLocator *Locator; + /// The context in which we are performing type checking. + DeclContext *dc; + /// The type of the initializer. Type initType; + /// Whether we applied a property delegate to the initializer expression. + PropertyDelegateTypeInfo appliedPropertyDelegate; + public: - explicit BindingListener(Pattern *&pattern, Expr *&initializer) - : pattern(pattern), initializer(initializer), - Locator(nullptr) { } + explicit BindingListener(TypeChecker &tc, Pattern *&pattern, + Expr *&initializer, DeclContext *dc) + : tc(tc), pattern(pattern), initializer(initializer), + Locator(nullptr), dc(dc) { + maybeApplyPropertyDelegate(); + } - Type getInitType() const { return initType; } + /// Retrieve the type to which the pattern should be coerced. + Type getPatternInitType() const { + if (!appliedPropertyDelegate || initType->hasError()) + return initType; + + // We applied a property delegate, so dig the pattern initialization + // type out of the value property of the delegate. + return initType->getTypeOfMember( + dc->getParentModule(), + appliedPropertyDelegate.valueVar, + appliedPropertyDelegate.valueVar->getValueInterfaceType()); + } bool builtConstraints(ConstraintSystem &cs, Expr *expr) override { assert(!expr->isSemanticallyInOutExpr()); @@ -2482,13 +2505,27 @@ bool TypeChecker::typeCheckBinding(Pattern *&pattern, Expr *&initializer, cs.getConstraintLocator(expr, ConstraintLocator::ContextualType); // Collect constraints from the pattern. - initType = cs.generateConstraints(pattern, Locator); - if (!initType) + Type patternType = cs.generateConstraints(pattern, Locator); + if (!patternType) return true; - // Add a conversion constraint between the types. - cs.addConstraint(ConstraintKind::Conversion, cs.getType(expr), - initType, Locator, /*isFavored*/true); + if (appliedPropertyDelegate) { + // When we have applied a property delegate, the initializer type + // is the initialization of the property delegate instance. + initType = cs.getType(expr); + + // Add a conversion constraint between the pattern type and the + // property delegate's "value" type. + cs.addConstraint(ConstraintKind::Conversion, patternType, + getPatternInitType(), Locator, /*isFavored*/true); + } else { + // The initializer type is the type of the pattern. + initType = patternType; + + // Add a conversion constraint between the types. + cs.addConstraint(ConstraintKind::Conversion, cs.getType(expr), + patternType, Locator, /*isFavored*/true); + } // The expression has been pre-checked; save it in case we fail later. initializer = expr; @@ -2516,10 +2553,82 @@ bool TypeChecker::typeCheckBinding(Pattern *&pattern, Expr *&initializer, initializer = expr; return expr; } + + private: + // If the pattern contains a single variable that has an attached + // property delegate, set up the initializer expression to initialize + // the backing storage. + void maybeApplyPropertyDelegate() { + auto singleVar = pattern->getSingleVar(); + if (!singleVar) + return; + + auto delegateAttr = singleVar->getAttachedPropertyDelegate(); + if (!delegateAttr) + return; + + auto delegateNominal = evaluateOrDefault( + tc.Context.evaluator, CustomAttrNominalRequest{delegateAttr, dc}, + nullptr); + if (!delegateNominal) + return; + + auto delegateInfo = delegateNominal->getPropertyDelegateTypeInfo(); + if (!delegateInfo) + return; + + Type delegateType = singleVar->getAttachedPropertyDelegateType(); + if (!delegateType) + return; + + // If the property delegate is directly initialized, form the + // call. + auto &ctx = singleVar->getASTContext(); + auto typeExpr = + TypeExpr::createImplicitHack(delegateAttr->getTypeLoc().getLoc(), + Type(delegateType), ctx); + if (delegateAttr->getArg() != nullptr) { + if (initializer) { + singleVar->diagnose(diag::property_delegate_and_normal_init, + singleVar->getFullName()) + .highlight(delegateAttr->getRange()) + .highlight(initializer->getSourceRange()); + } + + initializer = CallExpr::create( + ctx, typeExpr, delegateAttr->getArg(), + delegateAttr->getArgumentLabels(), + delegateAttr->getArgumentLabelLocs(), /*hasTrailingClosure=*/false, + /*implicit=*/false); + } else if (delegateInfo.initialValueInit) { + // FIXME: we want to use the initialValueInit we found. + assert(initializer); + initializer = CallExpr::createImplicit( + ctx, typeExpr, {initializer}, {ctx.Id_initialValue}); + } else { + singleVar->diagnose(diag::property_delegate_init_without_initial_value, + singleVar->getFullName(), delegateType) + .highlight(initializer->getSourceRange()); + ctx.Diags.diagnose(delegateAttr->getLocation(), + diag::property_delegate_direct_init) + .fixItInsertAfter(initializer->getSourceRange().End, + "(<# initializer args #>)"); + delegateNominal->diagnose(diag::kind_declname_declared_here, + delegateNominal->getDescriptiveKind(), + delegateNominal->getFullName()); + return; + } + + // Note that we have applied to property delegate, so we can adjust + // the initializer type later. + appliedPropertyDelegate = delegateInfo; + } + }; - assert(initializer && "type-checking an uninitialized binding?"); - BindingListener listener(pattern, initializer); + BindingListener listener(*this, pattern, initializer, DC); + if (!initializer) + return true; TypeLoc contextualType; auto contextualPurpose = CTP_Unused; @@ -2557,7 +2666,7 @@ bool TypeChecker::typeCheckBinding(Pattern *&pattern, Expr *&initializer, // FIXME: initTy should be the same as resultTy; now that typeCheckExpression() // returns a Type and not bool, we should be able to simplify the listener // implementation here. - auto initTy = listener.getInitType(); + auto initTy = listener.getPatternInitType(); if (initTy->hasDependentMember()) return true; @@ -2598,11 +2707,6 @@ bool TypeChecker::typeCheckPatternBinding(PatternBindingDecl *PBD, Pattern *pattern = PBD->getPattern(patternNumber); Expr *init = PBD->getInit(patternNumber); - if (!init) { - PBD->setInvalid(); - return true; - } - // Enter an initializer context if necessary. PatternBindingInitializer *initContext = nullptr; DeclContext *DC = PBD->getDeclContext(); @@ -2613,6 +2717,11 @@ bool TypeChecker::typeCheckPatternBinding(PatternBindingDecl *PBD, } bool hadError = typeCheckBinding(pattern, init, DC); + if (!init) { + PBD->setInvalid(); + return true; + } + PBD->setPattern(patternNumber, pattern, initContext); PBD->setInit(patternNumber, init); diff --git a/lib/Sema/TypeCheckDecl.cpp b/lib/Sema/TypeCheckDecl.cpp index 1ce596d3dc596..bdb95cef9e97c 100644 --- a/lib/Sema/TypeCheckDecl.cpp +++ b/lib/Sema/TypeCheckDecl.cpp @@ -33,6 +33,7 @@ #include "swift/AST/Initializer.h" #include "swift/AST/NameLookup.h" #include "swift/AST/PrettyStackTrace.h" +#include "swift/AST/PropertyDelegates.h" #include "swift/AST/ProtocolConformance.h" #include "swift/AST/ReferencedNameTracker.h" #include "swift/AST/TypeWalker.h" @@ -983,11 +984,12 @@ static void validatePatternBindingEntry(TypeChecker &tc, // even though the PBD is inside a TopLevelCodeDecl. TypeResolutionOptions options(TypeResolverContext::PatternBindingDecl); - if (binding->getInit(entryNumber)) { + if (binding->isInitialized(entryNumber)) { // If we have an initializer, we can also have unknown types. options |= TypeResolutionFlags::AllowUnspecifiedTypes; options |= TypeResolutionFlags::AllowUnboundGenerics; } + if (tc.typeCheckPattern(pattern, binding->getDeclContext(), options)) { setBoundVarsTypeError(pattern, tc.Context); binding->setInvalid(); @@ -2352,7 +2354,7 @@ class DeclChecker : public DeclVisitor { // If we have a type but no initializer, check whether the type is // default-initializable. If so, do it. if (PBD->getPattern(i)->hasType() && - !PBD->getInit(i) && + !PBD->isInitialized(i) && PBD->getPattern(i)->hasStorage() && !PBD->getPattern(i)->getType()->hasError()) { @@ -2385,7 +2387,7 @@ class DeclChecker : public DeclVisitor { } } - if (PBD->getInit(i)) { + if (PBD->isInitialized(i)) { // Add the attribute that preserves the "has an initializer" value across // module generation, as required for TBDGen. PBD->getPattern(i)->forEachVariable([&](VarDecl *VD) { @@ -2409,7 +2411,7 @@ class DeclChecker : public DeclVisitor { for (unsigned i = 0, e = PBD->getNumPatternEntries(); i != e; ++i) { auto entry = PBD->getPatternList()[i]; - if (entry.getInit() || isInSILMode) continue; + if (entry.isInitialized() || isInSILMode) continue; entry.getPattern()->forEachVariable([&](VarDecl *var) { // If the variable has no storage, it never needs an initializer. @@ -2480,7 +2482,7 @@ class DeclChecker : public DeclVisitor { // If the initializers in the PBD aren't checked yet, do so now. for (unsigned i = 0, e = PBD->getNumPatternEntries(); i != e; ++i) { - if (!PBD->getInit(i)) + if (!PBD->isInitialized(i)) continue; if (!PBD->isInitializerChecked(i)) diff --git a/test/decl/var/property_delegates.swift b/test/decl/var/property_delegates.swift index 39e08980bfe4d..d6b2f238dc595 100644 --- a/test/decl/var/property_delegates.swift +++ b/test/decl/var/property_delegates.swift @@ -114,7 +114,7 @@ enum SomeEnum { // expected-error@-1{{enums must not contain stored properties}} @Wrapper(value: 17) - static var x: Int = 17 // okay + static var x: Int } protocol SomeProtocol { @@ -135,14 +135,12 @@ extension HasDelegate { // expected-error@-1{{extensions must not contain stored properties}} @Wrapper(value: 17) - static var x: Int = 17 // okay + static var x: Int } class ClassWithDelegates { @Wrapper(value: 17) - var x: Int = 42 - - + var x: Int } class Superclass { @@ -173,3 +171,34 @@ struct MultipleDelegates { @WrapperWithInitialValue // expected-error 2{{property delegate can only apply to a single variable}} var (y, z) = (1, 2) } + +// --------------------------------------------------------------------------- +// Initialization +// --------------------------------------------------------------------------- + +struct Initialization { + @Wrapper(value: 17) + var x: Int + + @Wrapper(value: 17) + var x2: Double + + @Wrapper(value: 17) + var x3 = 42 // expected-error{{property 'x3' with attached delegate cannot initialize both the delegate type and the property}} + + @Wrapper(value: 17) + var x4 + + @WrapperWithInitialValue + var y = true + + // FIXME: It would be nice if we had a more detailed diagnostic here. + @WrapperWithInitialValue + var y2 = true // expected-error{{'Bool' is not convertible to 'Int'}} + + mutating func checkTypes(s: String) { + x2 = s // expected-error{{cannot assign value of type 'String' to type 'Double'}} + x4 = s // expected-error{{cannot assign value of type 'String' to type 'Int'}} + y = s // expected-error{{cannot assign value of type 'String' to type 'Bool'}} + } +} From 9c6242080988a62e6dcd35e710f7dcee1dd61efa Mon Sep 17 00:00:00 2001 From: Doug Gregor Date: Wed, 20 Mar 2019 13:59:13 -0700 Subject: [PATCH 04/31] [AST] Generalize PatternBindingEntry's "Lazy" flag to "Subsumed" The initializer associated with a lazy property should not be executed directly, because it is subsumed by code synthesized into the getter. Generalize the terminology here so we can re-use this path for property delegate initialization. --- include/swift/AST/Decl.h | 31 ++++++++++++++++++------------- lib/AST/ASTWalker.cpp | 2 +- lib/Parse/ParseDecl.cpp | 2 +- lib/SIL/SILProfiler.cpp | 2 +- lib/SILGen/SILGen.cpp | 6 +++--- lib/SILGen/SILGenConstructor.cpp | 2 +- lib/SILGen/SILGenDecl.cpp | 2 +- lib/SILGen/SILGenType.cpp | 4 ++-- lib/Sema/TypeCheckCaptures.cpp | 2 +- 9 files changed, 29 insertions(+), 24 deletions(-) diff --git a/include/swift/AST/Decl.h b/include/swift/AST/Decl.h index 8b6b39c07d0d3..9a5fd2fa57edd 100644 --- a/include/swift/AST/Decl.h +++ b/include/swift/AST/Decl.h @@ -1887,7 +1887,10 @@ class PatternBindingEntry { enum class Flags { Checked = 1 << 0, Removed = 1 << 1, - Lazy = 1 << 2 + /// Whether the contents of this initializer were subsumed by + /// some other initialization, e.g., a lazy property's initializer + /// gets subsumed by the getter body. + Subsumed = 1 << 2 }; llvm::PointerIntPair> PatternAndFlags; @@ -1932,8 +1935,10 @@ class PatternBindingEntry { return nullptr; return InitExpr.Node; } - Expr *getNonLazyInit() const { - return isInitializerLazy() ? nullptr : getInit(); + /// Retrieve the initializer if it should be executed to initialize this + /// particular pattern binding. + Expr *getExecutableInit() const { + return isInitializerSubsumed() ? nullptr : getInit(); } SourceRange getOrigInitRange() const; void setInit(Expr *E); @@ -1977,11 +1982,11 @@ class PatternBindingEntry { PatternAndFlags.setInt(PatternAndFlags.getInt() | Flags::Checked); } - bool isInitializerLazy() const { - return PatternAndFlags.getInt().contains(Flags::Lazy); + bool isInitializerSubsumed() const { + return PatternAndFlags.getInt().contains(Flags::Subsumed); } - void setInitializerLazy() { - PatternAndFlags.setInt(PatternAndFlags.getInt() | Flags::Lazy); + void setInitializerSubsumed() { + PatternAndFlags.setInt(PatternAndFlags.getInt() | Flags::Subsumed); } // Return the first variable initialized by this pattern. @@ -2081,8 +2086,8 @@ class PatternBindingDecl final : public Decl, Expr *getInit(unsigned i) const { return getPatternList()[i].getInit(); } - Expr *getNonLazyInit(unsigned i) const { - return getPatternList()[i].getNonLazyInit(); + Expr *getExecutableInit(unsigned i) const { + return getPatternList()[i].getExecutableInit(); } SourceRange getOrigInitRange(unsigned i) const { @@ -2123,12 +2128,12 @@ class PatternBindingDecl final : public Decl, getMutablePatternList()[i].setInitializerChecked(); } - bool isInitializerLazy(unsigned i) const { - return getPatternList()[i].isInitializerLazy(); + bool isInitializerSubsumed(unsigned i) const { + return getPatternList()[i].isInitializerSubsumed(); } - void setInitializerLazy(unsigned i) { - getMutablePatternList()[i].setInitializerLazy(); + void setInitializerSubsumed(unsigned i) { + getMutablePatternList()[i].setInitializerSubsumed(); } /// Does this binding declare something that requires storage? diff --git a/lib/AST/ASTWalker.cpp b/lib/AST/ASTWalker.cpp index 0d7c278253833..a488ae58f9a7b 100644 --- a/lib/AST/ASTWalker.cpp +++ b/lib/AST/ASTWalker.cpp @@ -159,7 +159,7 @@ class Traversal : public ASTVisitor()) { - PBDEntries.back().setInitializerLazy(); + PBDEntries.back().setInitializerSubsumed(); } if (init.hasCodeCompletion()) { diff --git a/lib/SIL/SILProfiler.cpp b/lib/SIL/SILProfiler.cpp index 050682d20ba1c..7631898979038 100644 --- a/lib/SIL/SILProfiler.cpp +++ b/lib/SIL/SILProfiler.cpp @@ -145,7 +145,7 @@ static void walkPatternForProfiling(PatternBindingDecl *PBD, ASTWalker &Walker) { if (PBD && !PBD->isStatic()) for (auto E : PBD->getPatternList()) - if (auto init = E.getNonLazyInit()) + if (auto init = E.getExecutableInit()) init->walk(Walker); } diff --git a/lib/SILGen/SILGen.cpp b/lib/SILGen/SILGen.cpp index 0515316222a14..2ae657a1cf9a7 100644 --- a/lib/SILGen/SILGen.cpp +++ b/lib/SILGen/SILGen.cpp @@ -944,7 +944,7 @@ static bool requiresIVarInitialization(SILGenModule &SGM, ClassDecl *cd) { if (!pbd) continue; for (auto entry : pbd->getPatternList()) - if (entry.getNonLazyInit()) + if (entry.getExecutableInit()) return true; } @@ -1113,7 +1113,7 @@ emitStoredPropertyInitialization(PatternBindingDecl *pbd, unsigned i) { auto *var = pbdEntry.getAnchoringVarDecl(); auto *init = pbdEntry.getInit(); auto *initDC = pbdEntry.getInitContext(); - assert(!pbdEntry.isInitializerLazy()); + assert(!pbdEntry.isInitializerSubsumed()); SILDeclRef constant(var, SILDeclRef::Kind::StoredPropertyInitializer); emitOrDelayFunction(*this, constant, @@ -1279,7 +1279,7 @@ void SILGenModule::emitObjCDestructorThunk(DestructorDecl *destructor) { void SILGenModule::visitPatternBindingDecl(PatternBindingDecl *pd) { assert(!TopLevelSGF && "script mode PBDs should be in TopLevelCodeDecls"); for (unsigned i = 0, e = pd->getNumPatternEntries(); i != e; ++i) - if (pd->getNonLazyInit(i)) + if (pd->getExecutableInit(i)) emitGlobalInitialization(pd, i); } diff --git a/lib/SILGen/SILGenConstructor.cpp b/lib/SILGen/SILGenConstructor.cpp index 2369d06fe7d7f..8e2aac6ae20b6 100644 --- a/lib/SILGen/SILGenConstructor.cpp +++ b/lib/SILGen/SILGenConstructor.cpp @@ -885,7 +885,7 @@ void SILGenFunction::emitMemberInitializers(DeclContext *dc, if (pbd->isStatic()) continue; for (auto entry : pbd->getPatternList()) { - auto init = entry.getNonLazyInit(); + auto init = entry.getExecutableInit(); if (!init) continue; // Cleanup after this initialization. diff --git a/lib/SILGen/SILGenDecl.cpp b/lib/SILGen/SILGenDecl.cpp index 0c2e353e17df0..8198ca4ae917c 100644 --- a/lib/SILGen/SILGenDecl.cpp +++ b/lib/SILGen/SILGenDecl.cpp @@ -1173,7 +1173,7 @@ void SILGenFunction::emitPatternBinding(PatternBindingDecl *PBD, // If an initial value expression was specified by the decl, emit it into // the initialization. Otherwise, mark it uninitialized for DI to resolve. - if (auto *Init = entry.getNonLazyInit()) { + if (auto *Init = entry.getExecutableInit()) { FullExpr Scope(Cleanups, CleanupLocation(Init)); emitExprInto(Init, initialization.get(), SILLocation(PBD)); } else { diff --git a/lib/SILGen/SILGenType.cpp b/lib/SILGen/SILGenType.cpp index c9b8f142a9160..9db3c537c57ec 100644 --- a/lib/SILGen/SILGenType.cpp +++ b/lib/SILGen/SILGenType.cpp @@ -1024,7 +1024,7 @@ class SILGenType : public TypeMemberVisitor { void visitPatternBindingDecl(PatternBindingDecl *pd) { // Emit initializers. for (unsigned i = 0, e = pd->getNumPatternEntries(); i != e; ++i) { - if (pd->getNonLazyInit(i)) { + if (pd->getExecutableInit(i)) { if (pd->isStatic()) SGM.emitGlobalInitialization(pd, i); else @@ -1142,7 +1142,7 @@ class SILGenExtension : public TypeMemberVisitor { void visitPatternBindingDecl(PatternBindingDecl *pd) { // Emit initializers for static variables. for (unsigned i = 0, e = pd->getNumPatternEntries(); i != e; ++i) { - if (pd->getNonLazyInit(i)) { + if (pd->getExecutableInit(i)) { assert(pd->isStatic() && "stored property in extension?!"); SGM.emitGlobalInitialization(pd, i); } diff --git a/lib/Sema/TypeCheckCaptures.cpp b/lib/Sema/TypeCheckCaptures.cpp index 88289207e853f..18aa61f87d708 100644 --- a/lib/Sema/TypeCheckCaptures.cpp +++ b/lib/Sema/TypeCheckCaptures.cpp @@ -706,7 +706,7 @@ void TypeChecker::computeCaptures(AnyFunctionRef AFR) { // Walk the initializers for all properties declared in the type with // an initializer. for (auto &elt : PBD->getPatternList()) { - if (elt.isInitializerLazy()) + if (elt.isInitializerSubsumed()) continue; if (auto *init = elt.getInit()) From 32b0245187caef8c38ac2aa710f20737f91087c6 Mon Sep 17 00:00:00 2001 From: Doug Gregor Date: Fri, 22 Mar 2019 14:27:26 -0700 Subject: [PATCH 05/31] [Parser] Consistently use consumeIdentifier() for normal identifiers. consumeIdentifier() provides the general way in which we consume an identifier token and fill in an Identifier. Use it consistently in the parser. --- lib/Parse/ParseDecl.cpp | 23 ++++++++++++----------- lib/Parse/ParseExpr.cpp | 17 +++++++++++------ lib/Parse/ParseGeneric.cpp | 4 ++-- lib/Parse/Parser.cpp | 7 ++++++- 4 files changed, 31 insertions(+), 20 deletions(-) diff --git a/lib/Parse/ParseDecl.cpp b/lib/Parse/ParseDecl.cpp index a04a89f0adb3c..53b3f82ba61ae 100644 --- a/lib/Parse/ParseDecl.cpp +++ b/lib/Parse/ParseDecl.cpp @@ -1243,9 +1243,8 @@ bool Parser::parseNewDeclAttribute(DeclAttributes &Attributes, SourceLoc AtLoc, return false; } - Identifier name = Context.getIdentifier(Tok.getText()); - - consumeToken(tok::identifier); + Identifier name; + consumeIdentifier(&name); auto range = SourceRange(Loc, Tok.getRange().getStart()); @@ -4251,8 +4250,7 @@ static ParameterList *parseOptionalAccessorArgument(SourceLoc SpecifierLoc, EndLoc = StartLoc; } else { // We have a name. - Name = P.Context.getIdentifier(P.Tok.getText()); - NameLoc = P.consumeToken(); + NameLoc = P.consumeIdentifier(&Name); auto DiagID = Kind == AccessorKind::Set ? diag::expected_rparen_set_name : @@ -6734,15 +6732,17 @@ Parser::parseDeclOperatorImpl(SourceLoc OperatorLoc, Identifier Name, SyntaxParsingContext GroupCtxt(SyntaxContext, SyntaxKind::IdentifierList); - identifiers.push_back(Context.getIdentifier(Tok.getText())); - identifierLocs.push_back(consumeToken(tok::identifier)); + Identifier name; + identifierLocs.push_back(consumeIdentifier(&name)); + identifiers.push_back(name); while (Tok.is(tok::comma)) { auto comma = consumeToken(); if (Tok.is(tok::identifier)) { - identifiers.push_back(Context.getIdentifier(Tok.getText())); - identifierLocs.push_back(consumeToken(tok::identifier)); + Identifier name; + identifierLocs.push_back(consumeIdentifier(&name)); + identifiers.push_back(name); } else { if (Tok.isNot(tok::eof)) { auto otherTokLoc = consumeToken(); @@ -7034,8 +7034,9 @@ Parser::parseDeclPrecedenceGroup(ParseDeclOptions flags, diagnose(Tok, diag::expected_precedencegroup_relation, attrName); return abortBody(); } - auto name = Context.getIdentifier(Tok.getText()); - relations.push_back({consumeToken(), name, nullptr}); + Identifier name; + SourceLoc nameLoc = consumeIdentifier(&name); + relations.push_back({nameLoc, name, nullptr}); if (skipUnspacedCodeCompleteToken()) return abortBody(/*hasCodeCompletion*/true); diff --git a/lib/Parse/ParseExpr.cpp b/lib/Parse/ParseExpr.cpp index 02ddea163c14d..7347a0f688d97 100644 --- a/lib/Parse/ParseExpr.cpp +++ b/lib/Parse/ParseExpr.cpp @@ -2132,8 +2132,9 @@ DeclName Parser::parseUnqualifiedDeclName(bool afterDot, DeclBaseName baseName; SourceLoc baseNameLoc; if (Tok.isAny(tok::identifier, tok::kw_Self, tok::kw_self)) { - baseName = Context.getIdentifier(Tok.getText()); - baseNameLoc = consumeToken(); + Identifier baseNameId; + baseNameLoc = consumeIdentifier(&baseNameId); + baseName = baseNameId; } else if (allowOperators && Tok.isAnyOperator()) { baseName = Context.getIdentifier(Tok.getText()); baseNameLoc = consumeToken(); @@ -2643,13 +2644,17 @@ parseClosureSignatureIfPresent(SmallVectorImpl &captureList, break; } - Identifier name = Tok.is(tok::identifier) ? - Context.getIdentifier(Tok.getText()) : Identifier(); + Identifier name; + SourceLoc nameLoc; + if (Tok.is(tok::identifier)) { + nameLoc = consumeIdentifier(&name); + } else { + nameLoc = consumeToken(tok::kw__); + } auto var = new (Context) ParamDecl(VarDecl::Specifier::Default, SourceLoc(), SourceLoc(), - Identifier(), Tok.getLoc(), name, nullptr); + Identifier(), nameLoc, name, nullptr); elements.push_back(var); - consumeToken(); // Consume a comma to continue. HasNext = consumeIf(tok::comma); diff --git a/lib/Parse/ParseGeneric.cpp b/lib/Parse/ParseGeneric.cpp index 9667768a7d7da..0c8be3302500f 100644 --- a/lib/Parse/ParseGeneric.cpp +++ b/lib/Parse/ParseGeneric.cpp @@ -297,8 +297,8 @@ ParserStatus Parser::parseGenericWhereClause( getLayoutConstraint(Context.getIdentifier(Tok.getText()), Context) ->isKnownLayout()) { // Parse a layout constraint. - auto LayoutName = Context.getIdentifier(Tok.getText()); - auto LayoutLoc = consumeToken(); + Identifier LayoutName; + auto LayoutLoc = consumeIdentifier(&LayoutName); auto LayoutInfo = parseLayoutConstraint(LayoutName); if (!LayoutInfo->isKnownLayout()) { // There was a bug in the layout constraint. diff --git a/lib/Parse/Parser.cpp b/lib/Parse/Parser.cpp index 1effd9dc30b03..fa7efc6cd0d09 100644 --- a/lib/Parse/Parser.cpp +++ b/lib/Parse/Parser.cpp @@ -864,7 +864,12 @@ bool Parser::parseSpecificIdentifier(StringRef expected, SourceLoc &loc, /// its name in Result. Otherwise, emit an error and return true. bool Parser::parseAnyIdentifier(Identifier &Result, SourceLoc &Loc, const Diagnostic &D) { - if (Tok.is(tok::identifier) || Tok.isAnyOperator()) { + if (Tok.is(tok::identifier)) { + Loc = consumeIdentifier(&Result); + return false; + } + + if (Tok.isAnyOperator()) { Result = Context.getIdentifier(Tok.getText()); Loc = Tok.getLoc(); consumeToken(); From 7f293f66b35ddf9f49be8867f4d301926e767691 Mon Sep 17 00:00:00 2001 From: Doug Gregor Date: Fri, 22 Mar 2019 15:33:53 -0700 Subject: [PATCH 06/31] [Parser] Allow use of $ declarations in all modes. Allow the use of declarations whose names start with $ in all modes. However, normal code cannot define new entities with names that start with $: only the implementation can do that, e.g., for property delegates. --- include/swift/AST/DiagnosticsParse.def | 4 ++-- include/swift/Parse/Lexer.h | 5 ++++ include/swift/Parse/Parser.h | 24 ++++++++++++++++++- lib/Parse/Lexer.cpp | 9 ------- lib/Parse/ParseExpr.cpp | 5 ++-- test/Parse/dollar_identifier.swift | 12 ++++++++-- test/PlaygroundTransform/import_error.swift | 2 +- .../Sema/sema_playground.swift.response | 2 +- 8 files changed, 45 insertions(+), 18 deletions(-) diff --git a/include/swift/AST/DiagnosticsParse.def b/include/swift/AST/DiagnosticsParse.def index 172c709dbc145..1d23bb3f5806f 100644 --- a/include/swift/AST/DiagnosticsParse.def +++ b/include/swift/AST/DiagnosticsParse.def @@ -1099,14 +1099,14 @@ ERROR(invalid_postfix_operator,none, ERROR(expected_member_name,PointsToFirstBadToken, "expected member name following '.'", ()) -ERROR(expected_dollar_numeric,none, - "expected numeric value following '$'", ()) ERROR(dollar_numeric_too_large,none, "numeric value following '$' is too large", ()) ERROR(numeric_literal_numeric_member,none, "expected named member of numeric literal", ()) ERROR(standalone_dollar_identifier,none, "'$' is not an identifier; use backticks to escape it", ()) +ERROR(dollar_identifier_decl,none, + "cannot declare entity %0 with a '$' prefix", (Identifier)) ERROR(anon_closure_arg_not_in_closure,none, "anonymous closure argument not contained in a closure", ()) diff --git a/include/swift/Parse/Lexer.h b/include/swift/Parse/Lexer.h index 4d2dd718c310e..ffb8674c06e1b 100644 --- a/include/swift/Parse/Lexer.h +++ b/include/swift/Parse/Lexer.h @@ -189,6 +189,11 @@ class Lexer { return CodeCompletionPtr != nullptr; } + /// Whether we are lexing a Swift interface file. + bool isSwiftInterface() const { + return LexMode == LexerMode::SwiftInterface; + } + /// Lex a token. If \c TriviaRetentionMode is \c WithTrivia, passed pointers /// to trivias are populated. void lex(Token &Result, ParsedTrivia &LeadingTriviaResult, diff --git a/include/swift/Parse/Parser.h b/include/swift/Parse/Parser.h index 0892d39ca6282..3b28c296cc330 100644 --- a/include/swift/Parse/Parser.h +++ b/include/swift/Parse/Parser.h @@ -519,10 +519,15 @@ class Parser { return consumeToken(); } - SourceLoc consumeIdentifier(Identifier *Result = nullptr) { + SourceLoc consumeIdentifier(Identifier *Result = nullptr, + bool allowDollarIdentifier = false) { assert(Tok.isAny(tok::identifier, tok::kw_self, tok::kw_Self)); if (Result) *Result = Context.getIdentifier(Tok.getText()); + + if (Tok.getText()[0] == '$' && !allowDollarIdentifier) + diagnoseDollarIdentifier(Tok); + return consumeToken(); } @@ -532,10 +537,27 @@ class Parser { if (!Tok.is(tok::kw__)) { Tok.setKind(tok::identifier); Result = Context.getIdentifier(Tok.getText()); + + if (Tok.getText()[0] == '$') + diagnoseDollarIdentifier(Tok); } return consumeToken(); } + /// When we have a token that is an identifier starting with '$', + /// diagnose it if not permitted in this mode. + void diagnoseDollarIdentifier(const Token &tok) { + assert(tok.getText()[0] == '$'); + + if (tok.getText().size() == 1 || + Context.LangOpts.EnableDollarIdentifiers || + isInSILMode() || L->isSwiftInterface()) + return; + + diagnose(tok.getLoc(), diag::dollar_identifier_decl, + Context.getIdentifier(tok.getText())); + } + /// Retrieve the location just past the end of the previous /// source location. SourceLoc getEndOfPreviousLoc(); diff --git a/lib/Parse/Lexer.cpp b/lib/Parse/Lexer.cpp index 1caa40abc68b4..afe5f9216160e 100644 --- a/lib/Parse/Lexer.cpp +++ b/lib/Parse/Lexer.cpp @@ -945,16 +945,7 @@ void Lexer::lexDollarIdent() { return formToken(tok::identifier, tokStart); } - // We reserve $nonNumeric for persistent bindings in the debugger and implicit - // variables, like storage for lazy properties. if (!isAllDigits) { - if (!LangOpts.EnableDollarIdentifiers && !InSILBody && - LexMode != LexerMode::SwiftInterface) - diagnose(tokStart, diag::expected_dollar_numeric); - - // Even if we diagnose, we go ahead and form an identifier token, - // in part to ensure that the basic behavior of the lexer is - // independent of language mode. return formToken(tok::identifier, tokStart); } else { return formToken(tok::dollarident, tokStart); diff --git a/lib/Parse/ParseExpr.cpp b/lib/Parse/ParseExpr.cpp index 7347a0f688d97..93101ecdc61e0 100644 --- a/lib/Parse/ParseExpr.cpp +++ b/lib/Parse/ParseExpr.cpp @@ -1548,7 +1548,7 @@ ParserResult Parser::parseExprPrimary(Diag<> ID, bool isExprBasic) { // call pattern. peekToken().isNot(tok::period, tok::period_prefix, tok::l_paren)) { Identifier name; - SourceLoc loc = consumeIdentifier(&name); + SourceLoc loc = consumeIdentifier(&name, /*allowDollarIdentifier=*/true); auto specifier = (InVarOrLetPattern != IVOLP_InVar) ? VarDecl::Specifier::Let : VarDecl::Specifier::Var; @@ -2133,7 +2133,8 @@ DeclName Parser::parseUnqualifiedDeclName(bool afterDot, SourceLoc baseNameLoc; if (Tok.isAny(tok::identifier, tok::kw_Self, tok::kw_self)) { Identifier baseNameId; - baseNameLoc = consumeIdentifier(&baseNameId); + baseNameLoc = consumeIdentifier( + &baseNameId, /*allowDollarIdentifier=*/true); baseName = baseNameId; } else if (allowOperators && Tok.isAnyOperator()) { baseName = Context.getIdentifier(Tok.getText()); diff --git a/test/Parse/dollar_identifier.swift b/test/Parse/dollar_identifier.swift index f72adb39b9b08..5406aef4c420a 100644 --- a/test/Parse/dollar_identifier.swift +++ b/test/Parse/dollar_identifier.swift @@ -58,6 +58,14 @@ func escapedDollarFunc() { func escapedDollarAnd() { // FIXME: Bad diagnostics. `$0` = 1 // expected-error {{expected expression}} - `$$` = 2 // expected-error {{expected numeric value following '$'}} - `$abc` = 3 // expected-error {{expected numeric value following '$'}} + `$$` = 2 + `$abc` = 3 +} + +func $declareWithDollar() { // expected-error{{cannot declare entity '$declareWithDollar' with a '$' prefix}} + var $foo = 17 // expected-error{{cannot declare entity '$foo' with a '$' prefix}} + func $bar() { } // expected-error{{cannot declare entity '$bar' with a '$' prefix}} + func wibble( + $a: Int, // expected-error{{cannot declare entity '$a' with a '$' prefix}} + $b c: Int) { } // expected-error{{cannot declare entity '$b' with a '$' prefix}} } diff --git a/test/PlaygroundTransform/import_error.swift b/test/PlaygroundTransform/import_error.swift index 015e8a9f35758..8e56e34d70673 100644 --- a/test/PlaygroundTransform/import_error.swift +++ b/test/PlaygroundTransform/import_error.swift @@ -2,4 +2,4 @@ // RUN: cp %s %t/main.swift // RUN: %target-swift-frontend -typecheck -playground %t/main.swift -verify -var $a = 2 // expected-error {{expected numeric value following '$'}} +var $a = 2 // expected-error {{cannot declare entity '$a' with a '$' prefix}} diff --git a/test/SourceKit/Sema/sema_playground.swift.response b/test/SourceKit/Sema/sema_playground.swift.response index 9659e116e45a6..eed06b64d476c 100644 --- a/test/SourceKit/Sema/sema_playground.swift.response +++ b/test/SourceKit/Sema/sema_playground.swift.response @@ -11,7 +11,7 @@ key.column: 5, key.filepath: sema_playground.swift, key.severity: source.diagnostic.severity.error, - key.description: "expected numeric value following '$'", + key.description: "cannot declare entity '$blah' with a '$' prefix", key.diagnostic_stage: source.diagnostic.stage.swift.sema } ] From b8061eab34bd4765904e803fe3b5afd65a196263 Mon Sep 17 00:00:00 2001 From: Doug Gregor Date: Tue, 2 Apr 2019 21:56:45 -0700 Subject: [PATCH 07/31] Synthesize backing storage property for properties with attached delegates. When a property has an attached property delegate, a backing storage property of the corresponding delegate type will be synthesized. Perform this synthesis, and also synthesize the getter/setter for the original property to reference the backing storage property. --- include/swift/AST/ASTContext.h | 7 + include/swift/AST/Decl.h | 21 ++ include/swift/AST/DiagnosticsSema.def | 6 + include/swift/AST/TypeCheckRequests.h | 57 +++- include/swift/AST/TypeCheckerTypeIDZone.def | 2 + lib/AST/ASTContext.cpp | 16 ++ lib/AST/Decl.cpp | 31 +++ lib/AST/TypeCheckRequests.cpp | 29 ++ lib/Sema/CodeSynthesis.cpp | 262 ++++++++++++++++-- lib/Sema/TypeCheckConstraints.cpp | 9 + lib/Sema/TypeCheckDecl.cpp | 60 +++- lib/Sema/TypeCheckPropertyDelegate.cpp | 96 ++++++- test/decl/var/property_delegates.swift | 241 +++++++++++++++- .../var/property_delegates_synthesis.swift | 55 ++++ 14 files changed, 851 insertions(+), 41 deletions(-) create mode 100644 test/decl/var/property_delegates_synthesis.swift diff --git a/include/swift/AST/ASTContext.h b/include/swift/AST/ASTContext.h index 6b025d5d77804..465ed623911de 100644 --- a/include/swift/AST/ASTContext.h +++ b/include/swift/AST/ASTContext.h @@ -833,6 +833,13 @@ class ASTContext final { const IterableDeclContext *idc, LazyMemberLoader *lazyLoader); + /// Access the side cache for property delegate backing property types, + /// used because TypeChecker::typeCheckBinding() needs somewhere to stash + /// the backing property type. + Type getSideCachedPropertyDelegateBackingPropertyType(VarDecl *var) const; + void setSideCachedPropertyDelegateBackingPropertyType(VarDecl *var, + Type type); + /// Returns memory usage of this ASTContext. size_t getTotalMemory() const; diff --git a/include/swift/AST/Decl.h b/include/swift/AST/Decl.h index 9a5fd2fa57edd..153adc8f65b8f 100644 --- a/include/swift/AST/Decl.h +++ b/include/swift/AST/Decl.h @@ -5048,6 +5048,27 @@ class VarDecl : public AbstractStorageDecl { /// types. Type getAttachedPropertyDelegateType() const; + /// Retrieve information about the attached property delegate type. + PropertyDelegateTypeInfo getAttachedPropertyDelegateTypeInfo() const; + + /// Retrieve the fully resolved attached property delegate type. + /// + /// This type will be the fully-resolved form of + /// \c getAttachedPropertyDelegateType(), which will not contain any + /// unbound generic types. It will be the type of the backing property. + Type getPropertyDelegateBackingPropertyType() const; + + /// Retrieve the backing storage property for a property that has an + /// attached property delegate. + /// + /// The backing storage property will be a stored property of the + /// delegate's type. This will be equivalent to + /// \c getAttachedPropertyDelegateType() when it is fully-specified; + /// if \c getAttachedPropertyDelegateType() involves an unbound + /// generic type, the backing storage property will be the appropriate + /// bound generic version. + VarDecl *getPropertyDelegateBackingProperty() const; + /// Return the Objective-C runtime name for this property. Identifier getObjCPropertyName() const; diff --git a/include/swift/AST/DiagnosticsSema.def b/include/swift/AST/DiagnosticsSema.def index 20cfc90ef4a05..131e07af9fdc9 100644 --- a/include/swift/AST/DiagnosticsSema.def +++ b/include/swift/AST/DiagnosticsSema.def @@ -4324,6 +4324,8 @@ ERROR(property_delegate_ambiguous_value_property, none, ERROR(property_delegate_wrong_initial_value_init, none, "'init(initialValue:)' parameter type (%0) must be the same as its " "'value' property type (%1) or an @autoclosure thereof", (Type, Type)) +ERROR(property_delegate_failable_initial_value_init, none, + "'init(initialValue:)' cannot be failable", ()) ERROR(property_delegate_ambiguous_initial_value_init, none, "property delegate type %0 has multiple initial-value initializers", (Type)) ERROR(property_delegate_type_requirement_not_accessible,none, @@ -4369,6 +4371,10 @@ NOTE(property_delegate_direct_init,none, "initialize the property delegate type directly with " "'(...') on the attribute", ()) +ERROR(property_delegate_incompatible_unbound, none, + "property delegate type %0 must either specify all generic arguments " + "or require only a single generic argument", (Type)) + #ifndef DIAG_NO_UNDEF # if defined(DIAG) # undef DIAG diff --git a/include/swift/AST/TypeCheckRequests.h b/include/swift/AST/TypeCheckRequests.h index f27191b92075d..483794b770d35 100644 --- a/include/swift/AST/TypeCheckRequests.h +++ b/include/swift/AST/TypeCheckRequests.h @@ -468,8 +468,8 @@ class AttachedPropertyDelegateRequest : void noteCycleStep(DiagnosticEngine &diags) const; }; -/// Request the nominal type declaration to which the given custom attribute -/// refers. +/// Request the raw (possibly unbound generic) type of the property delegate +/// that is attached to the given variable. class AttachedPropertyDelegateTypeRequest : public SimpleRequest { +public: + using SimpleRequest::SimpleRequest; + +private: + friend SimpleRequest; + + // Evaluation. + llvm::Expected + evaluate(Evaluator &evaluator, VarDecl *var) const; + +public: + // Caching + bool isCached() const; + + // Cycle handling + void diagnoseCycle(DiagnosticEngine &diags) const; + void noteCycleStep(DiagnosticEngine &diags) const; +}; + +/// Request the nominal type declaration to which the given custom attribute +/// refers. +class PropertyDelegateBackingPropertyRequest : + public SimpleRequest { +public: + using SimpleRequest::SimpleRequest; + +private: + friend SimpleRequest; + + // Evaluation. + llvm::Expected + evaluate(Evaluator &evaluator, VarDecl *var) const; + +public: + // Caching + bool isCached() const; + + // Cycle handling + void diagnoseCycle(DiagnosticEngine &diags) const; + void noteCycleStep(DiagnosticEngine &diags) const; +}; + // Allow AnyValue to compare two Type values, even though Type doesn't // support ==. template<> @@ -501,7 +553,6 @@ bool AnyValue::Holder::equals(const HolderBase &other) const; void simple_display(llvm::raw_ostream &out, const Type &type); - /// The zone number for the type checker. #define SWIFT_TYPE_CHECKER_REQUESTS_TYPEID_ZONE 10 diff --git a/include/swift/AST/TypeCheckerTypeIDZone.def b/include/swift/AST/TypeCheckerTypeIDZone.def index 573bb0904dc32..63b2d833b199b 100644 --- a/include/swift/AST/TypeCheckerTypeIDZone.def +++ b/include/swift/AST/TypeCheckerTypeIDZone.def @@ -28,3 +28,5 @@ SWIFT_TYPEID(MangleLocalTypeDeclRequest) SWIFT_TYPEID(PropertyDelegateTypeInfoRequest) SWIFT_TYPEID(AttachedPropertyDelegateRequest) SWIFT_TYPEID(AttachedPropertyDelegateTypeRequest) +SWIFT_TYPEID(PropertyDelegateBackingPropertyTypeRequest) +SWIFT_TYPEID(PropertyDelegateBackingPropertyRequest) diff --git a/lib/AST/ASTContext.cpp b/lib/AST/ASTContext.cpp index e2c6695ac693f..400641afd2fca 100644 --- a/lib/AST/ASTContext.cpp +++ b/lib/AST/ASTContext.cpp @@ -283,6 +283,9 @@ FOR_KNOWN_FOUNDATION_TYPES(CACHE_FOUNDATION_DECL) llvm::DenseMap> DefaultTypeRequestCaches; + /// Mapping from property declarations to the backing variable types. + llvm::DenseMap PropertyDelegateBackingVarTypes; + /// Structure that captures data that is segregated into different /// arenas. struct Arena { @@ -4350,3 +4353,16 @@ Type &ASTContext::getDefaultTypeRequestCache(SourceFile *SF, KnownProtocolKind kind) { return getImpl().DefaultTypeRequestCaches[SF][size_t(kind)]; } + +Type ASTContext::getSideCachedPropertyDelegateBackingPropertyType( + VarDecl *var) const { + return getImpl().PropertyDelegateBackingVarTypes[var]; +} + +void ASTContext::setSideCachedPropertyDelegateBackingPropertyType( + VarDecl *var, Type type) { + assert(!getImpl().PropertyDelegateBackingVarTypes[var] || + getImpl().PropertyDelegateBackingVarTypes[var]->isEqual(type)); + getImpl().PropertyDelegateBackingVarTypes[var] = type; +} + diff --git a/lib/AST/Decl.cpp b/lib/AST/Decl.cpp index 815c85152f81c..01309820096be 100644 --- a/lib/AST/Decl.cpp +++ b/lib/AST/Decl.cpp @@ -5224,6 +5224,21 @@ CustomAttr *VarDecl::getAttachedPropertyDelegate() const { nullptr); } +PropertyDelegateTypeInfo VarDecl::getAttachedPropertyDelegateTypeInfo() const { + auto attr = getAttachedPropertyDelegate(); + if (!attr) + return PropertyDelegateTypeInfo(); + + auto dc = getDeclContext(); + ASTContext &ctx = getASTContext(); + auto nominal = evaluateOrDefault( + ctx.evaluator, CustomAttrNominalRequest{attr, dc}, nullptr); + if (!nominal) + return PropertyDelegateTypeInfo(); + + return nominal->getPropertyDelegateTypeInfo(); +} + Type VarDecl::getAttachedPropertyDelegateType() const { auto &ctx = getASTContext(); auto mutableThis = const_cast(this); @@ -5232,6 +5247,22 @@ Type VarDecl::getAttachedPropertyDelegateType() const { Type()); } +Type VarDecl::getPropertyDelegateBackingPropertyType() const { + ASTContext &ctx = getASTContext(); + auto mutableThis = const_cast(this); + return evaluateOrDefault( + ctx.evaluator, PropertyDelegateBackingPropertyTypeRequest{mutableThis}, + Type()); +} + +VarDecl *VarDecl::getPropertyDelegateBackingProperty() const { + auto &ctx = getASTContext(); + auto mutableThis = const_cast(this); + return evaluateOrDefault(ctx.evaluator, + PropertyDelegateBackingPropertyRequest{mutableThis}, + nullptr); +} + Identifier VarDecl::getObjCPropertyName() const { if (auto attr = getAttrs().getAttribute()) { if (auto name = attr->getName()) diff --git a/lib/AST/TypeCheckRequests.cpp b/lib/AST/TypeCheckRequests.cpp index a375c8c3bb84d..694f494f72fca 100644 --- a/lib/AST/TypeCheckRequests.cpp +++ b/lib/AST/TypeCheckRequests.cpp @@ -593,6 +593,35 @@ void AttachedPropertyDelegateTypeRequest::noteCycleStep( std::get<0>(getStorage())->diagnose(diag::circular_reference_through); } +bool PropertyDelegateBackingPropertyTypeRequest::isCached() const { + auto var = std::get<0>(getStorage()); + return !var->getAttrs().isEmpty(); +} + +void PropertyDelegateBackingPropertyTypeRequest::diagnoseCycle( + DiagnosticEngine &diags) const { + std::get<0>(getStorage())->diagnose(diag::circular_reference); +} + +void PropertyDelegateBackingPropertyTypeRequest::noteCycleStep( + DiagnosticEngine &diags) const { + std::get<0>(getStorage())->diagnose(diag::circular_reference_through); +} +bool PropertyDelegateBackingPropertyRequest::isCached() const { + auto var = std::get<0>(getStorage()); + return !var->getAttrs().isEmpty(); +} + +void PropertyDelegateBackingPropertyRequest::diagnoseCycle( + DiagnosticEngine &diags) const { + std::get<0>(getStorage())->diagnose(diag::circular_reference); +} + +void PropertyDelegateBackingPropertyRequest::noteCycleStep( + DiagnosticEngine &diags) const { + std::get<0>(getStorage())->diagnose(diag::circular_reference_through); +} + void swift::simple_display( llvm::raw_ostream &out, const PropertyDelegateTypeInfo &propertyDelegate) { out << "{ "; diff --git a/lib/Sema/CodeSynthesis.cpp b/lib/Sema/CodeSynthesis.cpp index 812098d89b44d..0f4d8c0cb5e63 100644 --- a/lib/Sema/CodeSynthesis.cpp +++ b/lib/Sema/CodeSynthesis.cpp @@ -27,7 +27,9 @@ #include "swift/AST/GenericSignatureBuilder.h" #include "swift/AST/Initializer.h" #include "swift/AST/ParameterList.h" +#include "swift/AST/PropertyDelegates.h" #include "swift/AST/ProtocolConformance.h" +#include "swift/AST/TypeCheckRequests.h" #include "swift/Basic/Defer.h" #include "swift/ClangImporter/ClangModule.h" #include "llvm/ADT/SmallString.h" @@ -310,6 +312,16 @@ static void maybeMarkTransparent(AccessorDecl *accessor, ASTContext &ctx) { accessor->getAccessorKind() == AccessorKind::Set) return; + // Getters/setters for a property with a delegate are not @_transparent if + // the backing variable has more-restrictive access than the original + // property. + if (auto var = dyn_cast(accessor->getStorage())) { + if (auto backingVar = var->getPropertyDelegateBackingProperty()) { + if (backingVar->getFormalAccess() < var->getFormalAccess()) + return; + } + } + // Accessors for protocol storage requirements are never @_transparent // since they do not have bodies. // @@ -517,7 +529,9 @@ namespace { /// an override of it. Implementation, /// We're referencing the superclass's implementation of the storage. - Super + Super, + /// We're referencing the backing property for a property with a delegate + Delegate, }; } // end anonymous namespace @@ -526,6 +540,17 @@ static Expr *buildStorageReference(AccessorDecl *accessor, AbstractStorageDecl *storage, TargetImpl target, ASTContext &ctx) { + // Local function to "finish" the expression, creating a member reference + // to the underlying variable if there is one. + VarDecl *underlyingVar = nullptr; + auto finish = [&](Expr *result) -> Expr * { + if (!underlyingVar) + return result; + + return new (ctx) MemberRefExpr( + result, SourceLoc(), underlyingVar, DeclNameLoc(), /*Implicit=*/true); + }; + AccessSemantics semantics; SelfAccessorKind selfAccessKind; switch (target) { @@ -559,12 +584,22 @@ static Expr *buildStorageReference(AccessorDecl *accessor, selfAccessKind = SelfAccessorKind::Peer; } break; + + case TargetImpl::Delegate: { + auto var = cast(accessor->getStorage()); + storage = var->getPropertyDelegateBackingProperty(); + underlyingVar = var->getAttachedPropertyDelegateTypeInfo().valueVar; + semantics = AccessSemantics::DirectToStorage; + selfAccessKind = SelfAccessorKind::Peer; + break; + } } VarDecl *selfDecl = accessor->getImplicitSelfDecl(); if (!selfDecl) { assert(target != TargetImpl::Super); - return new (ctx) DeclRefExpr(storage, DeclNameLoc(), IsImplicit, semantics); + return finish( + new (ctx) DeclRefExpr(storage, DeclNameLoc(), IsImplicit, semantics)); } Expr *selfDRE = @@ -572,12 +607,12 @@ static Expr *buildStorageReference(AccessorDecl *accessor, if (auto subscript = dyn_cast(storage)) { Expr *indices = buildSubscriptIndexReference(ctx, accessor); - return SubscriptExpr::create(ctx, selfDRE, indices, storage, - IsImplicit, semantics); + return finish(SubscriptExpr::create(ctx, selfDRE, indices, storage, + IsImplicit, semantics)); } - return new (ctx) MemberRefExpr(selfDRE, SourceLoc(), storage, - DeclNameLoc(), IsImplicit, semantics); + return finish(new (ctx) MemberRefExpr(selfDRE, SourceLoc(), storage, + DeclNameLoc(), IsImplicit, semantics)); } /// Load the value of VD. If VD is an @override of another value, we call the @@ -727,7 +762,9 @@ void createPropertyStoreOrCallSuperclassSetter(AccessorDecl *accessor, LLVM_ATTRIBUTE_UNUSED static bool isSynthesizedComputedProperty(AbstractStorageDecl *storage) { return (storage->getAttrs().hasAttribute() || - storage->getAttrs().hasAttribute()); + storage->getAttrs().hasAttribute() || + (isa(storage) && + cast(storage)->getAttachedPropertyDelegate())); } /// Synthesize the body of a trivial getter. For a non-member vardecl or one @@ -738,8 +775,8 @@ static void synthesizeTrivialGetterBody(AccessorDecl *getter, TargetImpl target, ASTContext &ctx) { auto storage = getter->getStorage(); - assert(!storage->getAttrs().hasAttribute() && - !storage->getAttrs().hasAttribute()); + assert(!isSynthesizedComputedProperty(storage) || + target == TargetImpl::Delegate); SourceLoc loc = storage->getLoc(); @@ -787,6 +824,13 @@ static void synthesizeReadCoroutineGetterBody(AccessorDecl *getter, synthesizeTrivialGetterBody(getter, TargetImpl::Implementation, ctx); } +/// Synthesize the body of a getter for a property delegate, which +/// delegates to the delegate's unwrap property. +static void synthesizePropertyDelegateGetterBody(AccessorDecl *getter, + ASTContext &ctx) { + synthesizeTrivialGetterBody(getter, TargetImpl::Delegate, ctx); +} + /// Synthesize the body of a setter which just stores to the given storage /// declaration (which doesn't have to be the storage for the setter). static void @@ -817,6 +861,14 @@ static void synthesizeTrivialSetterBody(AccessorDecl *setter, storage, ctx); } +/// Synthesize the body of a setter for a property delegate, which +/// delegates to the delegate's unwrap property. +static void synthesizePropertyDelegateSetterBody(AccessorDecl *setter, + ASTContext &ctx) { + synthesizeTrivialSetterBodyWithStorage(setter, TargetImpl::Delegate, + setter->getStorage(), ctx); +} + static void synthesizeCoroutineAccessorBody(AccessorDecl *accessor, ASTContext &ctx) { assert(accessor->isCoroutine()); @@ -1275,7 +1327,7 @@ static void synthesizeLazyGetterBody(AbstractFunctionDecl *fn, void *context) { auto *InitValue = VD->getParentInitializer(); auto PBD = VD->getParentPatternBinding(); unsigned entryIndex = PBD->getPatternEntryIndexForVarDecl(VD); - assert(PBD->isInitializerLazy(entryIndex)); + assert(PBD->isInitializerSubsumed(entryIndex)); bool wasInitializerChecked = PBD->isInitializerChecked(entryIndex); PBD->setInitializerChecked(entryIndex); @@ -1381,8 +1433,95 @@ void swift::completeLazyVarImplementation(VarDecl *VD) { Storage->overwriteSetterAccess(AccessLevel::Private); } +llvm::Expected +PropertyDelegateBackingPropertyRequest::evaluate(Evaluator &evaluator, + VarDecl *var) const { + // Determine the type of the backing property. + auto delegateType = var->getPropertyDelegateBackingPropertyType(); + if (!delegateType) + return nullptr; + + // Compute the name of the storage type. + ASTContext &ctx = var->getASTContext(); + SmallString<64> nameBuf; + nameBuf = "$"; + nameBuf += var->getName().str(); + Identifier name = ctx.getIdentifier(nameBuf); + + // Determine the type of the storage. + bool isInvalid = delegateType->hasError(); + auto dc = var->getDeclContext(); + Type storageInterfaceType = delegateType; + + Type storageType = + var->getDeclContext()->mapTypeIntoContext(storageInterfaceType); + if (!storageType) { + storageType = ErrorType::get(ctx); + isInvalid = true; + } + + // Create the backing storage property and note it in the cache. + VarDecl *backingVar = new (ctx) VarDecl(/*IsStatic=*/var->isStatic(), + VarDecl::Specifier::Var, + /*IsCaptureList=*/false, + SourceLoc(), + name, dc); + backingVar->setInterfaceType(storageInterfaceType); + backingVar->setType(storageType); + backingVar->setImplicit(); + if (isInvalid) + backingVar->setInvalid(); + addMemberToContextIfNeeded(backingVar, dc, var); + + // Create the pattern binding declaration for the backing property. + Pattern *pbdPattern = new (ctx) NamedPattern(backingVar, /*implicit=*/true); + pbdPattern->setType(storageType); + pbdPattern = TypedPattern::createImplicit(ctx, pbdPattern, storageType); + auto pbd = PatternBindingDecl::createImplicit( + ctx, backingVar->getCorrectStaticSpelling(), pbdPattern, + /*init*/nullptr, dc, SourceLoc()); + addMemberToContextIfNeeded(pbd, dc, var); + + // Take the initializer from the original property. + if (auto parentPBD = var->getParentPatternBinding()) { + unsigned patternNumber = parentPBD->getPatternEntryIndexForVarDecl(var); + if (parentPBD->getInit(patternNumber) && + !parentPBD->isInitializerChecked(patternNumber)) { + auto &tc = *static_cast(ctx.getLazyResolver()); + tc.typeCheckPatternBinding(parentPBD, patternNumber); + } + + if (Expr *init = parentPBD->getInit(patternNumber)) { + pbd->setInit(0, init); + pbd->setInitializerChecked(0); + } + } + + // Mark the backing property as 'final'. There's no sensible way to override. + if (dc->getSelfClassDecl()) + makeFinal(ctx, backingVar); + + // Determine the access level for the backing property. + AccessLevel access = + std::min(AccessLevel::Internal, var->getFormalAccess()); + backingVar->overwriteAccess(access); + + // Determine setter access. + AccessLevel setterAccess = + std::min(AccessLevel::Internal, var->getSetterFormalAccess()); + backingVar->overwriteSetterAccess(setterAccess); + + return backingVar; +} + static bool wouldBeCircularSynthesis(AbstractStorageDecl *storage, AccessorKind kind) { + // All property delegate accessors are non-circular. + if (auto var = dyn_cast(storage)) { + if (var->getAttachedPropertyDelegate()) + return false; + } + switch (kind) { case AccessorKind::Get: return storage->getReadImpl() == ReadImplKind::Get; @@ -1433,10 +1572,12 @@ void swift::triggerAccessorSynthesis(TypeChecker &TC, if (!accessor) return; - accessor->setBodySynthesizer(&synthesizeAccessorBody); + if (!accessor->hasBody()) { + accessor->setBodySynthesizer(&synthesizeAccessorBody); - TC.Context.addSynthesizedDecl(accessor); - TC.DeclsToFinalize.insert(accessor); + TC.Context.addSynthesizedDecl(accessor); + TC.DeclsToFinalize.insert(accessor); + } }); } @@ -1458,6 +1599,35 @@ static void maybeAddAccessorsToLazyVariable(VarDecl *var, ASTContext &ctx) { addExpectedOpaqueAccessorsToStorage(var, ctx); } +static void maybeAddAccessorsForPropertyDelegate(VarDecl *var, + ASTContext &ctx) { + auto backingVar = var->getPropertyDelegateBackingProperty(); + if (!backingVar || backingVar->isInvalid()) + return; + + auto valueVar = var->getAttachedPropertyDelegateTypeInfo().valueVar; + assert(valueVar && "Cannot fail when the backing var is valid"); + + bool delegateSetterIsUsable = !var->isLet() && + valueVar->isSettable(nullptr) && + valueVar->isSetterAccessibleFrom(var->getInnermostDeclContext()); + + if (!var->getGetter()) { + addGetterToStorage(var, ctx); + } + + if (delegateSetterIsUsable) + var->overwriteImplInfo(StorageImplInfo::getMutableComputed()); + else + var->overwriteImplInfo(StorageImplInfo::getImmutableComputed()); + + if (!var->getSetter() && delegateSetterIsUsable) { + addSetterToStorage(var, ctx); + } + + addExpectedOpaqueAccessorsToStorage(var, ctx); +} + /// Try to add the appropriate accessors required a storage declaration. /// This needs to be idempotent. /// @@ -1473,6 +1643,14 @@ void swift::maybeAddAccessorsToStorage(AbstractStorageDecl *storage) { return; } + // Property delegates require backing storage. + if (auto var = dyn_cast(storage)) { + if (var->getAttachedPropertyDelegate()) { + maybeAddAccessorsForPropertyDelegate(var, ctx); + return; + } + } + auto *dc = storage->getDeclContext(); // Local variables don't otherwise get accessors. @@ -1550,6 +1728,16 @@ void swift::maybeAddAccessorsToStorage(AbstractStorageDecl *storage) { static void synthesizeGetterBody(AccessorDecl *getter, ASTContext &ctx) { + auto storage = getter->getStorage(); + + // Synthesize the getter for a property delegate. + if (auto var = dyn_cast(storage)) { + if (var->getAttachedPropertyDelegate()) { + synthesizePropertyDelegateGetterBody(getter, ctx); + return; + } + } + if (getter->hasForcedStaticDispatch()) { synthesizeTrivialGetterBody(getter, TargetImpl::Ordinary, ctx); return; @@ -1580,7 +1768,23 @@ static void synthesizeGetterBody(AccessorDecl *getter, static void synthesizeSetterBody(AccessorDecl *setter, ASTContext &ctx) { - switch (setter->getStorage()->getWriteImpl()) { + auto storage = setter->getStorage(); + + // Synthesize the setter for a property delegate. + if (auto var = dyn_cast(storage)) { + if (var->getAttachedPropertyDelegate()) { + if (var->getAccessor(AccessorKind::WillSet) || + var->getAccessor(AccessorKind::DidSet)) { + synthesizeObservedSetterBody(setter, TargetImpl::Delegate, ctx); + return; + } + + synthesizePropertyDelegateSetterBody(setter, ctx); + return; + } + } + + switch (storage->getWriteImpl()) { case WriteImplKind::Immutable: llvm_unreachable("synthesizing setter from immutable storage"); @@ -1650,6 +1854,10 @@ static void maybeAddMemberwiseDefaultArg(ParamDecl *arg, VarDecl *var, if (!var->getParentPattern()->getSingleVar()) return; + // If this property has a delegate, don't give it a default argument. + if (var->getAttachedPropertyDelegate()) + return; + // If we don't have an expression initializer or silgen can't assign a default // initializer, then we can't generate a default value. An example of where // silgen can assign a default is var x: Int? where the default is nil. @@ -1692,7 +1900,8 @@ bool swift::isMemberwiseInitialized(VarDecl *var) { if (var->isImplicit() || var->isStatic()) return false; - if (!var->hasStorage() && !var->getAttrs().hasAttribute()) + if (!var->hasStorage() && !var->getAttrs().hasAttribute() && + !var->getAttachedPropertyDelegate()) return false; // Initialized 'let' properties have storage, but don't get an argument @@ -1738,13 +1947,24 @@ ConstructorDecl *swift::createImplicitConstructor(TypeChecker &tc, tc.validateDecl(var); auto varInterfaceType = var->getValueInterfaceType(); - // If var is a lazy property, its value is provided for the underlying - // storage. We thus take an optional of the properties type. We only - // need to do this because the implicit constructor is added before all - // the properties are type checked. Perhaps init() synth should be moved - // later. - if (var->getAttrs().hasAttribute()) + 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 (auto delegateTypeInfo = + var->getAttachedPropertyDelegateTypeInfo()) { + // For a property that has a delegate, the presence of an + // init(initialValue:) in the delegate type implies that the + // property can be initialized from the original property type. + // When there is no init(initialValue:), the underlying storage + // type will need to be initialized. + if (!delegateTypeInfo.initialValueInit) { + varInterfaceType = var->getPropertyDelegateBackingPropertyType(); + } + } // Create the parameter. auto *arg = new (ctx) diff --git a/lib/Sema/TypeCheckConstraints.cpp b/lib/Sema/TypeCheckConstraints.cpp index b46d5d9b36ad8..b428a86ae28d3 100644 --- a/lib/Sema/TypeCheckConstraints.cpp +++ b/lib/Sema/TypeCheckConstraints.cpp @@ -2550,6 +2550,15 @@ bool TypeChecker::typeCheckBinding(Pattern *&pattern, Expr *&initializer, assert(solution.getConstraintSystem().getType(expr)->isEqual(initType)); + // Record the property delegate type and note that the initializer has + // been subsumed by the backing property. + if (appliedPropertyDelegate) { + auto var = pattern->getSingleVar(); + var->getParentPatternBinding()->setInitializerSubsumed(0); + tc.Context.setSideCachedPropertyDelegateBackingPropertyType( + var, initType->mapTypeOutOfContext()); + } + initializer = expr; return expr; } diff --git a/lib/Sema/TypeCheckDecl.cpp b/lib/Sema/TypeCheckDecl.cpp index bdb95cef9e97c..6768ce1b505b9 100644 --- a/lib/Sema/TypeCheckDecl.cpp +++ b/lib/Sema/TypeCheckDecl.cpp @@ -2017,6 +2017,21 @@ static bool computeIsGetterMutating(TypeChecker &TC, !storage->isStatic(); } + // If we have an attached property delegate, the getter is mutating if + // the "value" property of the delegate type is mutating and we're in + // a context that has value semantics. + if (auto var = dyn_cast(storage)) { + if (auto delegateInfo = var->getAttachedPropertyDelegateTypeInfo()) { + if (delegateInfo.valueVar && + (!storage->getGetter() || storage->getGetter()->isImplicit())) { + TC.validateDecl(delegateInfo.valueVar); + return delegateInfo.valueVar->isGetterMutating() && + var->isInstanceMember() && + doesContextHaveValueSemantics(var->getDeclContext()); + } + } + } + switch (storage->getReadImpl()) { case ReadImplKind::Stored: return false; @@ -2037,6 +2052,21 @@ static bool computeIsGetterMutating(TypeChecker &TC, static bool computeIsSetterMutating(TypeChecker &TC, AbstractStorageDecl *storage) { + // If we have an attached property delegate, the setter is mutating if + // the "value" property of the delegate type is mutating and we're in + // a context that has value semantics. + if (auto var = dyn_cast(storage)) { + if (auto delegateInfo = var->getAttachedPropertyDelegateTypeInfo()) { + if (delegateInfo.valueVar && + (!storage->getSetter() || storage->getSetter()->isImplicit())) { + TC.validateDecl(delegateInfo.valueVar); + return delegateInfo.valueVar->isSetterMutating() && + var->isInstanceMember() && + doesContextHaveValueSemantics(var->getDeclContext()); + } + } + } + auto impl = storage->getImplInfo(); switch (impl.getWriteImpl()) { case WriteImplKind::Immutable: @@ -2246,12 +2276,12 @@ class DeclChecker : public DeclVisitor { // Static stored properties are allowed, with restrictions // enforced below. if (isa(VD->getDeclContext()) && - !VD->isStatic()) { + !VD->isStatic() && !VD->isInvalid()) { // Enums can only have computed properties. TC.diagnose(VD->getLoc(), diag::enum_stored_property); VD->markInvalid(); } else if (isa(VD->getDeclContext()) && - !VD->isStatic() && + !VD->isStatic() && !VD->isInvalid() && !VD->getAttrs().getAttribute()) { TC.diagnose(VD->getLoc(), diag::extension_stored_property); VD->markInvalid(); @@ -2501,7 +2531,12 @@ class DeclChecker : public DeclVisitor { // If we entered an initializer context, contextualize any // auto-closures we might have created. - if (!DC->isLocalContext()) { + // Note that we don't contextualize the initializer for a property + // with a delegate, because the initializer will have been subsumed + // by the backing storage property. + if (!DC->isLocalContext() && + !(PBD->getSingleVar() && + PBD->getSingleVar()->getAttachedPropertyDelegate())) { auto *initContext = cast_or_null( entry.getInitContext()); if (initContext) { @@ -4339,7 +4374,8 @@ static bool shouldValidateMemberDuringFinalization(NominalTypeDecl *nominal, (isa(VD) && !cast(VD)->isStatic() && (cast(VD)->hasStorage() || - VD->getAttrs().hasAttribute()))) + VD->getAttrs().hasAttribute() || + cast(VD)->getAttachedPropertyDelegate()))) return true; // For classes, we need to validate properties and functions, @@ -4435,6 +4471,12 @@ static void finalizeType(TypeChecker &TC, NominalTypeDecl *nominal) { finalizeAbstractStorageDecl(TC, prop); completeLazyVarImplementation(prop); } + + // Ensure that we create the backing variable for a property delegate. + if (prop->getAttachedPropertyDelegate()) { + finalizeAbstractStorageDecl(TC, prop); + (void)prop->getPropertyDelegateBackingProperty(); + } } if (auto *CD = dyn_cast(nominal)) { @@ -5215,6 +5257,13 @@ void TypeChecker::addImplicitConstructors(NominalTypeDecl *decl) { } if (auto var = dyn_cast(member)) { + // If this variable has a property delegate, go validate it to ensure + // that we create the backing storage property. + if (var->getAttachedPropertyDelegate()) { + validateDecl(var); + maybeAddAccessorsToStorage(var); + } + if (isMemberwiseInitialized(var)) { // Initialized 'let' properties have storage, but don't get an argument // to the memberwise initializer since they already have an initial @@ -5244,7 +5293,7 @@ void TypeChecker::addImplicitConstructors(NominalTypeDecl *decl) { // synthesize an initial value (e.g. for an optional) then we suppress // generation of the default initializer. if (auto pbd = dyn_cast(member)) { - if (pbd->hasStorage() && !pbd->isStatic() && !pbd->isImplicit()) + if (pbd->hasStorage() && !pbd->isStatic()) { for (auto entry : pbd->getPatternList()) { if (entry.getInit()) continue; @@ -5261,6 +5310,7 @@ void TypeChecker::addImplicitConstructors(NominalTypeDecl *decl) { if (CheckDefaultInitializer && !pbd->isDefaultInitializable()) SuppressDefaultInitializer = true; } + } continue; } } diff --git a/lib/Sema/TypeCheckPropertyDelegate.cpp b/lib/Sema/TypeCheckPropertyDelegate.cpp index 549704a5a7b77..255fb7da1e268 100644 --- a/lib/Sema/TypeCheckPropertyDelegate.cpp +++ b/lib/Sema/TypeCheckPropertyDelegate.cpp @@ -114,9 +114,9 @@ static ConstructorDecl *findInitialValueInit(ASTContext &ctx, auto init = initialValueInitializers.front(); if (init->getFormalAccess() < nominal->getFormalAccess()) { init->diagnose(diag::property_delegate_type_requirement_not_accessible, - init->getFormalAccess(), init->getDescriptiveKind(), - init->getFullName(), nominal->getDeclaredType(), - nominal->getFormalAccess()); + init->getFormalAccess(), init->getDescriptiveKind(), + init->getFullName(), nominal->getDeclaredType(), + nominal->getFormalAccess()); return nullptr; } @@ -130,9 +130,9 @@ static ConstructorDecl *findInitialValueInit(ASTContext &ctx, ctx.getLazyResolver()->resolveDeclSignature(init); Type paramType; if (auto *curriedInitType = - init->getInterfaceType()->getAs()) { + init->getInterfaceType()->getAs()) { if (auto *initType = - curriedInitType->getResult()->getAs()) { + curriedInitType->getResult()->getAs()) { if (initType->getParams().size() == 1) { const auto ¶m = initType->getParams()[0]; if (!param.isInOut() && !param.isVariadic()) { @@ -166,7 +166,7 @@ static ConstructorDecl *findInitialValueInit(ASTContext &ctx, llvm::Expected PropertyDelegateTypeInfoRequest::evaluate( - Evaluator &eval, NominalTypeDecl *nominal) const { + Evaluator &eval, NominalTypeDecl *nominal) const { // We must have the @propertyDelegate attribute to continue. if (!nominal->getAttrs().hasAttribute()) { return PropertyDelegateTypeInfo(); @@ -195,7 +195,7 @@ AttachedPropertyDelegateRequest::evaluate(Evaluator &evaluator, auto mutableAttr = const_cast(attr); // Figure out which nominal declaration this custom attribute refers to. auto nominal = evaluateOrDefault( - ctx.evaluator, CustomAttrNominalRequest{mutableAttr, dc}, nullptr); + ctx.evaluator, CustomAttrNominalRequest{mutableAttr, dc}, nullptr); // If we didn't find a nominal type with a @propertyDelegate attribute, // skip this custom attribute. @@ -225,7 +225,7 @@ AttachedPropertyDelegateRequest::evaluate(Evaluator &evaluator, var->getAttrs().hasAttribute() || (var->getAttrs().hasAttribute() && var->getAttrs().getAttribute()->get() != - ReferenceOwnership::Strong)) { + ReferenceOwnership::Strong)) { int whichKind; if (var->getAttrs().hasAttribute()) whichKind = 0; @@ -276,13 +276,13 @@ AttachedPropertyDelegateRequest::evaluate(Evaluator &evaluator, return nullptr; } - + llvm::Expected AttachedPropertyDelegateTypeRequest::evaluate(Evaluator &evaluator, VarDecl *var) const { // Find the custom attribute for the attached property delegate. llvm::Expected customAttrVal = - evaluator(AttachedPropertyDelegateRequest{var}); + evaluator(AttachedPropertyDelegateRequest{var}); if (!customAttrVal) return customAttrVal.takeError(); @@ -292,7 +292,7 @@ AttachedPropertyDelegateTypeRequest::evaluate(Evaluator &evaluator, return Type(); auto resolution = - TypeResolution::forContextual(var->getDeclContext()); + TypeResolution::forContextual(var->getDeclContext()); TypeResolutionOptions options(TypeResolverContext::PatternBindingDecl); options |= TypeResolutionFlags::AllowUnboundGenerics; @@ -309,3 +309,77 @@ AttachedPropertyDelegateTypeRequest::evaluate(Evaluator &evaluator, return customAttrType; } + +llvm::Expected +PropertyDelegateBackingPropertyTypeRequest::evaluate( + Evaluator &evaluator, VarDecl *var) const { + llvm::Expected rawTypeResult = + evaluator(AttachedPropertyDelegateTypeRequest{var}); + if (!rawTypeResult) + return rawTypeResult; + + Type rawType = *rawTypeResult; + if (!rawType) + return Type(); + + if (!rawType->hasUnboundGenericType()) + return rawType->mapTypeOutOfContext(); + + auto binding = var->getParentPatternBinding(); + if (!binding) + return Type(); + + // If there's an initializer of some sort, checking it will determine the + // property delegate type. + unsigned index = binding->getPatternEntryIndexForVarDecl(var); + ASTContext &ctx = var->getASTContext(); + TypeChecker &tc = *static_cast(ctx.getLazyResolver()); + if (binding->isInitialized(index)) { + if (!binding->isInitializerChecked(index)) + tc.typeCheckPatternBinding(binding, index); + + Type type = ctx.getSideCachedPropertyDelegateBackingPropertyType(var); + assert(type || ctx.Diags.hadAnyError()); + return type; + } + + // Compose the type of property delegate with the type of the property. + + // We expect an unbound generic type here that refers to a single-parameter + // generic type. + auto delegateAttr = var->getAttachedPropertyDelegate(); + auto nominal = rawType->getAnyNominal(); + auto unboundGeneric = rawType->getAs(); + if (!unboundGeneric || + unboundGeneric->getDecl() != nominal || + !nominal->getGenericParams() || + nominal->getGenericParams()->size() != 1) { + ctx.Diags.diagnose(delegateAttr->getLocation(), + diag::property_delegate_incompatible_unbound, + rawType) + .highlight(delegateAttr->getTypeLoc().getSourceRange()); + return Type(); + } + + // Compute the type of the property to plug in to the delegate type. + tc.validateDecl(var); + Type propertyType = var->getType(); + + // Form the specialized type. + Type delegateType = tc.applyUnboundGenericArguments( + unboundGeneric, nominal, delegateAttr->getLocation(), + TypeResolution::forContextual(var->getDeclContext()), { propertyType }); + + // Make sure no unbound types remain; this could happen if there are outer + // unbound types that weren't resolved by the application of the property + // type. + if (delegateType->hasUnboundGenericType()) { + ctx.Diags.diagnose(delegateAttr->getLocation(), + diag::property_delegate_incompatible_unbound, + delegateType) + .highlight(delegateAttr->getTypeLoc().getSourceRange()); + return Type(); + } + + return delegateType->mapTypeOutOfContext(); +} diff --git a/test/decl/var/property_delegates.swift b/test/decl/var/property_delegates.swift index d6b2f238dc595..61e794fa59dd6 100644 --- a/test/decl/var/property_delegates.swift +++ b/test/decl/var/property_delegates.swift @@ -1,4 +1,4 @@ -// RUN: %target-typecheck-verify-swift -swift-version 5 +// RUN: %target-typecheck-verify-swift // --------------------------------------------------------------------------- // Property delegate type definitions @@ -82,6 +82,24 @@ struct MultipleInitialValues { // expected-error{{property delegate type } } +@propertyDelegate +struct InitialValueFailable { + var value: Value + + init?(initialValue: Value) { // expected-error{{'init(initialValue:)' cannot be failable}} + return nil + } +} + +@propertyDelegate +struct InitialValueFailableIUO { + var value: Value + + init!(initialValue: Value) { // expected-error{{'init(initialValue:)' cannot be failable}} + return nil + } +} + // --------------------------------------------------------------------------- // Property delegate type definitions // --------------------------------------------------------------------------- @@ -202,3 +220,224 @@ struct Initialization { y = s // expected-error{{cannot assign value of type 'String' to type 'Bool'}} } } + +// --------------------------------------------------------------------------- +// Delegate type formation +// --------------------------------------------------------------------------- +@propertyDelegate +struct IntWrapper { + var value: Int +} + +@propertyDelegate +struct WrapperForHashable { + var value: T +} + +@propertyDelegate +struct WrapperWithTwoParams { + var value: (T, U) +} + +struct NotHashable { } + +struct UseWrappersWithDifferentForm { + @IntWrapper + var x: Int + + @WrapperForHashable // expected-error{{type 'NotHashable' does not conform to protocol 'Hashable'}} + var y: NotHashable + + @WrapperForHashable + var yOkay: Int + + @WrapperWithTwoParams // expected-error{{property delegate type 'WrapperWithTwoParams' must either specify all generic arguments or require only a single generic argument}} + var z: Int + + @HasNestedDelegate.NestedDelegate // expected-error{{property delegate type 'HasNestedDelegate.NestedDelegate' must either specify all generic arguments or require only a single generic argument}} + var w: Int + + @HasNestedDelegate.NestedDelegate + var wOkay: Int +} + + +// --------------------------------------------------------------------------- +// Nested delegates +// --------------------------------------------------------------------------- +struct HasNestedDelegate { + @propertyDelegate + struct NestedDelegate { + var value: U + init(initialValue: U) { + self.value = initialValue + } + } + + @NestedDelegate + var y: [T] = [] +} + +struct UsesNestedDelegate { + @HasNestedDelegate.NestedDelegate + var y: [V] +} + +// --------------------------------------------------------------------------- +// Referencing the backing store +// --------------------------------------------------------------------------- +struct BackingStore { + @Wrapper + var x: T + + @WrapperWithInitialValue + private var y = true // expected-note{{'y' declared here}} + + func getXStorage() -> Wrapper { + return $x + } + + func getYStorage() -> WrapperWithInitialValue { + return self.$y + } +} + +func testBackingStore(bs: BackingStore) { + _ = bs.x + _ = bs.y // expected-error{{'y' is inaccessible due to 'private' protection level}} +} + +// --------------------------------------------------------------------------- +// Explicitly-specified accessors +// --------------------------------------------------------------------------- +struct DelegateWithAccessors { + @Wrapper + var x: Int { + return $x.value * 2 + } + + @WrapperWithInitialValue + var y: Int { + get { + return $y.value + } + + set { + $y.value = newValue / 2 + } + } + + mutating func test() { + x = y + y = x + } +} + +struct UseWillSetDidSet { + @Wrapper + var x: Int { + willSet { + print(newValue) + } + } + + @Wrapper + var y: Int { + didSet { + print(oldValue) + } + } + + @Wrapper + var z: Int { + willSet { + print(newValue) + } + + didSet { + print(oldValue) + } + } +} + +// --------------------------------------------------------------------------- +// Mutating/nonmutating +// --------------------------------------------------------------------------- +@propertyDelegate +struct DelegateWithNonMutatingSetter { + class Box { + var value: Value + init(value: Value) { + self.value = value + } + } + + var box: Box + + init(initialValue: Value) { + self.box = Box(value: initialValue) + } + + var value: Value { + get { return box.value } + nonmutating set { box.value = newValue } + } +} + +@propertyDelegate +struct DelegateWithMutatingGetter { + var readCount = 0 + var writeCount = 0 + var stored: Value + + init(initialValue: Value) { + self.stored = initialValue + } + + var value: Value { + mutating get { + readCount += 1 + return stored + } + set { + writeCount += 1 + stored = newValue + } + } +} + +struct UseMutatingnessDelegates { + @DelegateWithNonMutatingSetter + var x = true + + @DelegateWithMutatingGetter + var y = 17 + + @DelegateWithNonMutatingSetter + let z = 3.14159 // expected-note 2{{change 'let' to 'var' to make it mutable}} +} + +func testMutatingness() { + var mutable = UseMutatingnessDelegates() + + _ = mutable.x + mutable.x = false + + _ = mutable.y + mutable.y = 42 + + _ = mutable.z + mutable.z = 2.71828 // expected-error{{cannot assign to property: 'z' is a 'let' constant}} + + let nonmutable = UseMutatingnessDelegates() // expected-note 2{{change 'let' to 'var' to make it mutable}} + + // Okay due to nonmutating setter + _ = nonmutable.x + nonmutable.x = false + + _ = nonmutable.y // expected-error{{cannot use mutating getter on immutable value: 'nonmutable' is a 'let' constant}} + nonmutable.y = 42 // expected-error{{cannot use mutating getter on immutable value: 'nonmutable' is a 'let' constant}} + + _ = nonmutable.z + nonmutable.z = 2.71828 // expected-error{{cannot assign to property: 'z' is a 'let' constant}} +} diff --git a/test/decl/var/property_delegates_synthesis.swift b/test/decl/var/property_delegates_synthesis.swift new file mode 100644 index 0000000000000..b8b84a4f9b985 --- /dev/null +++ b/test/decl/var/property_delegates_synthesis.swift @@ -0,0 +1,55 @@ +// RUN: %target-swift-frontend -typecheck -dump-ast %s | %FileCheck %s + +@propertyDelegate +struct Wrapper { + var value: T + + init(initialValue: T) { + self.value = initialValue + } +} + +protocol DefaultInit { + init() +} + +// CHECK: struct_decl{{.*}}"UseWrapper" +struct UseWrapper { + // CHECK: var_decl{{.*}}"wrapped" + + // CHECK: accessor_decl{{.*}}get_for=wrapped + // CHECK: member_ref_expr{{.*}}UseWrapper.$wrapped + + // CHECK: accessor_decl{{.*}}set_for=wrapped + // CHECK: member_ref_expr{{.*}}UseWrapper.$wrapped + + // CHECK: accessor_decl{{.*}}_modify_for=wrapped + // CHECK: yield_stmt + // CHECK: member_ref_expr{{.*}}UseWrapper.wrapped + @Wrapper + var wrapped = T() + + // CHECK: pattern_binding_decl implicit + // CHECK-NEXT: pattern_typed implicit type='Wrapper' + // CHECK-NEXT: pattern_named implicit type='Wrapper' '$wrapped' + // CHECK: constructor_ref_call_expr + // CHECK-NEXT: declref_expr{{.*}}Wrapper.init(initialValue:) + init() { } +} + +struct UseWillSetDidSet { + // CHECK: var_decl{{.*}}"z" + + // CHECK: accessor_decl{{.*}}set_for=z + // CHECK: member_ref_expr{{.*}}UseWillSetDidSet.$z + @Wrapper + var z: Int { + willSet { + print(newValue) + } + + didSet { + print(oldValue) + } + } +} From b18a2902e5e2c6767703506015f76b226fb7a922 Mon Sep 17 00:00:00 2001 From: Doug Gregor Date: Wed, 3 Apr 2019 14:34:06 -0700 Subject: [PATCH 08/31] Implement access control for property delegates --- include/swift/AST/DiagnosticsSema.def | 15 ++++++ lib/Sema/CodeSynthesis.cpp | 8 +-- lib/Sema/TypeCheckAccess.cpp | 44 +++++++++++++++- test/decl/var/property_delegates.swift | 71 +++++++++++++++++++++++++- 4 files changed, 132 insertions(+), 6 deletions(-) diff --git a/include/swift/AST/DiagnosticsSema.def b/include/swift/AST/DiagnosticsSema.def index 131e07af9fdc9..9f96566865ba3 100644 --- a/include/swift/AST/DiagnosticsSema.def +++ b/include/swift/AST/DiagnosticsSema.def @@ -4375,6 +4375,21 @@ ERROR(property_delegate_incompatible_unbound, none, "property delegate type %0 must either specify all generic arguments " "or require only a single generic argument", (Type)) +ERROR(property_delegate_type_access,none, + "%select{%select{variable|constant}0|property}1 " + "%select{must be declared %select{" + "%select{private|fileprivate|internal|%error|%error}3|private or fileprivate}4" + "|cannot be declared " + "%select{in this context|fileprivate|internal|public|open}3}2 " + "because its property delegate type uses " + "%select{a private|a fileprivate|an internal|%error|%error}5 type", + (bool, bool, bool, AccessLevel, bool, AccessLevel)) +ERROR(property_delegate_type_not_usable_from_inline,none, + "property delegate type referenced from a '@usableFromInline' " + "%select{%select{variable|constant}0|property}1 " + "must be '@usableFromInline' or public", + (bool, bool)) + #ifndef DIAG_NO_UNDEF # if defined(DIAG) # undef DIAG diff --git a/lib/Sema/CodeSynthesis.cpp b/lib/Sema/CodeSynthesis.cpp index 0f4d8c0cb5e63..9f88308fa16c2 100644 --- a/lib/Sema/CodeSynthesis.cpp +++ b/lib/Sema/CodeSynthesis.cpp @@ -1608,9 +1608,11 @@ static void maybeAddAccessorsForPropertyDelegate(VarDecl *var, auto valueVar = var->getAttachedPropertyDelegateTypeInfo().valueVar; assert(valueVar && "Cannot fail when the backing var is valid"); - bool delegateSetterIsUsable = !var->isLet() && - valueVar->isSettable(nullptr) && - valueVar->isSetterAccessibleFrom(var->getInnermostDeclContext()); + bool delegateSetterIsUsable = + var->getSetter() || + (!var->isLet() && + valueVar->isSettable(nullptr) && + valueVar->isSetterAccessibleFrom(var->getInnermostDeclContext())); if (!var->getGetter()) { addGetterToStorage(var, ctx); diff --git a/lib/Sema/TypeCheckAccess.cpp b/lib/Sema/TypeCheckAccess.cpp index 3cafa4eb026c6..18b3582508048 100644 --- a/lib/Sema/TypeCheckAccess.cpp +++ b/lib/Sema/TypeCheckAccess.cpp @@ -491,7 +491,7 @@ class AccessControlChecker : public AccessControlCheckerBase, void checkTypedPattern(const TypedPattern *TP, bool isTypeContext, llvm::DenseSet &seenVars) { - const VarDecl *anyVar = nullptr; + VarDecl *anyVar = nullptr; TP->forEachVariable([&](VarDecl *V) { seenVars.insert(V); anyVar = V; @@ -521,6 +521,31 @@ class AccessControlChecker : public AccessControlCheckerBase, typeAccess); highlightOffendingType(TC, diag, complainRepr); }); + + // Check the property delegate type. + if (auto attr = anyVar->getAttachedPropertyDelegate()) { + checkTypeAccess(attr->getTypeLoc(), anyVar, + /*mayBeInferred=*/false, + [&](AccessScope typeAccessScope, + const TypeRepr *complainRepr, + DowngradeToWarning downgradeToWarning) { + auto typeAccess = typeAccessScope.accessLevelForDiagnostics(); + bool isExplicit = + anyVar->getAttrs().hasAttribute() || + isa(anyVar->getDeclContext()); + auto anyVarAccess = + isExplicit ? anyVar->getFormalAccess() + : typeAccessScope.requiredAccessForDiagnostics(); + auto diag = anyVar->diagnose(diag::property_delegate_type_access, + anyVar->isLet(), + isTypeContext, + isExplicit, + anyVarAccess, + isa(anyVar->getDeclContext()), + typeAccess); + highlightOffendingType(TC, diag, complainRepr); + }); + } } void visitPatternBindingDecl(PatternBindingDecl *PBD) { @@ -1081,7 +1106,7 @@ class UsableFromInlineChecker : public AccessControlCheckerBase, // FIXME: We need an access level to check against, so we pull one out // of some random VarDecl in the pattern. They're all going to be the // same, but still, ick. - const VarDecl *anyVar = nullptr; + VarDecl *anyVar = nullptr; TP->forEachVariable([&](VarDecl *V) { seenVars.insert(V); anyVar = V; @@ -1107,6 +1132,21 @@ class UsableFromInlineChecker : public AccessControlCheckerBase, isTypeContext); highlightOffendingType(TC, diag, complainRepr); }); + + if (auto attr = anyVar->getAttachedPropertyDelegate()) { + checkTypeAccess(attr->getTypeLoc(), + fixedLayoutStructContext ? fixedLayoutStructContext + : anyVar, + /*mayBeInferred*/false, + [&](AccessScope typeAccessScope, + const TypeRepr *complainRepr, + DowngradeToWarning downgradeToWarning) { + auto diag = anyVar->diagnose( + diag::property_delegate_type_not_usable_from_inline, + anyVar->isLet(), isTypeContext); + highlightOffendingType(TC, diag, complainRepr); + }); + } } void visitPatternBindingDecl(PatternBindingDecl *PBD) { diff --git a/test/decl/var/property_delegates.swift b/test/decl/var/property_delegates.swift index 61e794fa59dd6..bd8c89652a0fb 100644 --- a/test/decl/var/property_delegates.swift +++ b/test/decl/var/property_delegates.swift @@ -1,4 +1,4 @@ -// RUN: %target-typecheck-verify-swift +// RUN: %target-typecheck-verify-swift -swift-version 5 // --------------------------------------------------------------------------- // Property delegate type definitions @@ -441,3 +441,72 @@ func testMutatingness() { _ = nonmutable.z nonmutable.z = 2.71828 // expected-error{{cannot assign to property: 'z' is a 'let' constant}} } + +// --------------------------------------------------------------------------- +// Access control +// --------------------------------------------------------------------------- +struct HasPrivateDelegate { + @propertyDelegate + private struct PrivateDelegate { // expected-note{{type declared here}} + var value: U + init(initialValue: U) { + self.value = initialValue + } + } + + @PrivateDelegate + var y: [T] = [] + // expected-error@-1{{property must be declared private because its property delegate type uses a private type}} + + // Okay to reference private entities from a private property + @PrivateDelegate + private var z: [T] +} + +public struct HasUsableFromInlineDelegate { + @propertyDelegate + struct InternalDelegate { // expected-note{{type declared here}} + var value: U + init(initialValue: U) { + self.value = initialValue + } + } + + @InternalDelegate + @usableFromInline + var y: [T] = [] + // expected-error@-1{{property delegate type referenced from a '@usableFromInline' property must be '@usableFromInline' or public}} +} + +@propertyDelegate +class Box { + private(set) var value: Value + + init(initialValue: Value) { + self.value = initialValue + } +} + +struct UseBox { + @Box + var x = 17 + + @Box + var y: Int { + get { return $y.value } + set { } + } +} + +func testBox(ub: UseBox) { + _ = ub.x + ub.x = 5 // expected-error{{cannot assign to property: 'x' is a get-only property}} + + _ = ub.y + ub.y = 20 // expected-error{{cannot assign to property: 'ub' is a 'let' constant}} + + var mutableUB = ub + _ = mutableUB.y + mutableUB.y = 20 + mutableUB = ub +} From 6526cfa8d4bfd07c210b0c6d679b8a8b794529ac Mon Sep 17 00:00:00 2001 From: Doug Gregor Date: Wed, 3 Apr 2019 18:54:53 -0700 Subject: [PATCH 09/31] Memberwise initializer synthesis for properties with attached delegates. --- include/swift/AST/Decl.h | 13 +++-- lib/AST/Decl.cpp | 16 +++++- lib/Sema/CodeSynthesis.cpp | 10 ++-- lib/Sema/DebuggerTestingTransform.cpp | 2 +- lib/Sema/DerivedConformanceCodable.cpp | 4 +- lib/Sema/TypeCheckDecl.cpp | 47 ++++++++++++++++-- test/decl/var/property_delegates.swift | 69 ++++++++++++++++++++++++++ 7 files changed, 140 insertions(+), 21 deletions(-) diff --git a/include/swift/AST/Decl.h b/include/swift/AST/Decl.h index 153adc8f65b8f..d2b464c3322c2 100644 --- a/include/swift/AST/Decl.h +++ b/include/swift/AST/Decl.h @@ -2141,11 +2141,7 @@ class PatternBindingDecl final : public Decl, /// Determines whether this binding either has an initializer expression, or is /// default initialized, without performing any type checking on it. - /// - /// This is only valid to check for bindings which have storage. bool isDefaultInitializable() const { - assert(hasStorage()); - for (unsigned i = 0, e = getNumPatternEntries(); i < e; ++i) if (!isDefaultInitializable(i)) return false; @@ -4914,12 +4910,19 @@ class VarDecl : public AbstractStorageDecl { return nullptr; } + /// Whether there exists an initializer for this \c VarDecl. + bool isParentInitialized() const { + if (auto *PBD = getParentPatternBinding()) + return PBD->getPatternEntryForVarDecl(this).isInitialized(); + return false; + } + // Return whether this VarDecl has an initial value, either by checking // if it has an initializer in its parent pattern binding or if it has // the @_hasInitialValue attribute. bool hasInitialValue() const { return getAttrs().hasAttribute() || - getParentInitializer(); + isParentInitialized(); } VarDecl *getOverriddenDecl() const { diff --git a/lib/AST/Decl.cpp b/lib/AST/Decl.cpp index 01309820096be..500e528633db3 100644 --- a/lib/AST/Decl.cpp +++ b/lib/AST/Decl.cpp @@ -1536,7 +1536,7 @@ bool PatternBindingDecl::isDefaultInitializable(unsigned i) const { const auto entry = getPatternList()[i]; // If it has an initializer expression, this is trivially true. - if (entry.getInit()) + if (entry.isInitialized()) return true; if (entry.getPattern()->isNeverDefaultInitializable()) @@ -4922,7 +4922,7 @@ bool VarDecl::isSettable(const DeclContext *UseDC, // If the decl has an explicitly written initializer with a pattern binding, // then it isn't settable. - if (getParentInitializer() != nullptr) + if (isParentInitialized()) return false; // Normal lets (e.g. globals) are only mutable in the context of the @@ -5218,6 +5218,9 @@ StaticSpellingKind AbstractStorageDecl::getCorrectStaticSpelling() const { CustomAttr *VarDecl::getAttachedPropertyDelegate() const { auto &ctx = getASTContext(); + if (!ctx.getLazyResolver()) + return nullptr; + auto mutableThis = const_cast(this); return evaluateOrDefault(ctx.evaluator, AttachedPropertyDelegateRequest{mutableThis}, @@ -5241,6 +5244,9 @@ PropertyDelegateTypeInfo VarDecl::getAttachedPropertyDelegateTypeInfo() const { Type VarDecl::getAttachedPropertyDelegateType() const { auto &ctx = getASTContext(); + if (!ctx.getLazyResolver()) + return nullptr; + auto mutableThis = const_cast(this); return evaluateOrDefault(ctx.evaluator, AttachedPropertyDelegateTypeRequest{mutableThis}, @@ -5249,6 +5255,9 @@ Type VarDecl::getAttachedPropertyDelegateType() const { Type VarDecl::getPropertyDelegateBackingPropertyType() const { ASTContext &ctx = getASTContext(); + if (!ctx.getLazyResolver()) + return nullptr; + auto mutableThis = const_cast(this); return evaluateOrDefault( ctx.evaluator, PropertyDelegateBackingPropertyTypeRequest{mutableThis}, @@ -5257,6 +5266,9 @@ Type VarDecl::getPropertyDelegateBackingPropertyType() const { VarDecl *VarDecl::getPropertyDelegateBackingProperty() const { auto &ctx = getASTContext(); + if (!ctx.getLazyResolver()) + return nullptr; + auto mutableThis = const_cast(this); return evaluateOrDefault(ctx.evaluator, PropertyDelegateBackingPropertyRequest{mutableThis}, diff --git a/lib/Sema/CodeSynthesis.cpp b/lib/Sema/CodeSynthesis.cpp index 9f88308fa16c2..6d612720704f6 100644 --- a/lib/Sema/CodeSynthesis.cpp +++ b/lib/Sema/CodeSynthesis.cpp @@ -1464,7 +1464,7 @@ PropertyDelegateBackingPropertyRequest::evaluate(Evaluator &evaluator, VarDecl *backingVar = new (ctx) VarDecl(/*IsStatic=*/var->isStatic(), VarDecl::Specifier::Var, /*IsCaptureList=*/false, - SourceLoc(), + var->getLoc(), name, dc); backingVar->setInterfaceType(storageInterfaceType); backingVar->setType(storageType); @@ -1856,10 +1856,6 @@ static void maybeAddMemberwiseDefaultArg(ParamDecl *arg, VarDecl *var, if (!var->getParentPattern()->getSingleVar()) return; - // If this property has a delegate, don't give it a default argument. - if (var->getAttachedPropertyDelegate()) - return; - // If we don't have an expression initializer or silgen can't assign a default // initializer, then we can't generate a default value. An example of where // silgen can assign a default is var x: Int? where the default is nil. @@ -1882,7 +1878,7 @@ static void maybeAddMemberwiseDefaultArg(ParamDecl *arg, VarDecl *var, // the type will always be a sugared T? because we don't default init an // explicit Optional. if ((isa(var->getValueInterfaceType().getPointer()) && - !var->getParentInitializer()) || + !var->isParentInitialized()) || var->getAttrs().hasAttribute()) { arg->setDefaultArgumentKind(DefaultArgumentKind::NilLiteral); return; @@ -1909,7 +1905,7 @@ bool swift::isMemberwiseInitialized(VarDecl *var) { // Initialized 'let' properties have storage, but don't get an argument // to the memberwise initializer since they already have an initial // value that cannot be overridden. - if (var->isLet() && var->getParentInitializer()) + if (var->isLet() && var->isParentInitialized()) return false; return true; diff --git a/lib/Sema/DebuggerTestingTransform.cpp b/lib/Sema/DebuggerTestingTransform.cpp index 9a091a2ea01ad..a3ac3752cec57 100644 --- a/lib/Sema/DebuggerTestingTransform.cpp +++ b/lib/Sema/DebuggerTestingTransform.cpp @@ -177,7 +177,7 @@ class DebuggerTestingTransform : public ASTWalker { // Don't capture variables which aren't default-initialized. if (auto *VD = dyn_cast(DstDecl)) - if (!VD->getParentInitializer() && !VD->isInOut()) + if (!VD->isParentInitialized() && !VD->isInOut()) return {true, OriginalExpr}; // Rewrite the original expression into this: diff --git a/lib/Sema/DerivedConformanceCodable.cpp b/lib/Sema/DerivedConformanceCodable.cpp index 049efec3afc6a..525892e9db3f1 100644 --- a/lib/Sema/DerivedConformanceCodable.cpp +++ b/lib/Sema/DerivedConformanceCodable.cpp @@ -223,7 +223,7 @@ static bool validateCodingKeysEnum(DerivedConformance &derived, continue; } - if (varDecl->getParentInitializer()) + if (varDecl->isParentInitialized()) continue; // The var was not default initializable, and did not have an explicit @@ -870,7 +870,7 @@ static void deriveBodyDecodable_init(AbstractFunctionDecl *initDecl, void *) { lookupVarDeclForCodingKeysCase(conformanceDC, elt, targetDecl); // Don't output a decode statement for a var let with a default value. - if (varDecl->isLet() && varDecl->getParentInitializer() != nullptr) + if (varDecl->isLet() && varDecl->isParentInitialized()) continue; auto methodName = diff --git a/lib/Sema/TypeCheckDecl.cpp b/lib/Sema/TypeCheckDecl.cpp index 6768ce1b505b9..cd9a701f74d8a 100644 --- a/lib/Sema/TypeCheckDecl.cpp +++ b/lib/Sema/TypeCheckDecl.cpp @@ -4990,6 +4990,31 @@ static void diagnoseClassWithoutInitializers(TypeChecker &tc, } } + // Lazily construct a mapping from backing storage properties to the + // declared properties. + bool computedBackingToOriginalVars = false; + llvm::SmallDenseMap backingToOriginalVars; + auto getOriginalVar = [&](VarDecl *var) -> VarDecl * { + // If we haven't computed the mapping yet, do so now. + if (!computedBackingToOriginalVars) { + for (auto member : classDecl->getMembers()) { + if (auto var = dyn_cast(member)) { + if (auto backingVar = var->getPropertyDelegateBackingProperty()) { + backingToOriginalVars[backingVar] = var; + } + } + } + + computedBackingToOriginalVars = true; + } + + auto known = backingToOriginalVars.find(var); + if (known == backingToOriginalVars.end()) + return nullptr; + + return known->second; + }; + for (auto member : classDecl->getMembers()) { auto pbd = dyn_cast(member); if (!pbd) @@ -5000,12 +5025,19 @@ static void diagnoseClassWithoutInitializers(TypeChecker &tc, continue; for (auto entry : pbd->getPatternList()) { - if (entry.getInit()) continue; + if (entry.isInitialized()) continue; SmallVector vars; entry.getPattern()->collectVariables(vars); if (vars.empty()) continue; + // Replace the variables we found with the originals for diagnostic + // purposes. + for (auto &var : vars) { + if (auto originalVar = getOriginalVar(var)) + var = originalVar; + } + auto varLoc = vars[0]->getLoc(); Optional diag; @@ -5235,6 +5267,7 @@ void TypeChecker::addImplicitConstructors(NominalTypeDecl *decl) { } } else { + SmallPtrSet backingStorageVars; for (auto member : decl->getMembers()) { if (auto ctor = dyn_cast(member)) { // Initializers that were synthesized to fulfill derived conformances @@ -5259,16 +5292,22 @@ void TypeChecker::addImplicitConstructors(NominalTypeDecl *decl) { if (auto var = dyn_cast(member)) { // If this variable has a property delegate, go validate it to ensure // that we create the backing storage property. - if (var->getAttachedPropertyDelegate()) { + if (auto backingVar = var->getPropertyDelegateBackingProperty()) { validateDecl(var); maybeAddAccessorsToStorage(var); + + backingStorageVars.insert(backingVar); } + // Ignore the backing storage for properties with attached delegates. + if (backingStorageVars.count(var) > 0) + continue; + if (isMemberwiseInitialized(var)) { // Initialized 'let' properties have storage, but don't get an argument // to the memberwise initializer since they already have an initial // value that cannot be overridden. - if (var->isLet() && var->getParentInitializer()) { + if (var->isLet() && var->isParentInitialized()) { // We cannot handle properties like: // let (a,b) = (1,2) @@ -5295,7 +5334,7 @@ void TypeChecker::addImplicitConstructors(NominalTypeDecl *decl) { if (auto pbd = dyn_cast(member)) { if (pbd->hasStorage() && !pbd->isStatic()) { for (auto entry : pbd->getPatternList()) { - if (entry.getInit()) continue; + if (entry.isInitialized()) continue; // If one of the bound variables is @NSManaged, go ahead no matter // what. diff --git a/test/decl/var/property_delegates.swift b/test/decl/var/property_delegates.swift index bd8c89652a0fb..1aaae571c0d9f 100644 --- a/test/decl/var/property_delegates.swift +++ b/test/decl/var/property_delegates.swift @@ -510,3 +510,72 @@ func testBox(ub: UseBox) { mutableUB.y = 20 mutableUB = ub } + +// --------------------------------------------------------------------------- +// Memberwise initializers +// --------------------------------------------------------------------------- +struct MemberwiseInits { + @Wrapper + var x: Bool + + @WrapperWithInitialValue + var y: T +} + +func testMemberwiseInits() { + // expected-error@+1{{type '(Wrapper, Double) -> MemberwiseInits'}} + let _: Int = MemberwiseInits.init + + _ = MemberwiseInits(x: Wrapper(value: true), y: 17) +} + +struct DefaultedMemberwiseInits { + @Wrapper(value: true) + var x: Bool + + @WrapperWithInitialValue + var y: Int = 17 +} + +func testDefaultedMemberwiseInits() { + _ = DefaultedMemberwiseInits() + _ = DefaultedMemberwiseInits(x: Wrapper(value: false), y: 42) + + _ = DefaultedMemberwiseInits(y: 42) + _ = DefaultedMemberwiseInits(x: Wrapper(value: false)) +} + +// --------------------------------------------------------------------------- +// Default initializers +// --------------------------------------------------------------------------- +struct DefaultInitializerStruct { + @Wrapper(value: true) + var x + + @WrapperWithInitialValue + var y: Int = 10 +} + +struct NoDefaultInitializerStruct { // expected-note{{'init(x:)' declared here}} + @Wrapper + var x: Bool +} + +class DefaultInitializerClass { + @Wrapper(value: true) + final var x + + @WrapperWithInitialValue + final var y: Int = 10 +} + +class NoDefaultInitializerClass { // expected-error{{class 'NoDefaultInitializerClass' has no initializers}} + @Wrapper + final var x: Bool // expected-note{{stored property 'x' without initial value prevents synthesized initializers}} +} + +func testDefaultInitializers() { + _ = DefaultInitializerStruct() + _ = DefaultInitializerClass() + _ = NoDefaultInitializerStruct() // expected-error{{missing argument for parameter 'x' in call}} +} From 198751e6c0f862641f7371a14e5a042efd371f1f Mon Sep 17 00:00:00 2001 From: Doug Gregor Date: Fri, 5 Apr 2019 11:06:25 -0700 Subject: [PATCH 10/31] Improve test for nonmutating due to a reference-type delegate --- test/decl/var/property_delegates.swift | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/test/decl/var/property_delegates.swift b/test/decl/var/property_delegates.swift index 1aaae571c0d9f..ef49e62673971 100644 --- a/test/decl/var/property_delegates.swift +++ b/test/decl/var/property_delegates.swift @@ -406,6 +406,15 @@ struct DelegateWithMutatingGetter { } } +@propertyDelegate +class ClassDelegate { + var value: Value + + init(initialValue: Value) { + self.value = initialValue + } +} + struct UseMutatingnessDelegates { @DelegateWithNonMutatingSetter var x = true @@ -415,6 +424,9 @@ struct UseMutatingnessDelegates { @DelegateWithNonMutatingSetter let z = 3.14159 // expected-note 2{{change 'let' to 'var' to make it mutable}} + + @ClassDelegate + var w = "Hello" } func testMutatingness() { @@ -429,6 +441,9 @@ func testMutatingness() { _ = mutable.z mutable.z = 2.71828 // expected-error{{cannot assign to property: 'z' is a 'let' constant}} + _ = mutable.w + mutable.w = "Goodbye" + let nonmutable = UseMutatingnessDelegates() // expected-note 2{{change 'let' to 'var' to make it mutable}} // Okay due to nonmutating setter @@ -440,6 +455,10 @@ func testMutatingness() { _ = nonmutable.z nonmutable.z = 2.71828 // expected-error{{cannot assign to property: 'z' is a 'let' constant}} + + // Okay due to implicitly nonmutating setter + _ = nonmutable.w + nonmutable.w = "World" } // --------------------------------------------------------------------------- From cc68b12d1a5fd75eba0588e682c8c0b4da34557b Mon Sep 17 00:00:00 2001 From: Doug Gregor Date: Thu, 11 Apr 2019 10:23:16 -0700 Subject: [PATCH 11/31] [SILGen] Initialization of instance properties with property delegates The initialization of an instance property that has an attached property delegate involves the initial value written on the property declaration, the implicit memberwise initializer, and the default arguments to the implicit memberwise initializer. Implement SILGen support for each of these cases. There is a small semantic change to the creation of the implicit memberwise initializer due to SE-0242 (default arguments for the memberwise initializer). Specifically, the memberwise initializer will use the original property type for the parameter to memberwise initializer when either of the following is true: - The corresponding property has an initial value specified with the `=` syntax, e.g., `@Lazy var i = 17`, or - The corresponding property has no initial value, but the property delegate type has an `init(initialValue:)`. The specific case that changed is when a property has an initial value specified as a direct initialization of the delegate *and* the property delegate type has an `init(initialValue:)`, e.g., ```swift struct X { @Lazy(closure: { ... }) var i: Int } ``` Previously, this would have synthesized an initializer: ```swift init(i: Int = ???) { ... } ``` However, there is no way for the initialization specified within the declaration of i to be expressed via the default argument. Now, it synthesizes an initializer: ```swift init(i: Lazy = Lazy(closure: { ... })) ``` --- include/swift/AST/ASTTypeIDZone.def | 1 + include/swift/AST/ASTTypeIDs.h | 1 + include/swift/AST/Decl.h | 24 ++- include/swift/AST/Expr.h | 15 +- include/swift/AST/TypeCheckRequests.h | 13 +- include/swift/AST/TypeCheckerTypeIDZone.def | 2 +- lib/AST/ASTContext.cpp | 19 ++ lib/AST/Decl.cpp | 28 ++- lib/AST/TypeCheckRequests.cpp | 16 +- lib/SIL/TypeLowering.cpp | 10 ++ lib/SILGen/SILGen.cpp | 13 ++ lib/SILGen/SILGenConstructor.cpp | 78 +++++++- lib/SILGen/SILGenFunction.cpp | 18 +- lib/Sema/CSApply.cpp | 3 +- lib/Sema/CSGen.cpp | 5 +- lib/Sema/CodeSynthesis.cpp | 111 +++++++++--- lib/Sema/TypeCheckError.cpp | 10 ++ lib/Sema/TypeChecker.h | 2 + test/SILGen/property_delegates.swift | 190 ++++++++++++++++++++ test/decl/var/property_delegates.swift | 9 +- 20 files changed, 517 insertions(+), 51 deletions(-) create mode 100644 test/SILGen/property_delegates.swift diff --git a/include/swift/AST/ASTTypeIDZone.def b/include/swift/AST/ASTTypeIDZone.def index 9e2e956740704..fa43f4f5adb13 100644 --- a/include/swift/AST/ASTTypeIDZone.def +++ b/include/swift/AST/ASTTypeIDZone.def @@ -17,5 +17,6 @@ SWIFT_TYPEID_NAMED(NominalTypeDecl *, NominalTypeDecl) SWIFT_TYPEID_NAMED(VarDecl *, VarDecl) SWIFT_TYPEID(Type) +SWIFT_TYPEID(PropertyDelegateBackingPropertyInfo) SWIFT_TYPEID(PropertyDelegateTypeInfo) SWIFT_TYPEID_NAMED(CustomAttr *, CustomAttr) diff --git a/include/swift/AST/ASTTypeIDs.h b/include/swift/AST/ASTTypeIDs.h index 34336e2e18219..6a5b007b46862 100644 --- a/include/swift/AST/ASTTypeIDs.h +++ b/include/swift/AST/ASTTypeIDs.h @@ -22,6 +22,7 @@ namespace swift { class CustomAttr; class NominalTypeDecl; +struct PropertyDelegateBackingPropertyInfo; struct PropertyDelegateTypeInfo; class Type; class VarDecl; diff --git a/include/swift/AST/Decl.h b/include/swift/AST/Decl.h index d2b464c3322c2..aa83b16afc35b 100644 --- a/include/swift/AST/Decl.h +++ b/include/swift/AST/Decl.h @@ -365,7 +365,7 @@ class alignas(1 << DeclAlignInBits) Decl { IsStatic : 1 ); - SWIFT_INLINE_BITFIELD(VarDecl, AbstractStorageDecl, 4+1+1+1+1, + SWIFT_INLINE_BITFIELD(VarDecl, AbstractStorageDecl, 4+1+1+1+1+1, /// The specifier associated with this variable or parameter. This /// determines the storage semantics of the value e.g. mutability. Specifier : 4, @@ -384,7 +384,10 @@ class alignas(1 << DeclAlignInBits) Decl { /// Whether this is a property defined in the debugger's REPL. /// FIXME: Remove this once LLDB has proper support for resilience. - IsREPLVar : 1 + IsREPLVar : 1, + + /// Whether this is the backing storage for a property delegate. + IsPropertyDelegateBackingProperty : 1 ); SWIFT_INLINE_BITFIELD(ParamDecl, VarDecl, 1 + NumDefaultArgumentKindBits, @@ -4744,6 +4747,7 @@ class VarDecl : public AbstractStorageDecl { Bits.VarDecl.IsDebuggerVar = false; Bits.VarDecl.IsREPLVar = false; Bits.VarDecl.HasNonPatternBindingInit = false; + Bits.VarDecl.IsPropertyDelegateBackingProperty = false; } /// This is the type specified, including location information. @@ -5072,6 +5076,22 @@ class VarDecl : public AbstractStorageDecl { /// bound generic version. VarDecl *getPropertyDelegateBackingProperty() const; + /// Whether this is a property with a property delegate that was initialized + /// via a value of the original type, e.g., + /// + /// \code + /// @Lazy var i = 17 + /// \end + bool isPropertyDelegateInitializedWithInitialValue() const; + + /// If this property is the backing storage for a property with an attached + /// property delegate, return the original property. + VarDecl *getOriginalDelegatedProperty() const; + + /// Set the property that delegates to this property as it's backing + /// property. + void setOriginalDelegatedProperty(VarDecl *originalProperty); + /// Return the Objective-C runtime name for this property. Identifier getObjCPropertyName() const; diff --git a/include/swift/AST/Expr.h b/include/swift/AST/Expr.h index d690da7f4094d..04691dc5575f4 100644 --- a/include/swift/AST/Expr.h +++ b/include/swift/AST/Expr.h @@ -364,6 +364,10 @@ class alignas(8) Expr { NumElements : 32 ); + SWIFT_INLINE_BITFIELD(OpaqueValueExpr, Expr, 1, + IsPlaceholder : 1 + ); + } Bits; private: @@ -3778,8 +3782,15 @@ class OpaqueValueExpr : public Expr { SourceLoc Loc; public: - explicit OpaqueValueExpr(SourceLoc Loc, Type Ty) - : Expr(ExprKind::OpaqueValue, /*Implicit=*/true, Ty), Loc(Loc) { } + explicit OpaqueValueExpr(SourceLoc Loc, Type Ty, bool isPlaceholder = false) + : Expr(ExprKind::OpaqueValue, /*Implicit=*/true, Ty), Loc(Loc) { + Bits.OpaqueValueExpr.IsPlaceholder = isPlaceholder; + } + + /// Whether this opaque value expression represents a placeholder that + /// is injected before type checking to act as a placeholder for some + /// value to be specified later. + bool isPlaceholder() const { return Bits.OpaqueValueExpr.IsPlaceholder; } SourceRange getSourceRange() const { return Loc; } diff --git a/include/swift/AST/TypeCheckRequests.h b/include/swift/AST/TypeCheckRequests.h index 483794b770d35..79c6ecb6298f1 100644 --- a/include/swift/AST/TypeCheckRequests.h +++ b/include/swift/AST/TypeCheckRequests.h @@ -30,6 +30,7 @@ namespace swift { class GenericParamList; +struct PropertyDelegateBackingPropertyInfo; class RequirementRepr; class SpecializeAttr; struct TypeLoc; @@ -520,12 +521,12 @@ class PropertyDelegateBackingPropertyTypeRequest : void noteCycleStep(DiagnosticEngine &diags) const; }; -/// Request the nominal type declaration to which the given custom attribute -/// refers. -class PropertyDelegateBackingPropertyRequest : - public SimpleRequest { public: using SimpleRequest::SimpleRequest; @@ -534,7 +535,7 @@ class PropertyDelegateBackingPropertyRequest : friend SimpleRequest; // Evaluation. - llvm::Expected + llvm::Expected evaluate(Evaluator &evaluator, VarDecl *var) const; public: diff --git a/include/swift/AST/TypeCheckerTypeIDZone.def b/include/swift/AST/TypeCheckerTypeIDZone.def index 63b2d833b199b..87e92c331e848 100644 --- a/include/swift/AST/TypeCheckerTypeIDZone.def +++ b/include/swift/AST/TypeCheckerTypeIDZone.def @@ -29,4 +29,4 @@ SWIFT_TYPEID(PropertyDelegateTypeInfoRequest) SWIFT_TYPEID(AttachedPropertyDelegateRequest) SWIFT_TYPEID(AttachedPropertyDelegateTypeRequest) SWIFT_TYPEID(PropertyDelegateBackingPropertyTypeRequest) -SWIFT_TYPEID(PropertyDelegateBackingPropertyRequest) +SWIFT_TYPEID(PropertyDelegateBackingPropertyInfoRequest) diff --git a/lib/AST/ASTContext.cpp b/lib/AST/ASTContext.cpp index 400641afd2fca..cdb2339a4a26f 100644 --- a/lib/AST/ASTContext.cpp +++ b/lib/AST/ASTContext.cpp @@ -286,6 +286,10 @@ FOR_KNOWN_FOUNDATION_TYPES(CACHE_FOUNDATION_DECL) /// Mapping from property declarations to the backing variable types. llvm::DenseMap PropertyDelegateBackingVarTypes; + /// A mapping from the backing storage of a property that has a delegate + /// to the original property with the delegate. + llvm::DenseMap OriginalDelegatedProperties; + /// Structure that captures data that is segregated into different /// arenas. struct Arena { @@ -4366,3 +4370,18 @@ void ASTContext::setSideCachedPropertyDelegateBackingPropertyType( getImpl().PropertyDelegateBackingVarTypes[var] = type; } +VarDecl *VarDecl::getOriginalDelegatedProperty() const { + if (!Bits.VarDecl.IsPropertyDelegateBackingProperty) + return nullptr; + + ASTContext &ctx = getASTContext(); + assert(ctx.getImpl().OriginalDelegatedProperties.count(this) > 0); + return ctx.getImpl().OriginalDelegatedProperties[this]; +} + +void VarDecl::setOriginalDelegatedProperty(VarDecl *originalProperty) { + Bits.VarDecl.IsPropertyDelegateBackingProperty = true; + ASTContext &ctx = getASTContext(); + assert(ctx.getImpl().OriginalDelegatedProperties.count(this) == 0); + ctx.getImpl().OriginalDelegatedProperties[this] = originalProperty; +} diff --git a/lib/AST/Decl.cpp b/lib/AST/Decl.cpp index 500e528633db3..25193dd1cf1f5 100644 --- a/lib/AST/Decl.cpp +++ b/lib/AST/Decl.cpp @@ -5270,9 +5270,31 @@ VarDecl *VarDecl::getPropertyDelegateBackingProperty() const { return nullptr; auto mutableThis = const_cast(this); - return evaluateOrDefault(ctx.evaluator, - PropertyDelegateBackingPropertyRequest{mutableThis}, - nullptr); + return evaluateOrDefault( + ctx.evaluator, + PropertyDelegateBackingPropertyInfoRequest{mutableThis}, + PropertyDelegateBackingPropertyInfo()).backingVar; +} + +bool VarDecl::isPropertyDelegateInitializedWithInitialValue() const { + auto &ctx = getASTContext(); + if (!ctx.getLazyResolver()) + return false; + + auto mutableThis = const_cast(this); + + // If there is no initializer, the initialization form depends on + // whether the property delegate type has an init(initialValue:). + if (!isParentInitialized()) { + auto delegateTypeInfo = getAttachedPropertyDelegateTypeInfo(); + return delegateTypeInfo.initialValueInit != nullptr; + } + + // Otherwise, check whether the '=' initialization form was used. + return evaluateOrDefault( + ctx.evaluator, + PropertyDelegateBackingPropertyInfoRequest{mutableThis}, + PropertyDelegateBackingPropertyInfo()).originalInitialValue != nullptr; } Identifier VarDecl::getObjCPropertyName() const { diff --git a/lib/AST/TypeCheckRequests.cpp b/lib/AST/TypeCheckRequests.cpp index 694f494f72fca..d3d8995b6164f 100644 --- a/lib/AST/TypeCheckRequests.cpp +++ b/lib/AST/TypeCheckRequests.cpp @@ -607,17 +607,17 @@ void PropertyDelegateBackingPropertyTypeRequest::noteCycleStep( DiagnosticEngine &diags) const { std::get<0>(getStorage())->diagnose(diag::circular_reference_through); } -bool PropertyDelegateBackingPropertyRequest::isCached() const { +bool PropertyDelegateBackingPropertyInfoRequest::isCached() const { auto var = std::get<0>(getStorage()); return !var->getAttrs().isEmpty(); } -void PropertyDelegateBackingPropertyRequest::diagnoseCycle( +void PropertyDelegateBackingPropertyInfoRequest::diagnoseCycle( DiagnosticEngine &diags) const { std::get<0>(getStorage())->diagnose(diag::circular_reference); } -void PropertyDelegateBackingPropertyRequest::noteCycleStep( +void PropertyDelegateBackingPropertyInfoRequest::noteCycleStep( DiagnosticEngine &diags) const { std::get<0>(getStorage())->diagnose(diag::circular_reference_through); } @@ -637,6 +637,16 @@ void swift::simple_display( out << " }"; } +void swift::simple_display( + llvm::raw_ostream &out, + const PropertyDelegateBackingPropertyInfo &backingInfo) { + out << "{ "; + if (backingInfo.backingVar) + backingInfo.backingVar->dumpRef(out); + out << " }"; +} + + template<> bool swift::AnyValue::Holder::equals(const HolderBase &other) const { assert(typeID == other.typeID && "Caller should match type IDs"); diff --git a/lib/SIL/TypeLowering.cpp b/lib/SIL/TypeLowering.cpp index f6c941145fac5..e4af4bddde5d5 100644 --- a/lib/SIL/TypeLowering.cpp +++ b/lib/SIL/TypeLowering.cpp @@ -22,6 +22,7 @@ #include "swift/AST/Module.h" #include "swift/AST/NameLookup.h" #include "swift/AST/Pattern.h" +#include "swift/AST/PropertyDelegates.h" #include "swift/AST/Types.h" #include "swift/ClangImporter/ClangModule.h" #include "swift/SIL/SILArgument.h" @@ -1773,6 +1774,15 @@ static CanAnyFunctionType getStoredPropertyInitializerInterfaceType( CanType resultTy = VD->getParentPattern()->getType()->mapTypeOutOfContext() ->getCanonicalType(); + + // If this is the backing storage for a property with an attached + // delegate that was initialized with '=', the stored property initializer + // will be in terms of the original property's type. + if (auto originalProperty = VD->getOriginalDelegatedProperty()) { + if (originalProperty->isPropertyDelegateInitializedWithInitialValue()) + resultTy = originalProperty->getValueInterfaceType()->getCanonicalType(); + } + auto sig = TC.getEffectiveGenericSignature(DC); return CanAnyFunctionType::get(sig, {}, resultTy); diff --git a/lib/SILGen/SILGen.cpp b/lib/SILGen/SILGen.cpp index 2ae657a1cf9a7..9473d53b3b23c 100644 --- a/lib/SILGen/SILGen.cpp +++ b/lib/SILGen/SILGen.cpp @@ -22,8 +22,10 @@ #include "swift/AST/NameLookup.h" #include "swift/AST/ParameterList.h" #include "swift/AST/PrettyStackTrace.h" +#include "swift/AST/PropertyDelegates.h" #include "swift/AST/ProtocolConformance.h" #include "swift/AST/ResilienceExpansion.h" +#include "swift/AST/TypeCheckRequests.h" #include "swift/Basic/Statistic.h" #include "swift/Basic/Timer.h" #include "swift/ClangImporter/ClangModule.h" @@ -1115,6 +1117,17 @@ emitStoredPropertyInitialization(PatternBindingDecl *pbd, unsigned i) { auto *initDC = pbdEntry.getInitContext(); assert(!pbdEntry.isInitializerSubsumed()); + // If this is the backing storage for a property with an attached delegate + // that was initialized with `=`, use that expression as the initializer. + if (auto originalProperty = var->getOriginalDelegatedProperty()) { + auto delegateInfo = evaluateOrDefault( + getASTContext().evaluator, + PropertyDelegateBackingPropertyInfoRequest{originalProperty}, + PropertyDelegateBackingPropertyInfo()); + if (delegateInfo.originalInitialValue) + init = delegateInfo.originalInitialValue; + } + SILDeclRef constant(var, SILDeclRef::Kind::StoredPropertyInitializer); emitOrDelayFunction(*this, constant, [this,constant,init,initDC,var](SILFunction *f) { diff --git a/lib/SILGen/SILGenConstructor.cpp b/lib/SILGen/SILGenConstructor.cpp index 8e2aac6ae20b6..ac6c2e7cc6e24 100644 --- a/lib/SILGen/SILGenConstructor.cpp +++ b/lib/SILGen/SILGenConstructor.cpp @@ -20,6 +20,8 @@ #include "swift/AST/ASTMangler.h" #include "swift/AST/GenericEnvironment.h" #include "swift/AST/ParameterList.h" +#include "swift/AST/PropertyDelegates.h" +#include "swift/AST/TypeCheckRequests.h" #include "swift/Basic/Defer.h" #include "swift/SIL/SILArgument.h" #include "swift/SIL/SILUndef.h" @@ -97,6 +99,40 @@ static RValue emitImplicitValueConstructorArg(SILGenFunction &SGF, return RValue(SGF, loc, type, mvArg); } +/// If the field has a property delegate for which we will need to call the +/// delegate type's init(initialValue:), set up that evaluation and call the +/// \c body with the expression to form the property delegate instance from +/// the initial value type. +/// +/// \returns true if this was such a delegate, \c false otherwise. +static bool maybeEmitPropertyDelegateInitFromValue( + SILGenFunction &SGF, + SILLocation loc, + VarDecl *field, + RValue &&arg, + llvm::function_ref body) { + auto originalProperty = field->getOriginalDelegatedProperty(); + if (!originalProperty || + !originalProperty->isPropertyDelegateInitializedWithInitialValue()) + return false; + + ASTContext &ctx = field->getASTContext(); + auto delegateInfo = evaluateOrDefault( + ctx.evaluator, + PropertyDelegateBackingPropertyInfoRequest{originalProperty}, + PropertyDelegateBackingPropertyInfo()); + if (!delegateInfo || !delegateInfo.initializeFromOriginal) + return false; + + SILGenFunction::OpaqueValueRAII opaqueValue( + SGF, + delegateInfo.underlyingValue, + std::move(arg).getAsSingleValue(SGF, loc)); + + body(delegateInfo.initializeFromOriginal); + return true; +} + static void emitImplicitValueConstructor(SILGenFunction &SGF, ConstructorDecl *ctor) { RegularLocation Loc(ctor); @@ -138,7 +174,6 @@ static void emitImplicitValueConstructor(SILGenFunction &SGF, // If we have an indirect return slot, initialize it in-place. if (resultSlot) { - auto elti = elements.begin(), eltEnd = elements.end(); for (VarDecl *field : decl->getStoredProperties()) { auto fieldTy = selfTy.getFieldType(field, SGF.SGM.M); @@ -164,7 +199,14 @@ static void emitImplicitValueConstructor(SILGenFunction &SGF, assert(elti != eltEnd && "number of args does not match number of fields"); (void)eltEnd; - std::move(*elti).forwardInto(SGF, Loc, init.get()); + FullExpr scope(SGF.Cleanups, field->getParentPatternBinding()); + if (!maybeEmitPropertyDelegateInitFromValue( + SGF, Loc, field, std::move(*elti), + [&](Expr *expr) { + SGF.emitExprInto(expr, init.get()); + })) { + std::move(*elti).forwardInto(SGF, Loc, init.get()); + } ++elti; } SGF.B.createReturn(ImplicitReturnLocation::getImplicitReturnLoc(Loc), @@ -188,9 +230,17 @@ static void emitImplicitValueConstructor(SILGenFunction &SGF, v = SGF.emitRValue(field->getParentInitializer()) .forwardAsSingleStorageValue(SGF, fieldTy, Loc); } else { + FullExpr scope(SGF.Cleanups, field->getParentPatternBinding()); assert(elti != eltEnd && "number of args does not match number of fields"); (void)eltEnd; - v = std::move(*elti).forwardAsSingleStorageValue(SGF, fieldTy, Loc); + if (!maybeEmitPropertyDelegateInitFromValue( + SGF, Loc, field, std::move(*elti), + [&](Expr *expr) { + v = SGF.emitRValue(expr) + .forwardAsSingleStorageValue(SGF, fieldTy, Loc); + })) { + v = std::move(*elti).forwardAsSingleStorageValue(SGF, fieldTy, Loc); + } ++elti; } @@ -871,6 +921,17 @@ static Type getInitializationTypeInContext( DeclContext *fromDC, DeclContext *toDC, Pattern *pattern) { auto interfaceType = pattern->getType()->mapTypeOutOfContext(); + + // If this pattern is initializing the backing storage for a property + // with an attached delegate that is initialized with `=`, the + // initialization type is the original property type. + if (auto singleVar = pattern->getSingleVar()) { + if (auto originalProperty = singleVar->getOriginalDelegatedProperty()) { + if (originalProperty->isPropertyDelegateInitializedWithInitialValue()) + interfaceType = originalProperty->getValueInterfaceType(); + } + } + auto resultType = toDC->mapTypeIntoContext(interfaceType); return resultType; @@ -928,6 +989,17 @@ void SILGenFunction::emitMemberInitializers(DeclContext *dc, resultType, origResultType, SGFContext()); + // If we have the backing storage for a property with an attached + // property delegate initialized with `=`, inject the value into an + // instance of the delegate. + if (auto singleVar = pbd->getSingleVar()) { + (void)maybeEmitPropertyDelegateInitFromValue( + *this, init, singleVar, std::move(result), + [&](Expr *expr) { + result = emitRValue(expr); + }); + } + emitMemberInit(*this, selfDecl, entry.getPattern(), std::move(result)); } } diff --git a/lib/SILGen/SILGenFunction.cpp b/lib/SILGen/SILGenFunction.cpp index 855ff8adfe05b..0e9d529760621 100644 --- a/lib/SILGen/SILGenFunction.cpp +++ b/lib/SILGen/SILGenFunction.cpp @@ -20,6 +20,7 @@ #include "SILGenFunctionBuilder.h" #include "Scope.h" #include "swift/AST/Initializer.h" +#include "swift/AST/PropertyDelegates.h" #include "swift/SIL/SILArgument.h" #include "swift/SIL/SILProfiler.h" #include "swift/SIL/SILUndef.h" @@ -651,14 +652,27 @@ void SILGenFunction::emitGeneratorFunction(SILDeclRef function, VarDecl *var) { auto decl = function.getAbstractFunctionDecl(); auto *dc = decl->getInnermostDeclContext(); auto interfaceType = var->getValueInterfaceType(); + auto varType = var->getType(); + + // If this is the backing storage for a property with an attached + // delegate that was initialized with '=', the stored property initializer + // will be in terms of the original property's type. + if (auto originalProperty = var->getOriginalDelegatedProperty()) { + if (originalProperty->isPropertyDelegateInitializedWithInitialValue()) { + interfaceType = originalProperty->getValueInterfaceType(); + varType = originalProperty->getType(); + } + } + emitProlog(/*paramList*/ nullptr, /*selfParam*/ nullptr, interfaceType, dc, false); - prepareEpilog(var->getType(), false, CleanupLocation::get(loc)); + prepareEpilog(varType, false, CleanupLocation::get(loc)); auto pbd = var->getParentPatternBinding(); auto entry = pbd->getPatternEntryForVarDecl(var); auto subs = getForwardingSubstitutionMap(); - auto resultType = decl->mapTypeIntoContext(interfaceType)->getCanonicalType(); + auto contextualType = dc->mapTypeIntoContext(interfaceType); + auto resultType = contextualType->getCanonicalType(); auto origResultType = AbstractionPattern(resultType); SmallVector directResults; diff --git a/lib/Sema/CSApply.cpp b/lib/Sema/CSApply.cpp index 59c9c4c355078..709031830584d 100644 --- a/lib/Sema/CSApply.cpp +++ b/lib/Sema/CSApply.cpp @@ -3189,7 +3189,8 @@ namespace { } Expr *visitOpaqueValueExpr(OpaqueValueExpr *expr) { - llvm_unreachable("Already type-checked"); + assert(expr->isPlaceholder() && "Already type-checked"); + return expr; } Expr *visitDefaultArgumentExpr(DefaultArgumentExpr *expr) { diff --git a/lib/Sema/CSGen.cpp b/lib/Sema/CSGen.cpp index 6ce0ef68395a9..d15b9785fbbdd 100644 --- a/lib/Sema/CSGen.cpp +++ b/lib/Sema/CSGen.cpp @@ -2490,7 +2490,8 @@ namespace { } Type visitOpaqueValueExpr(OpaqueValueExpr *expr) { - llvm_unreachable("Already type checked"); + assert(expr->isPlaceholder() && "Already type checked"); + return expr->getType(); } Type visitDefaultArgumentExpr(DefaultArgumentExpr *expr) { @@ -3288,7 +3289,7 @@ namespace { expr = value->second; continue; } else { - assert(eraseOpenExistentialsOnly && + assert((eraseOpenExistentialsOnly || OVE->isPlaceholder()) && "Didn't see this OVE in a containing OpenExistentialExpr?"); // NOTE: In 'eraseOpenExistentialsOnly' mode, ASTWalker may walk // into other kind of expressions holding OVE. diff --git a/lib/Sema/CodeSynthesis.cpp b/lib/Sema/CodeSynthesis.cpp index 6d612720704f6..b140bc7a9b492 100644 --- a/lib/Sema/CodeSynthesis.cpp +++ b/lib/Sema/CodeSynthesis.cpp @@ -1433,13 +1433,40 @@ void swift::completeLazyVarImplementation(VarDecl *VD) { Storage->overwriteSetterAccess(AccessLevel::Private); } -llvm::Expected -PropertyDelegateBackingPropertyRequest::evaluate(Evaluator &evaluator, +/// Given the initializer for the given property with an attached property +/// delegate, dig out the original initialization expression. +static Expr *findOriginalPropertyDelegateInitialValue(VarDecl *var, + Expr *init) { + auto attr = var->getAttachedPropertyDelegate(); + assert(attr && "No attached property delegate?"); + + // Direct initialization implies no original initial value. + if (attr->getArg()) + return nullptr; + + // Look through any expressions wrapping the initializer. + init = init->getSemanticsProvidingExpr(); + auto initCall = dyn_cast(init); + if (!initCall) + return nullptr; + + auto initArg = cast(initCall->getArg())->getElement(0); + initArg = initArg->getSemanticsProvidingExpr(); + if (auto autoclosure = dyn_cast(initArg)) { + initArg = + autoclosure->getSingleExpressionBody()->getSemanticsProvidingExpr(); + } + + return initArg; +} + +llvm::Expected +PropertyDelegateBackingPropertyInfoRequest::evaluate(Evaluator &evaluator, VarDecl *var) const { // Determine the type of the backing property. auto delegateType = var->getPropertyDelegateBackingPropertyType(); if (!delegateType) - return nullptr; + return PropertyDelegateBackingPropertyInfo(); // Compute the name of the storage type. ASTContext &ctx = var->getASTContext(); @@ -1471,6 +1498,7 @@ PropertyDelegateBackingPropertyRequest::evaluate(Evaluator &evaluator, backingVar->setImplicit(); if (isInvalid) backingVar->setInvalid(); + backingVar->setOriginalDelegatedProperty(var); addMemberToContextIfNeeded(backingVar, dc, var); // Create the pattern binding declaration for the backing property. @@ -1483,18 +1511,19 @@ PropertyDelegateBackingPropertyRequest::evaluate(Evaluator &evaluator, addMemberToContextIfNeeded(pbd, dc, var); // Take the initializer from the original property. - if (auto parentPBD = var->getParentPatternBinding()) { - unsigned patternNumber = parentPBD->getPatternEntryIndexForVarDecl(var); - if (parentPBD->getInit(patternNumber) && - !parentPBD->isInitializerChecked(patternNumber)) { - auto &tc = *static_cast(ctx.getLazyResolver()); - tc.typeCheckPatternBinding(parentPBD, patternNumber); - } + auto parentPBD = var->getParentPatternBinding(); + unsigned patternNumber = parentPBD->getPatternEntryIndexForVarDecl(var); + if (parentPBD->getInit(patternNumber) && + !parentPBD->isInitializerChecked(patternNumber)) { + auto &tc = *static_cast(ctx.getLazyResolver()); + tc.typeCheckPatternBinding(parentPBD, patternNumber); + } - if (Expr *init = parentPBD->getInit(patternNumber)) { - pbd->setInit(0, init); - pbd->setInitializerChecked(0); - } + Expr *originalInitialValue = nullptr; + if (Expr *init = parentPBD->getInit(patternNumber)) { + pbd->setInit(0, init); + pbd->setInitializerChecked(0); + originalInitialValue = findOriginalPropertyDelegateInitialValue(var, init); } // Mark the backing property as 'final'. There's no sensible way to override. @@ -1511,7 +1540,35 @@ PropertyDelegateBackingPropertyRequest::evaluate(Evaluator &evaluator, std::min(AccessLevel::Internal, var->getSetterFormalAccess()); backingVar->overwriteSetterAccess(setterAccess); - return backingVar; + // Get the property delegate information. + auto delegateInfo = var->getAttachedPropertyDelegateTypeInfo(); + if (!delegateInfo.initialValueInit) { + assert(!originalInitialValue); + return PropertyDelegateBackingPropertyInfo( + backingVar, nullptr, nullptr, nullptr); + } + + // Form the initialization of the backing property from a value of the + // original property's type. + OpaqueValueExpr *origValue = + new (ctx) OpaqueValueExpr(SourceLoc(), var->getType(), + /*isPlaceholder=*/true); + auto typeExpr = TypeExpr::createImplicit(storageType, ctx); + Expr *initializer = + CallExpr::createImplicit(ctx, typeExpr, {origValue}, + {ctx.Id_initialValue}); + + // Type-check the initialization. + auto &tc = *static_cast(ctx.getLazyResolver()); + tc.typeCheckExpression(initializer, dc); + if (auto initializerContext = + dyn_cast_or_null( + pbd->getPatternEntryForVarDecl(backingVar).getInitContext())) { + tc.contextualizeInitializer(initializerContext, initializer); + } + tc.checkPropertyDelegateErrorHandling(parentPBD, initializer); + return PropertyDelegateBackingPropertyInfo( + backingVar, originalInitialValue, initializer, origValue); } static bool wouldBeCircularSynthesis(AbstractStorageDecl *storage, @@ -1884,9 +1941,13 @@ static void maybeAddMemberwiseDefaultArg(ParamDecl *arg, VarDecl *var, return; } + // If there's a backing storage property, the memberwise initializer + // will be in terms of that. + VarDecl *backingStorageVar = var->getPropertyDelegateBackingProperty(); + // Set the default value to the variable. When we emit this in silgen // we're going to call the variable's initializer expression. - arg->setStoredProperty(var); + arg->setStoredProperty(backingStorageVar ? backingStorageVar : var); arg->setDefaultArgumentKind(DefaultArgumentKind::StoredProperty); } @@ -1952,15 +2013,15 @@ ConstructorDecl *swift::createImplicitConstructor(TypeChecker &tc, // the properties are type checked. Perhaps init() synth should be // moved later. varInterfaceType = OptionalType::get(varInterfaceType); - } else if (auto delegateTypeInfo = - var->getAttachedPropertyDelegateTypeInfo()) { - // For a property that has a delegate, the presence of an - // init(initialValue:) in the delegate type implies that the - // property can be initialized from the original property type. - // When there is no init(initialValue:), the underlying storage - // type will need to be initialized. - if (!delegateTypeInfo.initialValueInit) { - varInterfaceType = var->getPropertyDelegateBackingPropertyType(); + } else if (Type backingPropertyType = + var->getPropertyDelegateBackingPropertyType()) { + // For a property that has a delegate, 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->isPropertyDelegateInitializedWithInitialValue()) { + varInterfaceType = backingPropertyType; } } diff --git a/lib/Sema/TypeCheckError.cpp b/lib/Sema/TypeCheckError.cpp index 0fbbda4741f38..97e5d4f7983e9 100644 --- a/lib/Sema/TypeCheckError.cpp +++ b/lib/Sema/TypeCheckError.cpp @@ -934,6 +934,10 @@ class Context { return Context(Kind::CatchGuard); } + static Context forPatternBinding(PatternBindingDecl *binding) { + return getContextForPatternBinding(binding); + } + Context withInterpolatedString(InterpolatedStringLiteralExpr *E) const { Context copy = *this; copy.InterpolatedString = E; @@ -1659,3 +1663,9 @@ void TypeChecker::checkEnumElementErrorHandling(EnumElementDecl *elt) { init->walk(checker); } } + +void TypeChecker::checkPropertyDelegateErrorHandling( + PatternBindingDecl *binding, Expr *expr) { + CheckErrorCoverage checker(*this, Context::forPatternBinding(binding)); + expr->walk(checker); +} diff --git a/lib/Sema/TypeChecker.h b/lib/Sema/TypeChecker.h index 5d52e738e7b51..938e4bf59535f 100644 --- a/lib/Sema/TypeChecker.h +++ b/lib/Sema/TypeChecker.h @@ -2040,6 +2040,8 @@ class TypeChecker final : public LazyResolver { void checkFunctionErrorHandling(AbstractFunctionDecl *D); void checkInitializerErrorHandling(Initializer *I, Expr *E); void checkEnumElementErrorHandling(EnumElementDecl *D); + void checkPropertyDelegateErrorHandling(PatternBindingDecl *binding, + Expr *expr); void addExprForDiagnosis(Expr *E1, Expr *Result) { DiagnosedExprs[E1] = Result; diff --git a/test/SILGen/property_delegates.swift b/test/SILGen/property_delegates.swift new file mode 100644 index 0000000000000..7fad31750f7bc --- /dev/null +++ b/test/SILGen/property_delegates.swift @@ -0,0 +1,190 @@ +// RUN: %target-swift-frontend -primary-file %s -emit-silgen | %FileCheck %s +// FIXME: switch to %target-swift-emit-silgen once we have syntax tree support + +@propertyDelegate +struct Wrapper { + var value: T +} + +@propertyDelegate +struct WrapperWithInitialValue { + var value: T + + init(initialValue: T) { + self.value = initialValue + } +} + +protocol DefaultInit { + init() +} + +extension Int: DefaultInit { } + +struct HasMemberwiseInit { + @Wrapper(value: false) + var x: Bool + + @WrapperWithInitialValue + var y: T = T() + + @WrapperWithInitialValue(initialValue: 17) + var z: Int +} + +func forceHasMemberwiseInit() { + _ = HasMemberwiseInit(x: Wrapper(value: true), y: 17, z: WrapperWithInitialValue(initialValue: 42)) + _ = HasMemberwiseInit(x: Wrapper(value: true)) + _ = HasMemberwiseInit(y: 17) + _ = HasMemberwiseInit(z: WrapperWithInitialValue(initialValue: 42)) + _ = HasMemberwiseInit() +} + +// HasMemberwiseInit.x.setter +// CHECK-LABEL: sil hidden [transparent] [ossa] @$s18property_delegates17HasMemberwiseInitV1xSbvs : $@convention(method) (Bool, @inout HasMemberwiseInit) -> () { +// CHECK: bb0(%0 : $Bool, %1 : $*HasMemberwiseInit): +// CHECK: [[MODIFY_SELF:%.*]] = begin_access [modify] [unknown] %1 : $*HasMemberwiseInit +// CHECK: [[X_BACKING:%.*]] = struct_element_addr [[MODIFY_SELF]] : $*HasMemberwiseInit, #HasMemberwiseInit.$x +// CHECK: [[X_BACKING_VALUE:%.*]] = struct_element_addr [[X_BACKING]] : $*Wrapper, #Wrapper.value +// CHECK: assign %0 to [[X_BACKING_VALUE]] : $*Bool +// CHECK: end_access [[MODIFY_SELF]] : $*HasMemberwiseInit + +// variable initialization expression of HasMemberwiseInit.$x +// CHECK-LABEL: sil hidden [transparent] [ossa] @$s18property_delegates17HasMemberwiseInitV2$xAA7WrapperVySbGvpfi : $@convention(thin) () -> Wrapper { +// CHECK: integer_literal $Builtin.Int1, 0 +// CHECK-NOT: return +// CHECK: function_ref @$sSb22_builtinBooleanLiteralSbBi1__tcfC : $@convention(method) (Builtin.Int1, @thin Bool.Type) -> Bool +// CHECK-NOT: return +// CHECK: function_ref @$s18property_delegates7WrapperV5valueACyxGx_tcfC : $@convention(method) <τ_0_0> (@in τ_0_0, @thin Wrapper<τ_0_0>.Type) -> @out Wrapper<τ_0_0> // user: %9 +// CHECK: return {{%.*}} : $Wrapper + +// variable initialization expression of HasMemberwiseInit.$y +// CHECK-LABEL: sil hidden [transparent] [ossa] @$s18property_delegates17HasMemberwiseInitV2$yAA23WrapperWithInitialValueVyxGvpfi : $@convention(thin) () -> @out T { +// CHECK: bb0(%0 : $*T): +// CHECK-NOT: return +// CHECK: witness_method $T, #DefaultInit.init!allocator.1 : (Self.Type) -> () -> Self : $@convention(witness_method: DefaultInit) <τ_0_0 where τ_0_0 : DefaultInit> (@thick τ_0_0.Type) -> @out τ_0_0 + +// variable initialization expression of HasMemberwiseInit.$z +// CHECK-LABEL: sil hidden [transparent] [ossa] @$s18property_delegates17HasMemberwiseInitV2$zAA23WrapperWithInitialValueVySiGvpfi : $@convention(thin) () -> WrapperWithInitialValue { +// CHECK: bb0: +// CHECK-NOT: return +// CHECK: integer_literal $Builtin.IntLiteral, 17 +// CHECK-NOT: return +// CHECK: function_ref @$s18property_delegates23WrapperWithInitialValueV07initialF0ACyxGx_tcfC : $@convention(method) <τ_0_0> (@in τ_0_0, @thin WrapperWithInitialValue<τ_0_0>.Type) -> @out WrapperWithInitialValue<τ_0_0> + +// default argument 0 of HasMemberwiseInit.init(x:y:z:) +// CHECK: sil hidden [ossa] @$s18property_delegates17HasMemberwiseInitV1x1y1zACyxGAA7WrapperVySbG_xAA0F16WithInitialValueVySiGtcfcfA_ : $@convention(thin) () -> Wrapper + +// default argument 1 of HasMemberwiseInit.init(x:y:z:) +// CHECK: sil hidden [ossa] @$s18property_delegates17HasMemberwiseInitV1x1y1zACyxGAA7WrapperVySbG_xAA0F16WithInitialValueVySiGtcfcfA0_ : $@convention(thin) () -> @out T { + +// default argument 2 of HasMemberwiseInit.init(x:y:z:) +// CHECK: sil hidden [ossa] @$s18property_delegates17HasMemberwiseInitV1x1y1zACyxGAA7WrapperVySbG_xAA0F16WithInitialValueVySiGtcfcfA1_ : $@convention(thin) () -> WrapperWithInitialValue { + + +// HasMemberwiseInit.init() +// CHECK-LABEL: sil hidden [ossa] @$s18property_delegates17HasMemberwiseInitVACyxGycfC : $@convention(method) (@thin HasMemberwiseInit.Type) -> @out HasMemberwiseInit { + +// Initialization of x +// CHECK-NOT: return +// CHECK: function_ref @$s18property_delegates17HasMemberwiseInitV2$xAA7WrapperVySbGvpfi : $@convention(thin) <τ_0_0 where τ_0_0 : DefaultInit> () -> Wrapper + +// Initialization of y +// CHECK-NOT: return +// CHECK: function_ref @$s18property_delegates17HasMemberwiseInitV2$yAA23WrapperWithInitialValueVyxGvpfi : $@convention(thin) <τ_0_0 where τ_0_0 : DefaultInit> () -> @out τ_0_0 +// CHECK-NOT: return +// CHECK: function_ref @$s18property_delegates23WrapperWithInitialValueV07initialF0ACyxGx_tcfC : $@convention(method) <τ_0_0> (@in τ_0_0, @thin WrapperWithInitialValue<τ_0_0>.Type) -> @out WrapperWithInitialValue<τ_0_0> + +// Initialization of z +// CHECK-NOT: return +// CHECK: function_ref @$s18property_delegates17HasMemberwiseInitV2$zAA23WrapperWithInitialValueVySiGvpfi : $@convention(thin) <τ_0_0 where τ_0_0 : DefaultInit> () -> WrapperWithInitialValue + +// CHECK: return + +// CHECK-LABEL: sil private [ossa] @$s18property_delegates9HasNestedV1yACyxGSayxG_tc33_ +// CHECK: [[STACK_SLOT:%.*]] = alloc_stack $HasNested.PrivateDelegate> +// CHECK: [[METATYPE:%.*]] = metatype $@thin HasNested.PrivateDelegate>.Type +// CHECK: [[ARRAY_STACK_SLOT:%.*]] = alloc_stack $Array +// CHECK: store %0 to [init] [[ARRAY_STACK_SLOT]] : $*Array +// CHECK: [[INIT:%.*]] = function_ref @$s18property_delegates9HasNestedV15PrivateDelegate{{.*}}initialValue +// CHECK: [[DELEGATE_INSTANCE:%.*]] = apply [[INIT]]([[STACK_SLOT]], [[ARRAY_STACK_SLOT]], [[METATYPE]]) +// CHECK: [[DELEGATE_VALUE:%.*]] = load [take] [[STACK_SLOT]] : $*HasNested.PrivateDelegate> +// CHECK: struct $HasNested ([[DELEGATE_VALUE]] : $HasNested.PrivateDelegate>) +struct HasNested { + @propertyDelegate + private struct PrivateDelegate { + var value: U + init(initialValue: U) { + self.value = initialValue + } + } + + @PrivateDelegate + private var y: [T] = [] + + static func blah(y: [T]) -> HasNested { + return HasNested(y: y) + } +} + +// FIXME: For now, we are only checking that we don't crash. +struct HasDefaultInit { + @Wrapper(value: true) + var x + + @WrapperWithInitialValue + var y = 25 + + static func defaultInit() -> HasDefaultInit { + return HasDefaultInit() + } + + static func memberwiseInit(x: Bool, y: Int) -> HasDefaultInit { + return HasDefaultInit(x: Wrapper(value: x), y: y) + } +} + +struct DelegateWithAccessors { + @Wrapper + var x: Int { + // CHECK-LABEL: sil hidden [ossa] @$s18property_delegates21DelegateWithAccessorsV1xSivg + // CHECK-NOT: return + // CHECK: integer_literal $Builtin.IntLiteral, 42 + return 42 + + // Synthesized setter + // CHECK-LABEL: sil hidden [transparent] [ossa] @$s18property_delegates21DelegateWithAccessorsV1xSivs : $@convention(method) (Int, @inout DelegateWithAccessors) -> () + // CHECK-NOT: return + // CHECK: struct_element_addr {{%.*}} : $*DelegateWithAccessors, #DelegateWithAccessors.$x + } + + mutating func test() { + x = 17 + } +} + +func consumeOldValue(_: Int) { } +func consumeNewValue(_: Int) { } + +struct DelegateWithDidSetWillSet { + // CHECK-LABEL: sil hidden [ossa] @$s18property_delegates022DelegateWithDidSetW + // CHECK: function_ref @$s18property_delegates022DelegateWithDidSetWillF0V1xSivw + // CHECK: struct_element_addr {{%.*}} : $*DelegateWithDidSetWillSet, #DelegateWithDidSetWillSet.$x + // CHECK-NEXT: struct_element_addr {{%.*}} : $*Wrapper, #Wrapper.value + // CHECK-NEXT: assign %0 to {{%.*}} : $*Int + // CHECK: function_ref @$s18property_delegates022DelegateWithDidSetWillF0V1xSivW + @Wrapper + var x: Int { + didSet { + consumeNewValue(oldValue) + } + + willSet { + consumeOldValue(newValue) + } + } + + mutating func test(x: Int) { + self.x = x + } +} diff --git a/test/decl/var/property_delegates.swift b/test/decl/var/property_delegates.swift index ef49e62673971..51d3853fb6a44 100644 --- a/test/decl/var/property_delegates.swift +++ b/test/decl/var/property_delegates.swift @@ -554,14 +554,21 @@ struct DefaultedMemberwiseInits { @WrapperWithInitialValue var y: Int = 17 + + @WrapperWithInitialValue(initialValue: 17) + var z: Int } func testDefaultedMemberwiseInits() { _ = DefaultedMemberwiseInits() - _ = DefaultedMemberwiseInits(x: Wrapper(value: false), y: 42) + _ = DefaultedMemberwiseInits( + x: Wrapper(value: false), + y: 42, + z: WrapperWithInitialValue(initialValue: 42)) _ = DefaultedMemberwiseInits(y: 42) _ = DefaultedMemberwiseInits(x: Wrapper(value: false)) + _ = DefaultedMemberwiseInits(z: WrapperWithInitialValue(initialValue: 42)) } // --------------------------------------------------------------------------- From 9e224001176f72361d636977aa39372cd608b06b Mon Sep 17 00:00:00 2001 From: Doug Gregor Date: Thu, 11 Apr 2019 12:46:52 -0700 Subject: [PATCH 12/31] Add missing header file --- include/swift/AST/PropertyDelegates.h | 115 ++++++++++++++++++++++++++ 1 file changed, 115 insertions(+) create mode 100644 include/swift/AST/PropertyDelegates.h diff --git a/include/swift/AST/PropertyDelegates.h b/include/swift/AST/PropertyDelegates.h new file mode 100644 index 0000000000000..1a91092980a66 --- /dev/null +++ b/include/swift/AST/PropertyDelegates.h @@ -0,0 +1,115 @@ +//===--- PropertyDelegates.h - Property Delegate ASTs -----------*- C++ -*-===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2014 - 2018 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 defines helper types for property delegates. +// +//===----------------------------------------------------------------------===// + +#ifndef SWIFT_AST_PROPERTY_DELEGATES_H +#define SWIFT_AST_PROPERTY_DELEGATES_H + +namespace llvm { + class raw_ostream; +} + +namespace swift { + +class ConstructorDecl; +class CustomAttr; +class Expr; +class VarDecl; +class OpaqueValueExpr; + +/// Describes a property delegate type. +struct PropertyDelegateTypeInfo { + /// The property through to which access that uses this delegate type is + /// delegated. + VarDecl *valueVar = nullptr; + + /// The initializer init(initialValue:) that will be called when the + /// initiqlizing the property delegate type from a value of the property type. + ConstructorDecl *initialValueInit = nullptr; + + /// Whether this is a valid property delegate. + bool isValid() const { + return valueVar != nullptr; + } + + explicit operator bool() const { return isValid(); } + + friend bool operator==(const PropertyDelegateTypeInfo &lhs, + const PropertyDelegateTypeInfo &rhs) { + return lhs.valueVar == rhs.valueVar && + lhs.initialValueInit == rhs.initialValueInit; + } +}; + +/// Describes the backing property of a property that has an attached delegate. +struct PropertyDelegateBackingPropertyInfo { + /// The backing property. + VarDecl *backingVar = nullptr; + + /// When the original default value is specified in terms of an '=' + /// initializer on the initial property, e.g., + /// + /// \code + /// @Lazy var i = 17 + /// \end + /// + /// This is the specified initial value (\c 17), which is suitable for + /// embedding in the expression \c initializeFromOriginal. + Expr *originalInitialValue = nullptr; + + /// An expression that initializes the backing property from a value of + /// the original property's type (e.g., via `init(initialValue:)`), or + /// \c NULL if the backing property can only be initialized directly. + Expr *initializeFromOriginal = nullptr; + + /// When \c initializeFromOriginal is non-NULL, the opaque value that + /// is used as a stand-in for a value of the original property's type. + OpaqueValueExpr *underlyingValue = nullptr; + + PropertyDelegateBackingPropertyInfo() { } + + PropertyDelegateBackingPropertyInfo(VarDecl *backingVar, + Expr *originalInitialValue, + Expr *initializeFromOriginal, + OpaqueValueExpr *underlyingValue) + : backingVar(backingVar), originalInitialValue(originalInitialValue), + initializeFromOriginal(initializeFromOriginal), + underlyingValue(underlyingValue) { } + + /// Whether this is a valid property delegate. + bool isValid() const { + return backingVar != nullptr; + } + + explicit operator bool() const { return isValid(); } + + friend bool operator==(const PropertyDelegateBackingPropertyInfo &lhs, + const PropertyDelegateBackingPropertyInfo &rhs) { + // FIXME: Can't currently compare expressions. + return lhs.backingVar == rhs.backingVar; + } +}; + +void simple_display( + llvm::raw_ostream &out, + const PropertyDelegateTypeInfo &propertyDelegate); + +void simple_display( + llvm::raw_ostream &out, + const PropertyDelegateBackingPropertyInfo &backingInfo); + +} // end namespace swift + +#endif // SWIFT_AST_PROPERTY_DELEGATES_H From 2e01b0edeb949892ca7f71de51ec53a034edd763 Mon Sep 17 00:00:00 2001 From: Erik Eckstein Date: Thu, 4 Apr 2019 11:24:30 -0700 Subject: [PATCH 13/31] SIL: add assign_by_delegate instruction Used for property delegates. --- docs/SIL.rst | 26 ++++++++++ include/swift/SIL/SILBuilder.h | 10 ++++ include/swift/SIL/SILCloner.h | 12 +++++ include/swift/SIL/SILInstruction.h | 59 ++++++++++++++++++---- include/swift/SIL/SILNode.h | 4 ++ include/swift/SIL/SILNodes.def | 2 + include/swift/Serialization/ModuleFormat.h | 2 +- lib/IRGen/IRGenSIL.cpp | 3 ++ lib/ParseSIL/ParseSIL.cpp | 28 ++++++++++ lib/SIL/MemAccessUtils.cpp | 1 + lib/SIL/OperandOwnership.cpp | 10 ++++ lib/SIL/SILInstructions.cpp | 15 +++++- lib/SIL/SILPrinter.cpp | 8 +++ lib/SIL/SILVerifier.cpp | 47 +++++++++++++++++ lib/SILOptimizer/Utils/SILInliner.cpp | 1 + lib/Serialization/DeserializeSIL.cpp | 2 + lib/Serialization/SerializeSIL.cpp | 2 + 17 files changed, 218 insertions(+), 14 deletions(-) diff --git a/docs/SIL.rst b/docs/SIL.rst index 3bb31eb6714e9..893b4db6d8856 100644 --- a/docs/SIL.rst +++ b/docs/SIL.rst @@ -2333,6 +2333,32 @@ with a sequence that also correctly destroys the current value. This instruction is only valid in Raw SIL and is rewritten as appropriate by the definitive initialization pass. +assign_by_delegate +`````````````````` +:: + + sil-instruction ::= 'assign_by_delegate' sil-operand 'to' sil-operand ',' 'init' sil-operand ',' 'set' sil-operand + + assign_by_delegate %0 : $S to %1 : $*T, init %2 : $F, set %3 : $G + // $S can be a value or address type + // $T must be the type of a property delegate. + // $F must be a function type, taking $S as a single argument and returning $T + // $G must be a function type, taking $S as a single argument and with not return value + +Similar to the ``assign`` instruction, but the assignment is done via a +delegate. + +In case of an initialization, the function ``%2`` is called with ``%0`` as +argument. The result is stored to ``%1``. In case ``%2`` is an address type, +it is simply passed as a first out-argument to ``%2``. + +In case of a re-assignment, the function ``%3`` is called with ``%0`` as +argument. As ``%3`` is a setter (e.g. for the property in the containing +nominal type), the destination address ``%1`` is not used in this case. + +This instruction is only valid in Raw SIL and is rewritten as appropriate +by the definitive initialization pass. + mark_uninitialized `````````````````` :: diff --git a/include/swift/SIL/SILBuilder.h b/include/swift/SIL/SILBuilder.h index a2790eabd60b6..a8448a8dada52 100644 --- a/include/swift/SIL/SILBuilder.h +++ b/include/swift/SIL/SILBuilder.h @@ -842,6 +842,16 @@ class SILBuilder { Qualifier)); } + AssignByDelegateInst *createAssignByDelegate(SILLocation Loc, + SILValue Src, SILValue Dest, + SILValue Initializer, + SILValue Setter, + AssignOwnershipQualifier Qualifier) { + return insert(new (getModule()) + AssignByDelegateInst(getSILDebugLocation(Loc), Src, Dest, + Initializer, Setter, Qualifier)); + } + StoreBorrowInst *createStoreBorrow(SILLocation Loc, SILValue Src, SILValue DestAddr) { return insert(new (getModule()) diff --git a/include/swift/SIL/SILCloner.h b/include/swift/SIL/SILCloner.h index 6c6eafa76d969..2d61ebe4e661b 100644 --- a/include/swift/SIL/SILCloner.h +++ b/include/swift/SIL/SILCloner.h @@ -1204,6 +1204,18 @@ void SILCloner::visitAssignInst(AssignInst *Inst) { Inst->getOwnershipQualifier())); } +template +void SILCloner::visitAssignByDelegateInst(AssignByDelegateInst *Inst) { + getBuilder().setCurrentDebugScope(getOpScope(Inst->getDebugScope())); + recordClonedInstruction( + Inst, getBuilder().createAssignByDelegate(getOpLocation(Inst->getLoc()), + getOpValue(Inst->getSrc()), + getOpValue(Inst->getDest()), + getOpValue(Inst->getInitializer()), + getOpValue(Inst->getSetter()), + Inst->getOwnershipQualifier())); +} + template void SILCloner::visitMarkUninitializedInst(MarkUninitializedInst *Inst) { diff --git a/include/swift/SIL/SILInstruction.h b/include/swift/SIL/SILInstruction.h index 5b4c7cee24023..f417d491f6520 100644 --- a/include/swift/SIL/SILInstruction.h +++ b/include/swift/SIL/SILInstruction.h @@ -3684,19 +3684,17 @@ enum class AssignOwnershipQualifier { }; static_assert(2 == SILNode::NumAssignOwnershipQualifierBits, "Size mismatch"); -/// AssignInst - Represents an abstract assignment to a memory location, which -/// may either be an initialization or a store sequence. This is only valid in -/// Raw SIL. -class AssignInst - : public InstructionBase { - friend SILBuilder; +template +class AssignInstBase + : public InstructionBase { - FixedOperandList<2> Operands; +protected: + FixedOperandList Operands; - AssignInst(SILDebugLocation DebugLoc, SILValue Src, SILValue Dest, - AssignOwnershipQualifier Qualifier = - AssignOwnershipQualifier::Unknown); + template + AssignInstBase(SILDebugLocation DebugLoc, T&&...args) : + InstructionBase(DebugLoc), + Operands(this, std::forward(args)...) { } public: enum { @@ -3711,7 +3709,20 @@ class AssignInst ArrayRef getAllOperands() const { return Operands.asArray(); } MutableArrayRef getAllOperands() { return Operands.asArray(); } +}; + +/// AssignInst - Represents an abstract assignment to a memory location, which +/// may either be an initialization or a store sequence. This is only valid in +/// Raw SIL. +class AssignInst + : public AssignInstBase { + friend SILBuilder; + AssignInst(SILDebugLocation DebugLoc, SILValue Src, SILValue Dest, + AssignOwnershipQualifier Qualifier = + AssignOwnershipQualifier::Unknown); + +public: AssignOwnershipQualifier getOwnershipQualifier() const { return AssignOwnershipQualifier( SILInstruction::Bits.AssignInst.OwnershipQualifier); @@ -3721,6 +3732,32 @@ class AssignInst } }; +/// AssignByDelegateInst - Represents an abstract assignment via a delegate, +/// which may either be an initialization or a store sequence. This is only +/// valid in Raw SIL. +class AssignByDelegateInst + : public AssignInstBase { + friend SILBuilder; + + AssignByDelegateInst(SILDebugLocation DebugLoc, SILValue Src, SILValue Dest, + SILValue Initializer, SILValue Setter, + AssignOwnershipQualifier Qualifier = + AssignOwnershipQualifier::Unknown); + +public: + + SILValue getInitializer() { return Operands[2].get(); } + SILValue getSetter() { return Operands[3].get(); } + + AssignOwnershipQualifier getOwnershipQualifier() const { + return AssignOwnershipQualifier( + SILInstruction::Bits.AssignByDelegateInst.OwnershipQualifier); + } + void setOwnershipQualifier(AssignOwnershipQualifier qualifier) { + SILInstruction::Bits.AssignByDelegateInst.OwnershipQualifier = unsigned(qualifier); + } +}; + /// Indicates that a memory location is uninitialized at this point and needs to /// be initialized by the end of the function and before any escape point for /// this instruction. This is only valid in Raw SIL. diff --git a/include/swift/SIL/SILNode.h b/include/swift/SIL/SILNode.h index d582c4b3901ab..8a6f546f5b6e7 100644 --- a/include/swift/SIL/SILNode.h +++ b/include/swift/SIL/SILNode.h @@ -285,6 +285,10 @@ class alignas(8) SILNode { NumAssignOwnershipQualifierBits, OwnershipQualifier : NumAssignOwnershipQualifierBits ); + SWIFT_INLINE_BITFIELD(AssignByDelegateInst, NonValueInstruction, + NumAssignOwnershipQualifierBits, + OwnershipQualifier : NumAssignOwnershipQualifierBits + ); SWIFT_INLINE_BITFIELD(UncheckedOwnershipConversionInst,SingleValueInstruction, NumVOKindBits, diff --git a/include/swift/SIL/SILNodes.def b/include/swift/SIL/SILNodes.def index d90c2d1e85c4c..783235390098f 100644 --- a/include/swift/SIL/SILNodes.def +++ b/include/swift/SIL/SILNodes.def @@ -805,6 +805,8 @@ NON_VALUE_INST(StoreInst, store, SILInstruction, MayWrite, DoesNotRelease) NON_VALUE_INST(AssignInst, assign, SILInstruction, MayWrite, DoesNotRelease) +NON_VALUE_INST(AssignByDelegateInst, assign_by_delegate, + SILInstruction, MayWrite, DoesNotRelease) NON_VALUE_INST(MarkFunctionEscapeInst, mark_function_escape, SILInstruction, None, DoesNotRelease) NON_VALUE_INST(DebugValueInst, debug_value, diff --git a/include/swift/Serialization/ModuleFormat.h b/include/swift/Serialization/ModuleFormat.h index e64af939471db..095446581bb96 100644 --- a/include/swift/Serialization/ModuleFormat.h +++ b/include/swift/Serialization/ModuleFormat.h @@ -52,7 +52,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 = 487; // Last change: Opaque result types generic params +const uint16_t SWIFTMODULE_VERSION_MINOR = 488; // assign_by_delegate using DeclIDField = BCFixed<31>; diff --git a/lib/IRGen/IRGenSIL.cpp b/lib/IRGen/IRGenSIL.cpp index a87e7939c3d65..2708f36b0f72b 100644 --- a/lib/IRGen/IRGenSIL.cpp +++ b/lib/IRGen/IRGenSIL.cpp @@ -933,6 +933,9 @@ class IRGenSILFunction : void visitAssignInst(AssignInst *i) { llvm_unreachable("assign is not valid in canonical SIL"); } + void visitAssignByDelegateInst(AssignByDelegateInst *i) { + llvm_unreachable("assign_by_delegate is not valid in canonical SIL"); + } void visitMarkUninitializedInst(MarkUninitializedInst *i) { llvm_unreachable("mark_uninitialized is not valid in canonical SIL"); } diff --git a/lib/ParseSIL/ParseSIL.cpp b/lib/ParseSIL/ParseSIL.cpp index c5226b31b296c..48d9286272193 100644 --- a/lib/ParseSIL/ParseSIL.cpp +++ b/lib/ParseSIL/ParseSIL.cpp @@ -3540,6 +3540,34 @@ bool SILParser::parseSILInstruction(SILBuilder &B) { break; } + case SILInstructionKind::AssignByDelegateInst: { + SILValue Src, DestAddr, InitFn, SetFn; + SourceLoc DestLoc; + AssignOwnershipQualifier AssignQualifier; + if (parseTypedValueRef(Src, B) || + parseVerbatim("to") || + parseAssignOwnershipQualifier(AssignQualifier, *this) || + parseTypedValueRef(DestAddr, DestLoc, B) || + P.parseToken(tok::comma, diag::expected_tok_in_sil_instr, ",") || + parseVerbatim("init") || + parseTypedValueRef(InitFn, B) || + P.parseToken(tok::comma, diag::expected_tok_in_sil_instr, ",") || + parseVerbatim("set") || + parseTypedValueRef(SetFn, B) || + parseSILDebugLocation(InstLoc, B)) + return true; + + if (!DestAddr->getType().isAddress()) { + P.diagnose(DestLoc, diag::sil_operand_not_address, "destination", + OpcodeName); + return true; + } + + ResultVal = B.createAssignByDelegate(InstLoc, Src, DestAddr, InitFn, SetFn, + AssignQualifier); + break; + } + case SILInstructionKind::BeginAccessInst: case SILInstructionKind::BeginUnpairedAccessInst: case SILInstructionKind::EndAccessInst: diff --git a/lib/SIL/MemAccessUtils.cpp b/lib/SIL/MemAccessUtils.cpp index 7fdb51295cc23..28c540fc98163 100644 --- a/lib/SIL/MemAccessUtils.cpp +++ b/lib/SIL/MemAccessUtils.cpp @@ -678,6 +678,7 @@ void swift::visitAccessedAddress(SILInstruction *I, llvm_unreachable("unexpected memory access."); case SILInstructionKind::AssignInst: + case SILInstructionKind::AssignByDelegateInst: visitor(&I->getAllOperands()[AssignInst::Dest]); return; diff --git a/lib/SIL/OperandOwnership.cpp b/lib/SIL/OperandOwnership.cpp index b0ac2139ca1f7..e6694dbd3ac67 100644 --- a/lib/SIL/OperandOwnership.cpp +++ b/lib/SIL/OperandOwnership.cpp @@ -840,6 +840,16 @@ OperandOwnershipKindClassifier::visitAssignInst(AssignInst *i) { UseLifetimeConstraint::MustBeInvalidated); } +OperandOwnershipKindMap +OperandOwnershipKindClassifier::visitAssignByDelegateInst(AssignByDelegateInst *i) { + if (getValue() != i->getSrc()) { + return Map::allLive(); + } + + return Map::compatibilityMap(ValueOwnershipKind::Owned, + UseLifetimeConstraint::MustBeInvalidated); +} + OperandOwnershipKindMap OperandOwnershipKindClassifier::visitStoreInst(StoreInst *i) { if (getValue() != i->getSrc()) { diff --git a/lib/SIL/SILInstructions.cpp b/lib/SIL/SILInstructions.cpp index 8df99bb2b6764..cda5c617f7a3b 100644 --- a/lib/SIL/SILInstructions.cpp +++ b/lib/SIL/SILInstructions.cpp @@ -813,11 +813,22 @@ StringRef swift::getSILAccessEnforcementName(SILAccessEnforcement enforcement) { } AssignInst::AssignInst(SILDebugLocation Loc, SILValue Src, SILValue Dest, - AssignOwnershipQualifier Qualifier) - : InstructionBase(Loc), Operands(this, Src, Dest) { + AssignOwnershipQualifier Qualifier) : + AssignInstBase(Loc, Src, Dest) { SILInstruction::Bits.AssignInst.OwnershipQualifier = unsigned(Qualifier); } +AssignByDelegateInst::AssignByDelegateInst(SILDebugLocation Loc, + SILValue Src, SILValue Dest, + SILValue Initializer, + SILValue Setter, + AssignOwnershipQualifier Qualifier) : + AssignInstBase(Loc, Src, Dest, Initializer, Setter) { + assert(Initializer->getType().is()); + SILInstruction::Bits.AssignByDelegateInst.OwnershipQualifier = + unsigned(Qualifier); +} + MarkFunctionEscapeInst * MarkFunctionEscapeInst::create(SILDebugLocation Loc, ArrayRef Elements, SILFunction &F) { diff --git a/lib/SIL/SILPrinter.cpp b/lib/SIL/SILPrinter.cpp index c353d48f44f8c..85c84369a1d0a 100644 --- a/lib/SIL/SILPrinter.cpp +++ b/lib/SIL/SILPrinter.cpp @@ -1309,6 +1309,14 @@ class SILPrinter : public SILInstructionVisitor { *this << getIDAndType(AI->getDest()); } + void visitAssignByDelegateInst(AssignByDelegateInst *AI) { + *this << getIDAndType(AI->getSrc()) << " to "; + printAssignOwnershipQualifier(AI->getOwnershipQualifier()); + *this << getIDAndType(AI->getDest()) + << ", init " << getIDAndType(AI->getInitializer()) + << ", set " << getIDAndType(AI->getSetter()); + } + void visitMarkUninitializedInst(MarkUninitializedInst *MU) { switch (MU->getKind()) { case MarkUninitializedInst::Var: *this << "[var] "; break; diff --git a/lib/SIL/SILVerifier.cpp b/lib/SIL/SILVerifier.cpp index b2baa10fc12e1..80175772128c6 100644 --- a/lib/SIL/SILVerifier.cpp +++ b/lib/SIL/SILVerifier.cpp @@ -1761,6 +1761,53 @@ class SILVerifier : public SILVerifierBase { "Store operand type and dest type mismatch"); } + void checkAssignByDelegateInst(AssignByDelegateInst *AI) { + SILValue Src = AI->getSrc(), Dest = AI->getDest(); + require(AI->getModule().getStage() == SILStage::Raw, + "assign instruction can only exist in raw SIL"); + require(Dest->getType().isAddress(), "Must store to an address dest"); + + unsigned indirectInitResults = Src->getType().isAddress() ? 1 : 0; + + SILValue initFn = AI->getInitializer(); + CanSILFunctionType initTy = initFn->getType().castTo(); + SILFunctionConventions initConv(initTy, AI->getModule()); + require(initConv.getNumIndirectSILResults() == indirectInitResults, + "init function has wrong number of indirect results"); + unsigned firstArgIdx = initConv.getSILArgIndexOfFirstParam(); + require(initConv.getNumSILArguments() == firstArgIdx + 1, + "init function has wrong number of arguments"); + require(Src->getType() == initConv.getSILArgumentType(firstArgIdx), + "wrong argument type of init function"); + switch (initConv.getNumIndirectSILResults()) { + case 0: + require(initConv.getNumDirectSILResults() == 1, + "wrong number of init function results"); + require(Dest->getType().getObjectType() == + initConv.getDirectSILResultTypes().front(), + "wrong init function result type"); + break; + case 1: + require(initConv.getNumDirectSILResults() == 0, + "wrong number of init function results"); + require(Dest->getType() == initConv.getIndirectSILResultTypes().front(), + "wrong indirect init function result type"); + break; + default: + require(false, "wrong number of indirect init function results"); + } + + SILValue setterFn = AI->getSetter(); + CanSILFunctionType setterTy = setterFn->getType().castTo(); + SILFunctionConventions setterConv(setterTy, AI->getModule()); + require(setterConv.getNumIndirectSILResults() == 0, + "set function has indirect results"); + require(setterConv.getNumSILArguments() == 1, + "init function has wrong number of arguments"); + require(Src->getType() == setterConv.getSILArgumentType(0), + "wrong argument type of init function"); + } + #define NEVER_LOADABLE_CHECKED_REF_STORAGE(Name, name, ...) \ void checkLoad##Name##Inst(Load##Name##Inst *LWI) { \ require(LWI->getType().isObject(), "Result of load must be an object"); \ diff --git a/lib/SILOptimizer/Utils/SILInliner.cpp b/lib/SILOptimizer/Utils/SILInliner.cpp index a91ac5a74fe41..bc618895bd945 100644 --- a/lib/SILOptimizer/Utils/SILInliner.cpp +++ b/lib/SILOptimizer/Utils/SILInliner.cpp @@ -731,6 +731,7 @@ InlineCost swift::instructionInlineCost(SILInstruction &I) { case SILInstructionKind::ValueMetatypeInst: case SILInstructionKind::WitnessMethodInst: case SILInstructionKind::AssignInst: + case SILInstructionKind::AssignByDelegateInst: case SILInstructionKind::BranchInst: case SILInstructionKind::CheckedCastBranchInst: case SILInstructionKind::CheckedCastValueBranchInst: diff --git a/lib/Serialization/DeserializeSIL.cpp b/lib/Serialization/DeserializeSIL.cpp index d0147d0ee54e6..097a29ad66bc4 100644 --- a/lib/Serialization/DeserializeSIL.cpp +++ b/lib/Serialization/DeserializeSIL.cpp @@ -1852,6 +1852,8 @@ bool SILDeserializer::readSILInstruction(SILFunction *Fn, SILBasicBlock *BB, qualifier); break; } + case SILInstructionKind::AssignByDelegateInst: + llvm_unreachable("not supported"); case SILInstructionKind::BindMemoryInst: { assert(RecordKind == SIL_ONE_TYPE_VALUES && "Layout should be OneTypeValues."); diff --git a/lib/Serialization/SerializeSIL.cpp b/lib/Serialization/SerializeSIL.cpp index 4a596519037ed..125b5f0711c2a 100644 --- a/lib/Serialization/SerializeSIL.cpp +++ b/lib/Serialization/SerializeSIL.cpp @@ -1705,6 +1705,8 @@ void SILSerializer::writeSILInstruction(const SILInstruction &SI) { addValueRef(operand)); break; } + case SILInstructionKind::AssignByDelegateInst: + llvm_unreachable("not supported"); case SILInstructionKind::BindMemoryInst: { auto *BI = cast(&SI); SILValue baseOperand = BI->getBase(); From 86fb74a34e0976a67b07699c6361c6b2b1a15073 Mon Sep 17 00:00:00 2001 From: Erik Eckstein Date: Thu, 4 Apr 2019 11:30:01 -0700 Subject: [PATCH 14/31] DI: support assign_by_delegate instruction --- include/swift/SIL/InstructionUtils.h | 4 +++ lib/SIL/InstructionUtils.cpp | 15 +++++++++ .../Mandatory/DIMemoryUseCollector.cpp | 31 ++++++++++++++++--- .../Mandatory/DIMemoryUseCollector.h | 2 +- .../Mandatory/DefiniteInitialization.cpp | 22 ++++++++++++- 5 files changed, 67 insertions(+), 7 deletions(-) diff --git a/include/swift/SIL/InstructionUtils.h b/include/swift/SIL/InstructionUtils.h index 88dd842db2973..cb307bdc63f8f 100644 --- a/include/swift/SIL/InstructionUtils.h +++ b/include/swift/SIL/InstructionUtils.h @@ -134,6 +134,10 @@ bool isSanitizerInstrumentation(SILInstruction *Instruction); /// argument of the partial apply if it is. SILValue isPartialApplyOfReabstractionThunk(PartialApplyInst *PAI); +/// Returns true if \p PAI is only used by an assign_by_delegate instruction as +/// init or set function. +bool onlyUsedByAssignByDelegate(PartialApplyInst *PAI); + /// If V is a function closure, return the reaching set of partial_apply's. void findClosuresForFunctionValue(SILValue V, TinyPtrVector &results); diff --git a/lib/SIL/InstructionUtils.cpp b/lib/SIL/InstructionUtils.cpp index 413c25a9f410f..067c4178dac54 100644 --- a/lib/SIL/InstructionUtils.cpp +++ b/lib/SIL/InstructionUtils.cpp @@ -377,6 +377,21 @@ SILValue swift::isPartialApplyOfReabstractionThunk(PartialApplyInst *PAI) { return Arg; } +bool swift::onlyUsedByAssignByDelegate(PartialApplyInst *PAI) { + bool usedByAssignByDelegate = false; + for (Operand *Op : PAI->getUses()) { + SILInstruction *User = Op->getUser(); + if (isa(User) && Op->getOperandNumber() >= 2) { + usedByAssignByDelegate = true; + continue; + } + if (isa(User)) + continue; + return false; + } + return usedByAssignByDelegate; +} + /// Given a block used as a noescape function argument, attempt to find all /// Swift closures that invoking the block will call. The StoredClosures may not /// actually be partial_apply instructions. They may be copied, block arguments, diff --git a/lib/SILOptimizer/Mandatory/DIMemoryUseCollector.cpp b/lib/SILOptimizer/Mandatory/DIMemoryUseCollector.cpp index 4f75d2497c893..f86629e50d75a 100644 --- a/lib/SILOptimizer/Mandatory/DIMemoryUseCollector.cpp +++ b/lib/SILOptimizer/Mandatory/DIMemoryUseCollector.cpp @@ -356,7 +356,12 @@ DIMemoryObjectInfo::getPathStringToElement(unsigned Element, getElementCountRec(Module, FieldType, false); if (Element < NumFieldElements) { Result += '.'; - Result += VD->getName().str(); + auto originalProperty = VD->getOriginalDelegatedProperty(); + if (originalProperty) { + Result += originalProperty->getName().str(); + } else { + Result += VD->getName().str(); + } getPathStringToElementRec(Module, FieldType, Element, Result); return VD; } @@ -415,9 +420,14 @@ bool DIMemoryObjectInfo::isElementLetProperty(unsigned Element) const { //===----------------------------------------------------------------------===// /// onlyTouchesTrivialElements - Return true if all of the accessed elements -/// have trivial type. +/// have trivial type and the access itself is a trivial instruction. bool DIMemoryUse::onlyTouchesTrivialElements( const DIMemoryObjectInfo &MI) const { + // assign_by_delegate calls functions to assign a value. This is not + // considered as trivial. + if (isa(Inst)) + return false; + auto *F = Inst->getFunction(); for (unsigned i = FirstElement, e = i + NumElements; i != e; ++i) { @@ -741,9 +751,12 @@ void ElementUseCollector::collectUses(SILValue Pointer, unsigned BaseEltNo) { #include "swift/AST/ReferenceStorage.def" // Stores *to* the allocation are writes. - if ((isa(User) || isa(User)) && + if ((isa(User) || isa(User) || + isa(User)) && Op->getOperandNumber() == 1) { if (PointeeType.is()) { + assert(!isa(User) && + "cannot assign a typle with assign_by_delegate"); UsesToScalarize.push_back(User); continue; } @@ -753,7 +766,7 @@ void ElementUseCollector::collectUses(SILValue Pointer, unsigned BaseEltNo) { DIUseKind Kind; if (InStructSubElement) Kind = DIUseKind::PartialStore; - else if (isa(User)) + else if (isa(User) || isa(User)) Kind = DIUseKind::InitOrAssign; else if (PointeeType.isTrivial(*User->getFunction())) Kind = DIUseKind::InitOrAssign; @@ -990,6 +1003,11 @@ void ElementUseCollector::collectUses(SILValue Pointer, unsigned BaseEltNo) { continue; } + if (auto *PAI = dyn_cast(User)) { + if (onlyUsedByAssignByDelegate(PAI)) + continue; + } + // Sanitizer instrumentation is not user visible, so it should not // count as a use and must not affect compile-time diagnostics. if (isSanitizerInstrumentation(User)) @@ -1466,8 +1484,11 @@ void ElementUseCollector::collectClassSelfUses( // If this is a partial application of self, then this is an escape point // for it. - if (isa(User)) + if (auto *PAI = dyn_cast(User)) { + if (onlyUsedByAssignByDelegate(PAI)) + continue; Kind = DIUseKind::Escape; + } trackUse(DIMemoryUse(User, Kind, 0, TheMemory.NumElements)); } diff --git a/lib/SILOptimizer/Mandatory/DIMemoryUseCollector.h b/lib/SILOptimizer/Mandatory/DIMemoryUseCollector.h index a11f655a7fb05..cc9b8c56add95 100644 --- a/lib/SILOptimizer/Mandatory/DIMemoryUseCollector.h +++ b/lib/SILOptimizer/Mandatory/DIMemoryUseCollector.h @@ -267,7 +267,7 @@ struct DIMemoryUse { } /// onlyTouchesTrivialElements - Return true if all of the accessed elements - /// have trivial type. + /// have trivial type and the access itself is a trivial instruction. bool onlyTouchesTrivialElements(const DIMemoryObjectInfo &MemoryInfo) const; /// getElementBitmask - Return a bitmask with the touched tuple elements diff --git a/lib/SILOptimizer/Mandatory/DefiniteInitialization.cpp b/lib/SILOptimizer/Mandatory/DefiniteInitialization.cpp index 5037766b7ab60..85203f9de0ec9 100644 --- a/lib/SILOptimizer/Mandatory/DefiniteInitialization.cpp +++ b/lib/SILOptimizer/Mandatory/DefiniteInitialization.cpp @@ -979,6 +979,8 @@ void LifetimeChecker::handleStoreUse(unsigned UseID) { addr = copyAddr->getDest(); else if (auto *assign = dyn_cast(inst)) addr = assign->getDest(); + else if (auto *assign = dyn_cast(inst)) + addr = assign->getDest(); else return false; @@ -1823,7 +1825,7 @@ void LifetimeChecker::handleSelfInitUse(unsigned UseID) { assert(TheMemory.NumElements == 1 && "delegating inits have a single elt"); // Lower Assign instructions if needed. - if (isa(Use.Inst)) + if (isa(Use.Inst) || isa(Use.Inst)) NeedsUpdateForInitState.push_back(UseID); } else { // super.init also requires that all ivars are initialized before the @@ -1899,6 +1901,24 @@ void LifetimeChecker::updateInstructionForInitState(DIMemoryUse &Use) { return; } + if (auto *AI = dyn_cast(Inst)) { + // Remove this instruction from our data structures, since we will be + // removing it. + Use.Inst = nullptr; + NonLoadUses.erase(Inst); + + if (TheMemory.isClassInitSelf() && + Use.Kind == DIUseKind::SelfInit) { + assert(InitKind == IsInitialization); + AI->setOwnershipQualifier(AssignOwnershipQualifier::Reinit); + } else { + AI->setOwnershipQualifier((InitKind == IsInitialization + ? AssignOwnershipQualifier::Init + : AssignOwnershipQualifier::Reassign)); + } + + return; + } // Ignore non-stores for SelfInits. assert(isa(Inst) && "Unknown store instruction!"); From 24e28caf3d39e8c68c787ce3d62e3f2aa156824e Mon Sep 17 00:00:00 2001 From: Erik Eckstein Date: Thu, 4 Apr 2019 11:30:28 -0700 Subject: [PATCH 15/31] Fix an assert in AccessEnformentSelections to support assign_by_delegate --- lib/SILOptimizer/Mandatory/AccessEnforcementSelection.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/SILOptimizer/Mandatory/AccessEnforcementSelection.cpp b/lib/SILOptimizer/Mandatory/AccessEnforcementSelection.cpp index 9b356e186d136..877a68eeea832 100644 --- a/lib/SILOptimizer/Mandatory/AccessEnforcementSelection.cpp +++ b/lib/SILOptimizer/Mandatory/AccessEnforcementSelection.cpp @@ -36,6 +36,7 @@ #include "swift/SIL/SILArgument.h" #include "swift/SIL/SILFunction.h" #include "swift/SIL/SILUndef.h" +#include "swift/SIL/InstructionUtils.h" #include "swift/SILOptimizer/Analysis/ClosureScope.h" #include "swift/SILOptimizer/Analysis/PostOrderAnalysis.h" #include "swift/SILOptimizer/PassManager/Transforms.h" @@ -255,7 +256,8 @@ static void checkUsesOfAccess(BeginAccessInst *access) { for (auto *use : access->getUses()) { auto user = use->getUser(); assert(!isa(user)); - assert(!isa(user)); + assert(!isa(user) || + onlyUsedByAssignByDelegate(cast(user))); } #endif } From e1674232c72a8baa136e17da28300acee860387e Mon Sep 17 00:00:00 2001 From: Erik Eckstein Date: Thu, 4 Apr 2019 11:30:55 -0700 Subject: [PATCH 16/31] RawSILInstLowering: lower assign_by_delegate --- .../Mandatory/RawSILInstLowering.cpp | 99 +++++++++++++++++++ 1 file changed, 99 insertions(+) diff --git a/lib/SILOptimizer/Mandatory/RawSILInstLowering.cpp b/lib/SILOptimizer/Mandatory/RawSILInstLowering.cpp index ab0a5979f4ff4..b1addde6cae7c 100644 --- a/lib/SILOptimizer/Mandatory/RawSILInstLowering.cpp +++ b/lib/SILOptimizer/Mandatory/RawSILInstLowering.cpp @@ -91,12 +91,101 @@ static void lowerAssignInstruction(SILBuilderWithScope &b, AssignInst *inst) { inst->eraseFromParent(); } +static void lowerAssignByDelegateInstruction(SILBuilderWithScope &b, + AssignByDelegateInst *inst, + SmallVectorImpl &accessMarkers) { + LLVM_DEBUG(llvm::dbgs() << " *** Lowering [isInit=" + << unsigned(inst->getOwnershipQualifier()) + << "]: " << *inst << "\n"); + + ++numAssignRewritten; + + SILValue src = inst->getSrc(); + SILValue dest = inst->getDest(); + SILLocation loc = inst->getLoc(); + SILArgumentConvention srcConvention(SILArgumentConvention::Indirect_Inout); + + switch (inst->getOwnershipQualifier()) { + case AssignOwnershipQualifier::Init: { + SILValue initFn = inst->getInitializer(); + CanSILFunctionType fTy = initFn->getType().castTo(); + SILFunctionConventions convention(fTy, inst->getModule()); + if (convention.hasIndirectSILResults()) { + b.createApply(loc, initFn, { dest, src }, false); + srcConvention = convention.getSILArgumentConvention(1); + } else { + SILValue wrappedSrc = b.createApply(loc, initFn, { src }, false); + b.createTrivialStoreOr(loc, wrappedSrc, dest, StoreOwnershipQualifier::Init); + srcConvention = convention.getSILArgumentConvention(0); + } + // TODO: remove the unused setter function, which usually is a dead + // partial_apply. + break; + } + case AssignOwnershipQualifier::Unknown: + case AssignOwnershipQualifier::Reassign: { + SILValue setterFn = inst->getSetter(); + CanSILFunctionType fTy = setterFn->getType().castTo(); + SILFunctionConventions convention(fTy, inst->getModule()); + assert(!convention.hasIndirectSILResults()); + srcConvention = convention.getSILArgumentConvention(0); + b.createApply(loc, setterFn, { src }, false); + + // The destination address is not used. Remove it if it is a dead access + // marker. This is important, because also the setter function contains + // access marker. In case those markers are dynamic it would cause a + // nested access violation. + if (auto *BA = dyn_cast(dest)) + accessMarkers.push_back(BA); + // TODO: remove the unused init function, which usually is a dead + // partial_apply. + break; + } + case AssignOwnershipQualifier::Reinit: + llvm_unreachable("wrong qualifier for assign_by_delegate"); + } + switch (srcConvention) { + case SILArgumentConvention::Indirect_In_Guaranteed: + b.createDestroyAddr(loc, src); + break; + case SILArgumentConvention::Direct_Guaranteed: + b.createDestroyValue(loc, src); + break; + case SILArgumentConvention::Direct_Unowned: + case SILArgumentConvention::Indirect_In: + case SILArgumentConvention::Indirect_In_Constant: + case SILArgumentConvention::Direct_Owned: + break; + case SILArgumentConvention::Indirect_Inout: + case SILArgumentConvention::Indirect_InoutAliasable: + case SILArgumentConvention::Indirect_Out: + case SILArgumentConvention::Direct_Deallocating: + llvm_unreachable("wrong convention for setter/initializer src argument"); + } + inst->eraseFromParent(); +} + +static void deleteDeadAccessMarker(BeginAccessInst *BA) { + for (Operand *Op : BA->getUses()) { + SILInstruction *User = Op->getUser(); + if (!isa(User)) + return; + } + for (Operand *Op : BA->getUses()) { + Op->getUser()->eraseFromParent(); + } + BA->eraseFromParent(); +} + /// lowerRawSILOperations - There are a variety of raw-sil instructions like /// 'assign' that are only used by this pass. Now that definite initialization /// checking is done, remove them. static bool lowerRawSILOperations(SILFunction &fn) { bool changed = false; + for (auto &bb : fn) { + SmallVector accessMarkers; + auto i = bb.begin(), e = bb.end(); while (i != e) { SILInstruction *inst = &*i; @@ -123,6 +212,13 @@ static bool lowerRawSILOperations(SILFunction &fn) { continue; } + if (auto *ai = dyn_cast(inst)) { + SILBuilderWithScope b(ai); + lowerAssignByDelegateInstruction(b, ai, accessMarkers); + changed = true; + continue; + } + // mark_uninitialized just becomes a noop, resolving to its operand. if (auto *mui = dyn_cast(inst)) { mui->replaceAllUsesWith(mui->getOperand()); @@ -138,6 +234,9 @@ static bool lowerRawSILOperations(SILFunction &fn) { continue; } } + for (BeginAccessInst *BA : accessMarkers) { + deleteDeadAccessMarker(BA); + } } return changed; } From 573a82ba9326bad4510b1e4455fe9635fc1b9258 Mon Sep 17 00:00:00 2001 From: Erik Eckstein Date: Thu, 4 Apr 2019 11:31:39 -0700 Subject: [PATCH 17/31] SILGen: generate assign_by_delegate for property delegates --- lib/SILGen/SILGenApply.cpp | 24 ++ lib/SILGen/SILGenFunction.h | 6 + lib/SILGen/SILGenLValue.cpp | 92 ++++ test/SILOptimizer/di-property-delegates.swift | 404 ++++++++++++++++++ 4 files changed, 526 insertions(+) create mode 100644 test/SILOptimizer/di-property-delegates.swift diff --git a/lib/SILGen/SILGenApply.cpp b/lib/SILGen/SILGenApply.cpp index aca29a051159b..024544be1bea0 100644 --- a/lib/SILGen/SILGenApply.cpp +++ b/lib/SILGen/SILGenApply.cpp @@ -4930,6 +4930,30 @@ RValue SILGenFunction::emitApplyAllocatingInitializer(SILLocation loc, return result; } +RValue SILGenFunction::emitApplyPropertyDelegateAllocator(SILLocation loc, + SubstitutionMap subs, + SILDeclRef ctorRef, + Type delegateTy, + CanAnyFunctionType funcTy) { + Callee callee = Callee::forDirect(*this, ctorRef, subs, loc); + + MetatypeType *MTty = MetatypeType::get(delegateTy); + auto metatypeVal = B.createMetatype(loc, getLoweredType(MTty)); + ManagedValue mtManagedVal = ManagedValue::forUnmanaged(metatypeVal); + RValue metatypeRVal(*this, loc, MTty->getCanonicalType(), mtManagedVal); + + ArgumentSource ArgSrc(loc, std::move(metatypeRVal)); + FormalEvaluationScope writebacks(*this); + CallEmission emission(*this, std::move(callee), std::move(writebacks)); + + AnyFunctionType::Param selfParam((Type(MTty)), Identifier(), + ParameterTypeFlags()); + emission.addSelfParam(loc, std::move(ArgSrc), selfParam, funcTy.getResult()); + + RValue RV = emission.apply(); + return RV; +} + /// Emit a literal that applies the various initializers. RValue SILGenFunction::emitLiteral(LiteralExpr *literal, SGFContext C) { ConcreteDeclRef builtinInit; diff --git a/lib/SILGen/SILGenFunction.h b/lib/SILGen/SILGenFunction.h index ac8902b1c267c..78bf800dca9d6 100644 --- a/lib/SILGen/SILGenFunction.h +++ b/lib/SILGen/SILGenFunction.h @@ -1471,6 +1471,12 @@ class LLVM_LIBRARY_VISIBILITY SILGenFunction PreparedArguments &&args, Type overriddenSelfType, SGFContext ctx); + RValue emitApplyPropertyDelegateAllocator(SILLocation loc, + SubstitutionMap subs, + SILDeclRef ctorRef, + Type delegateTy, + CanAnyFunctionType funcTy); + CleanupHandle emitBeginApply(SILLocation loc, ManagedValue fn, SubstitutionMap subs, ArrayRef args, CanSILFunctionType substFnType, diff --git a/lib/SILGen/SILGenLValue.cpp b/lib/SILGen/SILGenLValue.cpp index 17cbbf4143edf..5da83ae1c438f 100644 --- a/lib/SILGen/SILGenLValue.cpp +++ b/lib/SILGen/SILGenLValue.cpp @@ -26,6 +26,8 @@ #include "swift/AST/DiagnosticsCommon.h" #include "swift/AST/DiagnosticsSIL.h" #include "swift/AST/GenericEnvironment.h" +#include "swift/AST/PropertyDelegates.h" +#include "swift/AST/TypeCheckRequests.h" #include "swift/SIL/InstructionUtils.h" #include "swift/SIL/MemAccessUtils.h" #include "swift/SIL/PrettyStackTrace.h" @@ -1291,6 +1293,14 @@ namespace { { } + bool hasPropertyDelegate() const { + if (auto *VD = dyn_cast(Storage)) { + if (auto delegateInfo = VD->getAttachedPropertyDelegateTypeInfo()) + return delegateInfo.initialValueInit != nullptr; + } + return false; + } + void emitAssignWithSetter(SILGenFunction &SGF, SILLocation loc, LValue &&dest, ArgumentSource &&value) { assert(getAccessorDecl()->isSetter()); @@ -1344,6 +1354,86 @@ namespace { assert(getAccessorDecl()->isSetter()); SILDeclRef setter = Accessor; + if (hasPropertyDelegate() && IsOnSelfParameter) { + // This is delegated property. Instead of emitting a setter, emit an + // assign_by_delegate with the allocating initializer function and the + // setter function as arguments. DefiniteInitializtion will then decide + // between the two functions, depending if it's an initialization or a + // re-assignment. + // + VarDecl *field = dyn_cast(Storage); + VarDecl *backingVar = field->getPropertyDelegateBackingProperty(); + assert(backingVar); + CanType ValType = backingVar->getType()->getCanonicalType(); + SILType varStorageType = + SGF.SGM.Types.getSubstitutedStorageType(backingVar, ValType); + auto typeData = + getLogicalStorageTypeData(SGF.SGM, getTypeData().AccessKind, ValType); + + // Get the address of the storage property. + ManagedValue proj; + if (BaseFormalType->mayHaveSuperclass()) { + RefElementComponent REC(backingVar, LValueOptions(), varStorageType, + typeData); + proj = std::move(REC).project(SGF, loc, base); + } else { + assert(BaseFormalType->getStructOrBoundGenericStruct()); + StructElementComponent SEC(backingVar, varStorageType, typeData); + proj = std::move(SEC).project(SGF, loc, base); + } + + // Create the allocating initializer function. It captures the metadata. + auto delegateInfo = field->getAttachedPropertyDelegateTypeInfo(); + auto ctor = delegateInfo.initialValueInit; + SubstitutionMap subs = backingVar->getType()->getMemberSubstitutionMap( + SGF.getModule().getSwiftModule(), ctor); + + Type ity = ctor->getInterfaceType(); + AnyFunctionType *substIty = ity.subst(subs)->castTo(); + + auto initRef = SILDeclRef(ctor, SILDeclRef::Kind::Allocator) + .asForeign(requiresForeignEntryPoint(ctor)); + RValue initFuncRV = + SGF.emitApplyPropertyDelegateAllocator(loc, subs,initRef, + backingVar->getType(), + CanAnyFunctionType(substIty)); + ManagedValue initFn = std::move(initFuncRV).getAsSingleValue(SGF, loc); + + // Create the allocating setter function. It captures the base address. + auto setterInfo = SGF.getConstantInfo(setter); + SILValue setterFRef = SGF.emitGlobalFunctionRef(loc, setter, setterInfo); + auto setterSubs = SGF.getFunction().getForwardingSubstitutionMap(); + + SILType closureTy = SILGenBuilder::getPartialApplyResultType( + setterFRef->getType(), 1, SGF.SGM.M, setterSubs, + ParameterConvention::Direct_Guaranteed); + + CanSILFunctionType setterTy = setterFRef->getType().castTo(); + SILFunctionConventions setterConv(setterTy, SGF.SGM.M); + + SILValue capturedBase; + unsigned argIdx = setterConv.getNumSILArguments() - 1; + if (setterConv.getSILArgumentConvention(argIdx).isInoutConvention()) { + capturedBase = base.getValue(); + } else { + capturedBase = base.copy(SGF, loc).forward(SGF); + } + + PartialApplyInst *setterPAI = + SGF.B.createPartialApply(loc, setterFRef, setterFRef->getType(), + setterSubs, { capturedBase }, closureTy); + ManagedValue setterFn = SGF.emitManagedRValueWithCleanup(setterPAI); + + // Create the assign_by_delegate with the allocator and setter. + assert(value.isRValue()); + ManagedValue Mval = std::move(value).asKnownRValue(SGF). + getAsSingleValue(SGF, loc); + SGF.B.createAssignByDelegate(loc, Mval.forward(SGF), proj.forward(SGF), + initFn.getValue(), setterFn.getValue(), + AssignOwnershipQualifier::Unknown); + return; + } + FormalEvaluationScope scope(SGF); // Pass in just the setter. auto args = @@ -4126,6 +4216,8 @@ static bool trySetterPeephole(SILGenFunction &SGF, SILLocation loc, } auto &setterComponent = static_cast(component); + if (setterComponent.hasPropertyDelegate()) + return false; setterComponent.emitAssignWithSetter(SGF, loc, std::move(dest), std::move(src)); return true;; diff --git a/test/SILOptimizer/di-property-delegates.swift b/test/SILOptimizer/di-property-delegates.swift new file mode 100644 index 0000000000000..db1c0ffc75871 --- /dev/null +++ b/test/SILOptimizer/di-property-delegates.swift @@ -0,0 +1,404 @@ +// RUN: %empty-directory(%t) +// RUN: %target-build-swift %s -o %t/a.out +// RUN: %target-run %t/a.out | %FileCheck %s + +// REQUIRES: executable_test + +@propertyDelegate +struct Wrapper { + var value: T { + didSet { + print(" .. set \(value)") + } + } + + init(initialValue: T) { + print(" .. init \(initialValue)") + self.value = initialValue + } +} + +@propertyDelegate +final class ClassWrapper { + var value: T { + didSet { + print(" .. set \(value)") + } + } + + init(initialValue: T) { + print(" .. init \(initialValue)") + self.value = initialValue + } + + deinit { + print(" .. deinit \(value)") + } +} + +protocol IntInitializable { + init(_: Int) +} + +final class Payload : CustomStringConvertible, IntInitializable { + let payload: Int + + init(_ p: Int) { + self.payload = p + print(" + payload alloc \(payload)") + } + + deinit { + print(" - payload free \(payload)") + } + + var description: String { + return "value = \(payload)" + } +} + +struct IntStruct { + @Wrapper var wrapped: Int + + init() { + wrapped = 42 + wrapped = 27 + } + + init(conditional b: Bool) { + if b { + self.$wrapped = Wrapper(initialValue: 32) + } else { + wrapped = 42 + } + } + + init(dynamic b: Bool) { + if b { + wrapped = 42 + } + wrapped = 27 + } +} + +final class IntClass { + @Wrapper var wrapped: Int + + init() { + wrapped = 42 + wrapped = 27 + } + + init(conditional b: Bool) { + if b { + self.$wrapped = Wrapper(initialValue: 32) + } else { + wrapped = 42 + } + } + + init(dynamic b: Bool) { + if b { + wrapped = 42 + } + wrapped = 27 + } +} + +struct RefStruct { + @Wrapper var wrapped: Payload + + init() { + wrapped = Payload(42) + wrapped = Payload(27) + } + + init(conditional b: Bool) { + if b { + self.$wrapped = Wrapper(initialValue: Payload(32)) + } else { + wrapped = Payload(42) + } + } + + init(dynamic b: Bool) { + if b { + wrapped = Payload(42) + } + wrapped = Payload(27) + } +} + +final class GenericClass { + @Wrapper var wrapped: T + + init() { + wrapped = T(42) + wrapped = T(27) + } + + init(conditional b: Bool) { + if b { + self.$wrapped = Wrapper(initialValue: T(32)) + } else { + wrapped = T(42) + } + } + + init(dynamic b: Bool) { + if b { + wrapped = T(42) + } + wrapped = T(27) + } +} + +/* + +This does not work yet. + +struct IntStructWithClassWrapper { + @ClassWrapper var wrapped: Int + + init() { + wrapped = 42 + wrapped = 27 + } + + init(conditional b: Bool) { + if b { + self.$wrapped = ClassWrapper(initialValue: 32) + } else { + wrapped = 42 + } + } + + init(dynamic b: Bool) { + if b { + wrapped = 42 + } + wrapped = 27 + } +} +*/ + +func testIntStruct() { + // CHECK: ## IntStruct + print("\n## IntStruct") + + // CHECK-NEXT: .. init 42 + // CHECK-NEXT: .. set 27 + let t1 = IntStruct() + // CHECK-NEXT: 27 + print(t1.wrapped) + + // CHECK-NEXT: .. init 42 + let t2 = IntStruct(conditional: false) + // CHECK-NEXT: 42 + print(t2.wrapped) + + // CHECK-NEXT: .. init 32 + let t3 = IntStruct(conditional: true) + // CHECK-NEXT: 32 + print(t3.wrapped) + + // CHECK-NEXT: .. init 27 + let t4 = IntStruct(dynamic: false) + // CHECK-NEXT: 27 + print(t4.wrapped) + + // CHECK-NEXT: .. init 42 + // CHECK-NEXT: .. init 27 + let t5 = IntStruct(dynamic: true) + // CHECK-NEXT: 27 + print(t5.wrapped) +} + +func testIntClass() { + // CHECK: ## IntClass + print("\n## IntClass") + + // CHECK-NEXT: .. init 42 + // CHECK-NEXT: .. set 27 + let t1 = IntClass() + // CHECK-NEXT: 27 + print(t1.wrapped) + + // CHECK-NEXT: .. init 42 + let t2 = IntClass(conditional: false) + // CHECK-NEXT: 42 + print(t2.wrapped) + + // CHECK-NEXT: .. init 32 + let t3 = IntClass(conditional: true) + // CHECK-NEXT: 32 + print(t3.wrapped) + + // CHECK-NEXT: .. init 27 + let t4 = IntClass(dynamic: false) + // CHECK-NEXT: 27 + print(t4.wrapped) + + // CHECK-NEXT: .. init 42 + // CHECK-NEXT: .. init 27 + let t5 = IntClass(dynamic: true) + // CHECK-NEXT: 27 + print(t5.wrapped) +} + +func testRefStruct() { + // CHECK: ## RefStruct + print("\n## RefStruct") + + if true { + // CHECK-NEXT: + payload alloc 42 + // CHECK-NEXT: .. init value = 42 + // CHECK-NEXT: + payload alloc 27 + // CHECK-NEXT: .. set value = 27 + // CHECK-NEXT: - payload free 42 + let t1 = RefStruct() + // CHECK-NEXT: value = 27 + print(t1.wrapped) + // CHECK-NEXT: - payload free 27 + } + if true { + // CHECK-NEXT: + payload alloc 42 + // CHECK-NEXT: .. init value = 42 + let t2 = RefStruct(conditional: false) + // CHECK-NEXT: value = 42 + print(t2.wrapped) + // CHECK-NEXT: - payload free 42 + } + if true { + // CHECK-NEXT: + payload alloc 32 + // CHECK-NEXT: .. init value = 32 + let t3 = RefStruct(conditional: true) + // CHECK-NEXT: value = 32 + print(t3.wrapped) + // CHECK-NEXT: - payload free 32 + } + if true { + // CHECK-NEXT: + payload alloc 27 + // CHECK-NEXT: .. init value = 27 + let t4 = RefStruct(dynamic: false) + // CHECK-NEXT: value = 27 + print(t4.wrapped) + // CHECK-NEXT: - payload free 27 + } + if true { + // CHECK-NEXT: + payload alloc 42 + // CHECK-NEXT: .. init value = 42 + // CHECK-NEXT: + payload alloc 27 + // CHECK-NEXT: - payload free 42 + // CHECK-NEXT: .. init value = 27 + let t5 = RefStruct(dynamic: true) + // CHECK-NEXT: value = 27 + print(t5.wrapped) + // CHECK-NEXT: - payload free 27 + } +} + +func testGenericClass() { + // CHECK: ## GenericClass + print("\n## GenericClass") + + if true { + // CHECK-NEXT: + payload alloc 42 + // CHECK-NEXT: .. init value = 42 + // CHECK-NEXT: + payload alloc 27 + // CHECK-NEXT: .. set value = 27 + // CHECK-NEXT: - payload free 42 + let t1 = GenericClass() + // CHECK-NEXT: value = 27 + print(t1.wrapped) + // CHECK-NEXT: - payload free 27 + } + if true { + // CHECK-NEXT: + payload alloc 42 + // CHECK-NEXT: .. init value = 42 + let t2 = GenericClass(conditional: false) + // CHECK-NEXT: value = 42 + print(t2.wrapped) + // CHECK-NEXT: - payload free 42 + } + if true { + // CHECK-NEXT: + payload alloc 32 + // CHECK-NEXT: .. init value = 32 + let t3 = GenericClass(conditional: true) + // CHECK-NEXT: value = 32 + print(t3.wrapped) + // CHECK-NEXT: - payload free 32 + } + if true { + // CHECK-NEXT: + payload alloc 27 + // CHECK-NEXT: .. init value = 27 + let t4 = GenericClass(dynamic: false) + // CHECK-NEXT: value = 27 + print(t4.wrapped) + // CHECK-NEXT: - payload free 27 + } + if true { + // CHECK-NEXT: + payload alloc 42 + // CHECK-NEXT: .. init value = 42 + // CHECK-NEXT: + payload alloc 27 + // CHECK-NEXT: - payload free 42 + // CHECK-NEXT: .. init value = 27 + let t5 = GenericClass(dynamic: true) + // CHECK-NEXT: value = 27 + print(t5.wrapped) + // CHECK-NEXT: - payload free 27 + } +} + +/* +func testIntStructWithClassWrapper() { + // CHECK-DISABLED: ## IntStructWithClassWrapper + print("\n## IntStructWithClassWrapper") + + if true { + // CHECK-NEXT-DISABLED: .. init 42 + // CHECK-NEXT-DISABLED: .. set 27 + let t1 = IntStructWithClassWrapper() + // CHECK-NEXT-DISABLED: 27 + print(t1.wrapped) + // CHECK-NEXT-DISABLED: .. deinit 27 + } + if true { + // CHECK-NEXT-DISABLED: .. init 42 + let t2 = IntStructWithClassWrapper(conditional: false) + // CHECK-NEXT-DISABLED: 42 + print(t2.wrapped) + // CHECK-NEXT-DISABLED: .. deinit 42 + } + if true { + // CHECK-NEXT-DISABLED: .. init 32 + let t3 = IntStructWithClassWrapper(conditional: true) + // CHECK-NEXT-DISABLED: 32 + print(t3.wrapped) + // CHECK-NEXT-DISABLED: .. deinit 32 + } + if true { + // CHECK-NEXT-DISABLED: .. init 27 + let t4 = IntStructWithClassWrapper(dynamic: false) + // CHECK-NEXT-DISABLED: 27 + print(t4.wrapped) + // CHECK-NEXT-DISABLED: .. deinit 27 + } + if true { + // CHECK-NEXT-DISABLED: .. init 42 + // CHECK-NEXT-DISABLED: .. deinit 42 + // CHECK-NEXT-DISABLED: .. init 27 + let t5 = IntStructWithClassWrapper(dynamic: true) + // CHECK-NEXT-DISABLED: 27 + print(t5.wrapped) + // CHECK-NEXT-DISABLED: .. deinit 27 + } +} +*/ + +testIntStruct() +testIntClass() +testRefStruct() +testGenericClass() +//testIntStructWithClassWrapper() + From 4f56db2653a85084a093ed96b9669dd779ed1475 Mon Sep 17 00:00:00 2001 From: Doug Gregor Date: Fri, 12 Apr 2019 21:39:44 -0700 Subject: [PATCH 18/31] [Property delegates] Implement support for storageValue --- include/swift/AST/Decl.h | 23 ++++- include/swift/AST/DiagnosticsSema.def | 4 +- include/swift/AST/KnownIdentifiers.def | 1 + include/swift/AST/PropertyDelegates.h | 18 +++- lib/AST/ASTContext.cpp | 17 +++- lib/AST/Decl.cpp | 19 ++-- lib/SILGen/SILGen.cpp | 6 +- lib/SILGen/SILGenConstructor.cpp | 7 +- lib/Sema/CodeSynthesis.cpp | 133 +++++++++++++++++++++++-- lib/Sema/TypeCheckPropertyDelegate.cpp | 24 +++-- test/SILGen/property_delegates.swift | 17 ++++ test/decl/var/property_delegates.swift | 35 +++++++ 12 files changed, 263 insertions(+), 41 deletions(-) diff --git a/include/swift/AST/Decl.h b/include/swift/AST/Decl.h index aa83b16afc35b..7987a94eec21e 100644 --- a/include/swift/AST/Decl.h +++ b/include/swift/AST/Decl.h @@ -78,6 +78,7 @@ namespace swift { class ParameterTypeFlags; class Pattern; struct PrintOptions; + struct PropertyDelegateBackingPropertyInfo; struct PropertyDelegateTypeInfo; class ProtocolDecl; class ProtocolType; @@ -4717,6 +4718,17 @@ class AbstractStorageDecl : public ValueDecl { } }; +/// Describes which synthesized property for a property with an attached +/// delegate is being referenced. +enum class PropertyDelegateSynthesizedPropertyKind { + /// The backing storage property, which is a stored property of the + /// delegate type. + Backing, + /// A storage delegate (e.g., `$foo`), which is a wrapper over the + /// delegate instance's `storageValue` property. + StorageDelegate, +}; + /// VarDecl - 'var' and 'let' declarations. class VarDecl : public AbstractStorageDecl { public: @@ -5065,6 +5077,11 @@ class VarDecl : public AbstractStorageDecl { /// unbound generic types. It will be the type of the backing property. Type getPropertyDelegateBackingPropertyType() const; + /// Retrieve information about the backing properties of the attached + /// property delegate. + PropertyDelegateBackingPropertyInfo + getPropertyDelegateBackingPropertyInfo() const; + /// Retrieve the backing storage property for a property that has an /// attached property delegate. /// @@ -5086,7 +5103,11 @@ class VarDecl : public AbstractStorageDecl { /// If this property is the backing storage for a property with an attached /// property delegate, return the original property. - VarDecl *getOriginalDelegatedProperty() const; + /// + /// \param kind If not \c None, only returns the original property when + /// \c this property is the specified synthesized property. + VarDecl *getOriginalDelegatedProperty( + Optional kind = None) const; /// Set the property that delegates to this property as it's backing /// property. diff --git a/include/swift/AST/DiagnosticsSema.def b/include/swift/AST/DiagnosticsSema.def index 9f96566865ba3..d43a014f278c5 100644 --- a/include/swift/AST/DiagnosticsSema.def +++ b/include/swift/AST/DiagnosticsSema.def @@ -4317,10 +4317,10 @@ WARNING(hashvalue_implementation,none, //------------------------------------------------------------------------------ ERROR(property_delegate_no_value_property, none, "property delegate type %0 does not contain a non-static property " - "named 'value'", (Type)) + "named %1", (Type, Identifier)) ERROR(property_delegate_ambiguous_value_property, none, "property delegate type %0 has multiple non-static properties " - "named 'value'", (Type)) + "named %1", (Type, Identifier)) ERROR(property_delegate_wrong_initial_value_init, none, "'init(initialValue:)' parameter type (%0) must be the same as its " "'value' property type (%1) or an @autoclosure thereof", (Type, Type)) diff --git a/include/swift/AST/KnownIdentifiers.def b/include/swift/AST/KnownIdentifiers.def index fc0a792f8a3ad..5c0df84885e6e 100644 --- a/include/swift/AST/KnownIdentifiers.def +++ b/include/swift/AST/KnownIdentifiers.def @@ -97,6 +97,7 @@ IDENTIFIER(Self) IDENTIFIER(setObject) IDENTIFIER(simd) IDENTIFIER(storage) +IDENTIFIER(storageValue) IDENTIFIER(stringValue) IDENTIFIER(super) IDENTIFIER(superDecoder) diff --git a/include/swift/AST/PropertyDelegates.h b/include/swift/AST/PropertyDelegates.h index 1a91092980a66..74a44f7169fd5 100644 --- a/include/swift/AST/PropertyDelegates.h +++ b/include/swift/AST/PropertyDelegates.h @@ -37,8 +37,18 @@ struct PropertyDelegateTypeInfo { /// The initializer init(initialValue:) that will be called when the /// initiqlizing the property delegate type from a value of the property type. + /// + /// This initializer is optional, but if present will be used for the `=` + /// initialization syntax. ConstructorDecl *initialValueInit = nullptr; + /// The property through which the storage value ($foo) will be accessed, + /// hiding the underlying storage completely. + /// + /// This property is optional. If present, a computed property for `$foo` + /// will be created that redirects to this property. + VarDecl *storageValueVar = nullptr; + /// Whether this is a valid property delegate. bool isValid() const { return valueVar != nullptr; @@ -58,6 +68,10 @@ struct PropertyDelegateBackingPropertyInfo { /// The backing property. VarDecl *backingVar = nullptr; + /// The storage delegate property, if any. When present, this takes the name + /// '$foo' from `backingVar`. + VarDecl *storageDelegateVar = nullptr; + /// When the original default value is specified in terms of an '=' /// initializer on the initial property, e.g., /// @@ -81,10 +95,12 @@ struct PropertyDelegateBackingPropertyInfo { PropertyDelegateBackingPropertyInfo() { } PropertyDelegateBackingPropertyInfo(VarDecl *backingVar, + VarDecl *storageDelegateVar, Expr *originalInitialValue, Expr *initializeFromOriginal, OpaqueValueExpr *underlyingValue) - : backingVar(backingVar), originalInitialValue(originalInitialValue), + : backingVar(backingVar), storageDelegateVar(storageDelegateVar), + originalInitialValue(originalInitialValue), initializeFromOriginal(initializeFromOriginal), underlyingValue(underlyingValue) { } diff --git a/lib/AST/ASTContext.cpp b/lib/AST/ASTContext.cpp index cdb2339a4a26f..354b838d29732 100644 --- a/lib/AST/ASTContext.cpp +++ b/lib/AST/ASTContext.cpp @@ -31,6 +31,7 @@ #include "swift/AST/NameLookup.h" #include "swift/AST/ParameterList.h" #include "swift/AST/PrettyStackTrace.h" +#include "swift/AST/PropertyDelegates.h" #include "swift/AST/ProtocolConformance.h" #include "swift/AST/RawComment.h" #include "swift/AST/SubstitutionMap.h" @@ -4370,13 +4371,25 @@ void ASTContext::setSideCachedPropertyDelegateBackingPropertyType( getImpl().PropertyDelegateBackingVarTypes[var] = type; } -VarDecl *VarDecl::getOriginalDelegatedProperty() const { +VarDecl *VarDecl::getOriginalDelegatedProperty( + Optional kind) const { if (!Bits.VarDecl.IsPropertyDelegateBackingProperty) return nullptr; ASTContext &ctx = getASTContext(); assert(ctx.getImpl().OriginalDelegatedProperties.count(this) > 0); - return ctx.getImpl().OriginalDelegatedProperties[this]; + auto original = ctx.getImpl().OriginalDelegatedProperties[this]; + if (!kind) + return original; + + auto delegateInfo = original->getPropertyDelegateBackingPropertyInfo(); + switch (*kind) { + case PropertyDelegateSynthesizedPropertyKind::Backing: + return this == delegateInfo.backingVar ? original : nullptr; + + case PropertyDelegateSynthesizedPropertyKind::StorageDelegate: + return this == delegateInfo.storageDelegateVar ? original : nullptr; + } } void VarDecl::setOriginalDelegatedProperty(VarDecl *originalProperty) { diff --git a/lib/AST/Decl.cpp b/lib/AST/Decl.cpp index 25193dd1cf1f5..17abeba2b28ed 100644 --- a/lib/AST/Decl.cpp +++ b/lib/AST/Decl.cpp @@ -5264,16 +5264,21 @@ Type VarDecl::getPropertyDelegateBackingPropertyType() const { Type()); } -VarDecl *VarDecl::getPropertyDelegateBackingProperty() const { +PropertyDelegateBackingPropertyInfo +VarDecl::getPropertyDelegateBackingPropertyInfo() const { auto &ctx = getASTContext(); if (!ctx.getLazyResolver()) - return nullptr; + return PropertyDelegateBackingPropertyInfo(); auto mutableThis = const_cast(this); return evaluateOrDefault( ctx.evaluator, PropertyDelegateBackingPropertyInfoRequest{mutableThis}, - PropertyDelegateBackingPropertyInfo()).backingVar; + PropertyDelegateBackingPropertyInfo()); +} + +VarDecl *VarDecl::getPropertyDelegateBackingProperty() const { + return getPropertyDelegateBackingPropertyInfo().backingVar; } bool VarDecl::isPropertyDelegateInitializedWithInitialValue() const { @@ -5281,8 +5286,6 @@ bool VarDecl::isPropertyDelegateInitializedWithInitialValue() const { if (!ctx.getLazyResolver()) return false; - auto mutableThis = const_cast(this); - // If there is no initializer, the initialization form depends on // whether the property delegate type has an init(initialValue:). if (!isParentInitialized()) { @@ -5291,10 +5294,8 @@ bool VarDecl::isPropertyDelegateInitializedWithInitialValue() const { } // Otherwise, check whether the '=' initialization form was used. - return evaluateOrDefault( - ctx.evaluator, - PropertyDelegateBackingPropertyInfoRequest{mutableThis}, - PropertyDelegateBackingPropertyInfo()).originalInitialValue != nullptr; + return getPropertyDelegateBackingPropertyInfo().originalInitialValue + != nullptr; } Identifier VarDecl::getObjCPropertyName() const { diff --git a/lib/SILGen/SILGen.cpp b/lib/SILGen/SILGen.cpp index 9473d53b3b23c..d86d9d7b5d0cc 100644 --- a/lib/SILGen/SILGen.cpp +++ b/lib/SILGen/SILGen.cpp @@ -1120,10 +1120,8 @@ emitStoredPropertyInitialization(PatternBindingDecl *pbd, unsigned i) { // If this is the backing storage for a property with an attached delegate // that was initialized with `=`, use that expression as the initializer. if (auto originalProperty = var->getOriginalDelegatedProperty()) { - auto delegateInfo = evaluateOrDefault( - getASTContext().evaluator, - PropertyDelegateBackingPropertyInfoRequest{originalProperty}, - PropertyDelegateBackingPropertyInfo()); + auto delegateInfo = + originalProperty->getPropertyDelegateBackingPropertyInfo(); if (delegateInfo.originalInitialValue) init = delegateInfo.originalInitialValue; } diff --git a/lib/SILGen/SILGenConstructor.cpp b/lib/SILGen/SILGenConstructor.cpp index ac6c2e7cc6e24..c8a2fa994bdad 100644 --- a/lib/SILGen/SILGenConstructor.cpp +++ b/lib/SILGen/SILGenConstructor.cpp @@ -21,7 +21,6 @@ #include "swift/AST/GenericEnvironment.h" #include "swift/AST/ParameterList.h" #include "swift/AST/PropertyDelegates.h" -#include "swift/AST/TypeCheckRequests.h" #include "swift/Basic/Defer.h" #include "swift/SIL/SILArgument.h" #include "swift/SIL/SILUndef.h" @@ -116,11 +115,7 @@ static bool maybeEmitPropertyDelegateInitFromValue( !originalProperty->isPropertyDelegateInitializedWithInitialValue()) return false; - ASTContext &ctx = field->getASTContext(); - auto delegateInfo = evaluateOrDefault( - ctx.evaluator, - PropertyDelegateBackingPropertyInfoRequest{originalProperty}, - PropertyDelegateBackingPropertyInfo()); + auto delegateInfo = originalProperty->getPropertyDelegateBackingPropertyInfo(); if (!delegateInfo || !delegateInfo.initializeFromOriginal) return false; diff --git a/lib/Sema/CodeSynthesis.cpp b/lib/Sema/CodeSynthesis.cpp index b140bc7a9b492..99ea890304204 100644 --- a/lib/Sema/CodeSynthesis.cpp +++ b/lib/Sema/CodeSynthesis.cpp @@ -314,12 +314,18 @@ static void maybeMarkTransparent(AccessorDecl *accessor, ASTContext &ctx) { // Getters/setters for a property with a delegate are not @_transparent if // the backing variable has more-restrictive access than the original - // property. + // property. The same goes for its storage delegate. if (auto var = dyn_cast(accessor->getStorage())) { if (auto backingVar = var->getPropertyDelegateBackingProperty()) { if (backingVar->getFormalAccess() < var->getFormalAccess()) return; } + + if (auto original = var->getOriginalDelegatedProperty( + PropertyDelegateSynthesizedPropertyKind::StorageDelegate)) { + if (var->getFormalAccess() < original->getFormalAccess()) + return; + } } // Accessors for protocol storage requirements are never @_transparent @@ -531,7 +537,11 @@ namespace { /// We're referencing the superclass's implementation of the storage. Super, /// We're referencing the backing property for a property with a delegate + /// through the 'value' property. Delegate, + /// We're referencing the backing property for a property with a delegate + /// through the 'storage' property. + DelegateStorage, }; } // end anonymous namespace @@ -593,6 +603,17 @@ static Expr *buildStorageReference(AccessorDecl *accessor, selfAccessKind = SelfAccessorKind::Peer; break; } + + case TargetImpl::DelegateStorage: { + auto var = + cast(accessor->getStorage())->getOriginalDelegatedProperty(); + storage = var->getPropertyDelegateBackingProperty(); + underlyingVar = var->getAttachedPropertyDelegateTypeInfo().storageValueVar; + assert(underlyingVar); + semantics = AccessSemantics::DirectToStorage; + selfAccessKind = SelfAccessorKind::Peer; + break; + } } VarDecl *selfDecl = accessor->getImplicitSelfDecl(); @@ -776,7 +797,8 @@ static void synthesizeTrivialGetterBody(AccessorDecl *getter, ASTContext &ctx) { auto storage = getter->getStorage(); assert(!isSynthesizedComputedProperty(storage) || - target == TargetImpl::Delegate); + target == TargetImpl::Delegate || + target == TargetImpl::DelegateStorage); SourceLoc loc = storage->getLoc(); @@ -1460,18 +1482,87 @@ static Expr *findOriginalPropertyDelegateInitialValue(VarDecl *var, return initArg; } +/// Synthesize a computed property `$foo` for a property with an attached +/// delegate that has a `storageValue` property. +static VarDecl *synthesizePropertyDelegateStorageDelegateProperty( + ASTContext &ctx, VarDecl *var, Type delegateType, + VarDecl *storageValueVar) { + // Compute the name of the storage type. + SmallString<64> nameBuf; + nameBuf = "$"; + nameBuf += var->getName().str(); + Identifier name = ctx.getIdentifier(nameBuf); + + // Determine the type of the property. + Type propertyType = delegateType->getTypeOfMember( + var->getModuleContext(), storageValueVar, + storageValueVar->getValueInterfaceType()); + Type propertyInterfaceType = propertyType->mapTypeOutOfContext(); + + // Form the property. + auto dc = var->getDeclContext(); + VarDecl *property = new (ctx) VarDecl(/*IsStatic=*/var->isStatic(), + VarDecl::Specifier::Var, + /*IsCaptureList=*/false, + var->getLoc(), + name, dc); + property->setInterfaceType(propertyInterfaceType); + property->setType(propertyType); + property->setImplicit(); + property->setOriginalDelegatedProperty(var); + addMemberToContextIfNeeded(property, dc, var); + + // Create the pattern binding declaration for the property. + Pattern *pbdPattern = new (ctx) NamedPattern(property, /*implicit=*/true); + pbdPattern->setType(propertyType); + pbdPattern = TypedPattern::createImplicit(ctx, pbdPattern, propertyType); + auto pbd = PatternBindingDecl::createImplicit( + ctx, property->getCorrectStaticSpelling(), pbdPattern, + /*init*/nullptr, dc, SourceLoc()); + addMemberToContextIfNeeded(pbd, dc, var); + + // Determine the access level for the property. + AccessLevel access = + std::min(AccessLevel::Internal, var->getFormalAccess()); + property->overwriteAccess(access); + + // Determine setter access. + AccessLevel setterAccess = + std::min(AccessLevel::Internal, var->getSetterFormalAccess()); + property->overwriteSetterAccess(setterAccess); + + // Add the accessors we need. + bool hasSetter = storageValueVar->isSettable(nullptr) && + storageValueVar->isSetterAccessibleFrom(var->getInnermostDeclContext()); + addGetterToStorage(property, ctx); + if (hasSetter) { + addSetterToStorage(property, ctx); + property->overwriteImplInfo(StorageImplInfo::getMutableComputed()); + } else { + property->overwriteImplInfo(StorageImplInfo::getImmutableComputed()); + } + addExpectedOpaqueAccessorsToStorage(property, ctx); + + return property; +} + llvm::Expected PropertyDelegateBackingPropertyInfoRequest::evaluate(Evaluator &evaluator, - VarDecl *var) const { + VarDecl *var) const { // Determine the type of the backing property. auto delegateType = var->getPropertyDelegateBackingPropertyType(); if (!delegateType) return PropertyDelegateBackingPropertyInfo(); + auto delegateInfo = var->getAttachedPropertyDelegateTypeInfo(); + // Compute the name of the storage type. ASTContext &ctx = var->getASTContext(); SmallString<64> nameBuf; - nameBuf = "$"; + if (delegateInfo.storageValueVar) + nameBuf = "$__delegate_storage_$_"; + else + nameBuf = "$"; nameBuf += var->getName().str(); Identifier name = ctx.getIdentifier(nameBuf); @@ -1540,12 +1631,19 @@ PropertyDelegateBackingPropertyInfoRequest::evaluate(Evaluator &evaluator, std::min(AccessLevel::Internal, var->getSetterFormalAccess()); backingVar->overwriteSetterAccess(setterAccess); + // If there is a storage delegate property (storageValue) in the delegate, + // synthesize a computed property for '$foo'. + VarDecl *storageVar = nullptr; + if (delegateInfo.storageValueVar) { + storageVar = synthesizePropertyDelegateStorageDelegateProperty( + ctx, var, delegateType, delegateInfo.storageValueVar); + } + // Get the property delegate information. - auto delegateInfo = var->getAttachedPropertyDelegateTypeInfo(); if (!delegateInfo.initialValueInit) { assert(!originalInitialValue); return PropertyDelegateBackingPropertyInfo( - backingVar, nullptr, nullptr, nullptr); + backingVar, storageVar, nullptr, nullptr, nullptr); } // Form the initialization of the backing property from a value of the @@ -1568,7 +1666,7 @@ PropertyDelegateBackingPropertyInfoRequest::evaluate(Evaluator &evaluator, } tc.checkPropertyDelegateErrorHandling(parentPBD, initializer); return PropertyDelegateBackingPropertyInfo( - backingVar, originalInitialValue, initializer, origValue); + backingVar, storageVar, originalInitialValue, initializer, origValue); } static bool wouldBeCircularSynthesis(AbstractStorageDecl *storage, @@ -1577,6 +1675,10 @@ static bool wouldBeCircularSynthesis(AbstractStorageDecl *storage, if (auto var = dyn_cast(storage)) { if (var->getAttachedPropertyDelegate()) return false; + + if (var->getOriginalDelegatedProperty( + PropertyDelegateSynthesizedPropertyKind::StorageDelegate)) + return false; } switch (kind) { @@ -1795,6 +1897,12 @@ static void synthesizeGetterBody(AccessorDecl *getter, synthesizePropertyDelegateGetterBody(getter, ctx); return; } + + if (var->getOriginalDelegatedProperty( + PropertyDelegateSynthesizedPropertyKind::StorageDelegate)) { + synthesizeTrivialGetterBody(getter, TargetImpl::DelegateStorage, ctx); + return; + } } if (getter->hasForcedStaticDispatch()) { @@ -1841,6 +1949,17 @@ static void synthesizeSetterBody(AccessorDecl *setter, synthesizePropertyDelegateSetterBody(setter, ctx); return; } + + // Synthesize a getter for the storage delegate property of a property + // with an attached delegate. + if (auto original = var->getOriginalDelegatedProperty( + PropertyDelegateSynthesizedPropertyKind::StorageDelegate)) { + auto backingVar = original->getPropertyDelegateBackingProperty(); + synthesizeTrivialSetterBodyWithStorage(setter, + TargetImpl::DelegateStorage, + backingVar, ctx); + return; + } } switch (storage->getWriteImpl()) { diff --git a/lib/Sema/TypeCheckPropertyDelegate.cpp b/lib/Sema/TypeCheckPropertyDelegate.cpp index 255fb7da1e268..40c43718d6df8 100644 --- a/lib/Sema/TypeCheckPropertyDelegate.cpp +++ b/lib/Sema/TypeCheckPropertyDelegate.cpp @@ -24,13 +24,14 @@ #include "swift/AST/TypeCheckRequests.h" using namespace swift; -/// Find the "value" property in a property delegate to which access will +/// Find the named property in a property delegate to which access will /// be delegated. -static VarDecl *findValueProperty(ASTContext &ctx, NominalTypeDecl *nominal) { +static VarDecl *findValueProperty(ASTContext &ctx, NominalTypeDecl *nominal, + Identifier name, bool allowMissing) { SmallVector vars; { SmallVector decls; - nominal->lookupQualified(nominal, ctx.Id_value, NL_QualifiedDefault, decls); + nominal->lookupQualified(nominal, name, NL_QualifiedDefault, decls); for (const auto &foundDecl : decls) { auto foundVar = dyn_cast(foundDecl); if (!foundVar || foundVar->isStatic() || @@ -41,11 +42,13 @@ static VarDecl *findValueProperty(ASTContext &ctx, NominalTypeDecl *nominal) { } } - // Diagnose missing or ambiguous "value" properties. + // Diagnose missing or ambiguous properties. switch (vars.size()) { case 0: - nominal->diagnose(diag::property_delegate_no_value_property, - nominal->getDeclaredType()); + if (!allowMissing) { + nominal->diagnose(diag::property_delegate_no_value_property, + nominal->getDeclaredType(), name); + } return nullptr; case 1: @@ -53,7 +56,7 @@ static VarDecl *findValueProperty(ASTContext &ctx, NominalTypeDecl *nominal) { default: nominal->diagnose(diag::property_delegate_ambiguous_value_property, - nominal->getDeclaredType()); + nominal->getDeclaredType(), name); for (auto var : vars) { var->diagnose(diag::kind_declname_declared_here, var->getDescriptiveKind(), var->getFullName()); @@ -61,7 +64,7 @@ static VarDecl *findValueProperty(ASTContext &ctx, NominalTypeDecl *nominal) { return nullptr; } - // The 'value' property must be as accessible as the nominal type. + // The property must be as accessible as the nominal type. VarDecl *var = vars.front(); if (var->getFormalAccess() < nominal->getFormalAccess()) { var->diagnose(diag::property_delegate_type_requirement_not_accessible, @@ -175,13 +178,16 @@ PropertyDelegateTypeInfoRequest::evaluate( // Look for a non-static property named "value" in the property delegate // type. ASTContext &ctx = nominal->getASTContext(); - auto valueVar = findValueProperty(ctx, nominal); + auto valueVar = + findValueProperty(ctx, nominal, ctx.Id_value, /*allowMissing=*/false); if (!valueVar) return PropertyDelegateTypeInfo(); PropertyDelegateTypeInfo result; result.valueVar = valueVar; result.initialValueInit = findInitialValueInit(ctx, nominal, valueVar); + result.storageValueVar = + findValueProperty(ctx, nominal, ctx.Id_storageValue, /*allowMissing=*/true); return result; } diff --git a/test/SILGen/property_delegates.swift b/test/SILGen/property_delegates.swift index 7fad31750f7bc..4da6655dedaa7 100644 --- a/test/SILGen/property_delegates.swift +++ b/test/SILGen/property_delegates.swift @@ -188,3 +188,20 @@ struct DelegateWithDidSetWillSet { self.x = x } } + +@propertyDelegate +struct WrapperWithStorageValue { + var value: T + + var storageValue: Wrapper { + return Wrapper(value: value) + } +} + +struct UseWrapperWithStorageValue { + // UseWrapperWithStorageValue.$x.getter + // CHECK-LABEL: sil hidden [transparent] [ossa] @$s18property_delegates26UseWrapperWithStorageValueV2$xAA0D0VySiGvg : $@convention(method) (UseWrapperWithStorageValue) -> Wrapper + // CHECK-NOT: return + // CHECK: function_ref @$s18property_delegates23WrapperWithStorageValueV07storageF0AA0C0VyxGvg + @WrapperWithStorageValue(value: 17) var x: Int +} diff --git a/test/decl/var/property_delegates.swift b/test/decl/var/property_delegates.swift index 51d3853fb6a44..b13c4a0d7e947 100644 --- a/test/decl/var/property_delegates.swift +++ b/test/decl/var/property_delegates.swift @@ -605,3 +605,38 @@ func testDefaultInitializers() { _ = DefaultInitializerClass() _ = NoDefaultInitializerStruct() // expected-error{{missing argument for parameter 'x' in call}} } + +// --------------------------------------------------------------------------- +// Storage references +// --------------------------------------------------------------------------- +@propertyDelegate +struct WrapperWithStorageRef { + var value: T + + var storageValue: Wrapper { + return Wrapper(value: value) + } +} + +extension Wrapper { + var wrapperOnlyAPI: Int { return 17 } +} + +struct TestStorageRef { + @WrapperWithStorageRef var x: Int + + mutating func test() { + let _: Wrapper = $x + let i = $x.wrapperOnlyAPI + let _: Int = i + + // x is mutable, $x is not + x = 17 + $x = Wrapper(value: 42) // expected-error{{cannot assign to property: '$x' is immutable}} + } +} + +func testStorageRef(tsr: TestStorageRef) { + let _: Wrapper = tsr.$x +} + From f187d9218e17b8b9b7da34bfd59261b6682b1925 Mon Sep 17 00:00:00 2001 From: Doug Gregor Date: Fri, 12 Apr 2019 22:03:33 -0700 Subject: [PATCH 19/31] [Type checker] Don't allow a 'let' to have a property delegate. --- include/swift/AST/DiagnosticsSema.def | 4 ++++ lib/Sema/TypeCheckPropertyDelegate.cpp | 7 +++++++ test/decl/var/property_delegates.swift | 2 +- 3 files changed, 12 insertions(+), 1 deletion(-) diff --git a/include/swift/AST/DiagnosticsSema.def b/include/swift/AST/DiagnosticsSema.def index d43a014f278c5..4b4a42bf1e771 100644 --- a/include/swift/AST/DiagnosticsSema.def +++ b/include/swift/AST/DiagnosticsSema.def @@ -4347,6 +4347,10 @@ NOTE(previous_property_delegate_here,none, ERROR(property_delegate_local,none, "property delegates are not yet supported on local properties", ()) +ERROR(property_delegate_let, none, + "property delegate can only be applied to a 'var'", + ()) + ERROR(property_with_delegate_conflict_attribute,none, "property %0 with a delegate cannot also be " "%select{lazy|@NSCopying|@NSManaged|weak|unowned|unmanaged}1", diff --git a/lib/Sema/TypeCheckPropertyDelegate.cpp b/lib/Sema/TypeCheckPropertyDelegate.cpp index 40c43718d6df8..1b4c4a9db075e 100644 --- a/lib/Sema/TypeCheckPropertyDelegate.cpp +++ b/lib/Sema/TypeCheckPropertyDelegate.cpp @@ -225,6 +225,12 @@ AttachedPropertyDelegateRequest::evaluate(Evaluator &evaluator, return nullptr; } + // A property delegate cannot be attached to a 'let'. + if (var->isLet()) { + ctx.Diags.diagnose(attr->getLocation(), diag::property_delegate_let); + return nullptr; + } + // Check for conflicting attributes. if (var->getAttrs().hasAttribute() || var->getAttrs().hasAttribute() || @@ -341,6 +347,7 @@ PropertyDelegateBackingPropertyTypeRequest::evaluate( ASTContext &ctx = var->getASTContext(); TypeChecker &tc = *static_cast(ctx.getLazyResolver()); if (binding->isInitialized(index)) { + tc.validateDecl(var); if (!binding->isInitializerChecked(index)) tc.typeCheckPatternBinding(binding, index); diff --git a/test/decl/var/property_delegates.swift b/test/decl/var/property_delegates.swift index b13c4a0d7e947..28653266d00db 100644 --- a/test/decl/var/property_delegates.swift +++ b/test/decl/var/property_delegates.swift @@ -422,7 +422,7 @@ struct UseMutatingnessDelegates { @DelegateWithMutatingGetter var y = 17 - @DelegateWithNonMutatingSetter + @DelegateWithNonMutatingSetter // expected-error{{property delegate can only be applied to a 'var'}} let z = 3.14159 // expected-note 2{{change 'let' to 'var' to make it mutable}} @ClassDelegate From 03704fc6354183a1a97fb61725875189f9cd9f36 Mon Sep 17 00:00:00 2001 From: Doug Gregor Date: Fri, 12 Apr 2019 22:31:08 -0700 Subject: [PATCH 20/31] [Definite initialization] Avoid performing DI via nonmutating setters. Nonmutating setters for properties with attached delegates inside value-semantic types cause SIL verifier errors. Avoid performing DI on them for now. --- lib/SILGen/SILGenLValue.cpp | 22 ++++- .../di-property-delegates-errors.swift | 48 ++++++++++ test/SILOptimizer/di-property-delegates.swift | 92 ------------------- 3 files changed, 68 insertions(+), 94 deletions(-) create mode 100644 test/SILOptimizer/di-property-delegates-errors.swift diff --git a/lib/SILGen/SILGenLValue.cpp b/lib/SILGen/SILGenLValue.cpp index 5da83ae1c438f..68e7078d48077 100644 --- a/lib/SILGen/SILGenLValue.cpp +++ b/lib/SILGen/SILGenLValue.cpp @@ -1295,8 +1295,26 @@ namespace { bool hasPropertyDelegate() const { if (auto *VD = dyn_cast(Storage)) { - if (auto delegateInfo = VD->getAttachedPropertyDelegateTypeInfo()) - return delegateInfo.initialValueInit != nullptr; + if (auto delegateInfo = VD->getAttachedPropertyDelegateTypeInfo()) { + // If there is no init(initialValue:), we cannot rewrite an + // assignment into an initialization. + if (!delegateInfo.initialValueInit) + return false; + + // If we have a nonmutating setter on a value type, the call + // captures all of 'self' and we cannot rewrite an assignment + // into an initialization. + auto setter = VD->getSetter(); + if (setter->isNonMutating() && + setter->getDeclContext()->getSelfNominalTypeDecl() && + setter->isInstanceMember() && + !setter->getDeclContext()->getDeclaredInterfaceType() + ->hasReferenceSemantics()) { + return false; + } + + return true; + } } return false; } diff --git a/test/SILOptimizer/di-property-delegates-errors.swift b/test/SILOptimizer/di-property-delegates-errors.swift new file mode 100644 index 0000000000000..cbd19bcacc751 --- /dev/null +++ b/test/SILOptimizer/di-property-delegates-errors.swift @@ -0,0 +1,48 @@ +// RUN: %target-swift-frontend -emit-sil -verify %s +@propertyDelegate +final class ClassWrapper { + var value: T { + didSet { + print(" .. set \(value)") + } + } + + init(initialValue: T) { + print(" .. init \(initialValue)") + self.value = initialValue + } + + deinit { + print(" .. deinit \(value)") + } +} + +struct IntStructWithClassWrapper { + @ClassWrapper var wrapped: Int + + init() { + wrapped = 42 // expected-error{{'self' used before all stored properties are initialized}} + // expected-note@-1{{'self.wrapped' not initialized}} + } // expected-error{{return from initializer without initializing all stored properties}} + // expected-note@-1{{'self.wrapped' not initialized}} + + init(conditional b: Bool) { + if b { + self.$wrapped = ClassWrapper(initialValue: 32) + } else { + wrapped = 42 // expected-error{{'self' used before all stored properties are initialized}} + // expected-note@-1{{'self.wrapped' not initialized}} + } + } // expected-error{{return from initializer without initializing all stored properties}} + // expected-note@-1{{'self.wrapped' not initialized}} + + init(dynamic b: Bool) { + if b { + wrapped = 42 // expected-error{{'self' used before all stored properties are initialized}} + // expected-note@-1{{'self.wrapped' not initialized}} + } + wrapped = 27 // expected-error{{'self' used before all stored properties are initialized}} + // expected-note@-1{{'self.wrapped' not initialized}} + } // expected-error{{return from initializer without initializing all stored properties}} + // expected-note@-1{{'self.wrapped' not initialized}} +} diff --git a/test/SILOptimizer/di-property-delegates.swift b/test/SILOptimizer/di-property-delegates.swift index db1c0ffc75871..50e5d2a034538 100644 --- a/test/SILOptimizer/di-property-delegates.swift +++ b/test/SILOptimizer/di-property-delegates.swift @@ -18,24 +18,6 @@ struct Wrapper { } } -@propertyDelegate -final class ClassWrapper { - var value: T { - didSet { - print(" .. set \(value)") - } - } - - init(initialValue: T) { - print(" .. init \(initialValue)") - self.value = initialValue - } - - deinit { - print(" .. deinit \(value)") - } -} - protocol IntInitializable { init(_: Int) } @@ -153,34 +135,6 @@ final class GenericClass { } } -/* - -This does not work yet. - -struct IntStructWithClassWrapper { - @ClassWrapper var wrapped: Int - - init() { - wrapped = 42 - wrapped = 27 - } - - init(conditional b: Bool) { - if b { - self.$wrapped = ClassWrapper(initialValue: 32) - } else { - wrapped = 42 - } - } - - init(dynamic b: Bool) { - if b { - wrapped = 42 - } - wrapped = 27 - } -} -*/ func testIntStruct() { // CHECK: ## IntStruct @@ -350,55 +304,9 @@ func testGenericClass() { } } -/* -func testIntStructWithClassWrapper() { - // CHECK-DISABLED: ## IntStructWithClassWrapper - print("\n## IntStructWithClassWrapper") - - if true { - // CHECK-NEXT-DISABLED: .. init 42 - // CHECK-NEXT-DISABLED: .. set 27 - let t1 = IntStructWithClassWrapper() - // CHECK-NEXT-DISABLED: 27 - print(t1.wrapped) - // CHECK-NEXT-DISABLED: .. deinit 27 - } - if true { - // CHECK-NEXT-DISABLED: .. init 42 - let t2 = IntStructWithClassWrapper(conditional: false) - // CHECK-NEXT-DISABLED: 42 - print(t2.wrapped) - // CHECK-NEXT-DISABLED: .. deinit 42 - } - if true { - // CHECK-NEXT-DISABLED: .. init 32 - let t3 = IntStructWithClassWrapper(conditional: true) - // CHECK-NEXT-DISABLED: 32 - print(t3.wrapped) - // CHECK-NEXT-DISABLED: .. deinit 32 - } - if true { - // CHECK-NEXT-DISABLED: .. init 27 - let t4 = IntStructWithClassWrapper(dynamic: false) - // CHECK-NEXT-DISABLED: 27 - print(t4.wrapped) - // CHECK-NEXT-DISABLED: .. deinit 27 - } - if true { - // CHECK-NEXT-DISABLED: .. init 42 - // CHECK-NEXT-DISABLED: .. deinit 42 - // CHECK-NEXT-DISABLED: .. init 27 - let t5 = IntStructWithClassWrapper(dynamic: true) - // CHECK-NEXT-DISABLED: 27 - print(t5.wrapped) - // CHECK-NEXT-DISABLED: .. deinit 27 - } -} -*/ testIntStruct() testIntClass() testRefStruct() testGenericClass() -//testIntStructWithClassWrapper() From 03f674018b136b05ece7cf0337e74c79d251aa0b Mon Sep 17 00:00:00 2001 From: Doug Gregor Date: Sun, 14 Apr 2019 20:05:43 -0700 Subject: [PATCH 21/31] Eliminate a use-after-free in assign_by_delegate lowering. ASan is magic. Thank you, ASan. --- lib/SILOptimizer/Mandatory/RawSILInstLowering.cpp | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/lib/SILOptimizer/Mandatory/RawSILInstLowering.cpp b/lib/SILOptimizer/Mandatory/RawSILInstLowering.cpp index b1addde6cae7c..5c7ce810bb712 100644 --- a/lib/SILOptimizer/Mandatory/RawSILInstLowering.cpp +++ b/lib/SILOptimizer/Mandatory/RawSILInstLowering.cpp @@ -166,13 +166,16 @@ static void lowerAssignByDelegateInstruction(SILBuilderWithScope &b, } static void deleteDeadAccessMarker(BeginAccessInst *BA) { + SmallVector Users; for (Operand *Op : BA->getUses()) { SILInstruction *User = Op->getUser(); if (!isa(User)) return; + + Users.push_back(User); } - for (Operand *Op : BA->getUses()) { - Op->getUser()->eraseFromParent(); + for (SILInstruction *User: Users) { + User->eraseFromParent(); } BA->eraseFromParent(); } From 2e9f8cf981b3cfba07b3ff0557b2e9bee1c8794e Mon Sep 17 00:00:00 2001 From: Doug Gregor Date: Sun, 14 Apr 2019 22:35:39 -0700 Subject: [PATCH 22/31] Capture a placeholder opaque value expression when needed. --- include/swift/AST/CaptureInfo.h | 49 ++++++++++++++++++++++----- include/swift/AST/DiagnosticsSema.def | 2 +- include/swift/AST/TypeAlignments.h | 2 ++ lib/AST/CaptureInfo.cpp | 2 ++ lib/SIL/SILFunctionType.cpp | 21 ++++++++++++ lib/SIL/TypeLowering.cpp | 12 ++++++- lib/SILGen/SILGenFunction.cpp | 7 ++++ lib/SILGen/SILGenProlog.cpp | 19 ++++++++++- lib/Sema/CodeSynthesis.cpp | 2 +- lib/Sema/TypeCheckCaptures.cpp | 30 ++++++++++++++-- test/SILGen/property_delegates.swift | 45 ++++++++++++++++++++++++ 11 files changed, 176 insertions(+), 15 deletions(-) diff --git a/include/swift/AST/CaptureInfo.h b/include/swift/AST/CaptureInfo.h index c1cab207f0b37..b5f14d1fa896e 100644 --- a/include/swift/AST/CaptureInfo.h +++ b/include/swift/AST/CaptureInfo.h @@ -17,6 +17,7 @@ #include "swift/AST/TypeAlignments.h" #include "llvm/ADT/ArrayRef.h" #include "llvm/ADT/PointerIntPair.h" +#include "llvm/ADT/PointerUnion.h" #include namespace swift { @@ -31,13 +32,20 @@ template <> struct DenseMapInfo; namespace swift { class ValueDecl; class FuncDecl; +class OpaqueValueExpr; /// CapturedValue includes both the declaration being captured, along with flags /// that indicate how it is captured. class CapturedValue { - llvm::PointerIntPair Value; +public: + using Storage = + llvm::PointerIntPair, 2, + unsigned>; + +private: + Storage Value; - explicit CapturedValue(llvm::PointerIntPair V) : Value(V) {} + explicit CapturedValue(Storage V) : Value(V) {} public: friend struct llvm::DenseMapInfo; @@ -53,16 +61,21 @@ class CapturedValue { IsNoEscape = 1 << 1 }; - CapturedValue(ValueDecl *D, unsigned Flags) : Value(D, Flags) {} + CapturedValue(llvm::PointerUnion Ptr, + unsigned Flags) + : Value(Ptr, Flags) {} static CapturedValue getDynamicSelfMetadata() { - return CapturedValue(nullptr, 0); + return CapturedValue((ValueDecl *)nullptr, 0); } bool isDirect() const { return Value.getInt() & IsDirect; } bool isNoEscape() const { return Value.getInt() & IsNoEscape; } bool isDynamicSelfMetadata() const { return !Value.getPointer(); } + bool isOpaqueValue() const { + return Value.getPointer().is(); + } CapturedValue mergeFlags(CapturedValue cv) { assert(Value.getPointer() == cv.Value.getPointer() && @@ -73,7 +86,13 @@ class CapturedValue { ValueDecl *getDecl() const { assert(Value.getPointer() && "dynamic Self metadata capture does not " "have a value"); - return Value.getPointer(); + return Value.getPointer().dyn_cast(); + } + + OpaqueValueExpr *getOpaqueValue() const { + assert(Value.getPointer() && "dynamic Self metadata capture does not " + "have a value"); + return Value.getPointer().dyn_cast(); } unsigned getFlags() const { return Value.getInt(); } @@ -98,8 +117,7 @@ namespace llvm { template <> struct DenseMapInfo { using CapturedValue = swift::CapturedValue; - using PtrIntPairDenseMapInfo = - DenseMapInfo>; + using PtrIntPairDenseMapInfo = DenseMapInfo; static inline swift::CapturedValue getEmptyKey() { return CapturedValue{PtrIntPairDenseMapInfo::getEmptyKey()}; @@ -128,19 +146,20 @@ class DynamicSelfType; class CaptureInfo { const CapturedValue *Captures; DynamicSelfType *DynamicSelf; + OpaqueValueExpr *OpaqueValue; unsigned Count = 0; bool GenericParamCaptures : 1; bool Computed : 1; public: CaptureInfo() - : Captures(nullptr), DynamicSelf(nullptr), Count(0), + : Captures(nullptr), DynamicSelf(nullptr), OpaqueValue(nullptr), Count(0), GenericParamCaptures(0), Computed(0) { } bool hasBeenComputed() { return Computed; } bool isTrivial() { - return Count == 0 && !GenericParamCaptures && !DynamicSelf; + return Count == 0 && !GenericParamCaptures && !DynamicSelf && !OpaqueValue; } ArrayRef getCaptures() const { @@ -184,6 +203,18 @@ class CaptureInfo { DynamicSelf = dynamicSelf; } + bool hasOpaqueValueCapture() const { + return OpaqueValue != nullptr; + } + + OpaqueValueExpr *getOpaqueValue() const { + return OpaqueValue; + } + + void setOpaqueValue(OpaqueValueExpr *OVE) { + OpaqueValue = OVE; + } + void dump() const; void print(raw_ostream &OS) const; }; diff --git a/include/swift/AST/DiagnosticsSema.def b/include/swift/AST/DiagnosticsSema.def index 4b4a42bf1e771..3ce7c05e473cd 100644 --- a/include/swift/AST/DiagnosticsSema.def +++ b/include/swift/AST/DiagnosticsSema.def @@ -1074,7 +1074,7 @@ ERROR(c_function_pointer_from_generic_function,none, ERROR(c_function_pointer_from_function_with_context,none, "a C function pointer cannot be formed from a " "%select{local function|closure}0 that captures " - "%select{context|generic parameters|dynamic Self type}1", + "%select{context|generic parameters|dynamic Self type|<}1", (bool, unsigned)) ERROR(invalid_autoclosure_forwarding,none, "add () to forward @autoclosure parameter", ()) diff --git a/include/swift/AST/TypeAlignments.h b/include/swift/AST/TypeAlignments.h index 4d58eda2edde3..149b8ae24598d 100644 --- a/include/swift/AST/TypeAlignments.h +++ b/include/swift/AST/TypeAlignments.h @@ -39,6 +39,7 @@ namespace swift { class GenericTypeParamDecl; class NominalTypeDecl; class NormalProtocolConformance; + class OpaqueValueExpr; class OperatorDecl; class Pattern; class ProtocolDecl; @@ -105,6 +106,7 @@ LLVM_DECLARE_TYPE_ALIGNMENT(swift::ASTContext, 2); LLVM_DECLARE_TYPE_ALIGNMENT(swift::DeclContext, swift::DeclContextAlignInBits) LLVM_DECLARE_TYPE_ALIGNMENT(swift::Expr, swift::ExprAlignInBits) LLVM_DECLARE_TYPE_ALIGNMENT(swift::AbstractClosureExpr, swift::ExprAlignInBits) +LLVM_DECLARE_TYPE_ALIGNMENT(swift::OpaqueValueExpr, swift::ExprAlignInBits) LLVM_DECLARE_TYPE_ALIGNMENT(swift::ProtocolConformance, swift::DeclAlignInBits) LLVM_DECLARE_TYPE_ALIGNMENT(swift::NormalProtocolConformance, swift::DeclAlignInBits) diff --git a/lib/AST/CaptureInfo.cpp b/lib/AST/CaptureInfo.cpp index b43dc18e84416..de209b3558a4b 100644 --- a/lib/AST/CaptureInfo.cpp +++ b/lib/AST/CaptureInfo.cpp @@ -51,6 +51,8 @@ void CaptureInfo::print(raw_ostream &OS) const { OS << " "; if (hasDynamicSelfCapture()) OS << " "; + if (hasOpaqueValueCapture()) + OS << " "; interleave(getCaptures(), [&](const CapturedValue &capture) { diff --git a/lib/SIL/SILFunctionType.cpp b/lib/SIL/SILFunctionType.cpp index 648b8bad7ef5f..ab1b1c7284201 100644 --- a/lib/SIL/SILFunctionType.cpp +++ b/lib/SIL/SILFunctionType.cpp @@ -743,6 +743,27 @@ lowerCaptureContextParameters(SILModule &M, AnyFunctionRef function, continue; } + if (capture.isOpaqueValue()) { + OpaqueValueExpr *opaqueValue = capture.getOpaqueValue(); + auto canType = opaqueValue->getType()->mapTypeOutOfContext() + ->getCanonicalType(origGenericSig); + auto &loweredTL = + Types.getTypeLowering(AbstractionPattern(genericSig, canType), + canType, expansion); + auto loweredTy = loweredTL.getLoweredType(); + + ParameterConvention convention; + if (loweredTL.isAddressOnly()) { + convention = ParameterConvention::Indirect_In; + } else { + convention = ParameterConvention::Direct_Owned; + } + SILParameterInfo param(loweredTy.getASTType(), convention); + inputs.push_back(param); + + continue; + } + auto *VD = capture.getDecl(); auto type = VD->getInterfaceType(); auto canType = type->getCanonicalType(origGenericSig); diff --git a/lib/SIL/TypeLowering.cpp b/lib/SIL/TypeLowering.cpp index e4af4bddde5d5..50d14f81ea0c0 100644 --- a/lib/SIL/TypeLowering.cpp +++ b/lib/SIL/TypeLowering.cpp @@ -2120,6 +2120,7 @@ CaptureInfo TypeConverter::getLoweredLocalCaptures(AnyFunctionRef fn) { // First, bail out if there are no local captures at all. if (!fn.getCaptureInfo().hasLocalCaptures() && + !fn.getCaptureInfo().hasOpaqueValueCapture() && !fn.getCaptureInfo().hasDynamicSelfCapture()) { CaptureInfo info; info.setGenericParamCaptures( @@ -2142,7 +2143,8 @@ TypeConverter::getLoweredLocalCaptures(AnyFunctionRef fn) { bool capturesGenericParams = false; DynamicSelfType *capturesDynamicSelf = nullptr; - + OpaqueValueExpr *capturesOpaqueValue = nullptr; + std::function collectFunctionCaptures = [&](AnyFunctionRef curFn) { if (!visitedFunctions.insert(curFn).second) @@ -2152,6 +2154,8 @@ TypeConverter::getLoweredLocalCaptures(AnyFunctionRef fn) { capturesGenericParams = true; if (curFn.getCaptureInfo().hasDynamicSelfCapture()) capturesDynamicSelf = curFn.getCaptureInfo().getDynamicSelfType(); + if (curFn.getCaptureInfo().hasOpaqueValueCapture()) + capturesOpaqueValue = curFn.getCaptureInfo().getOpaqueValue(); SmallVector localCaptures; curFn.getCaptureInfo().getLocalCaptures(localCaptures); @@ -2266,6 +2270,11 @@ TypeConverter::getLoweredLocalCaptures(AnyFunctionRef fn) { resultingCaptures.push_back(capturePair.second); } + // If we captured an opaque value, add it. + if (capturesOpaqueValue) { + resultingCaptures.push_back(CapturedValue(capturesOpaqueValue, 0)); + } + // If we captured the dynamic 'Self' type and we have a 'self' value also, // add it as the final capture. Otherwise, add a fake hidden capture for // the dynamic 'Self' metatype. @@ -2282,6 +2291,7 @@ TypeConverter::getLoweredLocalCaptures(AnyFunctionRef fn) { auto &cachedCaptures = inserted.first->second; cachedCaptures.setGenericParamCaptures(capturesGenericParams); cachedCaptures.setDynamicSelfType(capturesDynamicSelf); + cachedCaptures.setOpaqueValue(capturesOpaqueValue); cachedCaptures.setCaptures(Context.AllocateCopy(resultingCaptures)); return cachedCaptures; diff --git a/lib/SILGen/SILGenFunction.cpp b/lib/SILGen/SILGenFunction.cpp index 0e9d529760621..da9ddf2270f0f 100644 --- a/lib/SILGen/SILGenFunction.cpp +++ b/lib/SILGen/SILGenFunction.cpp @@ -199,6 +199,13 @@ void SILGenFunction::emitCaptures(SILLocation loc, continue; } + if (capture.isOpaqueValue()) { + OpaqueValueExpr *opaqueValue = capture.getOpaqueValue(); + capturedArgs.push_back( + emitRValueAsSingleValue(opaqueValue).ensurePlusOne(*this, loc)); + continue; + } + auto *vd = capture.getDecl(); // FIXME: Expansion diff --git a/lib/SILGen/SILGenProlog.cpp b/lib/SILGen/SILGenProlog.cpp index 8145c4d8a79c0..0a84513d3d34d 100644 --- a/lib/SILGen/SILGenProlog.cpp +++ b/lib/SILGen/SILGenProlog.cpp @@ -455,7 +455,24 @@ void SILGenFunction::emitProlog(AnyFunctionRef TheClosure, SILValue val = F.begin()->createFunctionArgument(ty); (void) val; - return; + continue; + } + + if (capture.isOpaqueValue()) { + OpaqueValueExpr *opaqueValue = capture.getOpaqueValue(); + Type type = opaqueValue->getType()->mapTypeOutOfContext(); + type = GenericEnvironment::mapTypeIntoContext( + TheClosure.getGenericEnvironment(), type); + auto &lowering = getTypeLowering(type); + SILType ty = lowering.getLoweredType(); + SILValue val = F.begin()->createFunctionArgument(ty); + OpaqueValues[opaqueValue] = ManagedValue::forUnmanaged(val); + + // Opaque values are always passed 'owned', so add a clean up if needed. + if (!lowering.isTrivial()) + enterDestroyCleanup(val); + + continue; } emitCaptureArguments(*this, TheClosure, capture, ++ArgNo); diff --git a/lib/Sema/CodeSynthesis.cpp b/lib/Sema/CodeSynthesis.cpp index 99ea890304204..7b4da404ace37 100644 --- a/lib/Sema/CodeSynthesis.cpp +++ b/lib/Sema/CodeSynthesis.cpp @@ -1649,7 +1649,7 @@ PropertyDelegateBackingPropertyInfoRequest::evaluate(Evaluator &evaluator, // Form the initialization of the backing property from a value of the // original property's type. OpaqueValueExpr *origValue = - new (ctx) OpaqueValueExpr(SourceLoc(), var->getType(), + new (ctx) OpaqueValueExpr(var->getLoc(), var->getType(), /*isPlaceholder=*/true); auto typeExpr = TypeExpr::createImplicit(storageType, ctx); Expr *initializer = diff --git a/lib/Sema/TypeCheckCaptures.cpp b/lib/Sema/TypeCheckCaptures.cpp index 18aa61f87d708..994199eab8cfb 100644 --- a/lib/Sema/TypeCheckCaptures.cpp +++ b/lib/Sema/TypeCheckCaptures.cpp @@ -62,6 +62,7 @@ class FindCapturedVars : public ASTWalker { SourceLoc &GenericParamCaptureLoc; SourceLoc &DynamicSelfCaptureLoc; DynamicSelfType *&DynamicSelf; + OpaqueValueExpr *&OpaqueValue; llvm::SmallPtrSet Diagnosed; /// The AbstractClosureExpr or AbstractFunctionDecl being analyzed. AnyFunctionRef AFR; @@ -71,11 +72,13 @@ class FindCapturedVars : public ASTWalker { SourceLoc &GenericParamCaptureLoc, SourceLoc &DynamicSelfCaptureLoc, DynamicSelfType *&DynamicSelf, + OpaqueValueExpr *&OpaqueValue, AnyFunctionRef AFR) : TC(tc), Captures(Captures), GenericParamCaptureLoc(GenericParamCaptureLoc), DynamicSelfCaptureLoc(DynamicSelfCaptureLoc), DynamicSelf(DynamicSelf), + OpaqueValue(OpaqueValue), AFR(AFR) { CaptureLoc = getCaptureLoc(AFR); } @@ -427,6 +430,11 @@ class FindCapturedVars : public ASTWalker { DynamicSelfCaptureLoc = getCaptureLoc(innerClosure); DynamicSelf = captureInfo.getDynamicSelfType(); } + + if (!OpaqueValue) { + if (captureInfo.hasOpaqueValueCapture()) + OpaqueValue = captureInfo.getOpaqueValue(); + } } bool walkToDeclPre(Decl *D) override { @@ -637,6 +645,15 @@ class FindCapturedVars : public ASTWalker { return { false, E }; } + // Capture a placeholder opaque value. + if (auto opaqueValue = dyn_cast(E)) { + if (opaqueValue->isPlaceholder()) { + assert(!OpaqueValue || OpaqueValue == opaqueValue); + OpaqueValue = opaqueValue; + return { true, E }; + } + } + return { true, E }; } }; @@ -654,14 +671,17 @@ void TypeChecker::maybeDiagnoseCaptures(Expr *E, AnyFunctionRef AFR) { if (AFR.getCaptureInfo().hasGenericParamCaptures() || AFR.getCaptureInfo().hasDynamicSelfCapture() || - AFR.getCaptureInfo().hasLocalCaptures()) { + AFR.getCaptureInfo().hasLocalCaptures() || + AFR.getCaptureInfo().hasOpaqueValueCapture()) { unsigned kind; if (AFR.getCaptureInfo().hasLocalCaptures()) kind = 0; else if (AFR.getCaptureInfo().hasGenericParamCaptures()) kind = 1; - else + else if (AFR.getCaptureInfo().hasLocalCaptures()) kind = 2; + else + kind = 3; diagnose(E->getLoc(), diag::c_function_pointer_from_function_with_context, /*closure*/ AFR.getAbstractClosureExpr() != nullptr, @@ -680,10 +700,12 @@ void TypeChecker::computeCaptures(AnyFunctionRef AFR) { SourceLoc GenericParamCaptureLoc; SourceLoc DynamicSelfCaptureLoc; DynamicSelfType *DynamicSelf = nullptr; + OpaqueValueExpr *OpaqueValuePlaceholder = nullptr; FindCapturedVars finder(*this, Captures, GenericParamCaptureLoc, DynamicSelfCaptureLoc, DynamicSelf, + OpaqueValuePlaceholder, AFR); if (AFR.getBody()) AFR.getBody()->walk(finder); @@ -723,6 +745,10 @@ void TypeChecker::computeCaptures(AnyFunctionRef AFR) { AFR.getCaptureInfo().setGenericParamCaptures(true); } + // Anything can capture an opaque value placeholder. + if (OpaqueValuePlaceholder) + AFR.getCaptureInfo().setOpaqueValue(OpaqueValuePlaceholder); + // Only local functions capture dynamic 'Self'. if (AFR.getAsDeclContext()->getParent()->isLocalContext()) { if (GenericParamCaptureLoc.isValid()) diff --git a/test/SILGen/property_delegates.swift b/test/SILGen/property_delegates.swift index 4da6655dedaa7..fed1b73bd12da 100644 --- a/test/SILGen/property_delegates.swift +++ b/test/SILGen/property_delegates.swift @@ -205,3 +205,48 @@ struct UseWrapperWithStorageValue { // CHECK: function_ref @$s18property_delegates23WrapperWithStorageValueV07storageF0AA0C0VyxGvg @WrapperWithStorageValue(value: 17) var x: Int } + +@propertyDelegate +enum Lazy { + case uninitialized(() -> Value) + case initialized(Value) + + init(initialValue: @autoclosure @escaping () -> Value) { + self = .uninitialized(initialValue) + } + + var value: Value { + mutating get { + switch self { + case .uninitialized(let initializer): + let value = initializer() + self = .initialized(value) + return value + case .initialized(let value): + return value + } + } + set { + self = .initialized(newValue) + } + } +} + +struct UseLazy { + @Lazy var foo = 17 + @Lazy var bar = T() + @Lazy var wibble = [1, 2, 3] + + // CHECK-LABEL: sil hidden [ossa] @$s18property_delegates7UseLazyV3foo3bar6wibbleACyxGSi_xSaySiGtcfC : $@convention(method) (Int, @in T, @owned Array, @thin UseLazy.Type) -> @out UseLazy + // CHECK: function_ref @$s18property_delegates7UseLazyV4$fooAA0D0OySiGvpfiSiycfu_ : $@convention(thin) (@owned Int) -> Int + // CHECK: function_ref @$s18property_delegates4LazyO12initialValueACyxGxyXA_tcfC : $@convention(method) <τ_0_0> (@owned @callee_guaranteed () -> @out τ_0_0, @thin Lazy<τ_0_0>.Type) -> @out Lazy<τ_0_0> +} + +struct X { } + +func triggerUseLazy() { + _ = UseLazy() + _ = UseLazy(foo: 17) + _ = UseLazy(bar: 17) + _ = UseLazy(wibble: [1, 2, 3]) +} From 261b879b54b3b3f9b2df37575f8319a1e8ff0e45 Mon Sep 17 00:00:00 2001 From: Doug Gregor Date: Mon, 15 Apr 2019 09:20:35 -0700 Subject: [PATCH 23/31] [Property delegates] Rename storageValue to delegateValue --- include/swift/AST/Decl.h | 2 +- include/swift/AST/KnownIdentifiers.def | 2 +- include/swift/AST/PropertyDelegates.h | 4 ++-- lib/Sema/CodeSynthesis.cpp | 22 +++++++++++----------- lib/Sema/TypeCheckPropertyDelegate.cpp | 4 ++-- test/SILGen/property_delegates.swift | 4 ++-- test/decl/var/property_delegates.swift | 2 +- 7 files changed, 20 insertions(+), 20 deletions(-) diff --git a/include/swift/AST/Decl.h b/include/swift/AST/Decl.h index 7987a94eec21e..10a33587fb1fc 100644 --- a/include/swift/AST/Decl.h +++ b/include/swift/AST/Decl.h @@ -4725,7 +4725,7 @@ enum class PropertyDelegateSynthesizedPropertyKind { /// delegate type. Backing, /// A storage delegate (e.g., `$foo`), which is a wrapper over the - /// delegate instance's `storageValue` property. + /// delegate instance's `delegateValue` property. StorageDelegate, }; diff --git a/include/swift/AST/KnownIdentifiers.def b/include/swift/AST/KnownIdentifiers.def index 5c0df84885e6e..01ed9b788c862 100644 --- a/include/swift/AST/KnownIdentifiers.def +++ b/include/swift/AST/KnownIdentifiers.def @@ -47,6 +47,7 @@ IDENTIFIER(decode) IDENTIFIER(decodeIfPresent) IDENTIFIER(Decoder) IDENTIFIER(decoder) +IDENTIFIER(delegateValue) IDENTIFIER(dynamicallyCall) IDENTIFIER(dynamicMember) IDENTIFIER(Element) @@ -97,7 +98,6 @@ IDENTIFIER(Self) IDENTIFIER(setObject) IDENTIFIER(simd) IDENTIFIER(storage) -IDENTIFIER(storageValue) IDENTIFIER(stringValue) IDENTIFIER(super) IDENTIFIER(superDecoder) diff --git a/include/swift/AST/PropertyDelegates.h b/include/swift/AST/PropertyDelegates.h index 74a44f7169fd5..4d2ad3f645a3e 100644 --- a/include/swift/AST/PropertyDelegates.h +++ b/include/swift/AST/PropertyDelegates.h @@ -42,12 +42,12 @@ struct PropertyDelegateTypeInfo { /// initialization syntax. ConstructorDecl *initialValueInit = nullptr; - /// The property through which the storage value ($foo) will be accessed, + /// The property through which the delegate value ($foo) will be accessed, /// hiding the underlying storage completely. /// /// This property is optional. If present, a computed property for `$foo` /// will be created that redirects to this property. - VarDecl *storageValueVar = nullptr; + VarDecl *delegateValueVar = nullptr; /// Whether this is a valid property delegate. bool isValid() const { diff --git a/lib/Sema/CodeSynthesis.cpp b/lib/Sema/CodeSynthesis.cpp index 7b4da404ace37..423cd3f49f8b9 100644 --- a/lib/Sema/CodeSynthesis.cpp +++ b/lib/Sema/CodeSynthesis.cpp @@ -608,7 +608,7 @@ static Expr *buildStorageReference(AccessorDecl *accessor, auto var = cast(accessor->getStorage())->getOriginalDelegatedProperty(); storage = var->getPropertyDelegateBackingProperty(); - underlyingVar = var->getAttachedPropertyDelegateTypeInfo().storageValueVar; + underlyingVar = var->getAttachedPropertyDelegateTypeInfo().delegateValueVar; assert(underlyingVar); semantics = AccessSemantics::DirectToStorage; selfAccessKind = SelfAccessorKind::Peer; @@ -1483,10 +1483,10 @@ static Expr *findOriginalPropertyDelegateInitialValue(VarDecl *var, } /// Synthesize a computed property `$foo` for a property with an attached -/// delegate that has a `storageValue` property. +/// delegate that has a `delegateValue` property. static VarDecl *synthesizePropertyDelegateStorageDelegateProperty( ASTContext &ctx, VarDecl *var, Type delegateType, - VarDecl *storageValueVar) { + VarDecl *delegateVarVar) { // Compute the name of the storage type. SmallString<64> nameBuf; nameBuf = "$"; @@ -1495,8 +1495,8 @@ static VarDecl *synthesizePropertyDelegateStorageDelegateProperty( // Determine the type of the property. Type propertyType = delegateType->getTypeOfMember( - var->getModuleContext(), storageValueVar, - storageValueVar->getValueInterfaceType()); + var->getModuleContext(), delegateVarVar, + delegateVarVar->getValueInterfaceType()); Type propertyInterfaceType = propertyType->mapTypeOutOfContext(); // Form the property. @@ -1532,8 +1532,8 @@ static VarDecl *synthesizePropertyDelegateStorageDelegateProperty( property->overwriteSetterAccess(setterAccess); // Add the accessors we need. - bool hasSetter = storageValueVar->isSettable(nullptr) && - storageValueVar->isSetterAccessibleFrom(var->getInnermostDeclContext()); + bool hasSetter = delegateVarVar->isSettable(nullptr) && + delegateVarVar->isSetterAccessibleFrom(var->getInnermostDeclContext()); addGetterToStorage(property, ctx); if (hasSetter) { addSetterToStorage(property, ctx); @@ -1559,7 +1559,7 @@ PropertyDelegateBackingPropertyInfoRequest::evaluate(Evaluator &evaluator, // Compute the name of the storage type. ASTContext &ctx = var->getASTContext(); SmallString<64> nameBuf; - if (delegateInfo.storageValueVar) + if (delegateInfo.delegateValueVar) nameBuf = "$__delegate_storage_$_"; else nameBuf = "$"; @@ -1631,12 +1631,12 @@ PropertyDelegateBackingPropertyInfoRequest::evaluate(Evaluator &evaluator, std::min(AccessLevel::Internal, var->getSetterFormalAccess()); backingVar->overwriteSetterAccess(setterAccess); - // If there is a storage delegate property (storageValue) in the delegate, + // If there is a storage delegate property (delegateVar) in the delegate, // synthesize a computed property for '$foo'. VarDecl *storageVar = nullptr; - if (delegateInfo.storageValueVar) { + if (delegateInfo.delegateValueVar) { storageVar = synthesizePropertyDelegateStorageDelegateProperty( - ctx, var, delegateType, delegateInfo.storageValueVar); + ctx, var, delegateType, delegateInfo.delegateValueVar); } // Get the property delegate information. diff --git a/lib/Sema/TypeCheckPropertyDelegate.cpp b/lib/Sema/TypeCheckPropertyDelegate.cpp index 1b4c4a9db075e..e9295c694d485 100644 --- a/lib/Sema/TypeCheckPropertyDelegate.cpp +++ b/lib/Sema/TypeCheckPropertyDelegate.cpp @@ -186,8 +186,8 @@ PropertyDelegateTypeInfoRequest::evaluate( PropertyDelegateTypeInfo result; result.valueVar = valueVar; result.initialValueInit = findInitialValueInit(ctx, nominal, valueVar); - result.storageValueVar = - findValueProperty(ctx, nominal, ctx.Id_storageValue, /*allowMissing=*/true); + result.delegateValueVar = + findValueProperty(ctx, nominal, ctx.Id_delegateValue, /*allowMissing=*/true); return result; } diff --git a/test/SILGen/property_delegates.swift b/test/SILGen/property_delegates.swift index fed1b73bd12da..aa34eb1c1dc3e 100644 --- a/test/SILGen/property_delegates.swift +++ b/test/SILGen/property_delegates.swift @@ -193,7 +193,7 @@ struct DelegateWithDidSetWillSet { struct WrapperWithStorageValue { var value: T - var storageValue: Wrapper { + var delegateValue: Wrapper { return Wrapper(value: value) } } @@ -202,7 +202,7 @@ struct UseWrapperWithStorageValue { // UseWrapperWithStorageValue.$x.getter // CHECK-LABEL: sil hidden [transparent] [ossa] @$s18property_delegates26UseWrapperWithStorageValueV2$xAA0D0VySiGvg : $@convention(method) (UseWrapperWithStorageValue) -> Wrapper // CHECK-NOT: return - // CHECK: function_ref @$s18property_delegates23WrapperWithStorageValueV07storageF0AA0C0VyxGvg + // CHECK: function_ref @$s18property_delegates23WrapperWithStorageValueV08delegateF0AA0C0VyxGvg @WrapperWithStorageValue(value: 17) var x: Int } diff --git a/test/decl/var/property_delegates.swift b/test/decl/var/property_delegates.swift index 28653266d00db..7e4e640830755 100644 --- a/test/decl/var/property_delegates.swift +++ b/test/decl/var/property_delegates.swift @@ -613,7 +613,7 @@ func testDefaultInitializers() { struct WrapperWithStorageRef { var value: T - var storageValue: Wrapper { + var delegateValue: Wrapper { return Wrapper(value: value) } } From 7df695536ef226d2364fa5fcb8c493674d14738c Mon Sep 17 00:00:00 2001 From: Doug Gregor Date: Mon, 15 Apr 2019 23:27:24 -0700 Subject: [PATCH 24/31] [Property delegates] Fix printing of memberwise initializer default arguments --- include/swift/AST/PropertyDelegates.h | 4 +++ lib/AST/Decl.cpp | 43 +++++++++++++++++++++++++ lib/Sema/CodeSynthesis.cpp | 29 +---------------- test/IDE/print_property_delegates.swift | 38 ++++++++++++++++++++++ 4 files changed, 86 insertions(+), 28 deletions(-) create mode 100644 test/IDE/print_property_delegates.swift diff --git a/include/swift/AST/PropertyDelegates.h b/include/swift/AST/PropertyDelegates.h index 4d2ad3f645a3e..6fc018e84642f 100644 --- a/include/swift/AST/PropertyDelegates.h +++ b/include/swift/AST/PropertyDelegates.h @@ -126,6 +126,10 @@ void simple_display( llvm::raw_ostream &out, const PropertyDelegateBackingPropertyInfo &backingInfo); +/// Given the initializer for the given property with an attached property +/// delegate, dig out the original initialization expression. +Expr *findOriginalPropertyDelegateInitialValue(VarDecl *var, Expr *init); + } // end namespace swift #endif // SWIFT_AST_PROPERTY_DELEGATES_H diff --git a/lib/AST/Decl.cpp b/lib/AST/Decl.cpp index 17abeba2b28ed..7e4fdfe8c7e54 100644 --- a/lib/AST/Decl.cpp +++ b/lib/AST/Decl.cpp @@ -42,6 +42,7 @@ #include "swift/AST/TypeCheckRequests.h" #include "swift/AST/TypeLoc.h" #include "swift/AST/SwiftNameTranslation.h" +#include "swift/Parse/Lexer.h" #include "clang/Lex/MacroInfo.h" #include "llvm/ADT/SmallString.h" #include "llvm/ADT/SmallPtrSet.h" @@ -5522,6 +5523,31 @@ void ParamDecl::setDefaultArgumentInitContext(Initializer *initContext) { DefaultValueAndFlags.getPointer()->InitContext = initContext; } +Expr *swift::findOriginalPropertyDelegateInitialValue(VarDecl *var, + Expr *init) { + auto attr = var->getAttachedPropertyDelegate(); + assert(attr && "No attached property delegate?"); + + // Direct initialization implies no original initial value. + if (attr->getArg()) + return nullptr; + + // Look through any expressions wrapping the initializer. + init = init->getSemanticsProvidingExpr(); + auto initCall = dyn_cast(init); + if (!initCall) + return nullptr; + + auto initArg = cast(initCall->getArg())->getElement(0); + initArg = initArg->getSemanticsProvidingExpr(); + if (auto autoclosure = dyn_cast(initArg)) { + initArg = + autoclosure->getSingleExpressionBody()->getSemanticsProvidingExpr(); + } + + return initArg; +} + StringRef ParamDecl::getDefaultValueStringRepresentation( SmallVectorImpl &scratch) const { @@ -5552,6 +5578,23 @@ ParamDecl::getDefaultValueStringRepresentation( if (!existing.empty()) return existing; auto var = getStoredProperty(); + + if (auto original = var->getOriginalDelegatedProperty()) { + if (auto attr = original->getAttachedPropertyDelegate()) { + if (auto arg = attr->getArg()) { + SourceRange fullRange(attr->getTypeLoc().getSourceRange().Start, + arg->getEndLoc()); + auto charRange = Lexer::getCharSourceRangeFromSourceRange( + getASTContext().SourceMgr, fullRange); + return getASTContext().SourceMgr.extractText(charRange); + } + + auto init = findOriginalPropertyDelegateInitialValue( + original, original->getParentInitializer()); + return extractInlinableText(getASTContext().SourceMgr, init, scratch); + } + } + return extractInlinableText(getASTContext().SourceMgr, var->getParentInitializer(), scratch); diff --git a/lib/Sema/CodeSynthesis.cpp b/lib/Sema/CodeSynthesis.cpp index 423cd3f49f8b9..678a54c1d3b0a 100644 --- a/lib/Sema/CodeSynthesis.cpp +++ b/lib/Sema/CodeSynthesis.cpp @@ -1455,33 +1455,6 @@ void swift::completeLazyVarImplementation(VarDecl *VD) { Storage->overwriteSetterAccess(AccessLevel::Private); } -/// Given the initializer for the given property with an attached property -/// delegate, dig out the original initialization expression. -static Expr *findOriginalPropertyDelegateInitialValue(VarDecl *var, - Expr *init) { - auto attr = var->getAttachedPropertyDelegate(); - assert(attr && "No attached property delegate?"); - - // Direct initialization implies no original initial value. - if (attr->getArg()) - return nullptr; - - // Look through any expressions wrapping the initializer. - init = init->getSemanticsProvidingExpr(); - auto initCall = dyn_cast(init); - if (!initCall) - return nullptr; - - auto initArg = cast(initCall->getArg())->getElement(0); - initArg = initArg->getSemanticsProvidingExpr(); - if (auto autoclosure = dyn_cast(initArg)) { - initArg = - autoclosure->getSingleExpressionBody()->getSemanticsProvidingExpr(); - } - - return initArg; -} - /// Synthesize a computed property `$foo` for a property with an attached /// delegate that has a `delegateValue` property. static VarDecl *synthesizePropertyDelegateStorageDelegateProperty( @@ -1604,7 +1577,7 @@ PropertyDelegateBackingPropertyInfoRequest::evaluate(Evaluator &evaluator, // Take the initializer from the original property. auto parentPBD = var->getParentPatternBinding(); unsigned patternNumber = parentPBD->getPatternEntryIndexForVarDecl(var); - if (parentPBD->getInit(patternNumber) && + if (parentPBD->isInitialized(patternNumber) && !parentPBD->isInitializerChecked(patternNumber)) { auto &tc = *static_cast(ctx.getLazyResolver()); tc.typeCheckPatternBinding(parentPBD, patternNumber); diff --git a/test/IDE/print_property_delegates.swift b/test/IDE/print_property_delegates.swift new file mode 100644 index 0000000000000..5a654b1be113a --- /dev/null +++ b/test/IDE/print_property_delegates.swift @@ -0,0 +1,38 @@ +// RUN: %empty-directory(%t) + +// Directly printing the type-checked AST +// RUN: %target-swift-ide-test -print-ast-typechecked -source-filename %s | %FileCheck %s + +@propertyDelegate +struct Delegate { + var value: Value + + init(initialValue: Value) { + self.value = initialValue + } + + init(closure: () -> Value) { + self.value = closure() + } +} + +func foo() -> Int { return 17 } + +// CHECK: struct HasDelegates { +struct HasDelegates { + // CHECK: @Delegate var x: Int { + // CHECK-NEXT: get + // CHECK: var $x: Delegate + @Delegate(closure: foo) + var x: Int + + @Delegate + var y = true + + // Memberwise initializer. + // CHECK: init(x: Delegate = Delegate(closure: foo), y: Bool = true) +} + +func trigger() { + _ = HasDelegates(y: false) +} From 99d4e8090cdc735c8e20df8811fed9758d409b44 Mon Sep 17 00:00:00 2001 From: Doug Gregor Date: Tue, 16 Apr 2019 00:10:54 -0700 Subject: [PATCH 25/31] [Index] Handle memberwise initializers with defaulted arguments. --- include/swift/Sema/IDETypeChecking.h | 4 ++++ lib/Index/Index.cpp | 21 +++++++++++++++++---- test/Index/roles.swift | 12 ++++++++++++ 3 files changed, 33 insertions(+), 4 deletions(-) diff --git a/include/swift/Sema/IDETypeChecking.h b/include/swift/Sema/IDETypeChecking.h index 94a3ef0703b2f..da15b41b7f7ca 100644 --- a/include/swift/Sema/IDETypeChecking.h +++ b/include/swift/Sema/IDETypeChecking.h @@ -239,6 +239,10 @@ namespace swift { /// \param DC The DeclContext from which the subscript is being referenced. Optional getRootTypeOfKeypathDynamicMember(SubscriptDecl *subscript, const DeclContext *DC); + + /// Determine whether the given property is part of the memberwise initializer + /// for a struct. + bool isMemberwiseInitialized(VarDecl *var); } #endif diff --git a/lib/Index/Index.cpp b/lib/Index/Index.cpp index 31c39f564e6bc..1ceb8982b4118 100644 --- a/lib/Index/Index.cpp +++ b/lib/Index/Index.cpp @@ -334,6 +334,7 @@ class IndexSwiftASTWalker : public SourceEntityWalker { // get label locations auto *MemberwiseInit = DeclRef->getDecl(); std::vector LabelLocs; + ArrayRef Labels; auto NameLoc = DeclRef->getNameLoc(); if (NameLoc.isCompound()) { size_t LabelIndex = 0; @@ -341,27 +342,39 @@ class IndexSwiftASTWalker : public SourceEntityWalker { while ((ArgLoc = NameLoc.getArgumentLabelLoc(LabelIndex++)).isValid()) { LabelLocs.push_back(ArgLoc); } + Labels = MemberwiseInit->getFullName().getArgumentNames(); } else if (auto *CallParent = dyn_cast_or_null(getParentExpr())) { LabelLocs = CallParent->getArgumentLabelLocs(); + Labels = CallParent->getArgumentLabels(); } if (LabelLocs.empty()) return; + assert(Labels.size() == LabelLocs.size()); + // match labels to properties auto *TypeContext = MemberwiseInit->getDeclContext()->getSelfNominalTypeDecl(); if (!TypeContext || !shouldIndex(TypeContext, false)) return; - auto LabelIt = LabelLocs.begin(); + unsigned CurLabel = 0; for (auto Prop : TypeContext->getStoredProperties()) { - if (Prop->getParentInitializer() && Prop->isLet()) + if (auto Original = Prop->getOriginalDelegatedProperty()) + Prop = Original; + + if (!isMemberwiseInitialized(Prop)) + continue; + + if (CurLabel == LabelLocs.size()) + break; + + if (Labels[CurLabel] != Prop->getName()) continue; - assert(LabelIt != LabelLocs.end()); IndexSymbol Info; - if (initIndexSymbol(Prop, *LabelIt++, /*IsRef=*/true, Info)) + if (initIndexSymbol(Prop, LabelLocs[CurLabel++], /*IsRef=*/true, Info)) continue; if (startEntity(Prop, Info, /*IsRef=*/true)) finishCurrentEntity(); diff --git a/test/Index/roles.swift b/test/Index/roles.swift index 0ab6360880fa1..f35988d72062e 100644 --- a/test/Index/roles.swift +++ b/test/Index/roles.swift @@ -503,3 +503,15 @@ _ = \StructWithKeypath.[0] // CHECK: [[@LINE-1]]:24 | instance-property/subscript/Swift | subscript(_:) | s:14swift_ide_test17StructWithKeypathVyS2icip | Ref,Read | rel: 0 // CHECK: [[@LINE-2]]:24 | instance-method/acc-get/Swift | getter:subscript(_:) | s:14swift_ide_test17StructWithKeypathVyS2icig | Ref,Call,Impl | rel: 0 + +struct BStruct { + var x = 17 + var y = true + var z = "hello" +} + +func useDefaultInits() { + _ = BStruct(y: false) + // CHECK: [[@LINE-1]]:15 | instance-property/Swift | y | s:14swift_ide_test7BStructV1ySbvp | Ref,RelCont + // CHECK: [[@LINE-2]]:7 | constructor/Swift | init(x:y:z:) | s:14swift_ide_test7BStructV1x1y1zACSi_SbSStcfc | Ref,Call,RelCall,RelCont | rel: 1 +} From fcd2fd97e8ccb67b1712aa18c7c13f15b58ae555 Mon Sep 17 00:00:00 2001 From: Doug Gregor Date: Thu, 18 Apr 2019 16:28:04 -0700 Subject: [PATCH 26/31] [Property delegates] Don't create backing var for ill-formed delegate type. Fixes the crash-on-invalid in rdar://problem/49982937. --- lib/Sema/CodeSynthesis.cpp | 2 ++ test/decl/var/property_delegates.swift | 12 ++++++++++++ 2 files changed, 14 insertions(+) diff --git a/lib/Sema/CodeSynthesis.cpp b/lib/Sema/CodeSynthesis.cpp index 678a54c1d3b0a..d271734d589f5 100644 --- a/lib/Sema/CodeSynthesis.cpp +++ b/lib/Sema/CodeSynthesis.cpp @@ -1528,6 +1528,8 @@ PropertyDelegateBackingPropertyInfoRequest::evaluate(Evaluator &evaluator, return PropertyDelegateBackingPropertyInfo(); auto delegateInfo = var->getAttachedPropertyDelegateTypeInfo(); + if (!delegateInfo) + return PropertyDelegateBackingPropertyInfo(); // Compute the name of the storage type. ASTContext &ctx = var->getASTContext(); diff --git a/test/decl/var/property_delegates.swift b/test/decl/var/property_delegates.swift index 7e4e640830755..e6a6745c9ae1b 100644 --- a/test/decl/var/property_delegates.swift +++ b/test/decl/var/property_delegates.swift @@ -640,3 +640,15 @@ func testStorageRef(tsr: TestStorageRef) { let _: Wrapper = tsr.$x } +// --------------------------------------------------------------------------- +// Misc. semantic issues +// --------------------------------------------------------------------------- +@propertyDelegate +struct BrokenLazy { } +// expected-error@-1{{property delegate type 'BrokenLazy' does not contain a non-static property named 'value'}} +// expected-note@-2{{'BrokenLazy' declared here}} + +struct S { + @BrokenLazy // expected-error{{struct 'BrokenLazy' cannot be used as an attribute}} + var value: Int +} From 56d450b595a284f5732d7996f325b15b7c2b0162 Mon Sep 17 00:00:00 2001 From: Doug Gregor Date: Mon, 22 Apr 2019 21:23:00 -0700 Subject: [PATCH 27/31] [Property delegates] Contextualize direct initializers of custom attributes When a custom attribute is given a direct initializer, save and re-use the initializer context we create so that it can be associated with the enclosing pattern binding. Fixes assertions involving explicit closures in property delegates. --- lib/Parse/ParseDecl.cpp | 24 +++++++++++++++++++++--- lib/Sema/TypeCheckStmt.cpp | 25 +++++++++++++++++++++++-- test/decl/var/property_delegates.swift | 15 +++++++++++++++ 3 files changed, 59 insertions(+), 5 deletions(-) diff --git a/lib/Parse/ParseDecl.cpp b/lib/Parse/ParseDecl.cpp index 53b3f82ba61ae..77f7f7c779e2b 100644 --- a/lib/Parse/ParseDecl.cpp +++ b/lib/Parse/ParseDecl.cpp @@ -1711,6 +1711,18 @@ bool Parser::parseVersionTuple(llvm::VersionTuple &Version, return false; } +/// Check whether the attributes have already established an initializer +/// context within the given set of attributes. +static PatternBindingInitializer *findAttributeInitContent( + DeclAttributes &Attributes) { + for (auto custom : Attributes.getAttributes()) { + if (auto initContext = custom->getInitContext()) + return initContext; + } + + return nullptr; +} + /// \verbatim /// attribute: /// '_silgen_name' '(' identifier ')' @@ -1853,7 +1865,10 @@ bool Parser::parseDeclAttribute(DeclAttributes &Attributes, SourceLoc AtLoc) { // DeclContexts for any closures that may live inside of initializers. Optional initParser; if (!CurDeclContext->isLocalContext()) { - initContext = new (Context) PatternBindingInitializer(CurDeclContext); + initContext = findAttributeInitContent(Attributes); + if (!initContext) + initContext = new (Context) PatternBindingInitializer(CurDeclContext); + initParser.emplace(*this, initContext); } @@ -5242,10 +5257,14 @@ Parser::parseDeclVar(ParseDeclOptions Flags, } }); + // Check whether we have already established an initializer context. + PatternBindingInitializer *initContext = + findAttributeInitContent(Attributes); + // Remember this pattern/init pair for our ultimate PatternBindingDecl. The // Initializer will be added later when/if it is parsed. PBDEntries.push_back({pattern, /*EqualLoc*/ SourceLoc(), /*Init*/ nullptr, - /*InitContext*/ nullptr}); + initContext}); Expr *PatternInit = nullptr; @@ -5255,7 +5274,6 @@ Parser::parseDeclVar(ParseDeclOptions Flags, // If we're not in a local context, we'll need a context to parse initializers // into (should we have one). This happens for properties and global // variables in libraries. - PatternBindingInitializer *initContext = nullptr; // Record the variables that we're trying to initialize. This allows us // to cleanly reject "var x = x" when "x" isn't bound to an enclosing diff --git a/lib/Sema/TypeCheckStmt.cpp b/lib/Sema/TypeCheckStmt.cpp index 834e0f6cdf74e..0ae0c2154c76d 100644 --- a/lib/Sema/TypeCheckStmt.cpp +++ b/lib/Sema/TypeCheckStmt.cpp @@ -49,6 +49,26 @@ using namespace swift; #define DEBUG_TYPE "TypeCheckStmt" +#ifndef NDEBUG +/// Determine whether the given context is for the backing property of a +/// property delegate. +static bool isPropertyDelegateBackingInitContext(DeclContext *dc) { + auto initContext = dyn_cast(dc); + if (!initContext) return false; + + auto patternInitContext = dyn_cast(initContext); + if (!patternInitContext) return false; + + auto binding = patternInitContext->getBinding(); + if (!binding) return false; + + auto singleVar = binding->getSingleVar(); + if (!singleVar) return false; + + return singleVar->getOriginalDelegatedProperty() != nullptr; +} +#endif + namespace { class ContextualizeClosures : public ASTWalker { DeclContext *ParentDC; @@ -116,8 +136,9 @@ namespace { ParentDC->getContextKind() != DeclContextKind::TopLevelCodeDecl) { // If a closure is nested within an auto closure, we'll need to update // its parent to the auto closure parent. - assert(ParentDC->getContextKind() == - DeclContextKind::AbstractClosureExpr && + assert((ParentDC->getContextKind() == + DeclContextKind::AbstractClosureExpr || + isPropertyDelegateBackingInitContext(ParentDC)) && "Incorrect parent decl context for closure"); CE->setParent(ParentDC); } diff --git a/test/decl/var/property_delegates.swift b/test/decl/var/property_delegates.swift index e6a6745c9ae1b..3f96cdc55982a 100644 --- a/test/decl/var/property_delegates.swift +++ b/test/decl/var/property_delegates.swift @@ -28,6 +28,10 @@ struct WrapperAcceptingAutoclosure { init(initialValue fn: @autoclosure @escaping () -> T) { self.fn = fn } + + init(body fn: @escaping () -> T) { + self.fn = fn + } } @propertyDelegate @@ -652,3 +656,14 @@ struct S { @BrokenLazy // expected-error{{struct 'BrokenLazy' cannot be used as an attribute}} var value: Int } + +// --------------------------------------------------------------------------- +// Closures in initializers +// --------------------------------------------------------------------------- +struct UsesExplicitClosures { + @WrapperAcceptingAutoclosure(body: { 42 }) + var x: Int + + @WrapperAcceptingAutoclosure(body: { return 42 }) + var y: Int +} From 7ace136afb06c7739b87cb192b7317bb069bce8f Mon Sep 17 00:00:00 2001 From: Doug Gregor Date: Mon, 22 Apr 2019 22:14:50 -0700 Subject: [PATCH 28/31] ASTWalker support for custom attributes on properties. When we encounter a custom attributes on a property (via a pattern binding), visit those custom attributes. Provides basic indexing support. --- lib/AST/ASTWalker.cpp | 33 ++++++++++++++++++++++- test/Index/property_delegates.swift | 42 +++++++++++++++++++++++++++++ 2 files changed, 74 insertions(+), 1 deletion(-) create mode 100644 test/Index/property_delegates.swift diff --git a/lib/AST/ASTWalker.cpp b/lib/AST/ASTWalker.cpp index a488ae58f9a7b..dbeda8de15db2 100644 --- a/lib/AST/ASTWalker.cpp +++ b/lib/AST/ASTWalker.cpp @@ -121,7 +121,27 @@ class Traversal : public ASTVisitorgetAttrs().getAttributes()) { + CustomAttr *mutableCustomAttr = const_cast(customAttr); + if (doIt(mutableCustomAttr->getTypeLoc())) + return true; + + if (auto arg = customAttr->getArg()) { + if (auto newArg = doIt(arg)) + mutableCustomAttr->setArg(newArg); + else + return true; + } + } + + return false; + } + //===--------------------------------------------------------------------===// // Decls //===--------------------------------------------------------------------===// @@ -151,6 +171,16 @@ class Traversal : public ASTVisitorgetSingleVar()) { + if (visitCustomAttributes(singleVar)) + return true; + + isPropertyDelegateBackingProperty = + singleVar->getOriginalDelegatedProperty() != nullptr; + } + unsigned idx = 0U-1; for (auto entry : PBD->getPatternList()) { ++idx; @@ -159,6 +189,7 @@ class Traversal : public ASTVisitor { + public var value: T + + public init(initialValue: T) { + self.value = initialValue + } + + public init(body: () -> T) { + self.value = body() + } +} + +var globalInt: Int { return 17 } + +public struct HasDelegates { + @Wrapper + public var x: Int = globalInt + + @Wrapper(body: { globalInt }) + public var y: Int + + @Wrapper(body: { return globalInt }) + public var z: Int +} + +func useMemberwiseInits(i: Int) { + _ = HasDelegates(x: i) + _ = HasDelegates(y: Wrapper(initialValue: i)) +} + +// CHECK: 19:4 | struct/Swift | Wrapper | s:14swift_ide_test7WrapperV | Ref | rel: 0 +// CHECK: 20:14 | instance-property/Swift | x | s:14swift_ide_test12HasDelegatesV1xSivp | Def,RelChild | rel: 1 +// CHECK: 20:23 | variable/Swift | globalInt | s:14swift_ide_test9globalIntSivp | Ref,Read | rel: 0 + +// CHECK: 22:4 | struct/Swift | Wrapper | s:14swift_ide_test7WrapperV | Ref | rel: 0 +// CHECK: 22:20 | variable/Swift | globalInt | s:14swift_ide_test9globalIntSivp | Ref,Read | rel: 0 + +// CHECK: 25:4 | struct/Swift | Wrapper | s:14swift_ide_test7WrapperV | Ref | rel: 0 +// CHECK: 25:27 | variable/Swift | globalInt | s:14swift_ide_test9globalIntSivp | Ref,Read | rel: 0 From faa176f3ef996be560552dcd4b616e96dad88de4 Mon Sep 17 00:00:00 2001 From: Doug Gregor Date: Tue, 23 Apr 2019 11:31:39 -0700 Subject: [PATCH 29/31] Record and walk the semantic initializer for a custom attribute. --- include/swift/AST/Attr.h | 4 ++++ lib/AST/ASTWalker.cpp | 7 ++++++- lib/Sema/TypeCheckConstraints.cpp | 3 +++ 3 files changed, 13 insertions(+), 1 deletion(-) diff --git a/include/swift/AST/Attr.h b/include/swift/AST/Attr.h index 1605bd089f13e..bf30085d5d194 100644 --- a/include/swift/AST/Attr.h +++ b/include/swift/AST/Attr.h @@ -1428,6 +1428,7 @@ class CustomAttr final : public DeclAttribute, TypeLoc type; Expr *arg; PatternBindingInitializer *initContext; + Expr *semanticInit = nullptr; unsigned hasArgLabelLocs : 1; unsigned numArgLabels : 16; @@ -1463,6 +1464,9 @@ class CustomAttr final : public DeclAttribute, Expr *getArg() const { return arg; } void setArg(Expr *newArg) { arg = newArg; } + Expr *getSemanticInit() const { return semanticInit; } + void setSemanticInit(Expr *expr) { semanticInit = expr; } + PatternBindingInitializer *getInitContext() const { return initContext; } static bool classof(const DeclAttribute *DA) { diff --git a/lib/AST/ASTWalker.cpp b/lib/AST/ASTWalker.cpp index dbeda8de15db2..8dd0ff12bbd95 100644 --- a/lib/AST/ASTWalker.cpp +++ b/lib/AST/ASTWalker.cpp @@ -131,7 +131,12 @@ class Traversal : public ASTVisitorgetTypeLoc())) return true; - if (auto arg = customAttr->getArg()) { + if (auto semanticInit = customAttr->getSemanticInit()) { + if (auto newSemanticInit = doIt(semanticInit)) + mutableCustomAttr->setSemanticInit(newSemanticInit); + else + return true; + } else if (auto arg = customAttr->getArg()) { if (auto newArg = doIt(arg)) mutableCustomAttr->setArg(newArg); else diff --git a/lib/Sema/TypeCheckConstraints.cpp b/lib/Sema/TypeCheckConstraints.cpp index b428a86ae28d3..9b4f266d1446e 100644 --- a/lib/Sema/TypeCheckConstraints.cpp +++ b/lib/Sema/TypeCheckConstraints.cpp @@ -2557,6 +2557,9 @@ bool TypeChecker::typeCheckBinding(Pattern *&pattern, Expr *&initializer, var->getParentPatternBinding()->setInitializerSubsumed(0); tc.Context.setSideCachedPropertyDelegateBackingPropertyType( var, initType->mapTypeOutOfContext()); + + // Record the semantic initializer. + var->getAttachedPropertyDelegate()->setSemanticInit(expr); } initializer = expr; From 6218673eadeaf0d0c54f49d14e0d4789c77a4b21 Mon Sep 17 00:00:00 2001 From: Doug Gregor Date: Tue, 23 Apr 2019 21:56:03 -0700 Subject: [PATCH 30/31] [Property delegates] Use $$foo for the backing storage and make it private. When the property delegate type overrides the delegate value by providing a delegateValue property, name the backing storage $$foo and make it private. --- lib/Sema/CodeSynthesis.cpp | 11 ++++++++--- test/decl/var/property_delegates.swift | 7 ++++++- 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/lib/Sema/CodeSynthesis.cpp b/lib/Sema/CodeSynthesis.cpp index d271734d589f5..2e1da532a5a51 100644 --- a/lib/Sema/CodeSynthesis.cpp +++ b/lib/Sema/CodeSynthesis.cpp @@ -1535,7 +1535,7 @@ PropertyDelegateBackingPropertyInfoRequest::evaluate(Evaluator &evaluator, ASTContext &ctx = var->getASTContext(); SmallString<64> nameBuf; if (delegateInfo.delegateValueVar) - nameBuf = "$__delegate_storage_$_"; + nameBuf = "$$"; else nameBuf = "$"; nameBuf += var->getName().str(); @@ -1596,14 +1596,19 @@ PropertyDelegateBackingPropertyInfoRequest::evaluate(Evaluator &evaluator, if (dc->getSelfClassDecl()) makeFinal(ctx, backingVar); + // When there is a `delegateValue`, lower the access of the + AccessLevel defaultAccess = + delegateInfo.delegateValueVar ? AccessLevel::Private + : AccessLevel::Internal; + // Determine the access level for the backing property. AccessLevel access = - std::min(AccessLevel::Internal, var->getFormalAccess()); + std::min(defaultAccess, var->getFormalAccess()); backingVar->overwriteAccess(access); // Determine setter access. AccessLevel setterAccess = - std::min(AccessLevel::Internal, var->getSetterFormalAccess()); + std::min(defaultAccess, var->getSetterFormalAccess()); backingVar->overwriteSetterAccess(setterAccess); // If there is a storage delegate property (delegateVar) in the delegate, diff --git a/test/decl/var/property_delegates.swift b/test/decl/var/property_delegates.swift index 3f96cdc55982a..e605540888ce5 100644 --- a/test/decl/var/property_delegates.swift +++ b/test/decl/var/property_delegates.swift @@ -627,7 +627,11 @@ extension Wrapper { } struct TestStorageRef { - @WrapperWithStorageRef var x: Int + @WrapperWithStorageRef var x: Int // expected-note{{'$$x' declared here}} + + init(x: Int) { + self.$$x = WrapperWithStorageRef(value: x) + } mutating func test() { let _: Wrapper = $x @@ -642,6 +646,7 @@ struct TestStorageRef { func testStorageRef(tsr: TestStorageRef) { let _: Wrapper = tsr.$x + _ = tsr.$$x // expected-error{{'$$x' is inaccessible due to 'private' protection level}} } // --------------------------------------------------------------------------- From 0c26e4349eb2eec0e20026cbd36ba9f1c875fb14 Mon Sep 17 00:00:00 2001 From: Doug Gregor Date: Wed, 24 Apr 2019 21:25:33 -0700 Subject: [PATCH 31/31] Underscore the @propertyDelegate attribute to indicate that it's experimental --- include/swift/AST/Attr.def | 2 +- lib/Sema/TypeCheckPropertyDelegate.cpp | 4 +- test/IDE/complete_decl_attribute.swift | 8 +-- test/IDE/print_property_delegates.swift | 2 +- test/Index/property_delegates.swift | 2 +- test/SILGen/property_delegates.swift | 10 ++-- .../di-property-delegates-errors.swift | 2 +- test/SILOptimizer/di-property-delegates.swift | 2 +- test/decl/var/property_delegates.swift | 54 +++++++++---------- .../var/property_delegates_synthesis.swift | 2 +- 10 files changed, 44 insertions(+), 44 deletions(-) diff --git a/include/swift/AST/Attr.def b/include/swift/AST/Attr.def index e347378fb9bd1..ff1352c11e1c9 100644 --- a/include/swift/AST/Attr.def +++ b/include/swift/AST/Attr.def @@ -399,7 +399,7 @@ SIMPLE_DECL_ATTR(_implementationOnly, ImplementationOnly, DECL_ATTR(_custom, Custom, OnAnyDecl | UserInaccessible, 85) -SIMPLE_DECL_ATTR(propertyDelegate, PropertyDelegate, +SIMPLE_DECL_ATTR(_propertyDelegate, PropertyDelegate, OnStruct | OnClass | OnEnum, 86) diff --git a/lib/Sema/TypeCheckPropertyDelegate.cpp b/lib/Sema/TypeCheckPropertyDelegate.cpp index e9295c694d485..c71a4b1d54f71 100644 --- a/lib/Sema/TypeCheckPropertyDelegate.cpp +++ b/lib/Sema/TypeCheckPropertyDelegate.cpp @@ -170,7 +170,7 @@ static ConstructorDecl *findInitialValueInit(ASTContext &ctx, llvm::Expected PropertyDelegateTypeInfoRequest::evaluate( Evaluator &eval, NominalTypeDecl *nominal) const { - // We must have the @propertyDelegate attribute to continue. + // We must have the @_propertyDelegate attribute to continue. if (!nominal->getAttrs().hasAttribute()) { return PropertyDelegateTypeInfo(); } @@ -203,7 +203,7 @@ AttachedPropertyDelegateRequest::evaluate(Evaluator &evaluator, auto nominal = evaluateOrDefault( ctx.evaluator, CustomAttrNominalRequest{mutableAttr, dc}, nullptr); - // If we didn't find a nominal type with a @propertyDelegate attribute, + // If we didn't find a nominal type with a @_propertyDelegate attribute, // skip this custom attribute. if (!nominal || !nominal->getAttrs().hasAttribute()) continue; diff --git a/test/IDE/complete_decl_attribute.swift b/test/IDE/complete_decl_attribute.swift index 2b5664e401c5d..13815c5d1ae18 100644 --- a/test/IDE/complete_decl_attribute.swift +++ b/test/IDE/complete_decl_attribute.swift @@ -66,7 +66,7 @@ class C {} // KEYWORD3-NEXT: Keyword/None: objcMembers[#Class Attribute#]; name=objcMembers{{$}} // KEYWORD3-NEXT: Keyword/None: NSApplicationMain[#Class Attribute#]; name=NSApplicationMain{{$}} // KEYWORD3-NEXT: Keyword/None: usableFromInline[#Class Attribute#]; name=usableFromInline -// KEYWORD3-NEXT: Keyword/None: propertyDelegate[#Class Attribute#]; name=propertyDelegate +// KEYWORD3-NEXT: Keyword/None: _propertyDelegate[#Class Attribute#]; name=_propertyDelegate // KEYWORD3-NEXT: End completions @#^KEYWORD3_2^#IB @@ -81,7 +81,7 @@ enum E {} // KEYWORD4-NEXT: Keyword/None: dynamicCallable[#Enum Attribute#]; name=dynamicCallable // KEYWORD4-NEXT: Keyword/None: dynamicMemberLookup[#Enum Attribute#]; name=dynamicMemberLookup // KEYWORD4-NEXT: Keyword/None: usableFromInline[#Enum Attribute#]; name=usableFromInline -// KEYWORD4-NEXT: Keyword/None: propertyDelegate[#Enum Attribute#]; name=propertyDelegate +// KEYWORD4-NEXT: Keyword/None: _propertyDelegate[#Enum Attribute#]; name=_propertyDelegate // KEYWORD4-NEXT: End completions @@ -92,7 +92,7 @@ struct S{} // KEYWORD5-NEXT: Keyword/None: dynamicCallable[#Struct Attribute#]; name=dynamicCallable // KEYWORD5-NEXT: Keyword/None: dynamicMemberLookup[#Struct Attribute#]; name=dynamicMemberLookup // KEYWORD5-NEXT: Keyword/None: usableFromInline[#Struct Attribute#]; name=usableFromInline -// KEYWORD5-NEXT: Keyword/None: propertyDelegate[#Struct Attribute#]; name=propertyDelegate +// KEYWORD5-NEXT: Keyword/None: _propertyDelegate[#Struct Attribute#]; name=_propertyDelegate // KEYWORD5-NEXT: End completions @@ -120,5 +120,5 @@ struct S{} // KEYWORD_LAST-NEXT: Keyword/None: usableFromInline[#Declaration Attribute#]; name=usableFromInline{{$}} // KEYWORD_LAST-NEXT: Keyword/None: discardableResult[#Declaration Attribute#]; name=discardableResult // KEYWORD_LAST-NEXT: Keyword/None: GKInspectable[#Declaration Attribute#]; name=GKInspectable{{$}} -// KEYWORD_LAST-NEXT: Keyword/None: propertyDelegate[#Declaration Attribute#]; name=propertyDelegate +// KEYWORD_LAST-NEXT: Keyword/None: _propertyDelegate[#Declaration Attribute#]; name=_propertyDelegate // KEYWORD_LAST-NEXT: End completions diff --git a/test/IDE/print_property_delegates.swift b/test/IDE/print_property_delegates.swift index 5a654b1be113a..22b87d6496d9b 100644 --- a/test/IDE/print_property_delegates.swift +++ b/test/IDE/print_property_delegates.swift @@ -3,7 +3,7 @@ // Directly printing the type-checked AST // RUN: %target-swift-ide-test -print-ast-typechecked -source-filename %s | %FileCheck %s -@propertyDelegate +@_propertyDelegate struct Delegate { var value: Value diff --git a/test/Index/property_delegates.swift b/test/Index/property_delegates.swift index 5fc835a4d1546..6a46193a475ce 100644 --- a/test/Index/property_delegates.swift +++ b/test/Index/property_delegates.swift @@ -1,6 +1,6 @@ // RUN: %target-swift-ide-test -print-indexed-symbols -source-filename %s | %FileCheck -check-prefix=CHECK %s -@propertyDelegate +@_propertyDelegate public struct Wrapper { public var value: T diff --git a/test/SILGen/property_delegates.swift b/test/SILGen/property_delegates.swift index aa34eb1c1dc3e..75bf4f30ed59c 100644 --- a/test/SILGen/property_delegates.swift +++ b/test/SILGen/property_delegates.swift @@ -1,12 +1,12 @@ // RUN: %target-swift-frontend -primary-file %s -emit-silgen | %FileCheck %s // FIXME: switch to %target-swift-emit-silgen once we have syntax tree support -@propertyDelegate +@_propertyDelegate struct Wrapper { var value: T } -@propertyDelegate +@_propertyDelegate struct WrapperWithInitialValue { var value: T @@ -111,7 +111,7 @@ func forceHasMemberwiseInit() { // CHECK: [[DELEGATE_VALUE:%.*]] = load [take] [[STACK_SLOT]] : $*HasNested.PrivateDelegate> // CHECK: struct $HasNested ([[DELEGATE_VALUE]] : $HasNested.PrivateDelegate>) struct HasNested { - @propertyDelegate + @_propertyDelegate private struct PrivateDelegate { var value: U init(initialValue: U) { @@ -189,7 +189,7 @@ struct DelegateWithDidSetWillSet { } } -@propertyDelegate +@_propertyDelegate struct WrapperWithStorageValue { var value: T @@ -206,7 +206,7 @@ struct UseWrapperWithStorageValue { @WrapperWithStorageValue(value: 17) var x: Int } -@propertyDelegate +@_propertyDelegate enum Lazy { case uninitialized(() -> Value) case initialized(Value) diff --git a/test/SILOptimizer/di-property-delegates-errors.swift b/test/SILOptimizer/di-property-delegates-errors.swift index cbd19bcacc751..a1cd1c7606bc9 100644 --- a/test/SILOptimizer/di-property-delegates-errors.swift +++ b/test/SILOptimizer/di-property-delegates-errors.swift @@ -1,5 +1,5 @@ // RUN: %target-swift-frontend -emit-sil -verify %s -@propertyDelegate +@_propertyDelegate final class ClassWrapper { var value: T { didSet { diff --git a/test/SILOptimizer/di-property-delegates.swift b/test/SILOptimizer/di-property-delegates.swift index 50e5d2a034538..2ed8e0b8ab046 100644 --- a/test/SILOptimizer/di-property-delegates.swift +++ b/test/SILOptimizer/di-property-delegates.swift @@ -4,7 +4,7 @@ // REQUIRES: executable_test -@propertyDelegate +@_propertyDelegate struct Wrapper { var value: T { didSet { diff --git a/test/decl/var/property_delegates.swift b/test/decl/var/property_delegates.swift index e605540888ce5..07af58c3ba451 100644 --- a/test/decl/var/property_delegates.swift +++ b/test/decl/var/property_delegates.swift @@ -3,12 +3,12 @@ // --------------------------------------------------------------------------- // Property delegate type definitions // --------------------------------------------------------------------------- -@propertyDelegate +@_propertyDelegate struct Wrapper { var value: T } -@propertyDelegate +@_propertyDelegate struct WrapperWithInitialValue { var value: T @@ -17,7 +17,7 @@ struct WrapperWithInitialValue { } } -@propertyDelegate +@_propertyDelegate struct WrapperAcceptingAutoclosure { private let fn: () -> T @@ -34,30 +34,30 @@ struct WrapperAcceptingAutoclosure { } } -@propertyDelegate +@_propertyDelegate struct MissingValue { } // expected-error@-1{{property delegate type 'MissingValue' does not contain a non-static property named 'value'}} -@propertyDelegate +@_propertyDelegate struct StaticValue { static var value: Int = 17 } // expected-error@-3{{property delegate type 'StaticValue' does not contain a non-static property named 'value'}} -// expected-error@+1{{'@propertyDelegate' attribute cannot be applied to this declaration}} -@propertyDelegate +// expected-error@+1{{'@_propertyDelegate' attribute cannot be applied to this declaration}} +@_propertyDelegate protocol CannotBeADelegate { associatedtype Value var value: Value { get set } } -@propertyDelegate +@_propertyDelegate struct NonVisibleValueDelegate { private var value: Value // expected-error{{private property 'value' cannot have more restrictive access than its enclosing property delegate type 'NonVisibleValueDelegate' (which is internal)}} } -@propertyDelegate +@_propertyDelegate struct NonVisibleInitDelegate { var value: Value @@ -66,7 +66,7 @@ struct NonVisibleInitDelegate { } } -@propertyDelegate +@_propertyDelegate struct InitialValueTypeMismatch { var value: Value // expected-note{{'value' declared here}} @@ -75,7 +75,7 @@ struct InitialValueTypeMismatch { } } -@propertyDelegate +@_propertyDelegate struct MultipleInitialValues { // expected-error{{property delegate type 'MultipleInitialValues' has multiple initial-value initializers}} var value: Value? = nil @@ -86,7 +86,7 @@ struct MultipleInitialValues { // expected-error{{property delegate type } } -@propertyDelegate +@_propertyDelegate struct InitialValueFailable { var value: Value @@ -95,7 +95,7 @@ struct InitialValueFailable { } } -@propertyDelegate +@_propertyDelegate struct InitialValueFailableIUO { var value: Value @@ -107,12 +107,12 @@ struct InitialValueFailableIUO { // --------------------------------------------------------------------------- // Property delegate type definitions // --------------------------------------------------------------------------- -@propertyDelegate +@_propertyDelegate struct _lowercaseDelegate { // expected-error{{property delegate type name must start with an uppercase letter}} var value: T } -@propertyDelegate +@_propertyDelegate struct _UppercaseDelegate { var value: T } @@ -228,17 +228,17 @@ struct Initialization { // --------------------------------------------------------------------------- // Delegate type formation // --------------------------------------------------------------------------- -@propertyDelegate +@_propertyDelegate struct IntWrapper { var value: Int } -@propertyDelegate +@_propertyDelegate struct WrapperForHashable { var value: T } -@propertyDelegate +@_propertyDelegate struct WrapperWithTwoParams { var value: (T, U) } @@ -270,7 +270,7 @@ struct UseWrappersWithDifferentForm { // Nested delegates // --------------------------------------------------------------------------- struct HasNestedDelegate { - @propertyDelegate + @_propertyDelegate struct NestedDelegate { var value: U init(initialValue: U) { @@ -367,7 +367,7 @@ struct UseWillSetDidSet { // --------------------------------------------------------------------------- // Mutating/nonmutating // --------------------------------------------------------------------------- -@propertyDelegate +@_propertyDelegate struct DelegateWithNonMutatingSetter { class Box { var value: Value @@ -388,7 +388,7 @@ struct DelegateWithNonMutatingSetter { } } -@propertyDelegate +@_propertyDelegate struct DelegateWithMutatingGetter { var readCount = 0 var writeCount = 0 @@ -410,7 +410,7 @@ struct DelegateWithMutatingGetter { } } -@propertyDelegate +@_propertyDelegate class ClassDelegate { var value: Value @@ -469,7 +469,7 @@ func testMutatingness() { // Access control // --------------------------------------------------------------------------- struct HasPrivateDelegate { - @propertyDelegate + @_propertyDelegate private struct PrivateDelegate { // expected-note{{type declared here}} var value: U init(initialValue: U) { @@ -487,7 +487,7 @@ struct HasPrivateDelegate { } public struct HasUsableFromInlineDelegate { - @propertyDelegate + @_propertyDelegate struct InternalDelegate { // expected-note{{type declared here}} var value: U init(initialValue: U) { @@ -501,7 +501,7 @@ public struct HasUsableFromInlineDelegate { // expected-error@-1{{property delegate type referenced from a '@usableFromInline' property must be '@usableFromInline' or public}} } -@propertyDelegate +@_propertyDelegate class Box { private(set) var value: Value @@ -613,7 +613,7 @@ func testDefaultInitializers() { // --------------------------------------------------------------------------- // Storage references // --------------------------------------------------------------------------- -@propertyDelegate +@_propertyDelegate struct WrapperWithStorageRef { var value: T @@ -652,7 +652,7 @@ func testStorageRef(tsr: TestStorageRef) { // --------------------------------------------------------------------------- // Misc. semantic issues // --------------------------------------------------------------------------- -@propertyDelegate +@_propertyDelegate struct BrokenLazy { } // expected-error@-1{{property delegate type 'BrokenLazy' does not contain a non-static property named 'value'}} // expected-note@-2{{'BrokenLazy' declared here}} diff --git a/test/decl/var/property_delegates_synthesis.swift b/test/decl/var/property_delegates_synthesis.swift index b8b84a4f9b985..22f4403902ba0 100644 --- a/test/decl/var/property_delegates_synthesis.swift +++ b/test/decl/var/property_delegates_synthesis.swift @@ -1,6 +1,6 @@ // RUN: %target-swift-frontend -typecheck -dump-ast %s | %FileCheck %s -@propertyDelegate +@_propertyDelegate struct Wrapper { var value: T