diff --git a/include/swift/AST/ASTTypeIDZone.def b/include/swift/AST/ASTTypeIDZone.def new file mode 100644 index 0000000000000..de32152ae3291 --- /dev/null +++ b/include/swift/AST/ASTTypeIDZone.def @@ -0,0 +1,19 @@ +//===--- ASTTypeIDZone.def - Define the AST TypeID Zone ---------*- C++ -*-===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2014 - 2017 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 definition file describes the types in the "AST" TypeID zone, +// for use with the TypeID template. +// +//===----------------------------------------------------------------------===// +SWIFT_TYPEID_NAMED(NominalTypeDecl *, NominalTypeDecl) +SWIFT_TYPEID_NAMED(VarDecl *, VarDecl) +SWIFT_TYPEID(PropertyDelegateTypeInfo) diff --git a/include/swift/AST/ASTTypeIDs.h b/include/swift/AST/ASTTypeIDs.h new file mode 100644 index 0000000000000..6abcab370db90 --- /dev/null +++ b/include/swift/AST/ASTTypeIDs.h @@ -0,0 +1,36 @@ +//===--- ASTTypeIDs.h - AST Type Ids ----------------------------*- C++ -*-===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2019 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 TypeID support for AST types. +// +//===----------------------------------------------------------------------===// + +#ifndef SWIFT_AST_ASTTYPEIDS_H +#define SWIFT_AST_ASTTYPEIDS_H + +#include "swift/Basic/TypeID.h" +namespace swift { + +class NominalTypeDecl; +struct PropertyDelegateTypeInfo; +class VarDecl; + +#define SWIFT_AST_TYPEID_ZONE 1 + +// Define the AST type zone (zone 1) +#define SWIFT_TYPEID_ZONE SWIFT_AST_TYPEID_ZONE +#define SWIFT_TYPEID_HEADER "swift/AST/ASTTypeIDZone.def" +#include "swift/Basic/DefineTypeIDZone.h" + +} // end namespace swift + +#endif /* SWIFT_AST_ASTTYPEIDS_H */ diff --git a/include/swift/AST/Attr.def b/include/swift/AST/Attr.def index b36a01dcb4e87..2b769f9f88c3e 100644 --- a/include/swift/AST/Attr.def +++ b/include/swift/AST/Attr.def @@ -390,6 +390,12 @@ DECL_ATTR(_private, PrivateImport, SIMPLE_DECL_ATTR(_alwaysEmitIntoClient, AlwaysEmitIntoClient, OnVar | OnSubscript | OnAbstractFunction | UserInaccessible, 83) +SIMPLE_DECL_ATTR(propertyDelegate, PropertyDelegate, + OnStruct | OnClass | OnEnum, + 84) +DECL_ATTR(_custom, Custom, + OnAnyDecl | UserInaccessible, + 85) #undef TYPE_ATTR #undef DECL_ATTR_ALIAS diff --git a/include/swift/AST/Attr.h b/include/swift/AST/Attr.h index d6b359844f6a0..c9e5324a6f1d8 100644 --- a/include/swift/AST/Attr.h +++ b/include/swift/AST/Attr.h @@ -32,6 +32,7 @@ #include "swift/AST/Ownership.h" #include "swift/AST/PlatformKind.h" #include "swift/AST/Requirement.h" +#include "swift/AST/TrailingCallArguments.h" #include "swift/AST/TypeLoc.h" #include "llvm/ADT/SmallVector.h" #include "llvm/ADT/StringRef.h" @@ -49,6 +50,7 @@ class FuncDecl; class ClassDecl; class GenericFunctionType; class LazyConformanceLoader; +class PatternBindingInitializer; class TrailingWhereClause; /// TypeAttributes - These are attributes that may be applied to types. @@ -1408,6 +1410,54 @@ class ClangImporterSynthesizedTypeAttr : public DeclAttribute { } }; +/// Defines a custom attribute. +class CustomAttr final : public DeclAttribute, + public TrailingCallArguments { + TypeLoc type; + Expr *arg; + PatternBindingInitializer *initContext; + + unsigned hasArgLabelLocs : 1; + unsigned numArgLabels : 16; + + CustomAttr(SourceLoc atLoc, SourceRange range, TypeLoc type, + PatternBindingInitializer *initContext, Expr *arg, + ArrayRef argLabels, ArrayRef argLabelLocs, + bool implicit); + +public: + static CustomAttr *create(ASTContext &ctx, SourceLoc atLoc, TypeLoc type, + bool implicit = false) { + return create(ctx, atLoc, type, false, nullptr, SourceLoc(), { }, { }, { }, + SourceLoc(), implicit); + } + + static CustomAttr *create(ASTContext &ctx, SourceLoc atLoc, TypeLoc type, + bool hasInitializer, + PatternBindingInitializer *initContext, + SourceLoc lParenLoc, + ArrayRef args, + ArrayRef argLabels, + ArrayRef argLabelLocs, + SourceLoc rParenLoc, + bool implicit = false); + + unsigned getNumArguments() const { return numArgLabels; } + bool hasArgumentLabelLocs() const { return hasArgLabelLocs; } + + TypeLoc &getTypeLoc() { return type; } + const TypeLoc &getTypeLoc() const { return type; } + + Expr *getArg() const { return arg; } + void setArg(Expr *newArg) { arg = newArg; } + + PatternBindingInitializer *getInitContext() const { return initContext; } + + static bool classof(const DeclAttribute *DA) { + return DA->getKind() == DAK_Custom; + } +}; + /// Attributes that may be applied to declarations. class DeclAttributes { /// Linked list of declaration attributes. @@ -1584,6 +1634,8 @@ class DeclAttributes { SourceLoc getStartLoc(bool forModifiers = false) const; }; +void simple_display(llvm::raw_ostream &out, const DeclAttribute *attr); + } // end namespace swift #endif diff --git a/include/swift/AST/Decl.h b/include/swift/AST/Decl.h index 8c06c8b0c9e9e..03e8296b1fbfa 100644 --- a/include/swift/AST/Decl.h +++ b/include/swift/AST/Decl.h @@ -356,7 +356,7 @@ class alignas(1 << DeclAlignInBits) Decl { ValidKeyPathComponent : 1 ); - SWIFT_INLINE_BITFIELD(VarDecl, AbstractStorageDecl, 1+4+1+1+1+1, + SWIFT_INLINE_BITFIELD(VarDecl, AbstractStorageDecl, 1+4+1+1+1+1+1+1, /// Whether this property is a type property (currently unfortunately /// called 'static'). IsStatic : 1, @@ -379,7 +379,13 @@ 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 property has an associated property delegate. + HasPropertyDelegate : 1, + + /// Whether this is the backing storage for a property delegate. + IsPropertyDelegateBackingProperty : 1 ); SWIFT_INLINE_BITFIELD(ParamDecl, VarDecl, 1 + NumDefaultArgumentKindBits, @@ -1887,7 +1893,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; @@ -1895,7 +1904,10 @@ class PatternBindingEntry { // When the initializer is removed we don't actually clear the pointer // because we might need to get initializer's source range. Since the // initializer is ASTContext-allocated it is safe. - Expr *Node; + // + // The bit indicates whether the initializer is for an attached property + // delegate. + llvm::PointerIntPair NodeAndPropertyDelegateInit; /// The location of the equal '=' token. SourceLoc EqualLoc; }; @@ -1915,8 +1927,10 @@ class PatternBindingEntry { public: PatternBindingEntry(Pattern *P, SourceLoc EqualLoc, Expr *E, + bool InitIsPropertyDelegateInit, DeclContext *InitContext) - : PatternAndFlags(P, {}), InitExpr({E, EqualLoc}), + : PatternAndFlags(P, {}), + InitExpr({{E, InitIsPropertyDelegateInit}, EqualLoc}), InitContextAndIsText({InitContext, false}) { } @@ -1926,13 +1940,21 @@ class PatternBindingEntry { if (PatternAndFlags.getInt().contains(Flags::Removed) || InitContextAndIsText.getInt()) return nullptr; - return InitExpr.Node; + return InitExpr.NodeAndPropertyDelegateInit.getPointer(); + } + bool isPropertyDelegateInit() const { + return InitExpr.NodeAndPropertyDelegateInit.getInt(); } - 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); + void setIsPropertyDelegateInit(bool isPropertyDelegateInit) { + InitExpr.NodeAndPropertyDelegateInit.setInt(isPropertyDelegateInit); + } /// Gets the text of the initializer expression, stripping out inactive /// branches of any #ifs inside the expression. @@ -1963,7 +1985,9 @@ class PatternBindingEntry { /// Retrieve the initializer as it was written in the source. Expr *getInitAsWritten() const { - return InitContextAndIsText.getInt() ? nullptr : InitExpr.Node; + return InitContextAndIsText.getInt() + ? nullptr + : InitExpr.NodeAndPropertyDelegateInit.getPointer(); } bool isInitializerChecked() const { @@ -1973,11 +1997,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. @@ -2036,6 +2060,7 @@ class PatternBindingDecl final : public Decl, StaticSpellingKind StaticSpelling, SourceLoc VarLoc, Pattern *Pat, SourceLoc EqualLoc, Expr *E, + bool IsPropertyDelegateInit, DeclContext *Parent); static PatternBindingDecl *createImplicit(ASTContext &Ctx, @@ -2072,10 +2097,13 @@ 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(); } - + bool isPropertyDelegateInit(unsigned i) const { + return getPatternList()[i].isPropertyDelegateInit(); + } + SourceRange getOrigInitRange(unsigned i) const { return getPatternList()[i].getOrigInitRange(); } @@ -2114,12 +2142,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? @@ -4600,6 +4628,8 @@ class VarDecl : public AbstractStorageDecl { Bits.VarDecl.IsCaptureList = IsCaptureList; Bits.VarDecl.IsDebuggerVar = false; Bits.VarDecl.IsREPLVar = false; + Bits.VarDecl.HasPropertyDelegate = false; + Bits.VarDecl.IsPropertyDelegateBackingProperty = false; Bits.VarDecl.HasNonPatternBindingInit = false; } @@ -4872,6 +4902,47 @@ class VarDecl : public AbstractStorageDecl { Bits.VarDecl.IsREPLVar = IsREPLVar; } + /// Whether this variable has a property delegate attached. + bool hasPropertyDelegate() const; + + /// For a property delegate, retrieve the source location for the + /// 'by' keyword. + SourceLoc getPropertyDelegateByLoc() const; + + /// For a property delegate, retrieve the behavior type location + /// information. + TypeLoc &getPropertyDelegateTypeLoc(); + + /// For a property delegate, retrieve the behavior type location + /// information. + const TypeLoc &getPropertyDelegateTypeLoc() const; + + /// Retrieve the formal access level for the attached property delegate. + AccessLevel getPropertyDelegateFormalAccess() const; + + /// Retrieve the source location for access specifier of the property. + SourceLoc getPropertyDelegateAccessLoc() const; + + /// Add a property delegate to this variable. + void addPropertyDelegate(SourceLoc byLoc, + AccessLevel access, + SourceLoc accessLoc, + TypeLoc typeLoc); + + /// Retrieve the backing variable for the property delegate. + VarDecl *getPropertyDelegateBackingVar() const; + + /// Set the backing variable for the property delegate. + void setPropertyDelegateBackingVar(VarDecl *backingVar); + + /// Retrieve the property that delegates to this backing property, + /// or \c nullptr if this is not a backing 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/DiagnosticsParse.def b/include/swift/AST/DiagnosticsParse.def index fac18957f641b..b7bbf34c3d82d 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", ()) @@ -1620,6 +1620,16 @@ ERROR(pound_available_package_description_not_allowed, none, ERROR(availability_query_repeated_platform, none, "version for '%0' already specified", (StringRef)) +//------------------------------------------------------------------------------ +// MARK: property delegate diagnostics +//------------------------------------------------------------------------------ +ERROR(property_delegate_not_named, none, + "property delegate can only by written on a single-variable pattern", ()) +ERROR(expected_property_delegate_type_after_by,PointsToFirstBadToken, + "expected property delegate type after 'by'", ()) +ERROR(property_delegate_local,none, + "property delegates are not yet supported on local properties", ()) + //------------------------------------------------------------------------------ // MARK: syntax parsing diagnostics //------------------------------------------------------------------------------ diff --git a/include/swift/AST/DiagnosticsSema.def b/include/swift/AST/DiagnosticsSema.def index 408c2ce3c885a..9ede2b310bb8b 100644 --- a/include/swift/AST/DiagnosticsSema.def +++ b/include/swift/AST/DiagnosticsSema.def @@ -1108,6 +1108,9 @@ ERROR(attribute_requires_operator_identifier,none, ERROR(attribute_requires_single_argument,none, "'%0' requires a function with one argument", (StringRef)) +ERROR(nominal_type_not_attribute,none, + "%0 %1 cannot be used as an attribute", (DescriptiveDeclKind, DeclName)) + ERROR(mutating_invalid_global_scope,none, "%0 is only valid on methods", (SelfAccessKind)) ERROR(mutating_invalid_classes,none, "%0 isn't valid on methods in " @@ -4269,6 +4272,72 @@ WARNING(hashvalue_implementation,none, "conform type %0 to 'Hashable' by implementing 'hash(into:)' instead", (Type)) +//------------------------------------------------------------------------------ +// MARK: property delegate diagnostics +//------------------------------------------------------------------------------ +ERROR(property_delegate_attribute_not_on_var, none, + "property delegate attribute %0 can only be applied to a 'var'", + (DeclName)) +ERROR(property_delegate_not_unbound, none, + "property delegate type %0 must not provide generic arguments", (Type)) +ERROR(property_delegate_by_not_delegate, none, + "use of non-property delegate type %0 as a property delegate", (Type)) +NOTE(property_delegate_missing_attribute, none, + "type %0 must have the attribute '@propertyDelegate' to be used as " + "a property delegate", (Type)) +ERROR(property_delegate_not_single_parameter, none, + "property delegate type must have a single generic type parameter", ()) +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_ambiguous_initial_value_init, none, + "property delegate type %0 has multiple initial-value initializers", (Type)) +ERROR(property_delegate_init_without_initial_value, none, + "initializing property %0 with delegate %1 that lacks " + "an 'init(initialValue:)' initializer; use (...) instead", (DeclName, 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)) +ERROR(property_delegate_access_greater_than_original,none, + "specified property delegate access " + "'%select{private|fileprivate|internal|public|%error}0' " + "cannot be greater than that of " + "the original property " + "('%select{private|fileprivate|internal|public|open}1')", + (AccessLevel, AccessLevel)) +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(non_final_property_with_delegate,none, + "property %0 with a delegate must be 'final'", (DeclName)) +ERROR(property_with_delegate_overrides,none, + "property %0 with a delegate cannot override another property", + (DeclName)) +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_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/Expr.h b/include/swift/AST/Expr.h index 2fe2b97aa0e32..72cd20410d411 100644 --- a/include/swift/AST/Expr.h +++ b/include/swift/AST/Expr.h @@ -22,6 +22,7 @@ #include "swift/AST/DeclNameLoc.h" #include "swift/AST/FunctionRefKind.h" #include "swift/AST/ProtocolConformanceRef.h" +#include "swift/AST/TrailingCallArguments.h" #include "swift/AST/TypeAlignments.h" #include "swift/AST/TypeLoc.h" #include "swift/AST/TypeRepr.h" @@ -571,105 +572,6 @@ class alignas(8) Expr { } }; -/// Helper class to capture trailing call argument labels and related -/// information, for expression nodes that involve argument labels, trailing -/// closures, etc. -template -class TrailingCallArguments - : private llvm::TrailingObjects { - // We need to friend TrailingObjects twice here to work around an MSVC bug. - // If we have two functions of the same name with the parameter - // typename TrailingObjectsIdentifier::template OverloadToken where T is - // different for each function, then MSVC reports a "member function already - // defined or declared" error, which is incorrect. - using TrailingObjectsIdentifier = llvm::TrailingObjects; - friend TrailingObjectsIdentifier; - - using TrailingObjects = llvm::TrailingObjects; - friend TrailingObjects; - - Derived &asDerived() { - return *static_cast(this); - } - - const Derived &asDerived() const { - return *static_cast(this); - } - - size_t numTrailingObjects( - typename TrailingObjectsIdentifier::template OverloadToken) - const { - return asDerived().getNumArguments(); - } - - size_t numTrailingObjects( - typename TrailingObjectsIdentifier::template OverloadToken) - const { - return asDerived().hasArgumentLabelLocs() ? asDerived().getNumArguments() - : 0; - } - - /// Retrieve the buffer containing the argument labels. - MutableArrayRef getArgumentLabelsBuffer() { - return { this->template getTrailingObjects(), - asDerived().getNumArguments() }; - } - - /// Retrieve the buffer containing the argument label locations. - MutableArrayRef getArgumentLabelLocsBuffer() { - if (!asDerived().hasArgumentLabelLocs()) - return { }; - - return { this->template getTrailingObjects(), - asDerived().getNumArguments() }; - } - -protected: - /// Determine the total size to allocate. - static size_t totalSizeToAlloc(ArrayRef argLabels, - ArrayRef argLabelLocs, - bool hasTrailingClosure) { - return TrailingObjects::template totalSizeToAlloc( - argLabels.size(), argLabelLocs.size()); - } - - /// Initialize the actual call arguments. - void initializeCallArguments(ArrayRef argLabels, - ArrayRef argLabelLocs, - bool hasTrailingClosure) { - if (!argLabels.empty()) { - std::uninitialized_copy(argLabels.begin(), argLabels.end(), - this->template getTrailingObjects()); - } - - if (!argLabelLocs.empty()) - std::uninitialized_copy(argLabelLocs.begin(), argLabelLocs.end(), - this->template getTrailingObjects()); - } - -public: - /// Retrieve the argument labels provided at the call site. - ArrayRef getArgumentLabels() const { - return { this->template getTrailingObjects(), - asDerived().getNumArguments() }; - } - - /// Retrieve the buffer containing the argument label locations. - ArrayRef getArgumentLabelLocs() const { - if (!asDerived().hasArgumentLabelLocs()) - return { }; - - return { this->template getTrailingObjects(), - asDerived().getNumArguments() }; - } - - /// Retrieve the location of the ith argument label. - SourceLoc getArgumentLabelLoc(unsigned i) const { - auto locs = getArgumentLabelLocs(); - return i < locs.size() ? locs[i] : SourceLoc(); - } -}; - /// ErrorExpr - Represents a semantically erroneous subexpression in the AST, /// typically this will have an ErrorType. class ErrorExpr : public Expr { @@ -5422,7 +5324,27 @@ inline const SourceLoc *CollectionExpr::getTrailingSourceLocs() const { } #undef SWIFT_FORWARD_SOURCE_LOCS_TO - + +/// Pack the argument information into a single argument, to match the +/// representation expected by the AST. +/// +/// \param argLabels The argument labels, which might be updated by this +/// function. +/// +/// \param argLabelLocs The argument label locations, which might be updated by +/// this function. +Expr *packSingleArgument(ASTContext &ctx, SourceLoc lParenLoc, + ArrayRef args, + ArrayRef &argLabels, + ArrayRef &argLabelLocs, + SourceLoc rParenLoc, + Expr *trailingClosure, bool implicit, + SmallVectorImpl &argLabelsScratch, + SmallVectorImpl &argLabelLocsScratch, + llvm::function_ref getType = + [](const Expr *E) -> Type { + return E->getType(); + }); } // end namespace swift #endif diff --git a/include/swift/AST/NameLookupRequests.h b/include/swift/AST/NameLookupRequests.h index 5f7177a84a5c6..ae07bb30da2fb 100644 --- a/include/swift/AST/NameLookupRequests.h +++ b/include/swift/AST/NameLookupRequests.h @@ -16,6 +16,7 @@ #ifndef SWIFT_NAME_LOOKUP_REQUESTS_H #define SWIFT_NAME_LOOKUP_REQUESTS_H +#include "swift/AST/ASTTypeIDs.h" #include "swift/AST/SimpleRequest.h" #include "swift/Basic/Statistic.h" #include "llvm/ADT/TinyPtrVector.h" @@ -25,6 +26,7 @@ namespace swift { class ClassDecl; class TypeAliasDecl; class TypeDecl; +class VarDecl; /// Display a nominal type or extension thereof. void simple_display( @@ -238,6 +240,59 @@ class TypeDeclsFromWhereClauseRequest : void noteCycleStep(DiagnosticEngine &diags) const; }; +/// Request the nominal type declaration to which the given custom attribute +/// refers. +class CustomAttrNominalRequest : + public SimpleRequest { +public: + using SimpleRequest::SimpleRequest; + +private: + friend SimpleRequest; + + // Evaluation. + llvm::Expected + evaluate(Evaluator &evaluator, CustomAttr *attr, DeclContext *dc) const; + +public: + // Caching + bool isCached() const { return true; } + + // Cycle handling + void diagnoseCycle(DiagnosticEngine &diags) const; + void noteCycleStep(DiagnosticEngine &diags) const; +}; + +/// Request the property delegate declaration for the given property, if it +/// has one. +class AttachedPropertyDelegateDeclRequest : + public SimpleRequest { +public: + using SimpleRequest::SimpleRequest; + +private: + friend SimpleRequest; + + // Evaluation. + llvm::Expected + evaluate(Evaluator &evaluator, VarDecl *property) const; + +public: + // Caching + bool isCached() const; + + // Cycle handling + void diagnoseCycle(DiagnosticEngine &diags) const; + void noteCycleStep(DiagnosticEngine &diags) const; +}; + /// The zone number for name-lookup requests. #define SWIFT_NAME_LOOKUP_REQUESTS_TYPEID_ZONE 9 diff --git a/include/swift/AST/NameLookupTypeIDZone.def b/include/swift/AST/NameLookupTypeIDZone.def index d00cfbc1b437b..4ff213a1b3dad 100644 --- a/include/swift/AST/NameLookupTypeIDZone.def +++ b/include/swift/AST/NameLookupTypeIDZone.def @@ -20,3 +20,5 @@ SWIFT_TYPEID(SuperclassDeclRequest) SWIFT_TYPEID(ExtendedNominalRequest) SWIFT_TYPEID(SelfBoundsFromWhereClauseRequest) SWIFT_TYPEID(TypeDeclsFromWhereClauseRequest) +SWIFT_TYPEID(CustomAttrNominalRequest) +SWIFT_TYPEID(AttachedPropertyDelegateDeclRequest) diff --git a/include/swift/AST/PrintOptions.h b/include/swift/AST/PrintOptions.h index b527aa1c4ea90..4462c38d4da77 100644 --- a/include/swift/AST/PrintOptions.h +++ b/include/swift/AST/PrintOptions.h @@ -250,7 +250,10 @@ struct PrintOptions { /// Whether to skip placeholder members. bool SkipMissingMemberPlaceholders = true; - + + /// Whether to skip property delegates attached to properties. + bool SkipPropertyDelegates = false; + /// Whether to print a long attribute like '\@available' on a separate line /// from the declaration or other attributes. bool PrintLongAttrsOnSeparateLines = false; diff --git a/include/swift/AST/PropertyDelegates.h b/include/swift/AST/PropertyDelegates.h new file mode 100644 index 0000000000000..587cef5d11ec2 --- /dev/null +++ b/include/swift/AST/PropertyDelegates.h @@ -0,0 +1,58 @@ +//===--- PropertyDelegates.h - Property Behavior 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_BEHAVIORS_H +#define SWIFT_AST_PROPERTY_BEHAVIORS_H + +namespace llvm { + class raw_ostream; +} + +namespace swift { + +class VarDecl; +class ConstructorDecl; + +/// Describes a property delegate type. +struct PropertyDelegateTypeInfo { + /// The property through which we should "unwrap" the behavior type to access + /// the underlying value. + VarDecl *unwrapProperty = 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 unwrapProperty != nullptr; + } + + explicit operator bool() const { return isValid(); } + + friend bool operator==(const PropertyDelegateTypeInfo &lhs, + const PropertyDelegateTypeInfo &rhs) { + return lhs.unwrapProperty == rhs.unwrapProperty && + lhs.initialValueInit == rhs.initialValueInit; + } +}; + +void simple_display( + llvm::raw_ostream &out, const PropertyDelegateTypeInfo &propertyDelegate); + +} // end namespace swift + +#endif // SWIFT_AST_PROPERTY_BEHAVIORS_H diff --git a/include/swift/AST/TrailingCallArguments.h b/include/swift/AST/TrailingCallArguments.h new file mode 100644 index 0000000000000..527942181705b --- /dev/null +++ b/include/swift/AST/TrailingCallArguments.h @@ -0,0 +1,130 @@ +//===--- TrailingCallArguments.h - Trailing Call Arguments ------*- C++ -*-===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2014 - 2017 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 the TrailingCallArguments template, which is used +// to tail-allocate the names and source locations of argument labels in a +// call. +// +//===----------------------------------------------------------------------===// + +#ifndef SWIFT_AST_TRAILINGCALLARGUMENTS_H +#define SWIFT_AST_TRAILINGCALLARGUMENTS_H + +#include "swift/AST/Identifier.h" +#include "swift/Basic/SourceLoc.h" +#include "llvm/ADT/ArrayRef.h" +#include "llvm/Support/TrailingObjects.h" + +namespace swift { + +/// Helper class to capture trailing call argument labels and related +/// information, for expression nodes that involve argument labels, trailing +/// closures, etc. +template +class TrailingCallArguments + : private llvm::TrailingObjects { + // We need to friend TrailingObjects twice here to work around an MSVC bug. + // If we have two functions of the same name with the parameter + // typename TrailingObjectsIdentifier::template OverloadToken where T is + // different for each function, then MSVC reports a "member function already + // defined or declared" error, which is incorrect. + using TrailingObjectsIdentifier = llvm::TrailingObjects; + friend TrailingObjectsIdentifier; + + using TrailingObjects = llvm::TrailingObjects; + friend TrailingObjects; + + Derived &asDerived() { + return *static_cast(this); + } + + const Derived &asDerived() const { + return *static_cast(this); + } + + size_t numTrailingObjects( + typename TrailingObjectsIdentifier::template OverloadToken) + const { + return asDerived().getNumArguments(); + } + + size_t numTrailingObjects( + typename TrailingObjectsIdentifier::template OverloadToken) + const { + return asDerived().hasArgumentLabelLocs() ? asDerived().getNumArguments() + : 0; + } + + /// Retrieve the buffer containing the argument labels. + MutableArrayRef getArgumentLabelsBuffer() { + return { this->template getTrailingObjects(), + asDerived().getNumArguments() }; + } + + /// Retrieve the buffer containing the argument label locations. + MutableArrayRef getArgumentLabelLocsBuffer() { + if (!asDerived().hasArgumentLabelLocs()) + return { }; + + return { this->template getTrailingObjects(), + asDerived().getNumArguments() }; + } + +protected: + /// Determine the total size to allocate. + static size_t totalSizeToAlloc(ArrayRef argLabels, + ArrayRef argLabelLocs, + bool hasTrailingClosure) { + return TrailingObjects::template totalSizeToAlloc( + argLabels.size(), argLabelLocs.size()); + } + + /// Initialize the actual call arguments. + void initializeCallArguments(ArrayRef argLabels, + ArrayRef argLabelLocs, + bool hasTrailingClosure) { + if (!argLabels.empty()) { + std::uninitialized_copy(argLabels.begin(), argLabels.end(), + this->template getTrailingObjects()); + } + + if (!argLabelLocs.empty()) + std::uninitialized_copy(argLabelLocs.begin(), argLabelLocs.end(), + this->template getTrailingObjects()); + } + +public: + /// Retrieve the argument labels provided at the call site. + ArrayRef getArgumentLabels() const { + return { this->template getTrailingObjects(), + asDerived().getNumArguments() }; + } + + /// Retrieve the buffer containing the argument label locations. + ArrayRef getArgumentLabelLocs() const { + if (!asDerived().hasArgumentLabelLocs()) + return { }; + + return { this->template getTrailingObjects(), + asDerived().getNumArguments() }; + } + + /// Retrieve the location of the ith argument label. + SourceLoc getArgumentLabelLoc(unsigned i) const { + auto locs = getArgumentLabelLocs(); + return i < locs.size() ? locs[i] : SourceLoc(); + } +}; + +} // end namespace swift + +#endif /* SWIFT_AST_TRAILINGCALLARGUMENTS_H */ diff --git a/include/swift/AST/TypeCheckRequests.h b/include/swift/AST/TypeCheckRequests.h index 9d3f3a9df6361..1a0a951ad9ca9 100644 --- a/include/swift/AST/TypeCheckRequests.h +++ b/include/swift/AST/TypeCheckRequests.h @@ -16,9 +16,11 @@ #ifndef SWIFT_TYPE_CHECK_REQUESTS_H #define SWIFT_TYPE_CHECK_REQUESTS_H -#include "swift/AST/Type.h" +#include "swift/AST/ASTTypeIDs.h" #include "swift/AST/Evaluator.h" +#include "swift/AST/PropertyDelegates.h" #include "swift/AST/SimpleRequest.h" +#include "swift/AST/Type.h" #include "swift/AST/TypeResolutionStage.h" #include "swift/Basic/Statistic.h" #include "llvm/ADT/Hashing.h" @@ -389,6 +391,35 @@ 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; +}; + +/// For a property that has an attached property delegate, return information +/// about the delegate. +PropertyDelegateTypeInfo getAttachedPropertyDelegateInfo(VarDecl *var); + /// 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 94817690ed6e0..a1efe23fa3825 100644 --- a/include/swift/AST/TypeCheckerTypeIDZone.def +++ b/include/swift/AST/TypeCheckerTypeIDZone.def @@ -24,3 +24,4 @@ SWIFT_TYPEID(RequirementRequest) SWIFT_TYPEID(USRGenerationRequest) SWIFT_TYPEID(DefaultTypeRequest) SWIFT_TYPEID(MangleLocalTypeDeclRequest) +SWIFT_TYPEID(PropertyDelegateTypeInfoRequest) 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 5eae8ef8ff6f5..5f2aceccac527 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/include/swift/Serialization/ModuleFormat.h b/include/swift/Serialization/ModuleFormat.h index 7582501341ca5..c9f1256bf803c 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 = 479; // stored property default arg +const uint16_t SWIFTMODULE_VERSION_MINOR = 480; // property delegates using DeclIDField = BCFixed<31>; @@ -1022,6 +1022,9 @@ namespace decls_block { DeclIDField, // overridden decl AccessLevelField, // access level AccessLevelField, // setter access, if applicable + BCFixed<1>, // has property behavior + BCFixed<1>, // is property behavior backing var + AccessLevelField, // property behavior access level BCArray // accessors and dependencies >; @@ -1598,6 +1601,12 @@ namespace decls_block { BCArray >; + using CustomDeclAttrLayout = BCRecordLayout< + Custom_DECL_ATTR, + BCFixed<1>, // implicit flag + TypeIDField // type referenced by this custom attribute + >; + } /// Returns the encoding kind for the given decl. diff --git a/lib/AST/ASTContext.cpp b/lib/AST/ASTContext.cpp index 1e80013bf2591..fc1f026f7d36f 100644 --- a/lib/AST/ASTContext.cpp +++ b/lib/AST/ASTContext.cpp @@ -109,6 +109,25 @@ using AssociativityCacheType = MACRO(NSNumber) \ MACRO(NSValue) +namespace { + /// Information about a property delegate that has been attached to + /// a particular property. + struct AttachedPropertyDelegateInfo { + SourceLoc byLoc; + AccessLevel access; + SourceLoc accessLoc; + TypeLoc delegateTypeLoc; + VarDecl *backingVar = nullptr; + + AttachedPropertyDelegateInfo(SourceLoc byLoc, + AccessLevel access, + SourceLoc accessLoc, + TypeLoc delegateTypeLoc) + : byLoc(byLoc), access(access), accessLoc(accessLoc), + delegateTypeLoc(delegateTypeLoc) { } + }; +} + struct ASTContext::Implementation { Implementation(); ~Implementation(); @@ -298,6 +317,15 @@ FOR_KNOWN_FOUNDATION_TYPES(CACHE_FOUNDATION_DECL) llvm::DenseMap> DefaultTypeRequestCaches; + /// Information about the property delegate that have been attached to + /// properties. + llvm::DenseMap + AttachedPropertyDelegates; + + /// 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 { @@ -5058,3 +5086,73 @@ Type &ASTContext::getDefaultTypeRequestCache(SourceFile *SF, KnownProtocolKind kind) { return getImpl().DefaultTypeRequestCaches[SF][size_t(kind)]; } + +void VarDecl::addPropertyDelegate(SourceLoc byLoc, + AccessLevel access, + SourceLoc accessLoc, + TypeLoc typeLoc) { + assert(!Bits.VarDecl.HasPropertyDelegate); + Bits.VarDecl.HasPropertyDelegate = true; + + ASTContext &ctx = getASTContext(); + void *mem = ctx.Allocate(sizeof(AttachedPropertyDelegateInfo), + alignof(AttachedPropertyDelegateInfo)); + ctx.getImpl().AttachedPropertyDelegates[this] = + new (mem) AttachedPropertyDelegateInfo(byLoc, access, accessLoc, typeLoc); +} + +SourceLoc VarDecl::getPropertyDelegateByLoc() const { + assert(hasPropertyDelegate()); + return getASTContext().getImpl().AttachedPropertyDelegates[this]->byLoc; +} + +AccessLevel VarDecl::getPropertyDelegateFormalAccess() const { + assert(hasPropertyDelegate()); + return getASTContext().getImpl().AttachedPropertyDelegates[this]->access; +} + +SourceLoc VarDecl::getPropertyDelegateAccessLoc() const { + assert(hasPropertyDelegate()); + return getASTContext().getImpl().AttachedPropertyDelegates[this]->accessLoc; +} + +TypeLoc &VarDecl::getPropertyDelegateTypeLoc() { + assert(hasPropertyDelegate()); + ASTContext &ctx = getASTContext(); + return ctx.getImpl().AttachedPropertyDelegates[this]->delegateTypeLoc; +} + +const TypeLoc &VarDecl::getPropertyDelegateTypeLoc() const { + assert(hasPropertyDelegate()); + ASTContext &ctx = getASTContext(); + return ctx.getImpl().AttachedPropertyDelegates[this]->delegateTypeLoc; +} + +VarDecl *VarDecl::getPropertyDelegateBackingVar() const { + assert(hasPropertyDelegate()); + ASTContext &ctx = getASTContext(); + return ctx.getImpl().AttachedPropertyDelegates[this]->backingVar; +} + +void VarDecl::setPropertyDelegateBackingVar(VarDecl *backingVar) { + assert(hasPropertyDelegate()); + ASTContext &ctx = getASTContext(); + assert(!ctx.getImpl().AttachedPropertyDelegates[this]->backingVar); + ctx.getImpl().AttachedPropertyDelegates[this]->backingVar = backingVar; +} + +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/ASTPrinter.cpp b/lib/AST/ASTPrinter.cpp index 6a36595edc835..b0ed3239f2167 100644 --- a/lib/AST/ASTPrinter.cpp +++ b/lib/AST/ASTPrinter.cpp @@ -181,6 +181,8 @@ PrintOptions PrintOptions::printParseableInterfaceFile() { // the default to 'public' and mark the 'internal' things. result.PrintAccess = true; + result.SkipPropertyDelegates = true; + result.ExcludeAttrList = {DAK_ImplicitlyUnwrappedOptional, DAK_AccessControl, DAK_SetterAccess, DAK_Lazy}; @@ -746,6 +748,11 @@ class PrintAST : public ASTVisitor { void printBraceStmt(const BraceStmt *stmt, bool newlineIfEmpty = true); void printAccessorDecl(const AccessorDecl *decl); + /// Print the property delegate reference ("by ...") for the given + /// variable, if there is one. + /// \returns true if the property delegate was printed, false otherwise. + bool maybePrintPropertyDelegate(const VarDecl *decl); + public: void printPattern(const Pattern *pattern); @@ -1860,6 +1867,25 @@ void PrintAST::printAccessors(const AbstractStorageDecl *ASD) { indent(); } +bool PrintAST::maybePrintPropertyDelegate(const VarDecl *decl) { + if (!decl->hasPropertyDelegate() || Options.SkipPropertyDelegates) + return false; + + auto backingVar = decl->getPropertyDelegateBackingVar(); + if (backingVar && !isPublicOrUsableFromInline(backingVar)) + return false; + + if (!backingVar && + decl->getPropertyDelegateFormalAccess() < AccessLevel::Public) + return false; + + Printer << " by "; + printAccess(backingVar ? backingVar->getFormalAccess() + : decl->getPropertyDelegateFormalAccess()); + printTypeLoc(decl->getPropertyDelegateTypeLoc()); + return true; +} + void PrintAST::printMembersOfDecl(Decl *D, bool needComma, bool openBracket, bool closeBracket) { @@ -2147,6 +2173,12 @@ void PrintAST::visitPatternBindingDecl(PatternBindingDecl *decl) { printPatternType(entry.getPattern()); } + // Determine whether we have a property delegate that will be printed. + bool isPropertyDelegate = false; + if (auto var = decl->getSingleVar()) { + isPropertyDelegate = maybePrintPropertyDelegate(var); + } + if (Options.VarInitializers) { auto vd = entry.getAnchoringVarDecl(); if (entry.hasInitStringRepresentation() && @@ -2159,8 +2191,13 @@ void PrintAST::visitPatternBindingDecl(PatternBindingDecl *decl) { // If we're just printing a single pattern and it has accessors, // print the accessors here. It is an error to add accessors to a // pattern binding with multiple entries. - if (auto var = decl->getSingleVar()) { - printAccessors(var); + // + // Properties that have publicly-accessible delegates don't have their + // accessors printed. + if (!isPropertyDelegate) { + if (auto var = decl->getSingleVar()) { + printAccessors(var); + } } } } @@ -2471,7 +2508,8 @@ void PrintAST::visitVarDecl(VarDecl *decl) { printTypeLoc(tyLoc); } - printAccessors(decl); + if (!maybePrintPropertyDelegate(decl)) + printAccessors(decl); } void PrintAST::visitParamDecl(ParamDecl *decl) { diff --git a/lib/AST/ASTWalker.cpp b/lib/AST/ASTWalker.cpp index 82a020d62ddb2..e9486fe99dff9 100644 --- a/lib/AST/ASTWalker.cpp +++ b/lib/AST/ASTWalker.cpp @@ -158,8 +158,16 @@ class Traversal : public ASTVisitorsetPattern(idx, Pat, entry.getInitContext()); else return true; + + // Visit property delegate. + if (auto singleVar = PBD->getSingleVar()) { + if (singleVar->hasPropertyDelegate() && + doIt(singleVar->getPropertyDelegateTypeLoc())) + return true; + } + if (entry.getInit() && - (!entry.isInitializerLazy() || + (!entry.isInitializerSubsumed() || Walker.shouldWalkIntoLazyInitializers())) { #ifndef NDEBUG PrettyStackTraceDecl debugStack("walking into initializer for", PBD); diff --git a/lib/AST/Attr.cpp b/lib/AST/Attr.cpp index a8751e976c5d7..4541b4888d0e2 100644 --- a/lib/AST/Attr.cpp +++ b/lib/AST/Attr.cpp @@ -18,6 +18,7 @@ #include "swift/AST/ASTContext.h" #include "swift/AST/ASTPrinter.h" #include "swift/AST/Decl.h" +#include "swift/AST/Expr.h" #include "swift/AST/GenericEnvironment.h" #include "swift/AST/Module.h" #include "swift/AST/Types.h" @@ -618,6 +619,16 @@ bool DeclAttribute::printImpl(ASTPrinter &Printer, const PrintOptions &Options, break; } + case DAK_Custom: { + Printer.printAttrName("@"); + const TypeLoc &typeLoc = cast(this)->getTypeLoc(); + if (auto type = typeLoc.getType()) + type->print(Printer, Options); + else + typeLoc.getTypeRepr()->print(Printer, Options); + break; + } + case DAK_Count: llvm_unreachable("exceed declaration attribute kinds"); @@ -744,6 +755,8 @@ StringRef DeclAttribute::getAttrName() const { return "_implements"; case DAK_ClangImporterSynthesizedType: return "_clangImporterSynthesizedType"; + case DAK_Custom: + return "<>"; } llvm_unreachable("bad DeclAttrKind"); } @@ -1134,3 +1147,51 @@ TypeLoc ImplementsAttr::getProtocolType() const { TypeLoc &ImplementsAttr::getProtocolType() { return ProtocolType; } + +CustomAttr::CustomAttr(SourceLoc atLoc, SourceRange range, TypeLoc type, + PatternBindingInitializer *initContext, Expr *arg, + ArrayRef argLabels, + ArrayRef argLabelLocs, bool implicit) + : DeclAttribute(DAK_Custom, atLoc, range, implicit), + type(type), + arg(arg), + initContext(initContext) { + hasArgLabelLocs = !argLabelLocs.empty(); + numArgLabels = argLabels.size(); + initializeCallArguments(argLabels, argLabelLocs, + /*hasTrailingClosure=*/false); +} + +CustomAttr *CustomAttr::create(ASTContext &ctx, SourceLoc atLoc, TypeLoc type, + bool hasInitializer, + PatternBindingInitializer *initContext, + SourceLoc lParenLoc, + ArrayRef args, + ArrayRef argLabels, + ArrayRef argLabelLocs, + SourceLoc rParenLoc, + bool implicit) { + SmallVector argLabelsScratch; + SmallVector argLabelLocsScratch; + Expr *arg = nullptr; + if (hasInitializer) { + arg = packSingleArgument(ctx, lParenLoc, args, argLabels, argLabelLocs, + rParenLoc, nullptr, implicit, argLabelsScratch, + argLabelLocsScratch); + } + + SourceRange range(atLoc, type.getSourceRange().End); + if (arg) + range.End = arg->getEndLoc(); + + size_t size = totalSizeToAlloc(argLabels, argLabelLocs, + /*hasTrailingClosure=*/false); + void *mem = ctx.Allocate(size, alignof(CustomAttr)); + return new (mem) CustomAttr(atLoc, range, type, initContext, arg, argLabels, + argLabelLocs, implicit); +} + +void swift::simple_display(llvm::raw_ostream &out, const DeclAttribute *attr) { + if (attr) + attr->print(out); +} diff --git a/lib/AST/Decl.cpp b/lib/AST/Decl.cpp index 5ec743d08218f..de7e21d5c05da 100644 --- a/lib/AST/Decl.cpp +++ b/lib/AST/Decl.cpp @@ -1188,12 +1188,13 @@ PatternBindingDecl * PatternBindingDecl::create(ASTContext &Ctx, SourceLoc StaticLoc, StaticSpellingKind StaticSpelling, SourceLoc VarLoc, Pattern *Pat, SourceLoc EqualLoc, Expr *E, - DeclContext *Parent) { + bool IsPropertyDelegateInit, DeclContext *Parent) { DeclContext *BindingInitContext = nullptr; if (!Parent->isLocalContext()) BindingInitContext = new (Ctx) PatternBindingInitializer(Parent); - auto PBE = PatternBindingEntry(Pat, EqualLoc, E, BindingInitContext); + auto PBE = PatternBindingEntry(Pat, EqualLoc, E, IsPropertyDelegateInit, + BindingInitContext); auto *Result = create(Ctx, StaticLoc, StaticSpelling, VarLoc, PBE, Parent); if (BindingInitContext) @@ -1206,7 +1207,8 @@ PatternBindingDecl *PatternBindingDecl::createImplicit( ASTContext &Ctx, StaticSpellingKind StaticSpelling, Pattern *Pat, Expr *E, DeclContext *Parent, SourceLoc VarLoc) { auto *Result = create(Ctx, /*StaticLoc*/ SourceLoc(), StaticSpelling, VarLoc, - Pat, /*EqualLoc*/ SourceLoc(), E, Parent); + Pat, /*EqualLoc*/ SourceLoc(), E, + /*IsPropertyDelegateInit*/false, Parent); Result->setImplicit(); return Result; } @@ -1255,7 +1257,9 @@ PatternBindingDecl *PatternBindingDecl::createDeserialized( NumPatternEntries, Parent); for (auto &entry : PBD->getMutablePatternList()) { entry = PatternBindingEntry(/*Pattern*/ nullptr, /*EqualLoc*/ SourceLoc(), - /*Init*/ nullptr, /*InitContext*/ nullptr); + /*Init*/ nullptr, + /*IsPropertyDelegateInit*/false, + /*InitContext*/ nullptr); } return PBD; } @@ -1333,7 +1337,7 @@ void PatternBindingEntry::setInit(Expr *E) { } else { PatternAndFlags.setInt(F | Flags::Removed); } - InitExpr.Node = E; + InitExpr.NodeAndPropertyDelegateInit.setPointer(E); InitContextAndIsText.setInt(false); } @@ -5178,6 +5182,55 @@ void VarDecl::emitLetToVarNoteIfSimple(DeclContext *UseDC) const { } } +bool VarDecl::hasPropertyDelegate() const { + if (Bits.VarDecl.HasPropertyDelegate) + return true; + + auto mutableThis = const_cast(this); + ASTContext &ctx = getASTContext(); + auto dc = getInnermostDeclContext(); + for (auto custom : mutableThis->getAttrs().getAttributes()) { + auto mutableCustom = const_cast(custom); + auto nominal = evaluateOrDefault( + ctx.evaluator, CustomAttrNominalRequest{mutableCustom, dc}, nullptr); + if (!nominal) + continue; + + auto propertyDelegateAttr = + nominal->getAttrs().getAttribute(); + if (!propertyDelegateAttr) + continue; + + // Take the custom attribute and turn it into a property delegate on + // the variable. + // FIXME: This is a hack. + mutableThis->addPropertyDelegate(custom->getLocation(), + AccessLevel::Internal, + SourceLoc(), + custom->getTypeLoc()); + + // If there is an initializer, take that too. + if (auto init = custom->getArg()) { + if (auto binding = getParentPatternBinding()) { + unsigned index = binding->getPatternEntryIndexForVarDecl(this); + binding->setInit(index, init); + auto &entry = + const_cast(binding->getPatternList()[index]); + + entry.setIsPropertyDelegateInit(true); + if (auto initContext = custom->getInitContext()) { + entry.setInitContext(initContext); + initContext->setBinding(binding, index); + } + } + } + + return true; + } + + return false; +} + ParamDecl::ParamDecl(Specifier specifier, SourceLoc specifierLoc, SourceLoc argumentNameLoc, Identifier argumentName, SourceLoc parameterNameLoc, diff --git a/lib/AST/Expr.cpp b/lib/AST/Expr.cpp index 17f9082915ecd..b47c8594b0e38 100644 --- a/lib/AST/Expr.cpp +++ b/lib/AST/Expr.cpp @@ -1089,23 +1089,16 @@ computeSingleArgumentType(ASTContext &ctx, Expr *arg, bool implicit, arg->setType(TupleType::get(typeElements, ctx)); } -/// Pack the argument information into a single argument, to match the -/// representation expected by the AST. -/// -/// \param argLabels The argument labels, which might be updated by this -/// function. -/// -/// \param argLabelLocs The argument label locations, which might be updated by -/// this function. -static Expr * -packSingleArgument(ASTContext &ctx, SourceLoc lParenLoc, ArrayRef args, - ArrayRef &argLabels, - ArrayRef &argLabelLocs, SourceLoc rParenLoc, - Expr *trailingClosure, bool implicit, - SmallVectorImpl &argLabelsScratch, - SmallVectorImpl &argLabelLocsScratch, - llvm::function_ref getType = - [](const Expr *E) -> Type { return E->getType(); }) { +Expr * +swift::packSingleArgument(ASTContext &ctx, SourceLoc lParenLoc, + ArrayRef args, + ArrayRef &argLabels, + ArrayRef &argLabelLocs, + SourceLoc rParenLoc, + Expr *trailingClosure, bool implicit, + SmallVectorImpl &argLabelsScratch, + SmallVectorImpl &argLabelLocsScratch, + llvm::function_ref getType) { // Clear out our scratch space. argLabelsScratch.clear(); argLabelLocsScratch.clear(); diff --git a/lib/AST/NameLookup.cpp b/lib/AST/NameLookup.cpp index 9180840fffe26..b9e367be0b419 100644 --- a/lib/AST/NameLookup.cpp +++ b/lib/AST/NameLookup.cpp @@ -2093,6 +2093,60 @@ ExtendedNominalRequest::evaluate(Evaluator &evaluator, return nominalTypes.empty() ? nullptr : nominalTypes.front(); } +llvm::Expected +CustomAttrNominalRequest::evaluate(Evaluator &evaluator, + CustomAttr *attr, DeclContext *dc) const { + // Find the types referenced by the custom attribute. + auto &ctx = dc->getASTContext(); + TypeLoc &typeLoc = attr->getTypeLoc(); + DirectlyReferencedTypeDecls decls; + if (auto typeRepr = typeLoc.getTypeRepr()) { + decls = directReferencesForTypeRepr( + evaluator, ctx, typeRepr, dc); + } else if (Type type = typeLoc.getType()) { + decls = directReferencesForType(type); + } + + // Dig out the nominal type declarations. + SmallVector modulesFound; + bool anyObject = false; + auto nominals = resolveTypeDeclsToNominal(evaluator, ctx, decls, + modulesFound, anyObject); + if (nominals.size() == 1 && !isa(nominals.front())) + return nominals.front(); + + return nullptr; +} + +llvm::Expected +AttachedPropertyDelegateDeclRequest::evaluate(Evaluator &evaluator, + VarDecl *property) const { + if (!property->hasPropertyDelegate()) + return nullptr; + + // Find the types referenced by the property delegate reference in the + // property. + auto &ctx = property->getASTContext(); + TypeLoc &typeLoc = property->getPropertyDelegateTypeLoc(); + DirectlyReferencedTypeDecls decls; + if (auto typeRepr = typeLoc.getTypeRepr()) { + decls = directReferencesForTypeRepr( + evaluator, ctx, typeRepr, property->getInnermostDeclContext()); + } else if (Type type = typeLoc.getType()) { + decls = directReferencesForType(type); + } + + // Dig out the nominal type declarations. + SmallVector modulesFound; + bool anyObject = false; + auto nominals = resolveTypeDeclsToNominal(evaluator, ctx, decls, + modulesFound, anyObject); + if (nominals.size() == 1 && !isa(nominals.front())) + return nominals.front(); + + return nullptr; +} + void swift::getDirectlyInheritedNominalTypeDecls( llvm::PointerUnion decl, unsigned i, diff --git a/lib/AST/NameLookupRequests.cpp b/lib/AST/NameLookupRequests.cpp index 0a82db6d45099..4eb518acd8069 100644 --- a/lib/AST/NameLookupRequests.cpp +++ b/lib/AST/NameLookupRequests.cpp @@ -12,6 +12,7 @@ #include "swift/AST/NameLookupRequests.h" #include "swift/Subsystems.h" +#include "swift/AST/ASTContext.h" #include "swift/AST/Evaluator.h" #include "swift/AST/Decl.h" #include "swift/AST/Module.h" @@ -176,6 +177,34 @@ void TypeDeclsFromWhereClauseRequest::noteCycleStep( diags.diagnose(ext, diag::circular_reference_through); } +void CustomAttrNominalRequest::diagnoseCycle( + DiagnosticEngine &diags) const { + auto attr = std::get<0>(getStorage()); + ASTContext &ctx = std::get<1>(getStorage())->getASTContext(); + ctx.Diags.diagnose(attr->getLocation(), diag::circular_reference); +} + +void CustomAttrNominalRequest::noteCycleStep( + DiagnosticEngine &diags) const { + auto attr = std::get<0>(getStorage()); + ASTContext &ctx = std::get<1>(getStorage())->getASTContext(); + ctx.Diags.diagnose(attr->getLocation(), diag::circular_reference_through); +} + +bool AttachedPropertyDelegateDeclRequest::isCached() const { + return std::get<0>(getStorage())->hasPropertyDelegate(); +} + +void AttachedPropertyDelegateDeclRequest::diagnoseCycle( + DiagnosticEngine &diags) const { + std::get<0>(getStorage())->diagnose(diag::circular_reference); +} + +void AttachedPropertyDelegateDeclRequest::noteCycleStep( + DiagnosticEngine &diags) const { + std::get<0>(getStorage())->diagnose(diag::circular_reference_through); +} + // Define request evaluation functions for each of the name lookup requests. static AbstractRequestFunction *nameLookupRequestFunctions[] = { #define SWIFT_TYPEID(Name) \ diff --git a/lib/AST/TypeCheckRequests.cpp b/lib/AST/TypeCheckRequests.cpp index 98ffa63f9a2bb..c46d9a92deebf 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/NameLookupRequests.h" #include "swift/AST/TypeLoc.h" #include "swift/AST/TypeRepr.h" #include "swift/AST/Types.h" @@ -512,3 +513,49 @@ bool DefaultTypeRequest::getPerformLocalLookup(const KnownProtocolKind knownProt default: return false; } } + +bool PropertyDelegateTypeInfoRequest::isCached() const { + return true; +} + +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); +} + +PropertyDelegateTypeInfo swift::getAttachedPropertyDelegateInfo( + VarDecl *var) { + if (!var->hasPropertyDelegate()) + return PropertyDelegateTypeInfo(); + + // Find the attached property delegate type declaration. + ASTContext &ctx = var->getASTContext(); + auto behaviorType = evaluateOrDefault( + ctx.evaluator, AttachedPropertyDelegateDeclRequest(var), nullptr); + if (!behaviorType) + return PropertyDelegateTypeInfo(); + + return evaluateOrDefault( + ctx.evaluator, PropertyDelegateTypeInfoRequest(behaviorType), + PropertyDelegateTypeInfo()); +} + +void swift::simple_display( + llvm::raw_ostream &out, const PropertyDelegateTypeInfo &propertyDelegate) { + out << "{ "; + if (propertyDelegate.unwrapProperty) + out << propertyDelegate.unwrapProperty->printRef(); + else + out << "null"; + out << ", "; + if (propertyDelegate.initialValueInit) + out << propertyDelegate.initialValueInit->printRef(); + else + out << "null"; + out << " }"; +} diff --git a/lib/ClangImporter/ImportDecl.cpp b/lib/ClangImporter/ImportDecl.cpp index a2e37b545146c..c9470cbc8f770 100644 --- a/lib/ClangImporter/ImportDecl.cpp +++ b/lib/ClangImporter/ImportDecl.cpp @@ -145,7 +145,7 @@ createVarWithPattern(ASTContext &ctx, DeclContext *dc, Identifier name, Type ty, auto *patternBinding = PatternBindingDecl::create( ctx, /*StaticLoc*/ SourceLoc(), StaticSpellingKind::None, /*VarLoc*/ SourceLoc(), varPattern, /*EqualLoc*/ SourceLoc(), - /*InitExpr*/ nullptr, dc); + /*InitExpr*/ nullptr, /*IsPropertyDelegateInit*/ false, dc); if (isImplicit) patternBinding->setImplicit(); 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/ParseDecl.cpp b/lib/Parse/ParseDecl.cpp index 7ac1dc4176989..c25ee68dd9146 100644 --- a/lib/Parse/ParseDecl.cpp +++ b/lib/Parse/ParseDecl.cpp @@ -32,6 +32,7 @@ #include "swift/Basic/Defer.h" #include "swift/Basic/Statistic.h" #include "swift/Basic/StringExtras.h" +#include "clang/Basic/CharInfo.h" #include "llvm/Support/Compiler.h" #include "llvm/Support/MemoryBuffer.h" #include "llvm/Support/Path.h" @@ -929,6 +930,7 @@ bool Parser::parseNewDeclAttribute(DeclAttributes &Attributes, SourceLoc AtLoc, case DAK_RestatedObjCConformance: case DAK_SynthesizedProtocol: case DAK_ClangImporterSynthesizedType: + case DAK_Custom: llvm_unreachable("virtual attributes should not be parsed " "by attribute parsing code"); case DAK_SetterAccess: @@ -1242,9 +1244,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()); @@ -1852,8 +1853,66 @@ bool Parser::parseDeclAttribute(DeclAttributes &Attributes, SourceLoc AtLoc) { if (TypeAttributes::getAttrKindFromString(Tok.getText()) != TAK_Count) diagnose(Tok, diag::type_attribute_applied_to_decl); - else + else if (Tok.is(tok::identifier) && clang::isUppercase(Tok.getText()[0])) { + // Parse a custom attribute. + auto type = parseType(diag::expected_type); + if (type.hasCodeCompletion() || type.isNull()) { + if (Tok.is(tok::l_paren)) + skipSingle(); + + return true; + } + + // Parse the optional arguments. + SourceLoc lParenLoc, rParenLoc; + SmallVector args; + SmallVector argLabels; + SmallVector argLabelLocs; + Expr *trailingClosure = nullptr; + bool hasInitializer = false; + + // 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; + + if (Tok.isFollowingLParen()) { + SyntaxParsingContext InitCtx(SyntaxContext, + SyntaxKind::InitializerClause); + + // If we have no local context to parse the initial value into, create one + // for the PBD we'll eventually create. This allows us to have reasonable + // DeclContexts for any closures that may live inside of initializers. + Optional initParser; + if (!CurDeclContext->isLocalContext()) { + initContext = new (Context) PatternBindingInitializer(CurDeclContext); + initParser.emplace(*this, initContext); + } + + ParserStatus status = parseExprList(tok::l_paren, tok::r_paren, + /*isPostfix=*/false, + /*isExprBasic=*/true, + lParenLoc, args, argLabels, + argLabelLocs, + rParenLoc, + trailingClosure, + SyntaxKind::FunctionCallArgumentList); + if (status.hasCodeCompletion()) + return true; + + assert(!trailingClosure && "Cannot parse a trailing closure here"); + hasInitializer = true; + } + + // Form the attribute. + auto attr = CustomAttr::create(Context, AtLoc, type.get(), hasInitializer, + initContext, lParenLoc, args, argLabels, + argLabelLocs, rParenLoc); + Attributes.add(attr); + return false; + } else { diagnose(Tok, diag::unknown_attribute, Tok.getText()); + } // Recover by eating @foo(...) when foo is not known. consumeToken(); @@ -3330,7 +3389,8 @@ parseIdentifierDeclName(Parser &P, Identifier &Result, SourceLoc &Loc, // We parsed an identifier for the declaration. If we see another // identifier, it might've been a single identifier that got broken by a // space or newline accidentally. - if (P.Tok.isIdentifierOrUnderscore() && !P.Tok.isContextualDeclKeyword()) + if (P.Tok.isIdentifierOrUnderscore() && !P.Tok.isContextualDeclKeyword() && + !P.Tok.isContextualKeyword("by")) P.diagnoseConsecutiveIDs(Result.str(), Loc, DeclKindName); // Return success anyway @@ -4241,8 +4301,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 : @@ -4710,7 +4769,9 @@ Parser::parseDeclVarGetSet(Pattern *pattern, ParseDeclOptions Flags, TypedPattern::createImplicit(Context, new (Context) NamedPattern(storage), ErrorType::get(Context)); PatternBindingEntry entry(pattern, /*EqualLoc*/ SourceLoc(), - /*Init*/ nullptr, /*InitContext*/ nullptr); + /*Init*/ nullptr, + /*InitIsPropertyDelegateInit*/ false, + /*InitContext*/ nullptr); auto binding = PatternBindingDecl::create(Context, StaticLoc, StaticSpellingKind::None, VarLoc, entry, CurDeclContext); @@ -4967,6 +5028,13 @@ Parser::ParsedAccessors::classify(Parser &P, AbstractStorageDecl *storage, } } + // Whether we have a property delegate attached to the given variable, + // which affects which accessors can be provided / omitted. + bool hasPropertyDelegate = false; + if (auto var = dyn_cast(storage)) { + hasPropertyDelegate = var->hasPropertyDelegate(); + } + // The observing accessors have very specific restrictions. // Prefer to ignore them. if (WillSet || DidSet) { @@ -5015,7 +5083,8 @@ Parser::ParsedAccessors::classify(Parser &P, AbstractStorageDecl *storage, readImpl = ReadImplKind::Read; } else if (Address) { readImpl = ReadImplKind::Address; - + } else if (hasPropertyDelegate) { + readImpl = ReadImplKind::Get; // If there's a writing accessor of any sort, there must also be a // reading accessor. } else if (auto mutator = findFirstMutator()) { @@ -5072,7 +5141,10 @@ Parser::ParsedAccessors::classify(Parser &P, AbstractStorageDecl *storage, } else if (readImpl == ReadImplKind::Stored) { writeImpl = WriteImplKind::Stored; readWriteImpl = ReadWriteImplKind::Stored; - + // Otherwise, declare the setter if it has a property delegate. + } else if (hasPropertyDelegate) { + writeImpl = WriteImplKind::Set; + readWriteImpl = ReadWriteImplKind::MaterializeToTemporary; // Otherwise, it's immutable. } else { writeImpl = WriteImplKind::Immutable; @@ -5215,7 +5287,62 @@ Parser::parseDeclVar(ParseDeclOptions Flags, pattern = patternRes.get(); } - + + // Parse a property delegate. + VarDecl *propertyDelegateVar = nullptr; + if (Tok.isContextualKeyword("by")) { + SourceLoc byLoc = consumeToken(); + + // Parse the optional access specifier. + AccessLevel access = AccessLevel::Internal; + SourceLoc accessLoc; + switch (Tok.getKind()) { + case tok::kw_private: + access = AccessLevel::Private; + accessLoc = consumeToken(tok::kw_private); + break; + + case tok::kw_fileprivate: + access = AccessLevel::FilePrivate; + accessLoc = consumeToken(tok::kw_fileprivate); + break; + + case tok::kw_internal: + access = AccessLevel::Internal; + accessLoc = consumeToken(tok::kw_internal); + break; + + case tok::kw_public: + access = AccessLevel::Public; + accessLoc = consumeToken(tok::kw_public); + break; + + default: + break; + } + + ParserResult delegateType = + parseType(diag::expected_property_delegate_type_after_by); + if (delegateType.hasCodeCompletion()) + return makeResult(makeParserCodeCompletionStatus()); + + if (delegateType.isNonNull()) { + if (auto var = pattern->getSingleVar()) { + if (var->getDeclContext()->isLocalContext()) { + diagnose(byLoc, diag::property_delegate_local); + } + + var->addPropertyDelegate(byLoc, access, accessLoc, + delegateType.get()); + propertyDelegateVar = var; + } else { + // FIXME: Support AnyPattern as well, somehow. + diagnose(byLoc, diag::property_delegate_not_named) + .highlight(pattern->getSourceRange()); + } + } + } + // Configure all vars with attributes, 'static' and parent pattern. pattern->forEachVariable([&](VarDecl *VD) { VD->setStatic(StaticLoc.isValid()); @@ -5227,12 +5354,13 @@ Parser::parseDeclVar(ParseDeclOptions Flags, // 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, + /*InitIsPropertyDelegateInit*/ false, /*InitContext*/ nullptr}); Expr *PatternInit = nullptr; // Parse an initializer if present. - if (Tok.is(tok::equal)) { + if (Tok.is(tok::equal) || (propertyDelegateVar && Tok.is(tok::l_paren))) { SyntaxParsingContext InitCtx(SyntaxContext, SyntaxKind::InitializerClause); // 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 @@ -5270,9 +5398,17 @@ Parser::parseDeclVar(ParseDeclOptions Flags, if (initContext) initParser.emplace(*this, initContext); - - SourceLoc EqualLoc = consumeToken(tok::equal); - PBDEntries.back().setEqualLoc(EqualLoc); + bool isPropertyDelegateInit; + SourceLoc EqualLoc; + if (Tok.is(tok::equal)) { + EqualLoc = consumeToken(tok::equal); + PBDEntries.back().setEqualLoc(EqualLoc); + isPropertyDelegateInit = false; + } else { + // Points at the '(' for diagnostics. + EqualLoc = Tok.getLoc(); + isPropertyDelegateInit = true; + } ParserResult init = parseExpr(diag::expected_init_value); @@ -5295,6 +5431,7 @@ Parser::parseDeclVar(ParseDeclOptions Flags, // Remember this init for the PatternBindingDecl. PatternInit = init.getPtrOrNull(); PBDEntries.back().setInit(PatternInit); + PBDEntries.back().setIsPropertyDelegateInit(isPropertyDelegateInit); // If we set up an initialization context for a property or module-level // global, record it. @@ -5302,7 +5439,7 @@ Parser::parseDeclVar(ParseDeclOptions Flags, // If the attributes include @lazy, flag that on each initializer. if (Attributes.hasAttribute()) { - PBDEntries.back().setInitializerLazy(); + PBDEntries.back().setInitializerSubsumed(); } if (init.hasCodeCompletion()) { @@ -6679,15 +6816,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(); @@ -6975,8 +7114,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..373fc07c63fc1 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; @@ -2132,8 +2132,10 @@ 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, /*allowDollarIdentifier=*/true); + baseName = baseNameId; } else if (allowOperators && Tok.isAnyOperator()) { baseName = Context.getIdentifier(Tok.getText()); baseNameLoc = consumeToken(); @@ -2605,7 +2607,7 @@ parseClosureSignatureIfPresent(SmallVectorImpl &captureList, auto *PBD = PatternBindingDecl::create( Context, /*StaticLoc*/ SourceLoc(), StaticSpellingKind::None, /*VarLoc*/ nameLoc, pattern, /*EqualLoc*/ equalLoc, initializer, - CurDeclContext); + /*IsPropertyDelegateInit*/false, CurDeclContext); captureList.push_back(CaptureListEntry(VD, PBD)); } while (HasNext); @@ -2643,13 +2645,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/ParsePattern.cpp b/lib/Parse/ParsePattern.cpp index 677d985371679..198b9a0fa2798 100644 --- a/lib/Parse/ParsePattern.cpp +++ b/lib/Parse/ParsePattern.cpp @@ -920,7 +920,8 @@ ParserResult Parser::parsePattern() { PatternCtx.setCreateSyntax(SyntaxKind::IdentifierPattern); Identifier name; SourceLoc loc = consumeIdentifier(&name); - if (Tok.isIdentifierOrUnderscore() && !Tok.isContextualDeclKeyword()) + if (Tok.isIdentifierOrUnderscore() && !Tok.isContextualDeclKeyword() && + !Tok.isContextualKeyword("by")) diagnoseConsecutiveIDs(name.str(), loc, isLet ? "constant" : "variable"); return makeParserResult(createBindingFromPattern(loc, name, specifier)); 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(); 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 579c8796334b0..14e3c76b8c5b7 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; } @@ -1101,7 +1101,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, @@ -1268,7 +1268,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 1a3d203c6145a..852257196ea26 100644 --- a/lib/SILGen/SILGenConstructor.cpp +++ b/lib/SILGen/SILGenConstructor.cpp @@ -20,6 +20,7 @@ #include "swift/AST/ASTMangler.h" #include "swift/AST/GenericEnvironment.h" #include "swift/AST/ParameterList.h" +#include "swift/AST/TypeCheckRequests.h" #include "swift/Basic/Defer.h" #include "swift/SIL/SILArgument.h" #include "swift/SIL/SILUndef.h" @@ -97,6 +98,37 @@ 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:), do so. +/// +/// Otherwise, returns the given argument. +static RValue maybeEmitPropertyDelegateInitFromValue(SILGenFunction &SGF, + SILLocation loc, + VarDecl *field, + RValue &&arg, + Initialization *init) { + auto originalProperty = field->getOriginalDelegatedProperty(); + if (!originalProperty) + return std::move(arg); + + auto delegateInfo = getAttachedPropertyDelegateInfo(originalProperty); + auto initialValueInit = delegateInfo.initialValueInit; + if (!initialValueInit) + return std::move(arg); + + ConcreteDeclRef concreteInitialValueInit( + initialValueInit, + field->getType()->getMemberSubstitutionMap( + SGF.getModule().getSwiftModule(), initialValueInit)); + RValue result = SGF.emitApplyAllocatingInitializer( + loc, concreteInitialValueInit, std::move(arg), Type(), + init ? SGFContext(init) : SGFContext()); + if (result.isInContext()) + return result; + + return std::move(result).ensurePlusOne(SGF, loc); +} + static void emitImplicitValueConstructor(SILGenFunction &SGF, ConstructorDecl *ctor) { RegularLocation Loc(ctor); @@ -166,7 +198,12 @@ 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, CleanupLocation::get(Loc)); + auto v = maybeEmitPropertyDelegateInitFromValue( + SGF, Loc, field, std::move(*elti), init.get()); + if (!v.isInContext()) + std::move(v).forwardInto(SGF, Loc, init.get()); ++elti; } SGF.B.createReturn(ImplicitReturnLocation::getImplicitReturnLoc(Loc), @@ -192,7 +229,10 @@ static void emitImplicitValueConstructor(SILGenFunction &SGF, } else { assert(elti != eltEnd && "number of args does not match number of fields"); (void)eltEnd; - v = std::move(*elti).forwardAsSingleStorageValue(SGF, fieldTy, Loc); + FullExpr scope(SGF.Cleanups, CleanupLocation::get(Loc)); + v = maybeEmitPropertyDelegateInitFromValue( + SGF, Loc, field, std::move(*elti), nullptr) + .forwardAsSingleStorageValue(SGF, fieldTy, Loc); ++elti; } @@ -892,7 +932,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 67ea4a84448ce..0b4bfb9509aad 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 032a93de9e832..28110ae2122f6 100644 --- a/lib/SILGen/SILGenType.cpp +++ b/lib/SILGen/SILGenType.cpp @@ -1025,7 +1025,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 @@ -1135,7 +1135,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/CMakeLists.txt b/lib/Sema/CMakeLists.txt index 15c1cea16b6cc..d8bba146bdca0 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 + TypeCheckPropertyDelegates.cpp TypeCheckProtocol.cpp TypeCheckProtocolInference.cpp TypeCheckREPL.cpp diff --git a/lib/Sema/CSGen.cpp b/lib/Sema/CSGen.cpp index b4a2805a6657f..04367db68298f 100644 --- a/lib/Sema/CSGen.cpp +++ b/lib/Sema/CSGen.cpp @@ -2070,8 +2070,9 @@ namespace { // tuples, nested arrays, and dictionary literals. // // Otherwise, create a new type variable. + // FIXME: This should be in the solver. auto ty = Type(); - if (!var->hasNonPatternBindingInit()) { + if (!var->hasNonPatternBindingInit() && !var->hasPropertyDelegate()) { if (auto boundExpr = locator.trySimplifyToExpr()) { if (!boundExpr->isSemanticallyInOutExpr()) ty = CS.getType(boundExpr)->getRValueType(); diff --git a/lib/Sema/CodeSynthesis.cpp b/lib/Sema/CodeSynthesis.cpp index 03bd71cee4c17..f84d6335d5086 100644 --- a/lib/Sema/CodeSynthesis.cpp +++ b/lib/Sema/CodeSynthesis.cpp @@ -20,6 +20,7 @@ #include "TypeChecker.h" #include "TypeCheckObjC.h" #include "TypeCheckType.h" +#include "TypeCheckPropertyDelegates.h" #include "swift/AST/ASTWalker.h" #include "swift/AST/Availability.h" #include "swift/AST/Expr.h" @@ -28,6 +29,7 @@ #include "swift/AST/Initializer.h" #include "swift/AST/ParameterList.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" @@ -312,6 +314,19 @@ 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 (var->hasPropertyDelegate()) { + if (auto backingVar = + getOrSynthesizePropertyDelegateBackingProperty(var)) { + if (backingVar->getFormalAccess() < var->getFormalAccess()) + return; + } + } + } + // Accessors for protocol storage requirements are never @_transparent // since they do not have bodies. // @@ -522,7 +537,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 @@ -531,6 +548,17 @@ static Expr *buildStorageReference(AccessorDecl *accessor, AbstractStorageDecl *storage, TargetImpl target, ASTContext &ctx) { + // Local function to "finish" the expression, creating a member reference + // to the "unwrapping" variable if there is one. + VarDecl *unwrapVar = nullptr; + auto finish = [&](Expr *result) -> Expr * { + if (!unwrapVar) + return result; + + return new (ctx) MemberRefExpr( + result, SourceLoc(), unwrapVar, DeclNameLoc(), /*Implicit=*/true); + }; + AccessSemantics semantics; SelfAccessorKind selfAccessKind; switch (target) { @@ -564,12 +592,22 @@ static Expr *buildStorageReference(AccessorDecl *accessor, selfAccessKind = SelfAccessorKind::Peer; } break; + + case TargetImpl::Delegate: { + auto var = cast(accessor->getStorage()); + storage = getOrSynthesizePropertyDelegateBackingProperty(var); + unwrapVar = getAttachedPropertyDelegateInfo(var).unwrapProperty; + 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 = @@ -577,12 +615,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 @@ -732,7 +770,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)->hasPropertyDelegate())); } /// Synthesize the body of a trivial getter. For a non-member vardecl or one @@ -743,8 +783,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(); @@ -792,6 +832,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 @@ -822,6 +869,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()); @@ -1280,7 +1335,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); @@ -1386,8 +1441,123 @@ void swift::completeLazyVarImplementation(VarDecl *VD) { Storage->overwriteSetterAccess(AccessLevel::Private); } +VarDecl *swift::getOrSynthesizePropertyDelegateBackingProperty(VarDecl *var) { + VarDecl *backingVar = var->getPropertyDelegateBackingVar(); + if (backingVar) return backingVar; + + ASTContext &ctx = var->getASTContext(); + + // 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 storage. + auto dc = var->getDeclContext(); + Type storageInterfaceType = + applyPropertyDelegateType(var->getInterfaceType(), var, + TypeResolution::forInterface(dc)); + bool isInvalid = false; + if (!storageInterfaceType) { + storageInterfaceType = ErrorType::get(ctx); + isInvalid = true; + } + + Type storageType; + if (!isInvalid) { + storageType = applyPropertyDelegateType(var->getType(), var, + TypeResolution::forContextual(dc)); + } + if (!storageType) { + storageType = ErrorType::get(ctx); + isInvalid = true; + } + + // Create the backing storage property and note it in the cache. + backingVar = new (ctx) VarDecl(/*IsStatic=*/var->isStatic(), + VarDecl::Specifier::Var, + /*IsCaptureList=*/false, + var->getPropertyDelegateByLoc(), + name, dc); + var->setPropertyDelegateBackingVar(backingVar); + backingVar->setOriginalDelegatedProperty(var); + + 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, var->getPropertyDelegateByLoc()); + 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, which may be + // less than that of the original property. + AccessLevel varAccess = + var->getFormalAccessScope().requiredAccessForDiagnostics(); + AccessLevel access = + std::min(var->getPropertyDelegateFormalAccess(), varAccess); + + // If the access was reduced from what was explicitly specified for the + // delegate, complain. + if (access < var->getPropertyDelegateFormalAccess()) { + SourceLoc accessLoc = var->getPropertyDelegateAccessLoc(); + if (accessLoc.isValid()) { + ctx.Diags.diagnose(accessLoc, + diag::property_delegate_access_greater_than_original, + var->getPropertyDelegateFormalAccess(), + varAccess) + .fixItReplace(accessLoc, getAccessLevelSpelling(access)); + } + } + + backingVar->overwriteAccess(access); + + // Determine setter access. + AccessLevel setterAccess = + std::min(var->getPropertyDelegateFormalAccess(), + 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->hasPropertyDelegate()) + return false; + } + switch (kind) { case AccessorKind::Get: return storage->getReadImpl() == ReadImplKind::Get; @@ -1438,10 +1608,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); + } }); } @@ -1463,6 +1635,33 @@ static void maybeAddAccessorsToLazyVariable(VarDecl *var, ASTContext &ctx) { addExpectedOpaqueAccessorsToStorage(var, ctx); } +static void maybeAddAccessorsForPropertyDelegate(VarDecl *var, + ASTContext &ctx) { + auto backingVar = getOrSynthesizePropertyDelegateBackingProperty(var); + if (!backingVar || backingVar->isInvalid()) + return; + + auto unwrapVar = getAttachedPropertyDelegateInfo(var).unwrapProperty; + assert(unwrapVar && "Cannot fail when the backing var is valid"); + + if (!var->getGetter()) { + addGetterToStorage(var, ctx); + } + + bool delegateSetterIsUsable = unwrapVar->isSettable(nullptr) && + unwrapVar->isSetterAccessibleFrom(var->getInnermostDeclContext()); + if (!var->getSetter() && delegateSetterIsUsable) { + addSetterToStorage(var, ctx); + } + + if (var->getSetter()) + var->overwriteImplInfo(StorageImplInfo::getMutableComputed()); + else + var->overwriteImplInfo(StorageImplInfo::getImmutableComputed()); + + addExpectedOpaqueAccessorsToStorage(var, ctx); +} + /// Try to add the appropriate accessors required a storage declaration. /// This needs to be idempotent. /// @@ -1478,6 +1677,14 @@ void swift::maybeAddAccessorsToStorage(AbstractStorageDecl *storage) { return; } + // Property delegates require backing storage. + if (auto var = dyn_cast(storage)) { + if (var->hasPropertyDelegate()) { + maybeAddAccessorsForPropertyDelegate(var, ctx); + return; + } + } + auto *dc = storage->getDeclContext(); // Local variables don't otherwise get accessors. @@ -1555,6 +1762,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->hasPropertyDelegate()) { + synthesizePropertyDelegateGetterBody(getter, ctx); + return; + } + } + if (getter->hasForcedStaticDispatch()) { synthesizeTrivialGetterBody(getter, TargetImpl::Ordinary, ctx); return; @@ -1585,7 +1802,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->hasPropertyDelegate()) { + 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"); @@ -1655,6 +1888,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->hasPropertyDelegate()) + 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. @@ -1715,15 +1952,17 @@ ConstructorDecl *swift::createImplicitConstructor(TypeChecker &tc, auto var = dyn_cast(member); if (!var) continue; - + // Implicit, computed, and static properties are not initialized. - // The exception is lazy properties, which due to batch mode we may or + // The exception is lazy properties and properties that have an + // attached delegate, which due to batch mode we may or // may not have yet finalized, so they may currently be "stored" or // "computed" in the current AST state. if (var->isImplicit() || var->isStatic()) continue; - if (!var->hasStorage() && !var->getAttrs().hasAttribute()) + if (!var->hasStorage() && !var->getAttrs().hasAttribute() && + !var->hasPropertyDelegate()) continue; // Initialized 'let' properties have storage, but don't get an argument @@ -1737,13 +1976,28 @@ 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 (var->hasPropertyDelegate()) { + // 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. + auto delegateTypeInfo = getAttachedPropertyDelegateInfo(var); + if (!delegateTypeInfo.initialValueInit) { + if (auto backingVar = + getOrSynthesizePropertyDelegateBackingProperty(var)) { + varInterfaceType = backingVar->getValueInterfaceType(); + accessLevel = std::min(accessLevel, backingVar->getFormalAccess()); + } + } + } // Create the parameter. auto *arg = new (ctx) diff --git a/lib/Sema/CodeSynthesis.h b/lib/Sema/CodeSynthesis.h index 11e7f9e8dc3c5..f12a0af132b2b 100644 --- a/lib/Sema/CodeSynthesis.h +++ b/lib/Sema/CodeSynthesis.h @@ -57,6 +57,10 @@ void triggerAccessorSynthesis(TypeChecker &TC, AbstractStorageDecl *storage); /// which must be lazy. void completeLazyVarImplementation(VarDecl *lazyVar); +/// Retrieve or synthesize the backing property for the given property +/// delegate. +VarDecl *getOrSynthesizePropertyDelegateBackingProperty(VarDecl *var); + /// Describes the kind of implicit constructor that will be /// generated. enum class ImplicitConstructorKind { diff --git a/lib/Sema/TypeCheckAccess.cpp b/lib/Sema/TypeCheckAccess.cpp index 59f13d7a3856c..5352f79aa90c1 100644 --- a/lib/Sema/TypeCheckAccess.cpp +++ b/lib/Sema/TypeCheckAccess.cpp @@ -14,6 +14,7 @@ // //===----------------------------------------------------------------------===// +#include "CodeSynthesis.h" #include "TypeChecker.h" #include "TypeCheckAccess.h" #include "swift/AST/AccessScopeChecker.h" @@ -481,7 +482,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; @@ -511,6 +512,36 @@ class AccessControlChecker : public AccessControlCheckerBase, typeAccess); highlightOffendingType(TC, diag, complainRepr); }); + + // Check the property delegate type. + if (anyVar->hasPropertyDelegate()) { + auto backingVar = getOrSynthesizePropertyDelegateBackingProperty(anyVar); + if (!backingVar) + return; + + checkTypeAccess(anyVar->getPropertyDelegateTypeLoc(), backingVar, + /*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 = TC.diagnose(anyVar->getPropertyDelegateByLoc(), + diag::property_delegate_type_access, + anyVar->isLet(), + isTypeContext, + isExplicit, + anyVarAccess, + isa(anyVar->getDeclContext()), + typeAccess); + highlightOffendingType(TC, diag, complainRepr); + }); + } } void visitPatternBindingDecl(PatternBindingDecl *PBD) { @@ -1062,7 +1093,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; @@ -1088,6 +1119,24 @@ class UsableFromInlineChecker : public AccessControlCheckerBase, isTypeContext); highlightOffendingType(TC, diag, complainRepr); }); + + if (anyVar->hasPropertyDelegate()) { + checkTypeAccess(anyVar->getPropertyDelegateTypeLoc(), + fixedLayoutStructContext ? fixedLayoutStructContext + : anyVar, + /*mayBeInferred*/false, + [&](AccessScope typeAccessScope, + const TypeRepr *complainRepr, + DowngradeToWarning downgradeToWarning) { + auto diagID = diag::property_delegate_type_not_usable_from_inline; + if (fixedLayoutStructContext) + diagID = diag::pattern_type_not_usable_from_inline_fixed_layout; + + auto diag = TC.diagnose(anyVar->getPropertyDelegateByLoc(), + diagID, anyVar->isLet(), isTypeContext); + highlightOffendingType(TC, diag, complainRepr); + }); + } } void visitPatternBindingDecl(PatternBindingDecl *PBD) { diff --git a/lib/Sema/TypeCheckAttr.cpp b/lib/Sema/TypeCheckAttr.cpp index a092507bdfcc1..ff33a66d6443e 100644 --- a/lib/Sema/TypeCheckAttr.cpp +++ b/lib/Sema/TypeCheckAttr.cpp @@ -20,8 +20,10 @@ #include "swift/AST/GenericSignatureBuilder.h" #include "swift/AST/ASTVisitor.h" #include "swift/AST/ClangModuleLoader.h" +#include "swift/AST/DiagnosticsParse.h" #include "swift/AST/GenericEnvironment.h" #include "swift/AST/NameLookup.h" +#include "swift/AST/NameLookupRequests.h" #include "swift/AST/ParameterList.h" #include "swift/AST/TypeCheckRequests.h" #include "swift/AST/Types.h" @@ -122,6 +124,8 @@ class AttributeEarlyChecker : public AttributeVisitor { IGNORED_ATTR(WeakLinked) IGNORED_ATTR(DynamicReplacement) IGNORED_ATTR(PrivateImport) + IGNORED_ATTR(PropertyDelegate) + IGNORED_ATTR(Custom) #undef IGNORED_ATTR void visitAlignmentAttr(AlignmentAttr *attr) { @@ -833,6 +837,9 @@ class AttributeChecker : public AttributeVisitor { void visitFrozenAttr(FrozenAttr *attr); void visitNonOverrideAttr(NonOverrideAttr *attr); + + void visitPropertyDelegateAttr(PropertyDelegateAttr *attr); + void visitCustomAttr(CustomAttr *attr); }; } // end anonymous namespace @@ -2400,6 +2407,67 @@ void AttributeChecker::visitNonOverrideAttr(NonOverrideAttr *attr) { } } +void AttributeChecker::visitPropertyDelegateAttr(PropertyDelegateAttr *attr) { + if (auto nominal = dyn_cast(D)) { + ASTContext &ctx = nominal->getASTContext(); + (void)evaluateOrDefault( + ctx.evaluator, PropertyDelegateTypeInfoRequest(nominal), + PropertyDelegateTypeInfo()); + } +} + +void AttributeChecker::visitCustomAttr(CustomAttr *attr) { + auto dc = D->getInnermostDeclContext(); + + // Figure out which nominal declaration this custom attribute refers to. + auto nominal = evaluateOrDefault( + TC.Context.evaluator, CustomAttrNominalRequest{attr, dc}, nullptr); + + // If there is no nominal type with this name, complain about this being + // an unknown attribute. + if (!nominal) { + std::string typeName; + if (auto typeRepr = attr->getTypeLoc().getTypeRepr()) { + llvm::raw_string_ostream out(typeName); + typeRepr->print(out); + } else { + typeName = attr->getTypeLoc().getType().getString(); + } + + TC.diagnose(attr->getLocation(), diag::unknown_attribute, + typeName); + attr->setInvalid(); + return; + } + + // If there is a nominal type but it has no @propertyDelegate attribute, + // then this type cannot be used as a custom attribute. + if (!nominal->getAttrs().hasAttribute()) { + TC.diagnose(attr->getLocation(), diag::nominal_type_not_attribute, + nominal->getDescriptiveKind(), nominal->getFullName()); + nominal->diagnose(diag::decl_declared_here, nominal->getFullName()); + attr->setInvalid(); + return; + } + + // Property delegates can only be applied to variables. + if (!isa(D)) { + TC.diagnose(attr->getLocation(), + diag::property_delegate_attribute_not_on_var, + nominal->getFullName()); + nominal->diagnose(diag::decl_declared_here, nominal->getFullName()); + attr->setInvalid(); + return; + } + + // Check the type completely. + // FIXME: This should be handled elsewhere. + TypeResolutionOptions options(TypeResolverContext::None); + options |= TypeResolutionFlags::AllowUnboundGenerics; + TC.validateType(attr->getTypeLoc(), TypeResolution::forContextual(dc), + options); +} + void TypeChecker::checkDeclAttributes(Decl *D) { AttributeChecker Checker(*this, D); @@ -2415,6 +2483,10 @@ void TypeChecker::checkTypeModifyingDeclAttributes(VarDecl *var) { if (auto *attr = var->getAttrs().getAttribute()) checkReferenceOwnershipAttr(var, attr); + + if (var->hasPropertyDelegate()) { + + } } void TypeChecker::checkReferenceOwnershipAttr(VarDecl *var, diff --git a/lib/Sema/TypeCheckCaptures.cpp b/lib/Sema/TypeCheckCaptures.cpp index 2fc3b0ea53f8c..3ea79e6fedbb4 100644 --- a/lib/Sema/TypeCheckCaptures.cpp +++ b/lib/Sema/TypeCheckCaptures.cpp @@ -774,7 +774,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()) diff --git a/lib/Sema/TypeCheckConstraints.cpp b/lib/Sema/TypeCheckConstraints.cpp index daee66f83b21d..2163453e8f10b 100644 --- a/lib/Sema/TypeCheckConstraints.cpp +++ b/lib/Sema/TypeCheckConstraints.cpp @@ -20,6 +20,7 @@ #include "MiscDiagnostics.h" #include "TypeChecker.h" #include "TypeCheckType.h" +#include "TypeCheckPropertyDelegates.h" #include "TypoCorrection.h" #include "swift/AST/ASTVisitor.h" #include "swift/AST/ASTWalker.h" @@ -32,6 +33,7 @@ #include "swift/AST/ProtocolConformance.h" #include "swift/AST/SubstitutionMap.h" #include "swift/AST/TypeCheckerDebugConsumer.h" +#include "swift/AST/TypeCheckRequests.h" #include "swift/Basic/Statistic.h" #include "swift/Parse/Confusables.h" #include "swift/Parse/Lexer.h" @@ -2454,7 +2456,8 @@ TypeChecker::getTypeOfCompletionOperator(DeclContext *DC, Expr *LHS, } bool TypeChecker::typeCheckBinding(Pattern *&pattern, Expr *&initializer, - DeclContext *DC) { + DeclContext *DC, + bool IsPropertyDelegateInit) { /// Type checking listener for pattern binding initializers. class BindingListener : public ExprTypeCheckListener { @@ -2464,15 +2467,35 @@ bool TypeChecker::typeCheckBinding(Pattern *&pattern, Expr *&initializer, /// The locator we're using. ConstraintLocator *Locator; + /// Whether the initializer is a property delegate initializer, specified + /// directly on the property delegate (vs. following an '='). + bool isPropertyDelegateInit; + /// The type of the initializer. Type initType; + /// Whether we applied a property delegate to the initializer expression. + bool appliedPropertyDelegate = false; + public: - explicit BindingListener(Pattern *&pattern, Expr *&initializer) + explicit BindingListener(Pattern *&pattern, Expr *&initializer, + bool isPropertyDelegateInit) : pattern(pattern), initializer(initializer), - Locator(nullptr) { } + Locator(nullptr), isPropertyDelegateInit(isPropertyDelegateInit) { + 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 type argument of the property delegate type. + auto boundGeneric = initType->getAs(); + assert(boundGeneric && boundGeneric->getGenericArgs().size() == 1); + return boundGeneric->getGenericArgs()[0]; + } bool builtConstraints(ConstraintSystem &cs, Expr *expr) override { assert(!expr->isSemanticallyInOutExpr()); @@ -2486,6 +2509,17 @@ bool TypeChecker::typeCheckBinding(Pattern *&pattern, Expr *&initializer, if (!initType) return true; + // If we applied a property delegate to the initializer, perform the + // corresponding application to the pattern type. + if (appliedPropertyDelegate) { + auto var = pattern->getSingleVar(); + auto dc = var->getInnermostDeclContext(); + initType = applyPropertyDelegateType( + initType, var, TypeResolution::forContextual(dc)); + if (!initType) + return true; + } + // Add a conversion constraint between the types. cs.addConstraint(ConstraintKind::Conversion, cs.getType(expr), initType, Locator, /*isFavored*/true); @@ -2505,6 +2539,7 @@ bool TypeChecker::typeCheckBinding(Pattern *&pattern, Expr *&initializer, } Expr *appliedSolution(Solution &solution, Expr *expr) override { + // Convert the initializer to the type of the pattern. expr = solution.coerceToType(expr, initType, Locator, false /* ignoreTopLevelInjection */); @@ -2516,10 +2551,66 @@ 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 || !singleVar->hasPropertyDelegate()) + return; + + // Gather all of the information we'll need to + auto info = getAttachedPropertyDelegateInfo(singleVar); + if (!info) + return; + + TypeResolution resolution = + TypeResolution::forContextual(singleVar->getInnermostDeclContext()); + auto unboundType = getUnboundPropertyDelegateType(singleVar, resolution); + if (!unboundType) + return; + + // If this isn't directly initializing the property delegate, we require + // the property delegate type to have an initializer + // init(initialValue:) to perform initialization. + if (!info.initialValueInit && !isPropertyDelegateInit) { + singleVar->diagnose(diag::property_delegate_init_without_initial_value, + singleVar->getFullName(), Type(unboundType)) + .highlight(initializer->getSourceRange()); + auto unboundDecl = unboundType->getDecl(); + unboundDecl->diagnose(diag::kind_declname_declared_here, + unboundDecl->getDescriptiveKind(), + unboundDecl->getFullName()); + return; + } + + // Form a call to an initializer of the property delegate type. + auto &ctx = singleVar->getASTContext(); + auto typeExpr = TypeExpr::createImplicit(Type(unboundType), ctx); + if (isPropertyDelegateInit) { + // Call the property delegate initializer as spelled in the + // parenthesized initializer. + initializer = CallExpr::create( + ctx, typeExpr, initializer, { }, { }, + /*hasTrailingClosure=*/false, /*implicit=*/true); + } else { + // Form a call to the init(initialValue:) initializer of the property + // delegate type. + initializer = CallExpr::createImplicit( + ctx, typeExpr, {initializer}, {ctx.Id_initialValue}); + } + + // Note that we have applied to property delegate, so we can adjust + // the initializer type later. + appliedPropertyDelegate = true; + } + }; assert(initializer && "type-checking an uninitialized binding?"); - BindingListener listener(pattern, initializer); + BindingListener listener(pattern, initializer, IsPropertyDelegateInit); TypeLoc contextualType; auto contextualPurpose = CTP_Unused; @@ -2557,7 +2648,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; @@ -2612,7 +2703,8 @@ bool TypeChecker::typeCheckPatternBinding(PatternBindingDecl *PBD, DC = initContext; } - bool hadError = typeCheckBinding(pattern, init, DC); + bool hadError = typeCheckBinding( + pattern, init, DC, PBD->isPropertyDelegateInit(patternNumber)); PBD->setPattern(patternNumber, pattern, initContext); PBD->setInit(patternNumber, init); @@ -2620,6 +2712,15 @@ bool TypeChecker::typeCheckPatternBinding(PatternBindingDecl *PBD, PBD->setInvalid(); PBD->setInitializerChecked(patternNumber); + + // If this was an initializer for a property with a property delegate, + // the initializer expression initializes the backing storage. That + // initializer subsumes this one. + if (auto singleVar = pattern->getSingleVar()) { + if (singleVar->hasPropertyDelegate()) + PBD->setInitializerSubsumed(patternNumber); + } + return hadError; } @@ -2869,7 +2970,8 @@ bool TypeChecker::typeCheckStmtCondition(StmtCondition &cond, DeclContext *dc, // If the pattern didn't get a type, it's because we ran into some // unknown types along the way. We'll need to check the initializer. auto init = elt.getInitializer(); - hadError |= typeCheckBinding(pattern, init, dc); + hadError |= typeCheckBinding( + pattern, init, dc, /*isPropertyDelegateInit=*/false); elt.setPattern(pattern); elt.setInitializer(init); hadAnyFalsable |= pattern->isRefutablePattern(); diff --git a/lib/Sema/TypeCheckDecl.cpp b/lib/Sema/TypeCheckDecl.cpp index 24fc3defc2bdd..150dcef40d2dc 100644 --- a/lib/Sema/TypeCheckDecl.cpp +++ b/lib/Sema/TypeCheckDecl.cpp @@ -1929,6 +1929,22 @@ 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 (var->hasPropertyDelegate()) { + auto info = getAttachedPropertyDelegateInfo(var); + if (info.unwrapProperty && + (!storage->getGetter() || storage->getGetter()->isImplicit())) { + TC.validateDecl(info.unwrapProperty); + return info.unwrapProperty->isGetterMutating() && + var->isInstanceMember() && + doesContextHaveValueSemantics(var->getDeclContext()); + } + } + } + switch (storage->getReadImpl()) { case ReadImplKind::Stored: return false; @@ -1949,6 +1965,22 @@ 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 (var->hasPropertyDelegate()) { + auto info = getAttachedPropertyDelegateInfo(var); + if (info.unwrapProperty && + (!storage->getSetter() || storage->getSetter()->isImplicit())) { + TC.validateDecl(info.unwrapProperty); + return info.unwrapProperty->isSetterMutating() && + var->isInstanceMember() && + doesContextHaveValueSemantics(var->getDeclContext()); + } + } + } + auto impl = storage->getImplInfo(); switch (impl.getWriteImpl()) { case WriteImplKind::Immutable: @@ -2150,12 +2182,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(); @@ -2405,7 +2437,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()->hasPropertyDelegate())) { auto *initContext = cast_or_null( entry.getInitContext()); if (initContext) { @@ -4281,7 +4318,8 @@ static bool shouldValidateMemberDuringFinalization(NominalTypeDecl *nominal, (isa(VD) && !cast(VD)->isStatic() && (cast(VD)->hasStorage() || - VD->getAttrs().hasAttribute()))) + VD->getAttrs().hasAttribute() || + cast(VD)->hasPropertyDelegate()))) return true; // For classes, we need to validate properties and functions, @@ -4377,6 +4415,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->hasPropertyDelegate()) { + finalizeAbstractStorageDecl(TC, prop); + (void)getOrSynthesizePropertyDelegateBackingProperty(prop); + } } if (auto *CD = dyn_cast(nominal)) { @@ -5150,6 +5194,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->hasPropertyDelegate()) { + validateDecl(var); + maybeAddAccessorsToStorage(var); + } + if (var->hasStorage() && !var->isStatic() && !var->isInvalid()) { // Initialized 'let' properties have storage, but don't get an argument // to the memberwise initializer since they already have an initial @@ -5179,7 +5230,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; @@ -5196,6 +5247,7 @@ void TypeChecker::addImplicitConstructors(NominalTypeDecl *decl) { if (CheckDefaultInitializer && !pbd->isDefaultInitializable()) SuppressDefaultInitializer = true; } + } continue; } } diff --git a/lib/Sema/TypeCheckDeclOverride.cpp b/lib/Sema/TypeCheckDeclOverride.cpp index d9c651418c2f9..90bf83aba5b75 100644 --- a/lib/Sema/TypeCheckDeclOverride.cpp +++ b/lib/Sema/TypeCheckDeclOverride.cpp @@ -1320,6 +1320,8 @@ namespace { UNINTERESTING_ATTR(WeakLinked) UNINTERESTING_ATTR(Frozen) UNINTERESTING_ATTR(HasInitialValue) + UNINTERESTING_ATTR(PropertyDelegate) + UNINTERESTING_ATTR(Custom) #undef UNINTERESTING_ATTR void visitAvailableAttr(AvailableAttr *attr) { @@ -1620,7 +1622,7 @@ static bool checkSingleOverride(ValueDecl *override, ValueDecl *base) { // If the overriding declaration does not have the 'override' modifier on // it, complain. - if (!override->getAttrs().hasAttribute() && + if (!override->getAttrs().hasAttribute(/*AllowInvalid=*/true) && overrideRequiresKeyword(base) != OverrideRequiresKeyword::Never && !override->isImplicit() && override->getDeclContext()->getParentSourceFile()) { diff --git a/lib/Sema/TypeCheckPattern.cpp b/lib/Sema/TypeCheckPattern.cpp index 0b3a0a7981e4e..dd46239328162 100644 --- a/lib/Sema/TypeCheckPattern.cpp +++ b/lib/Sema/TypeCheckPattern.cpp @@ -21,6 +21,7 @@ #include "swift/Basic/StringExtras.h" #include "swift/AST/ASTWalker.h" #include "swift/AST/ASTVisitor.h" +#include "swift/AST/GenericSignature.h" #include "swift/AST/NameLookup.h" #include "swift/AST/ParameterList.h" #include "llvm/Support/SaveAndRestore.h" diff --git a/lib/Sema/TypeCheckPropertyDelegates.cpp b/lib/Sema/TypeCheckPropertyDelegates.cpp new file mode 100644 index 0000000000000..7f27c2387adf1 --- /dev/null +++ b/lib/Sema/TypeCheckPropertyDelegates.cpp @@ -0,0 +1,307 @@ +//===--- TypeCheckPropertyDelegate.cpp - Property Behaviors ---------------===// +// +// 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 "TypeCheckPropertyDelegates.h" +#include "TypeChecker.h" +#include "swift/AST/ASTContext.h" +#include "swift/AST/Decl.h" +#include "swift/AST/DiagnosticsSema.h" +#include "swift/AST/GenericSignature.h" +#include "swift/AST/NameLookupRequests.h" +#include "swift/AST/TypeCheckRequests.h" +#include "swift/AST/Types.h" +using namespace swift; + +UnboundGenericType *swift::getUnboundPropertyDelegateType( + VarDecl *var, TypeResolution resolution) { + assert(var->hasPropertyDelegate() && "Only call with property delegates"); + + if (var->getPropertyDelegateTypeLoc().wasValidated()) { + if (var->getPropertyDelegateTypeLoc().isError()) + return nullptr; + + Type storedType = var->getPropertyDelegateTypeLoc().getType(); + switch (resolution.getStage()) { + case TypeResolutionStage::Contextual: + return var->getInnermostDeclContext()->mapTypeIntoContext(storedType) + ->getAs(); + + case TypeResolutionStage::Interface: + case TypeResolutionStage::Structural: + return storedType->castTo(); + } + } + + // A property with a delegate cannot be declared in a protocol, enum, or + // an extension. + ASTContext &ctx = var->getASTContext(); + auto dc = var->getDeclContext(); + 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(SourceRange(var->getPropertyDelegateByLoc(), + var->getPropertyDelegateTypeLoc() + .getSourceRange().End)); + + var->getPropertyDelegateTypeLoc().setInvalidType(ctx); + var->setInvalid(); + return nullptr; + } + + // Properties with delegates must be 'final' (or within a final class). + if (auto classDecl = dyn_cast(dc)) { + if (!var->isFinal() && !classDecl->isFinal()) { + var->diagnose(diag::non_final_property_with_delegate, var->getFullName()) + .highlight(SourceRange(var->getPropertyDelegateByLoc(), + var->getPropertyDelegateTypeLoc() + .getSourceRange().End)) + .fixItInsert(var->getAttributeInsertionLoc(/*forModifier=*/true), + "final"); + var->getAttrs().add(new (ctx) FinalAttr(/*isImplicit=*/true)); + } + + if (auto overrideAttr = var->getAttrs().getAttribute()) { + var->diagnose(diag::property_with_delegate_overrides, var->getFullName()) + .highlight(SourceRange(var->getPropertyDelegateByLoc(), + var->getPropertyDelegateTypeLoc() + .getSourceRange().End)) + .fixItRemove(overrideAttr->getLocation()); + overrideAttr->setInvalid(); + } + } + + // 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); + var->getPropertyDelegateTypeLoc().setInvalidType(ctx); + return nullptr; + } + + // We haven't resolved the property delegate type yet; do so now. + SourceLoc byLoc = var->getPropertyDelegateByLoc(); + TypeResolutionOptions options(TypeResolverContext::ProtocolWhereClause); + options |= TypeResolutionFlags::AllowUnboundGenerics; + Type unboundBehaviorType = resolution.resolveType( + var->getPropertyDelegateTypeLoc().getTypeRepr(), options); + + if (!unboundBehaviorType || unboundBehaviorType->hasError()) { + var->getPropertyDelegateTypeLoc().setInvalidType(ctx); + return nullptr; + } + + // We expect a nominal type with the @propertyDelegate attribute. + auto nominalDecl = unboundBehaviorType->getAnyNominal(); + if (!nominalDecl || + !nominalDecl->getAttrs().hasAttribute()) { + ctx.Diags.diagnose(byLoc, diag::property_delegate_by_not_delegate, + unboundBehaviorType) + .highlight(var->getPropertyDelegateTypeLoc().getSourceRange()); + if (nominalDecl && !isa(nominalDecl)) { + nominalDecl->diagnose(diag::property_delegate_missing_attribute, + nominalDecl->getDeclaredInterfaceType()) + .fixItInsert( + nominalDecl->getAttributeInsertionLoc(/*forModifier=*/false), + "@propertyDelegate"); + } + + var->getPropertyDelegateTypeLoc().setInvalidType(ctx); + return nullptr; + } + + // We expect an unbound generic type here. + auto unboundGeneric = unboundBehaviorType->getAs(); + if (!unboundGeneric || unboundGeneric->getDecl() != nominalDecl) { + ctx.Diags.diagnose(byLoc, diag::property_delegate_not_unbound, + unboundBehaviorType) + .highlight(var->getPropertyDelegateTypeLoc().getSourceRange()); + var->getPropertyDelegateTypeLoc().setInvalidType(ctx); + return nullptr; + } + + // Make sure it's a valid property delegate type. + if (!evaluateOrDefault( + ctx.evaluator, PropertyDelegateTypeInfoRequest(nominalDecl), + PropertyDelegateTypeInfo())) { + var->getPropertyDelegateTypeLoc().setInvalidType(ctx); + return nullptr; + } + + switch (resolution.getStage()) { + case TypeResolutionStage::Contextual: + case TypeResolutionStage::Structural: + // Don't cache these results. + break; + + case TypeResolutionStage::Interface: + var->getPropertyDelegateTypeLoc().setType(unboundBehaviorType); + break; + } + return unboundGeneric; +} + +llvm::Expected +PropertyDelegateTypeInfoRequest::evaluate( + Evaluator &eval, NominalTypeDecl *nominal) const { + PropertyDelegateTypeInfo result; + + // We must have the @propertyDelegate attribute to continue. + if (!nominal->getAttrs().hasAttribute()) { + return result; + } + + // Ensure that we have a single-parameter generic type. + if (!nominal->getGenericParams() || + nominal->getGenericParams()->size() != 1) { + nominal->diagnose(diag::property_delegate_not_single_parameter); + return result; + } + + // Look for a non-static property named "value" in the property delegate + // type. + ASTContext &ctx = nominal->getASTContext(); + ctx.getLazyResolver()->resolveDeclSignature(nominal); + SmallVector unwrapVars; + { + 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; + + unwrapVars.push_back(foundVar); + } + } + + // Diagnose missing or ambiguous "value" properties. + switch (unwrapVars.size()) { + case 0: + nominal->diagnose(diag::property_delegate_no_value_property, + nominal->getDeclaredType()); + return PropertyDelegateTypeInfo(); + + case 1: + result.unwrapProperty = unwrapVars.front(); + break; + + default: + nominal->diagnose(diag::property_delegate_ambiguous_value_property, + nominal->getDeclaredType()); + for (auto var : unwrapVars) { + var->diagnose(diag::kind_declname_declared_here, + var->getDescriptiveKind(), var->getFullName()); + } + return PropertyDelegateTypeInfo(); + } + + // The 'value' property must be as accessible as the nominal type. + if (result.unwrapProperty->getFormalAccess() < nominal->getFormalAccess()) { + auto var = result.unwrapProperty; + var->diagnose(diag::property_delegate_type_requirement_not_accessible, + var->getFormalAccess(), var->getDescriptiveKind(), + var->getFullName(), nominal->getDeclaredType(), + nominal->getFormalAccess()); + return PropertyDelegateTypeInfo(); + } + + // Determine whether we have an init(initialValue:), and diagnose + // ambiguities. + { + 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: + break; + + case 1: { + // '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()); + } else { + result.initialValueInit = init; + } + 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()); + } + break; + } + } + + return result; +} + +Type swift::applyPropertyDelegateType(Type type, VarDecl *var, + TypeResolution resolution) { + auto unboundGeneric = getUnboundPropertyDelegateType(var, resolution); + if (!unboundGeneric) + return Type(); + + TypeChecker &tc = + *static_cast(var->getASTContext().getLazyResolver()); + SourceLoc byLoc = var->getPropertyDelegateByLoc(); + return tc.applyUnboundGenericArguments(unboundGeneric, + unboundGeneric->getDecl(), + byLoc, resolution, { type }); +} diff --git a/lib/Sema/TypeCheckPropertyDelegates.h b/lib/Sema/TypeCheckPropertyDelegates.h new file mode 100644 index 0000000000000..5721cff3cc8d8 --- /dev/null +++ b/lib/Sema/TypeCheckPropertyDelegates.h @@ -0,0 +1,40 @@ +//===--- CSDiagnostics.h - Constraint Diagnostics -------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2019 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 provides utilities for working with property delegates. +// +//===----------------------------------------------------------------------===// +#ifndef SWIFT_SEMA_TYPECHECKPROPERTYBEHAVIORS_H +#define SWIFT_SEMA_TYPECHECKPROPERTYBEHAVIORS_H + +#include "swift/AST/Type.h" +#include "swift/AST/PropertyDelegates.h" +#include "TypeCheckType.h" + +namespace swift { + +class UnboundGenericType; +class VarDecl; + +/// Retrieve the unbound property delegate type for the given property. +UnboundGenericType *getUnboundPropertyDelegateType(VarDecl *var, + TypeResolution resolution); + +/// Given the type of the given variable (which must have a property +/// behavior), form the stored property type that results from applying +/// the behavior. +Type applyPropertyDelegateType(Type type, VarDecl *var, + TypeResolution resolution); + +} // end namespace + +#endif /* SWIFT_SEMA_TYPECHECKPROPERTYBEHAVIORS_H */ diff --git a/lib/Sema/TypeCheckREPL.cpp b/lib/Sema/TypeCheckREPL.cpp index 6e6b08709dce0..b97db787826f8 100644 --- a/lib/Sema/TypeCheckREPL.cpp +++ b/lib/Sema/TypeCheckREPL.cpp @@ -324,7 +324,7 @@ void REPLChecker::processREPLTopLevelExpr(Expr *E) { PatternBindingDecl *metavarBinding = PatternBindingDecl::create( Context, /*StaticLoc*/ SourceLoc(), StaticSpellingKind::None, /*VarLoc*/ E->getStartLoc(), metavarPat, /*EqualLoc*/ SourceLoc(), E, - TLCD); + /*IsPropertyDelegateInit*/ false, TLCD); // Overwrite the body of the existing TopLevelCodeDecl. TLCD->setBody(BraceStmt::create(Context, @@ -399,7 +399,7 @@ void REPLChecker::processREPLTopLevelPatternBinding(PatternBindingDecl *PBD) { PatternBindingDecl *metavarBinding = PatternBindingDecl::create( Context, /*StaticLoc*/ SourceLoc(), StaticSpellingKind::None, /*VarLoc*/ PBD->getStartLoc(), metavarPat, /*EqualLoc*/ SourceLoc(), - patternEntry.getInit(), &SF); + patternEntry.getInit(), /*IsPropertyDelegateInit*/ false, &SF); auto MVBrace = BraceStmt::create(Context, metavarBinding->getStartLoc(), ASTNode(metavarBinding), diff --git a/lib/Sema/TypeChecker.h b/lib/Sema/TypeChecker.h index b9b030cc1321b..faad6211dfbe8 100644 --- a/lib/Sema/TypeChecker.h +++ b/lib/Sema/TypeChecker.h @@ -1507,7 +1507,8 @@ class TypeChecker final : public LazyResolver { /// Type-check an initialized variable pattern declaration. - bool typeCheckBinding(Pattern *&P, Expr *&Init, DeclContext *DC); + bool typeCheckBinding(Pattern *&P, Expr *&Init, DeclContext *DC, + bool IsPropertyDelegateInit); bool typeCheckPatternBinding(PatternBindingDecl *PBD, unsigned patternNumber); /// Type-check a for-each loop's pattern binding and sequence together. diff --git a/lib/Serialization/Deserialization.cpp b/lib/Serialization/Deserialization.cpp index 963c239e1511a..0dcfea889172c 100644 --- a/lib/Serialization/Deserialization.cpp +++ b/lib/Serialization/Deserialization.cpp @@ -2643,6 +2643,9 @@ class swift::DeclDeserializer { TypeID interfaceTypeID; ModuleFile::AccessorRecord accessors; DeclID overriddenID; + bool isPropertyDelegate; + bool isDelegatedPropertyBackingVar; + uint8_t rawPropertyDelegateAccessLevel; ArrayRef accessorAndDependencyIDs; decls_block::VarLayout::readRecord(scratch, nameID, contextID, @@ -2655,6 +2658,9 @@ class swift::DeclDeserializer { interfaceTypeID, overriddenID, rawAccessLevel, rawSetterAccessLevel, + isPropertyDelegate, + isDelegatedPropertyBackingVar, + rawPropertyDelegateAccessLevel, accessorAndDependencyIDs); Identifier name = MF.getIdentifier(nameID); @@ -2669,7 +2675,21 @@ class swift::DeclDeserializer { for (DeclID accessorID : accessorAndDependencyIDs.slice(0, numAccessors)) { accessors.IDs.push_back(accessorID); } - accessorAndDependencyIDs = accessorAndDependencyIDs.slice(numAccessors); + unsigned firstDependencyIdx = numAccessors; + TypeID delegateTypeID; + DeclID delegateBackingVarID; + if (isPropertyDelegate) { + delegateTypeID = accessorAndDependencyIDs[firstDependencyIdx++]; + delegateBackingVarID = accessorAndDependencyIDs[firstDependencyIdx++]; + } + + DeclID delegateOriginalVarID; + if (isDelegatedPropertyBackingVar) { + delegateOriginalVarID = accessorAndDependencyIDs[firstDependencyIdx++]; + } + + accessorAndDependencyIDs = + accessorAndDependencyIDs.slice(firstDependencyIdx); for (TypeID dependencyID : accessorAndDependencyIDs) { auto dependency = MF.getTypeChecked(dependencyID); @@ -2755,6 +2775,42 @@ class swift::DeclDeserializer { if (var->hasStorage()) AddAttribute(new (ctx) HasStorageAttr(/*isImplicit:*/true)); + if (isPropertyDelegate) { + Expected delegateType = MF.getTypeChecked(delegateTypeID); + if (!delegateType) { + DeclDeserializationError::Flags flags; + return llvm::make_error( + name, takeErrorInfo(delegateType.takeError()), flags); + } + + auto delegateAccessLevel = + getActualAccessLevel(rawPropertyDelegateAccessLevel); + if (!delegateAccessLevel) { + MF.error(); + return nullptr; + } + + var->addPropertyDelegate(SourceLoc(), *delegateAccessLevel, + SourceLoc(), + TypeLoc::withoutLoc(delegateType.get())); + + Expected backingDecl = MF.getDeclChecked(delegateBackingVarID); + if (!backingDecl) + return backingDecl; + + VarDecl *backingVar = cast(backingDecl.get()); + var->setPropertyDelegateBackingVar(backingVar); + } + + if (isDelegatedPropertyBackingVar) { + Expected originalDecl = MF.getDeclChecked(delegateOriginalVarID); + if (!originalDecl) + return originalDecl; + + VarDecl *originalVal = cast(originalDecl.get()); + var->setOriginalDelegatedProperty(originalVal); + } + return var; } @@ -4011,6 +4067,24 @@ llvm::Error DeclDeserializer::deserializeDeclAttributes() { break; } + case decls_block::Custom_DECL_ATTR: { + bool isImplicit; + TypeID typeID; + serialization::decls_block::CustomDeclAttrLayout::readRecord( + scratch, isImplicit, typeID); + + Expected deserialized = MF.getTypeChecked(typeID); + if (!deserialized) { + MF.fatal(deserialized.takeError()); + break; + } + + Attr = CustomAttr::create(ctx, SourceLoc(), + TypeLoc::withoutLoc(deserialized.get()), + isImplicit); + break; + } + #define SIMPLE_DECL_ATTR(NAME, CLASS, ...) \ case decls_block::CLASS##_DECL_ATTR: { \ bool isImplicit; \ diff --git a/lib/Serialization/Serialization.cpp b/lib/Serialization/Serialization.cpp index b1db428d1ffe8..5d63bcd831244 100644 --- a/lib/Serialization/Serialization.cpp +++ b/lib/Serialization/Serialization.cpp @@ -2380,6 +2380,15 @@ void Serializer::writeDeclAttribute(const DeclAttribute *DA) { addDeclRef(theAttr->getReplacedFunction()), pieces.size(), pieces); return; } + + case DAK_Custom: { + auto abbrCode = DeclTypeAbbrCodes[CustomDeclAttrLayout::Code]; + auto theAttr = cast(DA); + CustomDeclAttrLayout::emitRecord( + Out, ScratchRecord, abbrCode, theAttr->isImplicit(), + addTypeRef(theAttr->getTypeLoc().getType())); + return; + } } } @@ -3231,6 +3240,23 @@ void Serializer::writeDecl(const Decl *D) { SmallVector accessorsAndDependencies; for (auto accessor : accessors.Decls) accessorsAndDependencies.push_back(addDeclRef(accessor)); + uint8_t rawPropertyDelegateAccessLevel = + getRawStableAccessLevel(swift::AccessLevel::Internal); + if (var->hasPropertyDelegate()) { + accessorsAndDependencies.push_back( + addTypeRef(var->getPropertyDelegateTypeLoc().getType())); + accessorsAndDependencies.push_back( + addDeclRef(var->getPropertyDelegateBackingVar())); + rawPropertyDelegateAccessLevel = + getRawStableAccessLevel(var->getPropertyDelegateFormalAccess()); + } + + bool isDelegatedPropertyBackingVar = false; + if (auto original = var->getOriginalDelegatedProperty()) { + isDelegatedPropertyBackingVar = true; + accessorsAndDependencies.push_back(addDeclRef(original)); + } + for (Type dependency : collectDependenciesFromType(ty->getCanonicalType())) accessorsAndDependencies.push_back(addTypeRef(dependency)); @@ -3253,6 +3279,9 @@ void Serializer::writeDecl(const Decl *D) { addTypeRef(ty), addDeclRef(var->getOverriddenDecl()), rawAccessLevel, rawSetterAccessLevel, + var->hasPropertyDelegate(), + isDelegatedPropertyBackingVar, + rawPropertyDelegateAccessLevel, accessorsAndDependencies); break; } diff --git a/test/IDE/complete_decl_attribute.swift b/test/IDE/complete_decl_attribute.swift index 0782cff3ea0a6..85a43df81cc0f 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 diff --git a/test/IDE/print_property_delegates.swift b/test/IDE/print_property_delegates.swift new file mode 100644 index 0000000000000..48acb6756c9fe --- /dev/null +++ b/test/IDE/print_property_delegates.swift @@ -0,0 +1,47 @@ +// RUN: %empty-directory(%t) + +// Directly printing the type-checked AST +// RUN: %target-swift-ide-test -print-ast-typechecked -print-interface -source-filename %s | %FileCheck %s -check-prefix=CHECK-TYPECHECKED + +// Directly printing the non-type-checked AST +// RUN: %target-swift-ide-test -print-ast-not-typechecked -print-interface -source-filename %s | %FileCheck %s -check-prefix=CHECK-NOTTYPECHECKED + +// Creating a module, then printing from the module +// RUN: %target-swift-frontend(mock-sdk: %clang-importer-sdk) -emit-module -o %t %s +// RUN: %target-swift-ide-test -print-module -module-to-print=print_property_delegates -print-interface -I %t -source-filename %s | %FileCheck %s -check-prefix=CHECK-MODULE + +@propertyDelegate +public struct Wrapper { + public var value: T +} + +@propertyDelegate +public struct WrapperWithInitialValue { + public var value: T + + public init(initialValue: T) { + self.value = initialValue + } + + public init(alternate value: T) { + self.value = value + } +} + +// CHECK-TYPECHECKED: public struct HasDelegates { +public struct HasDelegates { + // CHECK-TYPECHECKED: public var x: Int by public Wrapper + // CHECK-NOTTYPECHECKED: public var x: Int by public Wrapper + // CHECK-MODULE: public var x: Int by public print_property_delegates.Wrapper + public var x: Int by public Wrapper + + // CHECK-TYPECHECKED: public private(set) var y: Int by public WrapperWithInitialValue + // CHECK-NOTTYPECHECKED: public private(set) var y: Int by public WrapperWithInitialValue + // CHECK-MODULE: public private(set) var y: Int by public print_property_delegates.WrapperWithInitialValue + public private(set) var y: Int by public WrapperWithInitialValue = 17 + + // CHECK-TYPECHECKED: public var z: Bool { get set } + // CHECK-NOTTYPECHECKED: public var z + // CHECK-MODULE: public var z: Bool + public var z by WrapperWithInitialValue(alternate: false) +} diff --git a/test/Index/property_delegates.swift b/test/Index/property_delegates.swift new file mode 100644 index 0000000000000..cd3aed1335dc5 --- /dev/null +++ b/test/Index/property_delegates.swift @@ -0,0 +1,13 @@ +// RUN: %target-swift-ide-test -print-indexed-symbols -source-filename %s | %FileCheck -check-prefix=CHECK %s + +@propertyDelegate +public struct Wrapper { + public var value: T +} + +public struct HasDelegates { + public var x: Int by public Wrapper +} + +// CHECK: 9:17 | struct/Swift | Int | s:Si | Ref | rel: 0 +// CHECK: 9:31 | struct/Swift | Wrapper | s:14swift_ide_test7WrapperV | Ref | rel: 0 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/ParseableInterface/property-delegates.swift b/test/ParseableInterface/property-delegates.swift new file mode 100644 index 0000000000000..df9ad7ad8c099 --- /dev/null +++ b/test/ParseableInterface/property-delegates.swift @@ -0,0 +1,61 @@ +// RUN: %empty-directory(%t) + +// RUN: %target-swift-frontend -typecheck -module-name Test -emit-parseable-module-interface-path %t/Test.swiftinterface %s +// RUN: %FileCheck %s < %t/Test.swiftinterface --check-prefix CHECK --check-prefix NONRESILIENT +// RUN: %target-swift-frontend -build-module-from-parseable-interface %t/Test.swiftinterface -o %t/Test.swiftmodule +// RUN: %target-swift-frontend -emit-module -o /dev/null -merge-modules -emit-parseable-module-interface-path - %t/Test.swiftmodule -module-name Test | %FileCheck %s --check-prefix CHECK --check-prefix NONRESILIENT + +// RUN: %target-swift-frontend -typecheck -module-name TestResilient -emit-parseable-module-interface-path %t/TestResilient.swiftinterface -enable-library-evolution %s +// RUN: %FileCheck %s < %t/TestResilient.swiftinterface --check-prefix CHECK --check-prefix RESILIENT + +// RUN: %target-swift-frontend -build-module-from-parseable-interface %t/TestResilient.swiftinterface -o %t/TestResilient.swiftmodule +// RUN: %target-swift-frontend -emit-module -o /dev/null -merge-modules -emit-parseable-module-interface-path - %t/TestResilient.swiftmodule -module-name TestResilient | %FileCheck %s --check-prefix CHECK --check-prefix RESILIENT + + +@propertyDelegate +public struct Wrapper { + public var value: T +} + +@propertyDelegate +public struct WrapperWithInitialValue { + public var value: T + + public init(initialValue: T) { + self.value = initialValue + } + + public init(alternate value: T) { + self.value = value + } +} + +// CHECK: public struct HasDelegates { +public struct HasDelegates { + // CHECK: public var x: {{(Swift.)?}}Int { + // CHECK-NEXT: get + // CHECK-NEXT: set + // CHECK-NEXT:} + public var x: Int by public Wrapper + + // CHECK: public var $x: Test{{(Resilient)?}}.Wrapper + + // CHECK: public var y: Swift.Int { + // CHECK-NEXT: get + // CHECK-NEXT: } + public private(set) var y: Int by public WrapperWithInitialValue = 17 + + // CHECK-NONRESILIENT: @_hasStorage @_hasInitialValue public var $y: Test.WrapperWithInitialValue { + // CHECK-RESILIENT: public var $y: TestResilient.WrapperWithInitialValue { + // CHECK: get + // CHECK-NEXT: } + + // CHECK: public var z: Swift.Bool { + // CHECK-NEXT: get + // CHECK-NEXT: set + // CHECK-NEXT:} + public var z by WrapperWithInitialValue(alternate: false) + + // CHECK-NONRESILIENT: @_hasInitialValue internal var $z: Test.WrapperWithInitialValue + // CHECK-RESILIENT-NOT: internal var $z +} 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/SILGen/property_delegates.swift b/test/SILGen/property_delegates.swift new file mode 100644 index 0000000000000..941a74cfa93f5 --- /dev/null +++ b/test/SILGen/property_delegates.swift @@ -0,0 +1,128 @@ +// RUN: %target-swift-frontend %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 + } +} + +struct HasMemberwiseInit { + var x: Bool by Wrapper + var y: T by WrapperWithInitialValue +} + +func forceHasMemberwiseInit() { + _ = HasMemberwiseInit(x: Wrapper(value: true), y: 17) +} + +// CHECK-LABEL: sil hidden [ossa] @$s18property_delegates17HasMemberwiseInitV1x1yACyxGAA7WrapperVySbG_xtcfC : $@convention(method) (Wrapper, @in T, @thin HasMemberwiseInit.Type) -> @out HasMemberwiseInit +// CHECK: [[X_STORAGE:%.*]] = struct_element_addr %0 : $*HasMemberwiseInit, #HasMemberwiseInit.$x +// CHECK: store %1 to [trivial] [[X_STORAGE]] : $*Wrapper + +// CHECK: [[Y_STORAGE:%.*]] = struct_element_addr %0 : $*HasMemberwiseInit, #HasMemberwiseInit.$y +// CHECK: [[METATYPE:%.*]] = metatype $@thin WrapperWithInitialValue.Type +// CHECK: [[INIT:%.*]] = function_ref @$s18property_delegates23WrapperWithInitialValueV07initialF0ACyxGx_tcfC : $@convention(method) <τ_0_0> (@in τ_0_0, @thin WrapperWithInitialV +// CHECK: apply [[INIT]]([[Y_STORAGE]], %2, [[METATYPE]]) + +// CHECK-LABEL: sil private [ossa] @$s18property_delegates9HasNestedV1yACyxGSayxG_ +// CHECK: [[METATYPE:%.*]] = metatype $@thin HasNested.PrivateDelegate>.Type +// CHECK: [[STACK_SLOT:%.*]] = alloc_stack $HasNested.PrivateDelegate> +// 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 + } + } + + private var y: [T] by PrivateDelegate = [] + + static func blah(y: [T]) -> HasNested { + return HasNested(y: y) + } +} + +// FIXME: For now, we are only checking that we don't crash. +struct HasDefaultInit { + var x by Wrapper(value: true) + var y by WrapperWithInitialValue = 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 { + var x: Int by Wrapper { + // 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 + } + + var y: Int by WrapperWithInitialValue { + // Synthesized getter + // CHECK-LABEL: sil hidden [transparent] [ossa] @$s18property_delegates21DelegateWithAccessorsV1ySivg : $@convention(method) (DelegateWithAccessors) -> Int + // CHECK-NOT: return + // CHECK: struct_extract %0 : $DelegateWithAccessors, #DelegateWithAccessors.$y + + // CHECK-LABEL: sil hidden [ossa] @$s18property_delegates21DelegateWithAccessorsV1ySivs : $@convention(method) (Int, @inout DelegateWithAccessors) -> () + // CHECK-NOT: struct_extract + // CHECK: return + set { } + } + + mutating func test() { + x = y + } +} + +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 + var x: Int by Wrapper { + didSet { + consumeNewValue(oldValue) + } + + willSet { + consumeOldValue(newValue) + } + } + + mutating func test(x: Int) { + self.x = x + } +} diff --git a/test/Serialization/Inputs/def_property_delegates.swift b/test/Serialization/Inputs/def_property_delegates.swift new file mode 100644 index 0000000000000..25b7e2b381ffd --- /dev/null +++ b/test/Serialization/Inputs/def_property_delegates.swift @@ -0,0 +1,14 @@ +@propertyDelegate +public struct SomeDelegate { + public var value: T + + public init(initialValue: T) { + self.value = initialValue + } +} + +public struct HasDelegates { + public var x: Int by SomeDelegate + public var y: Int by public SomeDelegate + public private(set) var z: Int by public SomeDelegate +} diff --git a/test/Serialization/property_delegates.swift b/test/Serialization/property_delegates.swift new file mode 100644 index 0000000000000..76d2f8722828a --- /dev/null +++ b/test/Serialization/property_delegates.swift @@ -0,0 +1,22 @@ +// RUN: %empty-directory(%t) +// RUN: %target-swift-frontend -emit-module -o %t %S/Inputs/def_property_delegates.swift +// RUN: %target-swift-frontend -typecheck -I%t -verify -verify-ignore-unknown %s + +import def_property_delegates + +func useDelegates(hd: HasDelegates) { + // Access the original properties + let _: Int = hd.x + let _: Int = hd.y + let _: Int = hd.z + + // Access the backing properties + _ = hd.$x // expected-error{{'$x' is inaccessible due to 'internal' protection level}} + _ = hd.$y // okay + let _: Int = hd.$y // expected-error{{cannot convert value of type 'SomeDelegate' to specified type 'Int'}} + _ = hd.$z // OK: + let _: Int = hd.$z // expected-error{{cannot convert value of type 'SomeDelegate' to specified type 'Int'}} + + var mutableHD = hd + mutableHD.$z = hd.$z // expected-error{{cannot assign to property: '$z' is immutable}} +} 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 } ] diff --git a/test/decl/var/property_delegates.swift b/test/decl/var/property_delegates.swift new file mode 100644 index 0000000000000..51d33af82c886 --- /dev/null +++ b/test/decl/var/property_delegates.swift @@ -0,0 +1,563 @@ +// RUN: %target-typecheck-verify-swift -swift-version 5 + +@propertyDelegate +struct Wrapper { // expected-note{{generic struct 'Wrapper' declared here}} + var value: T +} + +// --------------------------------------------------------------------------- +// Parsing +// --------------------------------------------------------------------------- + +func testParsing() { + var wrapped1: Int by Wrapper + // expected-error@-1{{property delegates are not yet supported on local properties}} + var wrapped2: Int by Wrapper + // expected-error@-1{{property delegates are not yet supported on local properties}} + + _ = wrapped1 + _ = wrapped2 +} + +func testParseError() { + let (a, b): (Int, Int) by Wrapper // expected-error{{property delegate can only by written on a single-variable pattern}} + let (c, d): (Int, Int) by // expected-error{{expected property delegate type after 'by'}} + + _ = a + _ = b + _ = c + _ = d +} + +// --------------------------------------------------------------------------- +// Type formation +// --------------------------------------------------------------------------- +func testExplicitWrapperType() { + var wrapped1: Int by Wrapper + // expected-error@-1{{property delegates are not yet supported on local properties}} + wrapped1 = "Hello" // expected-error{{cannot assign value of type 'String' to type 'Int'}} +} + +@propertyDelegate +struct NonGenericWrapper { } +// expected-error@-1{{property delegate type must have a single generic type parameter}} + +@propertyDelegate +struct TwoParameterWrapper { } +// expected-error@-1{{property delegate type must have a single generic type parameter}} + +@propertyDelegate +struct MissingValue { } +// expected-error@-1{{property delegate type 'MissingValue' does not contain a non-static property named 'value'}} + +// expected-note@+1{{type 'NotADelegate' must have the attribute '@propertyDelegate' to be used as a property delegate}}{{1-1=@propertyDelegate}} +struct NotADelegate { + var value: T +} + +// expected-error@+1{{'@propertyDelegate' attribute cannot be applied to this declaration}} +@propertyDelegate +protocol CannotBeADelegate { + associatedtype Value + var value: Value { get set } +} + +func testBadWrapperTypes() { + var wrapped1: Int by NonGenericWrapper // expected-error{{property delegate type 'NonGenericWrapper' must not provide generic arguments}} + // expected-error@-1{{property delegates are not yet supported on local properties}} + var wrapped2: Int by TwoParameterWrapper + // expected-error@-1{{property delegates are not yet supported on local properties}} + var wrapped3: Int by TwoParameterWrapper // expected-error{{property delegate type 'TwoParameterWrapper' must not provide generic arguments}} + // expected-error@-1{{property delegates are not yet supported on local properties}} + var wrapped4: Int by (Int) // expected-error{{use of non-property delegate type 'Int' as a property delegate}} + // expected-error@-1{{property delegates are not yet supported on local properties}} + var wrapped5: Int by Wrapper // expected-error{{property delegate type 'Wrapper' must not provide generic arguments}} + // expected-error@-1{{property delegates are not yet supported on local properties}} + var wrapped6: Int by NotADelegate // expected-error{{use of non-property delegate type 'NotADelegate' as a property delegate}} + // expected-error@-1{{property delegates are not yet supported on local properties}} + + wrapped1 = 0 + wrapped2 = 0 + wrapped3 = 0 + wrapped4 = 0 + wrapped5 = 0 + wrapped6 = 0 + _ = wrapped1 + _ = wrapped2 + _ = wrapped3 + _ = wrapped4 + _ = wrapped5 + _ = wrapped6 +} + +// --------------------------------------------------------------------------- +// Property delegates as members +// --------------------------------------------------------------------------- +struct HasDelegate { + var wrapped1: Int by Wrapper +} + +// --------------------------------------------------------------------------- +// Initial value initializers +// --------------------------------------------------------------------------- +@propertyDelegate +struct WrapperWithInitialValue { + var value: T + + init(initialValue: T) { + self.value = initialValue + } +} + +func testInitialValueInference(i: Int, s: String) { + // Inferring the type of the property itself + var x by WrapperWithInitialValue = i + // expected-error@-1{{property delegates are not yet supported on local properties}} + x = 3.14159 // expected-error{{cannot assign value of type 'Double' to type 'Int'}} + + // Inferring part of the type of the property itself + var y: Dictionary by WrapperWithInitialValue = [s: i] + // expected-error@-1{{property delegates are not yet supported on local properties}} + y = 3.14159 // expected-error{{cannot assign value of type 'Double' to type 'Dictionary'}} +} + +func testInitialValueWithoutDelegateSupport(i: Int) { + var x by Wrapper = i // expected-error{{initializing property 'x' with delegate 'Wrapper' that lacks an 'init(initialValue:)' initializer; use (...) instead}} + // expected-error@-1{{property delegates are not yet supported on local properties}} +} + +@propertyDelegate +struct WrapperWithAmbiguousInitialValue { // expected-error{{property delegate type 'WrapperWithAmbiguousInitialValue' has multiple initial-value initializers}} + var value: T + + init(initialValue: T?) { // expected-note{{initializer 'init(initialValue:)' declared here}} + self.value = initialValue! + } + + init(initialValue: T) { // expected-note{{initializer 'init(initialValue:)' declared here}} + self.value = initialValue + } +} + +extension Wrapper { + init(name: String, value: T) { + self.value = value + } +} + +func testDirectDelegateInitialization(s: String, i: Int) { + var x by Wrapper(value: i) + // expected-error@-1{{property delegates are not yet supported on local properties}} + x = 3.14159 // expected-error{{cannot assign value of type 'Double' to type 'Int'}} + + var y by Wrapper(name: "Hello", value: 3.14159) + // expected-error@-1{{property delegates are not yet supported on local properties}} + y = "hello" // expected-error{{cannot assign value of type 'String' to type 'Double'}} + + // FIXME: Diagnostic below should say "specified type 'Wrapper'. + var z: Int by Wrapper(name: "Hello", value: 3.14159) + // expected-error@-1{{property delegates are not yet supported on local properties}} + // expected-error@-2{{cannot convert value of type 'Wrapper' to specified type 'Int'}} +} + +// --------------------------------------------------------------------------- +// @autoclosure initializers +// --------------------------------------------------------------------------- +@propertyDelegate +struct WrapperAcceptingAutoclosure { + private let fn: () -> T + + var value: T { + return fn() + } + + init(initialValue fn: @autoclosure @escaping () -> T) { + self.fn = fn + } +} + +func seventeen() -> Int { return 17 } + +struct UseWrapperAcceptingAutoclosure { + var foo by WrapperAcceptingAutoclosure = seventeen() +} + +// --------------------------------------------------------------------------- +// Memberwise initializers +// --------------------------------------------------------------------------- +struct MemberwiseInits { + var x: Bool by Wrapper + var y: T by WrapperWithInitialValue +} + +func testMemberwiseInits() { + // expected-error@+1{{type '(Wrapper, Double) -> MemberwiseInits'}} + let _: Int = MemberwiseInits.init + + _ = MemberwiseInits(x: Wrapper(value: true), y: 17) +} + +struct DefaultedMemberwiseInits { + var x: Bool by Wrapper(value: true) + var y: Int by WrapperWithInitialValue = 17 +} + +func testDefaultedMemberwiseInits() { + // FIXME: We would like defaults for these, but it is not implemented yet. + _ = DefaultedMemberwiseInits() + _ = DefaultedMemberwiseInits(x: Wrapper(value: false), y: 42) + + _ = DefaultedMemberwiseInits(y: 42) + // expected-error@-1{{cannot invoke initializer for type 'DefaultedMemberwiseInits' with an argument list of type '(y: Int)'}} + // expected-note@-2{{overloads for 'DefaultedMemberwiseInits' exist with these partially matching parameter lists: (), (x: Wrapper, y: Int)}} + _ = DefaultedMemberwiseInits(x: Wrapper(value: false)) + // expected-error@-1{{cannot invoke initializer for type 'DefaultedMemberwiseInits' with an argument list of type '(x: Wrapper)'}} + // expected-note@-2{{overloads for 'DefaultedMemberwiseInits' exist with these partially matching parameter lists: (), (x: Wrapper, y: Int)}} +} + +// --------------------------------------------------------------------------- +// Default initializers +// --------------------------------------------------------------------------- +struct DefaultInitializerStruct { + var x by Wrapper(value: true) + var y: Int by WrapperWithInitialValue = 10 +} + +struct NoDefaultInitializerStruct { // expected-note{{'init(x:)' declared here}} + var x: Bool by Wrapper +} + +class DefaultInitializerClass { + final var x by Wrapper(value: true) + final var y: Int by WrapperWithInitialValue = 10 +} + +class NoDefaultInitializerClass { // expected-error{{class 'NoDefaultInitializerClass' has no initializers}} + // FIXME: Reference 'x' instead of '$x' here + final var x: Bool by Wrapper // 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}} +} + +// --------------------------------------------------------------------------- +// Referencing the backing store +// --------------------------------------------------------------------------- +extension MemberwiseInits { + func getXStorage() -> Wrapper { + return $x + } + + func getYStorage() -> WrapperWithInitialValue { + return self.$y + } +} + +// --------------------------------------------------------------------------- +// Nested delegates +// --------------------------------------------------------------------------- +struct HasNestedDelegate { + @propertyDelegate + struct NestedDelegate { + var value: U + init(initialValue: U) { + self.value = initialValue + } + } + + var y: [T] by NestedDelegate = [] +} + +struct UsesNestedDelegate { + var y: [V] by HasNestedDelegate.NestedDelegate +} + +// --------------------------------------------------------------------------- +// Access control +// --------------------------------------------------------------------------- +struct HasPrivateDelegate { + @propertyDelegate + private struct PrivateDelegate { // expected-note{{type declared here}} + var value: U + init(initialValue: U) { + self.value = initialValue + } + } + + var y: [T] by PrivateDelegate = [] + // 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 delegate instance + var z: [T] by private PrivateDelegate +} + +public struct HasUsableFromInlineDelegate { + @propertyDelegate + struct InternalDelegate { // expected-note{{type declared here}} + var value: U + init(initialValue: U) { + self.value = initialValue + } + } + + @usableFromInline + var y: [T] by InternalDelegate = [] + // 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 { + var x by Box = 17 + var y: Int by Box { + 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 +} + +// --------------------------------------------------------------------------- +// Access control for the delegate instance +// --------------------------------------------------------------------------- +struct HasPrivateDelegateInstance { + var x: [T] by fileprivate WrapperWithInitialValue = [] + var y: [T] by private WrapperWithInitialValue = [] // expected-note{{'$y' declared here}} + var z: [T] by public WrapperWithInitialValue = [] // expected-error{{specified property delegate access 'public' cannot be greater than that of the original property ('internal')}} + private(set) var w: [T] by WrapperWithInitialValue = [] + + func getStorage(which: Bool) -> WrapperWithInitialValue<[T]> { + return which ? $x : $y + } +} + +func testPrivateDelegateInstance(p: HasPrivateDelegateInstance) { + _ = p.$x + _ = p.$y // expected-error{{'$y' is inaccessible due to 'private' protection level}} + _ = p.$w + + var mutableP = p + mutableP.$w = p.$w // expected-error{{cannot assign to property: '$w' is immutable}} +} + +// --------------------------------------------------------------------------- +// Access control for the memberwise initializer +// --------------------------------------------------------------------------- +struct NotPrivateMemberwiseInits { + var x: Bool by Wrapper + var y: T by private WrapperWithInitialValue + + static func construct(x: Wrapper, y: T) + -> NotPrivateMemberwiseInits { + return NotPrivateMemberwiseInits(x: x, y: y) + } +} + +struct PrivateMemberwiseInits { // expected-note{{'init(x:y:)' declared here}} + var x: Bool by private Wrapper + var y: T by WrapperWithInitialValue + + static func construct(x: Wrapper, y: T) + -> PrivateMemberwiseInits { + return PrivateMemberwiseInits(x: x, y: y) + } +} + +func testMemberInitAccess(x: Wrapper, y: T) { + _ = NotPrivateMemberwiseInits(x: x, y: y) + + // expected-error@+1{{'PrivateMemberwiseInits' initializer is inaccessible due to 'private' protection level}} + _ = PrivateMemberwiseInits(x: x, y: y) +} + +// --------------------------------------------------------------------------- +// Miscellaneous restrictions +// --------------------------------------------------------------------------- +enum SomeEnum { + case foo + + var bar: Int by Wrapper(value: 17) // expected-error{{property 'bar' declared inside an enum cannot have a delegate}} + // expected-error@-1{{cannot convert value of type '(value: Int)' to specified type 'Int'}} + + static var x by Wrapper(value: 17) // okay +} + +protocol SomeProtocol { + var bar: Int by Wrapper(value: 17) // expected-error{{property 'bar' declared inside a protocol cannot have a delegate}} + // expected-error@-1{{initial value is not allowed here}} + + static var x: Int by Wrapper(value: 17) // expected-error{{property 'x' declared inside a protocol cannot have a delegate}} + // expected-error@-1{{static stored properties not supported in generic types}} + // expected-error@-2{{static stored properties not supported in generic types}} + // expected-error@-3{{initial value is not allowed here}} +} + +extension HasDelegate { + var inExt: Int by Wrapper(value: 17) // expected-error{{property 'inExt' declared inside an extension cannot have a delegate}} + // expected-error@-1{{cannot convert value of type '(value: Int)' to specified type 'Int'}} + + static var x by Wrapper(value: 17) // okay +} + +class ClassWithDelegates { + final var x by Wrapper(value: 17) + var y by Wrapper(value: 17) // expected-error{{property 'y' with a delegate must be 'final'}}{{3-3=final}} +} + +final class FinalClassWithDelegates { + var x by Wrapper(value: 17) +} + +class Superclass { + var x: Int = 0 +} + +class SubclassWithDelegate: Superclass { + final override var x by Wrapper(value: 17) // expected-error{{property 'x' with a delegate cannot override another property}} +} + +class C { } + +struct BadCombinations { + lazy var x: C by WrapperWithInitialValue = C() // expected-error{{property 'x' with a delegate cannot also be lazy}} + weak var y: C? by Wrapper // expected-error{{property 'y' with a delegate cannot also be weak}} + unowned var z: C by Wrapper // expected-error{{property 'z' with a delegate cannot also be unowned}} +} + +@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 + } +} + +// --------------------------------------------------------------------------- +// Explicitly-specified accessors +// --------------------------------------------------------------------------- + +struct DelegateWithAccessors { // expected-note{{'init(x:y:)' declared here}} + var x: Int by Wrapper { + return $x.value * 2 + } + + var y: Int by WrapperWithInitialValue { + set { + $y.value = newValue / 2 + } + } +} + +func testDelegateWithAccessors() { + _ = DelegateWithAccessors() // expected-error{{missing argument for parameter 'x' in call}} + _ = DelegateWithAccessors(x: Wrapper(value: 17), y: 42) +} + +// --------------------------------------------------------------------------- +// 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 { + var x by DelegateWithNonMutatingSetter = true + var y by DelegateWithMutatingGetter = 17 +} + +func testMutatingness() { + var mutable = UseMutatingnessDelegates() + + _ = mutable.x + mutable.x = false + + _ = mutable.y + mutable.y = 42 + + 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}} +} + +// --------------------------------------------------------------------------- +// Custom attribute syntax +// --------------------------------------------------------------------------- +struct TestAttributeSyntax { + @Wrapper var x: Int + @Wrapper(value: 17) var y + @WrapperWithInitialValue var z = 17 + + @Bad var bad1: Int // expected-error{{unknown attribute 'Bad'}} + @Int var bad2: Int // expected-error{{struct 'Int' cannot be used as an attribute}} + @Swift.Float var bad3: Int // expected-error{{struct 'Float' cannot be used as an attribute}} +} diff --git a/test/decl/var/property_delegates_synthesis.swift b/test/decl/var/property_delegates_synthesis.swift new file mode 100644 index 0000000000000..8c7649eb022cd --- /dev/null +++ b/test/decl/var/property_delegates_synthesis.swift @@ -0,0 +1,38 @@ +// 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 + var wrapped by Wrapper = 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() { } +} +