diff --git a/include/swift/AST/Decl.h b/include/swift/AST/Decl.h index 14512fde10633..78b63fb53edd2 100644 --- a/include/swift/AST/Decl.h +++ b/include/swift/AST/Decl.h @@ -4521,6 +4521,34 @@ class AbstractStorageDecl : public ValueDecl { return {}; } + /// This is the primary mechanism by which we can easily determine whether + /// this storage decl has any effects. + /// + /// \returns the getter decl iff this decl has only one accessor that is + /// a 'get' with an effect (i.e., 'async', 'throws', or both). + /// Otherwise returns nullptr. + AccessorDecl *getEffectfulGetAccessor() const; + + /// Performs a "limit check" on an effect possibly exhibited by this storage + /// decl with respect to some other storage decl that serves as the "limit." + /// This check says that \c this is less effectful than \c other if + /// \c this either does not exhibit the effect, or if it does, then \c other + /// also exhibits the effect. Thus, it is conceptually equivalent to + /// a less-than-or-equal (≤) check like so: + /// + /// \verbatim + /// + /// this->hasEffect(E) ≤ other->hasEffect(E) + /// + /// \endverbatim + /// + /// \param kind the single effect we are performing a check for. + /// + /// \returns true iff \c this decl either does not exhibit the effect, + /// or \c other also exhibits the effect. + bool isLessEffectfulThan(AbstractStorageDecl const* other, + EffectKind kind) const; + /// Return an accessor that this storage is expected to have, synthesizing /// one if necessary. Note that will always synthesize one, even if the /// accessor is not part of the expected opaque set for the storage, so use @@ -6327,16 +6355,18 @@ class AccessorDecl final : public FuncDecl { AccessorDecl(SourceLoc declLoc, SourceLoc accessorKeywordLoc, AccessorKind accessorKind, AbstractStorageDecl *storage, SourceLoc staticLoc, StaticSpellingKind staticSpelling, - bool throws, SourceLoc throwsLoc, + bool async, SourceLoc asyncLoc, bool throws, SourceLoc throwsLoc, bool hasImplicitSelfDecl, GenericParamList *genericParams, DeclContext *parent) : FuncDecl(DeclKind::Accessor, staticLoc, staticSpelling, /*func loc*/ declLoc, /*name*/ Identifier(), /*name loc*/ declLoc, - /*Async=*/false, SourceLoc(), throws, throwsLoc, + async, asyncLoc, throws, throwsLoc, hasImplicitSelfDecl, genericParams, parent), AccessorKeywordLoc(accessorKeywordLoc), Storage(storage) { + assert(!async || accessorKind == AccessorKind::Get + && "only get accessors can be async"); Bits.AccessorDecl.AccessorKind = unsigned(accessorKind); } @@ -6347,6 +6377,7 @@ class AccessorDecl final : public FuncDecl { AbstractStorageDecl *storage, SourceLoc staticLoc, StaticSpellingKind staticSpelling, + bool async, SourceLoc asyncLoc, bool throws, SourceLoc throwsLoc, GenericParamList *genericParams, DeclContext *parent, @@ -6365,7 +6396,7 @@ class AccessorDecl final : public FuncDecl { AccessorKind accessorKind, AbstractStorageDecl *storage, StaticSpellingKind staticSpelling, - bool throws, + bool async, bool throws, GenericParamList *genericParams, Type fnRetType, DeclContext *parent); @@ -6375,6 +6406,7 @@ class AccessorDecl final : public FuncDecl { AbstractStorageDecl *storage, SourceLoc staticLoc, StaticSpellingKind staticSpelling, + bool async, SourceLoc asyncLoc, bool throws, SourceLoc throwsLoc, GenericParamList *genericParams, ParameterList *parameterList, diff --git a/include/swift/AST/DiagnosticsParse.def b/include/swift/AST/DiagnosticsParse.def index b8d4dd625ca6e..97bc41afe9d78 100644 --- a/include/swift/AST/DiagnosticsParse.def +++ b/include/swift/AST/DiagnosticsParse.def @@ -278,6 +278,12 @@ ERROR(expected_lbrace_accessor,PointsToFirstBadToken, ERROR(expected_accessor_kw,none, "expected 'get', 'set', 'willSet', or 'didSet' keyword to " "start an accessor definition",()) +ERROR(invalid_accessor_specifier,none, + "'%0' accessor cannot have specifier '%1'", + (StringRef, StringRef)) +ERROR(invalid_accessor_with_effectful_get,none, + "'%0' accessor is not allowed on property with 'get' accessor that is 'async' or 'throws'", + (StringRef)) ERROR(missing_getter,none, "%select{variable|subscript}0 with %1 must also have a getter", (unsigned, StringRef)) diff --git a/include/swift/AST/DiagnosticsSema.def b/include/swift/AST/DiagnosticsSema.def index ad9faad2af110..38ef3cd060ba8 100644 --- a/include/swift/AST/DiagnosticsSema.def +++ b/include/swift/AST/DiagnosticsSema.def @@ -1584,6 +1584,10 @@ WARNING(enum_frozen_nonpublic,none, ERROR(getset_init,none, "variable with getter/setter cannot have an initial value", ()) +ERROR(effectful_not_representable_objc,none, + "%0 with 'throws' or 'async' is not representable in Objective-C", + (DescriptiveDeclKind)) + ERROR(unimplemented_static_var,none, "%select{ERROR|static|class}1 stored properties not supported" "%select{ in this context| in generic types| in classes| in protocol extensions}0" @@ -2600,6 +2604,10 @@ ERROR(override_class_declaration_in_extension,none, ERROR(override_throws,none, "cannot override non-throwing %select{method|initializer}0 with " "throwing %select{method|initializer}0", (bool)) +// TODO: this really could be merged with the above. +ERROR(override_with_more_effects,none, + "cannot override non-'%1' %0 with '%1' %0", + (DescriptiveDeclKind, StringRef)) ERROR(override_throws_objc,none, "overriding a throwing @objc %select{method|initializer}0 with " "a non-throwing %select{method|initializer}0 is not supported", (bool)) @@ -4150,21 +4158,21 @@ ERROR(isa_collection_downcast_pattern_value_unimplemented,none, ERROR(try_unhandled,none, "errors thrown from here are not handled", ()) ERROR(throwing_call_unhandled,none, - "call can throw, but the error is not handled", ()) + "%0 can throw, but the error is not handled", (StringRef)) ERROR(tryless_throwing_call_unhandled,none, - "call can throw, but it is not marked with 'try' and " - "the error is not handled", ()) + "%0 can throw, but it is not marked with 'try' and " + "the error is not handled", (StringRef)) ERROR(throw_in_nonthrowing_function,none, "error is not handled because the enclosing function " "is not declared 'throws'", ()) ERROR(throwing_call_in_rethrows_function,none, - "call can throw, but the error is not handled; a function declared " - "'rethrows' may only throw if its parameter does", ()) + "%0 can throw, but the error is not handled; a function declared " + "'rethrows' may only throw if its parameter does", (StringRef)) ERROR(tryless_throwing_call_in_rethrows_function,none, - "call can throw, but it is not marked with 'try' and " + "%0 can throw, but it is not marked with 'try' and " "the error is not handled; a function declared " - "'rethrows' may only throw if its parameter does", ()) + "'rethrows' may only throw if its parameter does", (StringRef)) ERROR(throw_in_rethrows_function,none, "a function declared 'rethrows' may only throw if its parameter does", ()) NOTE(because_rethrows_argument_throws,none, @@ -4176,11 +4184,11 @@ NOTE(because_rethrows_conformance_throws,none, "call is to 'rethrows' function, but a conformance has a throwing witness", ()) ERROR(throwing_call_in_nonthrowing_autoclosure,none, - "call can throw, but it is executed in a non-throwing " - "autoclosure",()) + "%0 can throw, but it is executed in a non-throwing " + "autoclosure",(StringRef)) ERROR(tryless_throwing_call_in_nonthrowing_autoclosure,none, - "call can throw, but it is not marked with 'try' and " - "it is executed in a non-throwing autoclosure",()) + "%0 can throw, but it is not marked with 'try' and " + "it is executed in a non-throwing autoclosure",(StringRef)) ERROR(throw_in_nonthrowing_autoclosure,none, "error is not handled because it is thrown in a non-throwing " "autoclosure", ()) @@ -4189,17 +4197,17 @@ ERROR(try_unhandled_in_nonexhaustive_catch,none, "errors thrown from here are not handled because the " "enclosing catch is not exhaustive", ()) ERROR(throwing_call_in_nonexhaustive_catch,none, - "call can throw, but the enclosing catch is not exhaustive", ()) + "%0 can throw, but the enclosing catch is not exhaustive", (StringRef)) ERROR(tryless_throwing_call_in_nonexhaustive_catch,none, - "call can throw, but it is not marked with 'try' and " - "the enclosing catch is not exhaustive", ()) + "%0 can throw, but it is not marked with 'try' and " + "the enclosing catch is not exhaustive", (StringRef)) ERROR(throw_in_nonexhaustive_catch,none, "error is not handled because the enclosing catch is not exhaustive", ()) -ERROR(throwing_call_in_illegal_context,none, - "call can throw, but errors cannot be thrown out of " +ERROR(throwing_op_in_illegal_context,none, + "%1 can throw, but errors cannot be thrown out of " "%select{<>|a default argument|a property wrapper initializer|a property initializer|a global variable initializer|an enum case raw value|a catch pattern|a catch guard expression|a defer body}0", - (unsigned)) + (unsigned, StringRef)) ERROR(throw_in_illegal_context,none, "errors cannot be thrown out of " "%select{<>|a default argument|a property wrapper initializer|a property initializer|a global variable initializer|an enum case raw value|a catch pattern|a catch guard expression|a defer body}0", @@ -4213,6 +4221,10 @@ ERROR(throwing_call_without_try,none, "call can throw but is not marked with 'try'", ()) ERROR(throwing_async_let_without_try,none, "reading 'async let' can throw but is not marked with 'try'", ()) +ERROR(throwing_prop_access_without_try,none, + "property access can throw but is not marked with 'try'", ()) +ERROR(throwing_subscript_access_without_try,none, + "subscript access can throw but is not marked with 'try'", ()) NOTE(note_forgot_try,none, "did you mean to use 'try'?", ()) NOTE(note_error_to_optional,none, @@ -4382,6 +4394,9 @@ ERROR(actor_isolated_from_escaping_closure,none, ERROR(actor_isolated_keypath_component,none, "cannot form key path to actor-isolated %0 %1", (DescriptiveDeclKind, DeclName)) +ERROR(effectful_keypath_component,none, + "cannot form key path to %0 with 'throws' or 'async'", + (DescriptiveDeclKind)) ERROR(local_function_executed_concurrently,none, "concurrently-executed %0 %1 must be marked as '@Sendable'", (DescriptiveDeclKind, DeclName)) @@ -5615,6 +5630,9 @@ ERROR(property_wrapper_let, none, ERROR(property_wrapper_computed, none, "property wrapper cannot be applied to a computed property", ()) +ERROR(property_wrapper_effectful,none, + "property wrappers currently cannot define an 'async' or 'throws' accessor", + ()) ERROR(property_with_wrapper_conflict_attribute,none, "property %0 with a wrapper cannot also be " diff --git a/include/swift/AST/StorageImpl.h b/include/swift/AST/StorageImpl.h index f2c5e882d446a..cfa90c714fbb1 100644 --- a/include/swift/AST/StorageImpl.h +++ b/include/swift/AST/StorageImpl.h @@ -45,6 +45,8 @@ enum class AccessorKind { #define ACCESSOR(ID) ID, #define LAST_ACCESSOR(ID) Last = ID #include "swift/AST/AccessorKinds.def" +#undef ACCESSOR +#undef LAST_ACCESSOR }; const unsigned NumAccessorKinds = unsigned(AccessorKind::Last) + 1; @@ -54,6 +56,22 @@ static inline IntRange allAccessorKinds() { AccessorKind(NumAccessorKinds)); } +/// \returns a user-readable string name for the accessor kind +static inline StringRef accessorKindName(AccessorKind ak) { + switch(ak) { + +#define ACCESSOR(ID) ID +#define SINGLETON_ACCESSOR(ID, KEYWORD) \ + case AccessorKind::ID: \ + return #KEYWORD; + +#include "swift/AST/AccessorKinds.def" + +#undef ACCESSOR_KEYWORD +#undef SINGLETON_ACCESSOR + } +} + /// Whether an access to storage is for reading, writing, or both. enum class AccessKind : uint8_t { /// The access is just to read the current value. diff --git a/include/swift/Parse/Parser.h b/include/swift/Parse/Parser.h index 4f4604fb3451e..3c87e03cafc66 100644 --- a/include/swift/Parse/Parser.h +++ b/include/swift/Parse/Parser.h @@ -1158,6 +1158,12 @@ class Parser { bool hasInitializer, const DeclAttributes &Attributes, SmallVectorImpl &Decls); + ParserStatus parseGetEffectSpecifier(ParsedAccessors &accessors, + SourceLoc &asyncLoc, + SourceLoc &throwsLoc, + bool &hasEffectfulGet, + AccessorKind currentKind, + SourceLoc const& currentLoc); void consumeAbstractFunctionBody(AbstractFunctionDecl *AFD, const DeclAttributes &Attrs); diff --git a/lib/AST/ASTPrinter.cpp b/lib/AST/ASTPrinter.cpp index f4034cedff5fa..a410a9106e2da 100644 --- a/lib/AST/ASTPrinter.cpp +++ b/lib/AST/ASTPrinter.cpp @@ -1018,6 +1018,24 @@ static StaticSpellingKind getCorrectStaticSpelling(const Decl *D) { } } +static bool hasAsyncGetter(const AbstractStorageDecl *ASD) { + if (auto getter = ASD->getAccessor(AccessorKind::Get)) { + assert(!getter->getAttrs().hasAttribute()); + return getter->hasAsync(); + } + + return false; +} + +static bool hasThrowsGetter(const AbstractStorageDecl *ASD) { + if (auto getter = ASD->getAccessor(AccessorKind::Get)) { + assert(!getter->getAttrs().hasAttribute()); + return getter->hasThrows(); + } + + return false; +} + static bool hasMutatingGetter(const AbstractStorageDecl *ASD) { return ASD->getAccessor(AccessorKind::Get) && ASD->isGetterMutating(); } @@ -1925,6 +1943,15 @@ void PrintAST::printAccessors(const AbstractStorageDecl *ASD) { return; } + // prints with a space prefixed + auto printWithSpace = [&](StringRef word) { + Printer << " "; + Printer.printKeyword(word, Options); + }; + + const bool asyncGet = hasAsyncGetter(ASD); + const bool throwsGet = hasThrowsGetter(ASD); + // We sometimes want to print the accessors abstractly // instead of listing out how they're actually implemented. bool inProtocol = isa(ASD->getDeclContext()); @@ -1935,27 +1962,27 @@ void PrintAST::printAccessors(const AbstractStorageDecl *ASD) { bool nonmutatingSetter = hasNonMutatingSetter(ASD); // We're about to print something like this: - // { mutating? get (nonmutating? set)? } + // { mutating? get async? throws? (nonmutating? set)? } // But don't print "{ get set }" if we don't have to. if (!inProtocol && !Options.PrintGetSetOnRWProperties && - settable && !mutatingGetter && !nonmutatingSetter) { + settable && !mutatingGetter && !nonmutatingSetter + && !asyncGet && !throwsGet) { return; } Printer << " {"; - if (mutatingGetter) { - Printer << " "; - Printer.printKeyword("mutating", Options); - } - Printer << " "; - Printer.printKeyword("get", Options); + if (mutatingGetter) printWithSpace("mutating"); + + printWithSpace("get"); + + if (asyncGet) printWithSpace("async"); + + if (throwsGet) printWithSpace("throws"); + if (settable) { - if (nonmutatingSetter) { - Printer << " "; - Printer.printKeyword("nonmutating", Options); - } - Printer << " "; - Printer.printKeyword("set", Options); + if (nonmutatingSetter) printWithSpace("nonmutating"); + + printWithSpace("set"); } Printer << " }"; return; @@ -1983,7 +2010,8 @@ void PrintAST::printAccessors(const AbstractStorageDecl *ASD) { !Options.PrintGetSetOnRWProperties && !Options.FunctionDefinitions && !ASD->isGetterMutating() && - !ASD->getAccessor(AccessorKind::Set)->isExplicitNonMutating()) { + !ASD->getAccessor(AccessorKind::Set)->isExplicitNonMutating() && + !asyncGet && !throwsGet) { return; } @@ -1999,7 +2027,15 @@ void PrintAST::printAccessors(const AbstractStorageDecl *ASD) { if (!PrintAccessorBody) { Printer << " "; printMutabilityModifiersIfNeeded(Accessor); + Printer.printKeyword(getAccessorLabel(Accessor->getAccessorKind()), Options); + + // handle any effects specifiers + if (Accessor->getAccessorKind() == AccessorKind::Get) { + if (asyncGet) printWithSpace("async"); + if (throwsGet) printWithSpace("throws"); + } + } else { { IndentRAII IndentMore(*this); @@ -2017,7 +2053,8 @@ void PrintAST::printAccessors(const AbstractStorageDecl *ASD) { bool isOnlyGetter = impl.getReadImpl() == ReadImplKind::Get && ASD->getAccessor(AccessorKind::Get); bool isGetterMutating = ASD->supportsMutation() || ASD->isGetterMutating(); - if (isOnlyGetter && !isGetterMutating && PrintAccessorBody && + bool hasEffects = asyncGet || throwsGet; + if (isOnlyGetter && !isGetterMutating && !hasEffects && PrintAccessorBody && Options.FunctionBody && Options.CollapseSingleGetterProperty) { Options.FunctionBody(ASD->getAccessor(AccessorKind::Get), Printer); indent(); diff --git a/lib/AST/Decl.cpp b/lib/AST/Decl.cpp index 2356a7299365d..4d3c3f7c1991d 100644 --- a/lib/AST/Decl.cpp +++ b/lib/AST/Decl.cpp @@ -2122,6 +2122,30 @@ AccessorDecl *AbstractStorageDecl::getSynthesizedAccessor(AccessorKind kind) con nullptr); } +AccessorDecl *AbstractStorageDecl::getEffectfulGetAccessor() const { + if (getAllAccessors().size() != 1) + return nullptr; + + if (auto accessor = getAccessor(AccessorKind::Get)) + if (accessor->hasAsync() || accessor->hasThrows()) + return accessor; + + return nullptr; +} + +bool AbstractStorageDecl::isLessEffectfulThan(AbstractStorageDecl const* other, + EffectKind kind) const { + bool allowedByOther = false; + if (auto otherGetter = other->getEffectfulGetAccessor()) + allowedByOther = otherGetter->hasEffect(kind); + + if (auto getter = getEffectfulGetAccessor()) + if (getter->hasEffect(kind) && !allowedByOther) + return false; // has the effect when other does not; it's more effectful! + + return true; // OK +} + AccessorDecl *AbstractStorageDecl::getOpaqueAccessor(AccessorKind kind) const { auto *accessor = getAccessor(kind); if (accessor && !accessor->isImplicit()) @@ -7420,6 +7444,7 @@ AccessorDecl *AccessorDecl::createImpl(ASTContext &ctx, AbstractStorageDecl *storage, SourceLoc staticLoc, StaticSpellingKind staticSpelling, + bool async, SourceLoc asyncLoc, bool throws, SourceLoc throwsLoc, GenericParamList *genericParams, DeclContext *parent, @@ -7432,7 +7457,7 @@ AccessorDecl *AccessorDecl::createImpl(ASTContext &ctx, !clangNode.isNull()); auto D = ::new (buffer) AccessorDecl(declLoc, accessorKeywordLoc, accessorKind, - storage, staticLoc, staticSpelling, throws, throwsLoc, + storage, staticLoc, staticSpelling, async, asyncLoc, throws, throwsLoc, hasImplicitSelfDecl, genericParams, parent); if (clangNode) D->setClangNode(clangNode); @@ -7446,12 +7471,12 @@ AccessorDecl * AccessorDecl::createDeserialized(ASTContext &ctx, AccessorKind accessorKind, AbstractStorageDecl *storage, StaticSpellingKind staticSpelling, - bool throws, GenericParamList *genericParams, + bool async, bool throws, GenericParamList *genericParams, Type fnRetType, DeclContext *parent) { assert(fnRetType && "Deserialized result type must not be null"); auto *const D = AccessorDecl::createImpl( ctx, SourceLoc(), SourceLoc(), accessorKind, storage, SourceLoc(), - staticSpelling, throws, SourceLoc(), genericParams, parent, ClangNode()); + staticSpelling, async, SourceLoc(), throws, SourceLoc(), genericParams, parent, ClangNode()); D->setResultInterfaceType(fnRetType); return D; } @@ -7463,6 +7488,7 @@ AccessorDecl *AccessorDecl::create(ASTContext &ctx, AbstractStorageDecl *storage, SourceLoc staticLoc, StaticSpellingKind staticSpelling, + bool async, SourceLoc asyncLoc, bool throws, SourceLoc throwsLoc, GenericParamList *genericParams, ParameterList * bodyParams, @@ -7471,7 +7497,7 @@ AccessorDecl *AccessorDecl::create(ASTContext &ctx, ClangNode clangNode) { auto *D = AccessorDecl::createImpl( ctx, declLoc, accessorKeywordLoc, accessorKind, storage, - staticLoc, staticSpelling, throws, throwsLoc, + staticLoc, staticSpelling, async, asyncLoc, throws, throwsLoc, genericParams, parent, clangNode); D->setParameters(bodyParams); D->setResultInterfaceType(fnRetType); diff --git a/lib/ClangImporter/ImportDecl.cpp b/lib/ClangImporter/ImportDecl.cpp index b16fcbc1bc865..dd1a54d758e49 100644 --- a/lib/ClangImporter/ImportDecl.cpp +++ b/lib/ClangImporter/ImportDecl.cpp @@ -171,9 +171,11 @@ static FuncDecl *createFuncOrAccessor(ASTContext &ctx, SourceLoc funcLoc, /*accessorKeywordLoc*/ SourceLoc(), accessorInfo->Kind, accessorInfo->Storage, /*StaticLoc*/ SourceLoc(), - StaticSpellingKind::None, throws, - /*ThrowsLoc=*/SourceLoc(), genericParams, - bodyParams, resultTy, dc, clangNode); + StaticSpellingKind::None, + throws, /*ThrowsLoc=*/SourceLoc(), + async, /*AsyncLoc=*/SourceLoc(), + genericParams, bodyParams, + resultTy, dc, clangNode); } else { return FuncDecl::createImported(ctx, funcLoc, name, nameLoc, async, throws, bodyParams, resultTy, genericParams, dc, @@ -606,6 +608,7 @@ static void makeEnumRawValueGetter(ClangImporter::Implementation &Impl, rawValueDecl, /*StaticLoc=*/SourceLoc(), StaticSpellingKind::None, + /*Async=*/false, /*AsyncLoc=*/SourceLoc(), /*Throws=*/false, /*ThrowsLoc=*/SourceLoc(), /*GenericParams=*/nullptr, params, @@ -682,6 +685,7 @@ static AccessorDecl *makeStructRawValueGetter( computedVar, /*StaticLoc=*/SourceLoc(), StaticSpellingKind::None, + /*Async=*/false, /*AsyncLoc=*/SourceLoc(), /*Throws=*/false, /*ThrowsLoc=*/SourceLoc(), /*GenericParams=*/nullptr, params, @@ -712,6 +716,7 @@ static AccessorDecl *makeFieldGetterDecl(ClangImporter::Implementation &Impl, importedFieldDecl, /*StaticLoc=*/SourceLoc(), StaticSpellingKind::None, + /*Async=*/false, /*AsyncLoc=*/SourceLoc(), /*Throws=*/false, /*ThrowsLoc=*/SourceLoc(), /*GenericParams=*/nullptr, params, @@ -745,6 +750,7 @@ static AccessorDecl *makeFieldSetterDecl(ClangImporter::Implementation &Impl, importedFieldDecl, /*StaticLoc=*/SourceLoc(), StaticSpellingKind::None, + /*Async=*/false, /*AsyncLoc=*/SourceLoc(), /*Throws=*/false, /*ThrowsLoc=*/SourceLoc(), /*GenericParams=*/nullptr, params, @@ -1690,6 +1696,7 @@ buildSubscriptGetterDecl(ClangImporter::Implementation &Impl, subscript, /*StaticLoc=*/SourceLoc(), subscript->getStaticSpelling(), + /*Async=*/false, /*AsyncLoc=*/SourceLoc(), /*Throws=*/false, /*ThrowsLoc=*/SourceLoc(), /*GenericParams=*/nullptr, @@ -1741,6 +1748,7 @@ buildSubscriptSetterDecl(ClangImporter::Implementation &Impl, subscript, /*StaticLoc=*/SourceLoc(), subscript->getStaticSpelling(), + /*Async=*/false, /*AsyncLoc=*/SourceLoc(), /*Throws=*/false, /*ThrowsLoc=*/SourceLoc(), /*GenericParams=*/nullptr, @@ -1926,6 +1934,7 @@ static bool addErrorDomain(NominalTypeDecl *swiftDecl, errorDomainPropertyDecl, /*StaticLoc=*/SourceLoc(), StaticSpellingKind::None, + /*Async=*/false, /*AsyncLoc=*/SourceLoc(), /*Throws=*/false, /*ThrowsLoc=*/SourceLoc(), /*GenericParams=*/nullptr, @@ -4281,6 +4290,7 @@ namespace { auto accessor = AccessorDecl::create( Impl.SwiftContext, SourceLoc(), SourceLoc(), AccessorKind::Get, swiftVar, SourceLoc(), StaticSpellingKind::KeywordStatic, + /*Async=*/false, /*AsyncLoc=*/SourceLoc(), /*throws=*/false, SourceLoc(), /*genericParams*/ nullptr, ParameterList::createEmpty(Impl.SwiftContext), swiftVar->getType(), swiftVar->getDeclContext()); @@ -9121,6 +9131,7 @@ ClangImporter::Implementation::createConstant(Identifier name, DeclContext *dc, var, /*StaticLoc=*/SourceLoc(), StaticSpellingKind::None, + /*Async=*/false, /*AsyncLoc=*/SourceLoc(), /*Throws=*/false, /*ThrowsLoc=*/SourceLoc(), /*GenericParams=*/nullptr, diff --git a/lib/Parse/ParseDecl.cpp b/lib/Parse/ParseDecl.cpp index 07471cc55896e..28e17136df0e5 100644 --- a/lib/Parse/ParseDecl.cpp +++ b/lib/Parse/ParseDecl.cpp @@ -5681,7 +5681,8 @@ static AccessorDecl *createAccessorFunc(SourceLoc DeclLoc, Parser::ParseDeclOptions Flags, AccessorKind Kind, AbstractStorageDecl *storage, - Parser *P, SourceLoc AccessorKeywordLoc) { + Parser *P, SourceLoc AccessorKeywordLoc, + SourceLoc asyncLoc, SourceLoc throwsLoc) { // First task, set up the value argument list. This is the "newValue" name // (for setters) followed by the index list (for subscripts). For // non-subscript getters, this degenerates down to "()". @@ -5742,7 +5743,8 @@ static AccessorDecl *createAccessorFunc(SourceLoc DeclLoc, AccessorKeywordLoc, Kind, storage, StaticLoc, StaticSpellingKind::None, - /*Throws=*/false, /*ThrowsLoc=*/SourceLoc(), + asyncLoc.isValid(), asyncLoc, + throwsLoc.isValid(), throwsLoc, (GenericParams ? GenericParams->clone(P->CurDeclContext) : nullptr), @@ -5959,6 +5961,13 @@ struct Parser::ParsedAccessors { /// ignored. AccessorDecl *add(AccessorDecl *accessor); + /// applies the function to each accessor that is not a 'get' + void forEachNonGet(llvm::function_ref func) { + for (auto accessor : Accessors) + if (!accessor->isGetter()) + func(accessor); + } + /// Find the first accessor that's not an observing accessor. AccessorDecl *findFirstNonObserver() { for (auto accessor : Accessors) { @@ -6018,6 +6027,78 @@ static bool parseAccessorIntroducer(Parser &P, return false; } +/// Parsing for effects specifiers. We expect the token cursor to be +/// at the point marked by the ^ below: +/// +/// \verbatim +/// getter-clause ::= ... "get" getter-effects? code-block +/// ^ +/// getter-effects ::= "throws" +/// getter-effects ::= "async" "throws"? +/// \endverbatim +/// +/// While only 'get' accessors currently support such specifiers, +/// this routine will also diagnose unspported effects specifiers on +/// other accessors. +/// +/// \param accessors the accessors we've parsed already. +/// \param asyncLoc is mutated with the location of 'async' if found. +/// \param throwsLoc is mutated with the location of 'throws' if found. +/// \param hasEffectfulGet maintains the state of parsing multiple accessors +/// to track whether we've already seen an effectful +/// 'get' accessor. It is mutated by this function. +/// \param currentKind is the kind of accessor we're parsing. +/// \param currentLoc is the location of the current accessor we're parsing. +/// \return the status of the parser after trying to parse these specifiers. +ParserStatus Parser::parseGetEffectSpecifier(ParsedAccessors &accessors, + SourceLoc &asyncLoc, + SourceLoc &throwsLoc, + bool &hasEffectfulGet, + AccessorKind currentKind, + SourceLoc const& currentLoc) { + ParserStatus Status; + + if (!shouldParseExperimentalConcurrency()) + return Status; + + if (isEffectsSpecifier(Tok)) { + if (currentKind == AccessorKind::Get) { + Status |= + parseEffectsSpecifiers(/*existingArrowLoc*/ SourceLoc(), asyncLoc, + /*reasync*/ nullptr, throwsLoc, + /*rethrows*/ nullptr); + + // If we've previously parsed a non-'get' accessor, raise diagnostics, + // because we're about to add an effectful 'get' accessor. + if (!hasEffectfulGet) { + accessors.forEachNonGet([&](AccessorDecl *nonGetAccessor) { + diagnose(nonGetAccessor->getLoc(), + diag::invalid_accessor_with_effectful_get, + accessorKindName(nonGetAccessor->getAccessorKind())); + }); + } + + hasEffectfulGet = true; + } else { + // effects are not valid on this accessor. emit error for each one. + do { + diagnose(Tok, diag::invalid_accessor_specifier, + accessorKindName(currentKind), Tok.getRawText()); + consumeToken(); + } while (isEffectsSpecifier(Tok)); + } + } + + // If we're about to set-up a non-'get' accessor when + // we have an effectful 'get', raise a diagnostic. + if (hasEffectfulGet && currentKind != AccessorKind::Get) { + diagnose(currentLoc, diag::invalid_accessor_with_effectful_get, + accessorKindName(currentKind)); + } + + return Status; +} + ParserStatus Parser::parseGetSet(ParseDeclOptions Flags, GenericParamList *GenericParams, ParameterList *Indices, @@ -6064,7 +6145,8 @@ ParserStatus Parser::parseGetSet(ParseDeclOptions Flags, createAccessorFunc(Tok.getLoc(), /*ValueNamePattern*/ nullptr, GenericParams, Indices, StaticLoc, Flags, AccessorKind::Get, storage, this, - /*AccessorKeywordLoc*/ SourceLoc()); + /*AccessorKeywordLoc*/ SourceLoc(), + /*asyncLoc*/SourceLoc(), /*throwsLoc*/SourceLoc()); accessors.add(getter); parseAbstractFunctionBody(getter); accessors.RBLoc = getter->getEndLoc(); @@ -6074,9 +6156,10 @@ ParserStatus Parser::parseGetSet(ParseDeclOptions Flags, Optional backtrack; backtrack.emplace(*this); - bool Invalid = false; + ParserStatus Status; bool accessorHasCodeCompletion = false; bool IsFirstAccessor = true; + bool hasEffectfulGet = false; accessors.LBLoc = consumeToken(tok::l_brace); while (!Tok.isAny(tok::r_brace, tok::eof)) { Optional AccessorCtx; @@ -6110,7 +6193,7 @@ ParserStatus Parser::parseGetSet(ParseDeclOptions Flags, // parsingLimitedSyntax mode cannot have a body. if (parsingLimitedSyntax) { diagnose(Tok, diag::expected_getset_in_protocol); - Invalid = true; + Status |= makeParserError(); break; } @@ -6154,10 +6237,18 @@ ParserStatus Parser::parseGetSet(ParseDeclOptions Flags, } auto *ValueNamePattern = parseOptionalAccessorArgument(Loc, *this, Kind); + // Next, parse effects specifiers. While it's only valid to have them + // on 'get' accessors, we also emit diagnostics if they show up on others. + SourceLoc asyncLoc; + SourceLoc throwsLoc; + Status |= parseGetEffectSpecifier(accessors, asyncLoc, throwsLoc, + hasEffectfulGet, Kind, Loc); + // Set up a function declaration. auto accessor = createAccessorFunc(Loc, ValueNamePattern, GenericParams, Indices, StaticLoc, Flags, - Kind, storage, this, Loc); + Kind, storage, this, Loc, + asyncLoc, throwsLoc); accessor->getAttrs() = Attributes; // Collect this accessor and detect conflicts. @@ -6185,7 +6276,7 @@ ParserStatus Parser::parseGetSet(ParseDeclOptions Flags, if (!Attributes.hasAttribute()) { diagnose(Tok, diag::expected_lbrace_accessor, getAccessorNameForDiagnostic(accessor, /*article*/ false)); - Invalid = true; + Status |= makeParserError(); break; } continue; @@ -6198,14 +6289,14 @@ ParserStatus Parser::parseGetSet(ParseDeclOptions Flags, // Collect all explicit accessors to a list. AccessorListCtx.collectNodesInPlace(SyntaxKind::AccessorList); // Parse the final '}'. - if (Invalid) + if (Status.isError()) skipUntil(tok::r_brace); parseMatchingToken(tok::r_brace, accessors.RBLoc, diag::expected_rbrace_in_getset, accessors.LBLoc); if (accessorHasCodeCompletion) return makeParserCodeCompletionStatus(); - return Invalid ? makeParserError() : makeParserSuccess(); + return Status; } /// Parse the brace-enclosed getter and setter for a variable. diff --git a/lib/Sema/DerivedConformanceEquatableHashable.cpp b/lib/Sema/DerivedConformanceEquatableHashable.cpp index b6b29585224da..f4d7dd9383dfb 100644 --- a/lib/Sema/DerivedConformanceEquatableHashable.cpp +++ b/lib/Sema/DerivedConformanceEquatableHashable.cpp @@ -892,6 +892,7 @@ static ValueDecl *deriveHashable_hashValue(DerivedConformance &derived) { /*FuncLoc=*/SourceLoc(), /*AccessorKeywordLoc=*/SourceLoc(), AccessorKind::Get, hashValueDecl, /*StaticLoc=*/SourceLoc(), StaticSpellingKind::None, + /*Async=*/false, /*AsyncLoc=*/SourceLoc(), /*Throws=*/false, /*ThrowsLoc=*/SourceLoc(), /*GenericParams=*/nullptr, params, intType, parentDC); diff --git a/lib/Sema/DerivedConformances.cpp b/lib/Sema/DerivedConformances.cpp index 53e97d80ac268..3d0391a453375 100644 --- a/lib/Sema/DerivedConformances.cpp +++ b/lib/Sema/DerivedConformances.cpp @@ -422,6 +422,7 @@ DerivedConformance::declareDerivedPropertyGetter(VarDecl *property, /*FuncLoc=*/SourceLoc(), /*AccessorKeywordLoc=*/SourceLoc(), AccessorKind::Get, property, /*StaticLoc=*/SourceLoc(), StaticSpellingKind::None, + /*Async=*/false, /*AsyncLoc=*/SourceLoc(), /*Throws=*/false, /*ThrowsLoc=*/SourceLoc(), /*GenericParams=*/nullptr, params, property->getInterfaceType(), parentDC); diff --git a/lib/Sema/MiscDiagnostics.cpp b/lib/Sema/MiscDiagnostics.cpp index ca5117cb26724..0202b04fc75ae 100644 --- a/lib/Sema/MiscDiagnostics.cpp +++ b/lib/Sema/MiscDiagnostics.cpp @@ -68,6 +68,7 @@ static Expr *isImplicitPromotionToOptional(Expr *E) { /// - Error about collection literals that default to Any collections in /// invalid positions. /// - Marker protocols cannot occur as the type of an as? or is expression. +/// - KeyPath expressions cannot refer to effectful properties / subscripts /// static void diagSyntacticUseRestrictions(const Expr *E, const DeclContext *DC, bool isExprStmt) { @@ -150,6 +151,9 @@ static void diagSyntacticUseRestrictions(const Expr *E, const DeclContext *DC, CallArgs.insert(DSE->getIndex()); if (auto *KPE = dyn_cast(E)) { + // raise an error if this KeyPath contains an effectful member. + checkForEffectfulKeyPath(KPE); + for (auto Comp : KPE->getComponents()) { if (auto *Arg = Comp.getIndexExpr()) CallArgs.insert(Arg); @@ -322,6 +326,25 @@ static void diagSyntacticUseRestrictions(const Expr *E, const DeclContext *DC, return { true, E }; } + /// Visit each component of the keypath and emit a diganostic if they + /// refer to a member that has effects. + void checkForEffectfulKeyPath(KeyPathExpr *keyPath) { + for (const auto &component : keyPath->getComponents()) { + if (component.hasDeclRef()) { + auto decl = component.getDeclRef().getDecl(); + if (auto asd = dyn_cast(decl)) { + if (auto getter = asd->getEffectfulGetAccessor()) { + Ctx.Diags.diagnose(component.getLoc(), + diag::effectful_keypath_component, + asd->getDescriptiveKind()); + Ctx.Diags.diagnose(asd->getLoc(), diag::kind_declared_here, + asd->getDescriptiveKind()); + } + } + } + } + } + void checkCheckedCastExpr(CheckedCastExpr *cast) { if (!isa(cast) && !isa(cast)) return; diff --git a/lib/Sema/TypeCheckConcurrency.cpp b/lib/Sema/TypeCheckConcurrency.cpp index 944a332053b84..b02648d2a76e1 100644 --- a/lib/Sema/TypeCheckConcurrency.cpp +++ b/lib/Sema/TypeCheckConcurrency.cpp @@ -650,6 +650,14 @@ ActorIsolationRestriction ActorIsolationRestriction::forDeclaration( isAccessibleAcrossActors = true; } + // Similarly, a computed property or subscript that has an 'async' getter + // provides an asynchronous context, and has no restrictions. + if (auto storageDecl = dyn_cast(decl)) { + if (auto effectfulGetter = storageDecl->getEffectfulGetAccessor()) + if (effectfulGetter->hasAsync()) + isAccessibleAcrossActors = true; + } + // Determine the actor isolation of the given declaration. switch (auto isolation = getActorIsolation(cast(decl))) { case ActorIsolation::ActorInstance: diff --git a/lib/Sema/TypeCheckConstraints.cpp b/lib/Sema/TypeCheckConstraints.cpp index aac00a4a10772..ab7b90cdc56c4 100644 --- a/lib/Sema/TypeCheckConstraints.cpp +++ b/lib/Sema/TypeCheckConstraints.cpp @@ -603,7 +603,7 @@ bool TypeChecker::typeCheckForEachBinding(DeclContext *dc, ForEachStmt *stmt) { if (conformanceRef.hasEffect(EffectKind::Throws) && stmt->getTryLoc().isInvalid()) { auto &diags = dc->getASTContext().Diags; - diags.diagnose(stmt->getAwaitLoc(), diag::throwing_call_unhandled) + diags.diagnose(stmt->getAwaitLoc(), diag::throwing_call_unhandled, "call") .fixItInsert(stmt->getAwaitLoc(), "try"); return failed(); diff --git a/lib/Sema/TypeCheckDeclObjC.cpp b/lib/Sema/TypeCheckDeclObjC.cpp index 1d10db5628a9e..bfcb221d352ab 100644 --- a/lib/Sema/TypeCheckDeclObjC.cpp +++ b/lib/Sema/TypeCheckDeclObjC.cpp @@ -997,6 +997,14 @@ bool swift::isRepresentableInObjC(const VarDecl *VD, ObjCReason Reason) { if (checkObjCActorIsolation(VD, Reason)) return false; + // effectful computed properties cannot be represented, and its an error + // to mark them as @objc + if (VD->getEffectfulGetAccessor()) { + VD->diagnose(diag::effectful_not_representable_objc, + VD->getDescriptiveKind()); + return false; + } + if (!Result) { SourceRange TypeRange = VD->getTypeSourceRangeForDiagnostics(); // TypeRange can be invalid; e.g. '@objc let foo = SwiftType()' @@ -1028,6 +1036,14 @@ bool swift::isRepresentableInObjC(const SubscriptDecl *SD, ObjCReason Reason) { if (checkObjCActorIsolation(SD, Reason)) return false; + // effectful subscripts cannot be represented, and its an error + // to mark them as @objc + if (SD->getEffectfulGetAccessor()) { + SD->diagnose(diag::effectful_not_representable_objc, + SD->getDescriptiveKind()); + return false; + } + // ObjC doesn't support class subscripts. if (!SD->isInstanceMember()) { SD->diagnose(diag::objc_invalid_on_static_subscript, diff --git a/lib/Sema/TypeCheckDeclOverride.cpp b/lib/Sema/TypeCheckDeclOverride.cpp index d72c346163d54..632abd35cb04c 100644 --- a/lib/Sema/TypeCheckDeclOverride.cpp +++ b/lib/Sema/TypeCheckDeclOverride.cpp @@ -1818,6 +1818,18 @@ static bool checkSingleOverride(ValueDecl *override, ValueDecl *base) { return true; } } + + // Make sure an effectful storage decl is only overridden by a storage + // decl with the same or fewer effect kinds. + if (!overrideASD->isLessEffectfulThan(baseASD, EffectKind::Async)) { + diags.diagnose(overrideASD, diag::override_with_more_effects, + overrideASD->getDescriptiveKind(), "async"); + return true; + } else if (!overrideASD->isLessEffectfulThan(baseASD, EffectKind::Throws)) { + diags.diagnose(overrideASD, diag::override_with_more_effects, + overrideASD->getDescriptiveKind(), "throws"); + return true; + } } // Various properties are only checked for the storage declarations diff --git a/lib/Sema/TypeCheckEffects.cpp b/lib/Sema/TypeCheckEffects.cpp index df860b4ee3e75..78cb566693d98 100644 --- a/lib/Sema/TypeCheckEffects.cpp +++ b/lib/Sema/TypeCheckEffects.cpp @@ -29,6 +29,8 @@ using namespace swift; +using EffectList = SmallVector; + static bool hasFunctionParameterWithEffect(EffectKind kind, Type type) { // Look through Optional types. type = type->lookThroughAllOptionalTypes(); @@ -218,6 +220,17 @@ bool ConformanceHasEffectRequest::evaluate( return false; } +/// \returns the getter decl iff its a prop/subscript with an effectful 'get' +static AccessorDecl* getEffectfulGetOnlyAccessor(ConcreteDeclRef cdr) { + if (!cdr) + return nullptr; + + if (auto storageDecl = dyn_cast(cdr.getDecl())) + return storageDecl->getEffectfulGetAccessor(); + + return nullptr; +} + namespace { /// A function reference. @@ -494,22 +507,30 @@ class PotentialEffectReason { /// a throwing conformance as one of its generic arguments. ByConformance, - /// The initializer of an 'async let' unconditionally throws. - AsyncLetThrows, + /// The initializer of an 'async let' can throw. + AsyncLet, + + /// The function accesses an unconditionally throws/async property. + PropertyAccess, + SubscriptAccess }; static StringRef kindToString(Kind k) { switch (k) { case Kind::Apply: return "Apply"; + case Kind::PropertyAccess: + return "PropertyAccess"; + case Kind::SubscriptAccess: + return "SubscriptAccess"; case Kind::ByClosure: return "ByClosure"; case Kind::ByDefaultClosure: return "ByDefaultClosure"; case Kind::ByConformance: return "ByConformance"; - case Kind::AsyncLetThrows: - return "AsyncLetThrows"; + case Kind::AsyncLet: + return "AsyncLet"; } } @@ -522,6 +543,12 @@ class PotentialEffectReason { static PotentialEffectReason forApply() { return PotentialEffectReason(Kind::Apply); } + static PotentialEffectReason forPropertyAccess() { + return PotentialEffectReason(Kind::PropertyAccess); + } + static PotentialEffectReason forSubscriptAccess() { + return PotentialEffectReason(Kind::SubscriptAccess); + } static PotentialEffectReason forClosure(Expr *E) { PotentialEffectReason result(Kind::ByClosure); result.TheExpression = E; @@ -533,8 +560,8 @@ class PotentialEffectReason { static PotentialEffectReason forConformance() { return PotentialEffectReason(Kind::ByConformance); } - static PotentialEffectReason forThrowingAsyncLet() { - return PotentialEffectReason(Kind::AsyncLetThrows); + static PotentialEffectReason forAsyncLet() { + return PotentialEffectReason(Kind::AsyncLet); } Kind getKind() const { return TheKind; } @@ -613,6 +640,18 @@ class Classification { public: Classification() {} + /// Return a classification for multiple effects. + static Classification forEffect(EffectList kinds, + ConditionalEffectKind conditionalKind, + PotentialEffectReason reason) { + Classification result; + + for (auto k : kinds) + result.merge(forEffect(k, conditionalKind, reason)); + + return result; + } + static Classification forEffect(EffectKind kind, ConditionalEffectKind conditionalKind, PotentialEffectReason reason) { @@ -621,22 +660,19 @@ class Classification { result.ThrowKind = conditionalKind; result.ThrowReason = reason; } else { - assert(reason.getKind() != PotentialEffectReason::Kind::AsyncLetThrows); - result.AsyncKind = conditionalKind; result.AsyncReason = reason; } return result; } - /// Return a classification saying that there's a throw site. + /// Return a classification saying that there's a throw / async operation. static Classification forUnconditional(EffectKind kind, PotentialEffectReason reason) { return forEffect(kind, ConditionalEffectKind::Always, reason); } - /// Return a classification saying that there's a rethrowing - /// throw site. + /// Return a classification saying that there's a rethrowing / reasync site. static Classification forConditional(EffectKind kind, PotentialEffectReason reason) { return forEffect(kind, ConditionalEffectKind::Conditional, reason); @@ -718,8 +754,10 @@ class ApplyClassifier { /// Check to see if the given function application throws or is async. Classification classifyApply(ApplyExpr *E) { - if (isa(E)) + if (isa(E)) { + assert(!E->implicitlyAsync()); return Classification(); + } // An apply expression is a potential throw site if the function throws. // But if the expression didn't type-check, suppress diagnostics. @@ -983,9 +1021,17 @@ class ApplyClassifier { return ShouldRecurse; } ShouldRecurse_t checkLookup(LookupExpr *E) { - return ShouldRecurse; // NOTE: currently, lookups can't throw + if (auto getter = getEffectfulGetOnlyAccessor(E->getMember())) + if (getter->hasThrows()) + ThrowKind = ConditionalEffectKind::Always; + + return ShouldRecurse; } ShouldRecurse_t checkDeclRef(DeclRefExpr *E) { + if (auto getter = getEffectfulGetOnlyAccessor(E->getDeclRef())) + if (getter->hasThrows()) + ThrowKind = ConditionalEffectKind::Always; + return ShouldNotRecurse; } ShouldRecurse_t checkAsyncLet(PatternBindingDecl *patternBinding) { @@ -1087,11 +1133,23 @@ class ApplyClassifier { return ShouldRecurse; } ShouldRecurse_t checkLookup(LookupExpr *E) { - // FIXME should the logic from CheckEffectsCoverage::checkLookup be here? + if (E->isImplicitlyAsync()) { + AsyncKind = ConditionalEffectKind::Always; + } else if (auto getter = getEffectfulGetOnlyAccessor(E->getMember())) { + if (getter->hasAsync()) + AsyncKind = ConditionalEffectKind::Always; + } + return ShouldRecurse; } ShouldRecurse_t checkDeclRef(DeclRefExpr *E) { - // FIXME should the logic from CheckEffectsCoverage::checkDeclRef be here? + if (E->isImplicitlyAsync()) { + AsyncKind = ConditionalEffectKind::Always; + } else if (auto getter = getEffectfulGetOnlyAccessor(E->getDeclRef())) { + if (getter->hasAsync()) + AsyncKind = ConditionalEffectKind::Always; + } + return ShouldNotRecurse; } ShouldRecurse_t checkAsyncLet(PatternBindingDecl *patternBinding) { @@ -1562,26 +1620,13 @@ class Context { IsNonExhaustiveCatch = value; } - static void diagnoseThrowInIllegalContext(DiagnosticEngine &Diags, - ASTNode node, - Kind kind) { - if (auto *e = node.dyn_cast()) { - if (isa(e)) { - Diags.diagnose(e->getLoc(), diag::throwing_call_in_illegal_context, - static_cast(kind)); - return; - } - } - - Diags.diagnose(node.getStartLoc(), diag::throw_in_illegal_context, - static_cast(kind)); - } - static void maybeAddRethrowsNote(DiagnosticEngine &Diags, SourceLoc loc, const PotentialEffectReason &reason) { switch (reason.getKind()) { case PotentialEffectReason::Kind::Apply: - case PotentialEffectReason::Kind::AsyncLetThrows: + case PotentialEffectReason::Kind::PropertyAccess: + case PotentialEffectReason::Kind::SubscriptAccess: + case PotentialEffectReason::Kind::AsyncLet: // Already fully diagnosed. return; case PotentialEffectReason::Kind::ByClosure: @@ -1598,13 +1643,43 @@ class Context { llvm_unreachable("bad reason kind"); } + /// get a user-friendly name for the source of the effect + static StringRef getEffectSourceName(const PotentialEffectReason &reason) { + switch (reason.getKind()) { + case PotentialEffectReason::Kind::Apply: + case PotentialEffectReason::Kind::ByClosure: + case PotentialEffectReason::Kind::ByDefaultClosure: + case PotentialEffectReason::Kind::ByConformance: + case PotentialEffectReason::Kind::AsyncLet: // FIXME: not really the right name? + return "call"; + + case PotentialEffectReason::Kind::PropertyAccess: + return "property access"; + case PotentialEffectReason::Kind::SubscriptAccess: + return "subscript access"; + } + } + void diagnoseUncoveredThrowSite(ASTContext &ctx, ASTNode E, const PotentialEffectReason &reason) { auto &Diags = ctx.Diags; auto message = diag::throwing_call_without_try; - if (reason.getKind() == PotentialEffectReason::Kind::AsyncLetThrows) + auto reasonKind = reason.getKind(); + + bool suggestTryFixIt = reasonKind == PotentialEffectReason::Kind::Apply; + + if (reasonKind == PotentialEffectReason::Kind::AsyncLet) { message = diag::throwing_async_let_without_try; + } else if (reasonKind == PotentialEffectReason::Kind::PropertyAccess) { + message = diag::throwing_prop_access_without_try; + suggestTryFixIt = true; + + } else if (reasonKind == PotentialEffectReason::Kind::SubscriptAccess) { + message = diag::throwing_subscript_access_without_try; + suggestTryFixIt = true; + } + auto loc = E.getStartLoc(); SourceLoc insertLoc; SourceRange highlight; @@ -1638,7 +1713,7 @@ class Context { // // Let's suggest couple of alternative fix-its // because complete context is unavailable. - if (reason.getKind() != PotentialEffectReason::Kind::Apply) + if (!suggestTryFixIt) return; Diags.diagnose(loc, diag::note_forgot_try) @@ -1652,8 +1727,8 @@ class Context { void diagnoseThrowInLegalContext(DiagnosticEngine &Diags, ASTNode node, bool isTryCovered, const PotentialEffectReason &reason, - Diag<> diagForThrowingCall, - Diag<> diagForTrylessThrowingCall) { + Diag diagForThrowingCall, + Diag diagForTrylessThrowingCall) { auto loc = node.getStartLoc(); // Allow the diagnostic to fire on the 'try' if we don't have @@ -1666,10 +1741,12 @@ class Context { return; } + auto effectSource = getEffectSourceName(reason); + if (isTryCovered) { - Diags.diagnose(loc, diagForThrowingCall); + Diags.diagnose(loc, diagForThrowingCall, effectSource); } else { - Diags.diagnose(loc, diagForTrylessThrowingCall); + Diags.diagnose(loc, diagForTrylessThrowingCall, effectSource); } maybeAddRethrowsNote(Diags, loc, reason); } @@ -1713,7 +1790,8 @@ class Context { case Kind::CatchPattern: case Kind::CatchGuard: case Kind::DeferBody: - diagnoseThrowInIllegalContext(Diags, E, getKind()); + Diags.diagnose(E.getStartLoc(), diag::throwing_op_in_illegal_context, + static_cast(getKind()), getEffectSourceName(reason)); return; } llvm_unreachable("bad context kind"); @@ -1780,25 +1858,43 @@ class Context { } llvm_unreachable("bad context kind"); } + /// I did not want to add 'await' as a PotentialEffectReason, since it's + /// not actually an effect. So, we have this odd boolean hanging around. + unsigned effectReasonToIndex(Optional maybeReason, + bool forAwait = false) { + // while not actually an effect, in some instances we diagnose the + // appearance of an await within a non-async context. + if (forAwait) + return 2; - /// NOTE: the values backing these enums match up with a %select - /// in the diagnostics! - enum AsyncSiteKind { - Unspecified = 0, - Call = 1, - Await = 2, - AsyncLet = 3, - Property = 4, - Subscript = 5 - }; + if (!maybeReason.hasValue()) + return 0; // Unspecified + + switch(maybeReason.getValue().getKind()) { + case PotentialEffectReason::Kind::ByClosure: + case PotentialEffectReason::Kind::ByDefaultClosure: + case PotentialEffectReason::Kind::ByConformance: + case PotentialEffectReason::Kind::Apply: + return 1; + + case PotentialEffectReason::Kind::AsyncLet: + return 3; + + case PotentialEffectReason::Kind::PropertyAccess: + return 4; + + case PotentialEffectReason::Kind::SubscriptAccess: + return 5; + } + } void diagnoseUncoveredAsyncSite(ASTContext &ctx, ASTNode node, - AsyncSiteKind kind) { + PotentialEffectReason reason) { SourceRange highlight = node.getSourceRange(); auto diag = diag::async_call_without_await; - switch (kind) { - case AsyncSiteKind::AsyncLet: + switch (reason.getKind()) { + case PotentialEffectReason::Kind::AsyncLet: // Reference to an 'async let' missing an 'await'. if (auto declR = dyn_cast_or_null(node.dyn_cast())) { if (auto var = dyn_cast(declR->getDecl())) { @@ -1811,16 +1907,18 @@ class Context { } LLVM_FALLTHROUGH; // fallthrough to a message about property access - case AsyncSiteKind::Property: + case PotentialEffectReason::Kind::PropertyAccess: diag = diag::async_prop_access_without_await; break; - case AsyncSiteKind::Subscript: + case PotentialEffectReason::Kind::SubscriptAccess: diag = diag::async_subscript_access_without_await; break; - case AsyncSiteKind::Unspecified: - case AsyncSiteKind::Call: { + case PotentialEffectReason::Kind::ByClosure: + case PotentialEffectReason::Kind::ByDefaultClosure: + case PotentialEffectReason::Kind::ByConformance: + case PotentialEffectReason::Kind::Apply: { if (Function) { // To produce a better error message, check if it is an autoclosure. // We do not use 'Context::isAutoClosure' b/c it gives conservative @@ -1844,9 +1942,6 @@ class Context { } break; } - - case AsyncSiteKind::Await: - llvm_unreachable("diagnosing an uncovered await?"); }; ctx.Diags.diagnose(node.getStartLoc(), diag) @@ -1897,13 +1992,16 @@ class Context { /// providing a \c kind helps tailor the emitted message. void diagnoseUnhandledAsyncSite(DiagnosticEngine &Diags, ASTNode node, - AsyncSiteKind kind) { + Optional maybeReason, + bool forAwait = false) { switch (getKind()) { - case Kind::PotentiallyHandled: + case Kind::PotentiallyHandled: { Diags.diagnose(node.getStartLoc(), diag::async_in_nonasync_function, - static_cast(kind), isAutoClosure()); + effectReasonToIndex(maybeReason, forAwait), + isAutoClosure()); maybeAddAsyncNote(Diags); return; + } case Kind::EnumElementInitializer: case Kind::GlobalVarInitializer: @@ -2295,8 +2393,7 @@ class CheckEffectsCoverage : public EffectsHandlingWalker classifier.ReasyncDC = ReasyncDC; auto classification = classifier.classifyApply(E); - checkThrowAsyncSite(E, /*requiresTry*/ true, classification, - Context::Call); + checkThrowAsyncSite(E, /*requiresTry*/ true, classification); if (!classification.isInvalid()) { // HACK: functions can get queued multiple times in @@ -2321,29 +2418,71 @@ class CheckEffectsCoverage : public EffectsHandlingWalker return !type || type->hasError() ? ShouldNotRecurse : ShouldRecurse; } + + static EffectList gatherEffects(AbstractFunctionDecl *afd) { + EffectList effects; + if (afd->hasAsync()) effects.push_back(EffectKind::Async); + if (afd->hasThrows()) effects.push_back(EffectKind::Throws); + return effects; + } + + /// check the kind of property with an effect to give better diagnostics + static PotentialEffectReason getKindOfEffectfulProp(ConcreteDeclRef cdr) { + if (isa(cdr.getDecl())) + return PotentialEffectReason::forSubscriptAccess(); + + assert(isa(cdr.getDecl())); + return PotentialEffectReason::forPropertyAccess(); + } + ShouldRecurse_t checkLookup(LookupExpr *E) { - if (E->isImplicitlyAsync()) { - Context::AsyncSiteKind lookupKind = Context::Property; - // check the kind of thing we're looking up to give better diagnostics - if (auto valueDecl = E->getMember().getDecl()) - if (isa(valueDecl)) - lookupKind = Context::Subscript; + auto member = E->getMember(); + if (auto getter = getEffectfulGetOnlyAccessor(member)) { + auto effects = gatherEffects(getter); + + // We might have a situation where the getter is just 'throws', but + // this specific Lookup is implicitly async due to actor-isolation. + if (E->isImplicitlyAsync()) { + assert(!getter->hasAsync() + && "an explicitly async decl accessed implicitly-async?"); + effects.push_back(EffectKind::Async); + } + checkThrowAsyncSite(E, getter->hasThrows(), + Classification::forEffect(effects, + ConditionalEffectKind::Always, + getKindOfEffectfulProp(member))); + + } else if (E->isImplicitlyAsync()) { checkThrowAsyncSite(E, /*requiresTry=*/false, Classification::forUnconditional(EffectKind::Async, - PotentialEffectReason::forApply()), - lookupKind); + getKindOfEffectfulProp(member))); } return ShouldRecurse; } ShouldRecurse_t checkDeclRef(DeclRefExpr *E) { - if (E->isImplicitlyAsync()) { + if (auto getter = getEffectfulGetOnlyAccessor(E->getDeclRef())) { + auto effects = gatherEffects(getter); + + // We might have a situation where the getter is just 'throws', but + // this specific DeclRef is implicitly async due to actor-isolation. + if (E->isImplicitlyAsync()) { + assert(!getter->hasAsync() + && "an explicitly async decl accessed implicitly-async?"); + effects.push_back(EffectKind::Async); + } + + checkThrowAsyncSite(E, getter->hasThrows(), + Classification::forEffect(effects, + ConditionalEffectKind::Always, + PotentialEffectReason::forPropertyAccess())); + + } else if (E->isImplicitlyAsync()) { checkThrowAsyncSite(E, /*requiresTry=*/false, Classification::forUnconditional(EffectKind::Async, - PotentialEffectReason::forApply()), - Context::Property); + PotentialEffectReason::forPropertyAccess())); } else if (auto decl = E->getDecl()) { if (auto var = dyn_cast(decl)) { @@ -2363,14 +2502,14 @@ class CheckEffectsCoverage : public EffectsHandlingWalker auto result = Classification::forUnconditional( EffectKind::Async, - PotentialEffectReason::forApply()); + PotentialEffectReason::forAsyncLet()); if (throws) { result.merge(Classification::forUnconditional( EffectKind::Throws, - PotentialEffectReason::forThrowingAsyncLet())); + PotentialEffectReason::forAsyncLet())); } - checkThrowAsyncSite(E, /*requiresTry=*/throws, result, - Context::AsyncLet); + checkThrowAsyncSite(E, /*requiresTry=*/throws, result); + } } } @@ -2382,7 +2521,7 @@ class CheckEffectsCoverage : public EffectsHandlingWalker // Diagnose async let in a context that doesn't handle async. if (!CurContext.handlesAsync(ConditionalEffectKind::Always)) { CurContext.diagnoseUnhandledAsyncSite(Ctx.Diags, patternBinding, - Context::AsyncLet); + PotentialEffectReason::forAsyncLet()); } return ShouldRecurse; @@ -2439,11 +2578,8 @@ class CheckEffectsCoverage : public EffectsHandlingWalker return ShouldRecurse; } - /// providing a \c kind helps tailor any possible diagnostic messages - /// related to async-ness void checkThrowAsyncSite(ASTNode E, bool requiresTry, - const Classification &classification, - Context::AsyncSiteKind kind) { + const Classification &classification) { // Suppress all diagnostics when there's an un-analyzable throw site. if (classification.isInvalid()) { Flags.set(ContextFlags::HasAnyThrowSite); @@ -2466,11 +2602,13 @@ class CheckEffectsCoverage : public EffectsHandlingWalker // Diagnose async calls in a context that doesn't handle async. if (!CurContext.handlesAsync(asyncKind)) { - CurContext.diagnoseUnhandledAsyncSite(Ctx.Diags, E, kind); + CurContext.diagnoseUnhandledAsyncSite(Ctx.Diags, E, + classification.getAsyncReason()); } // Diagnose async calls that are outside of an await context. else if (!Flags.has(ContextFlags::IsAsyncCovered)) { - CurContext.diagnoseUncoveredAsyncSite(Ctx, E, kind); + CurContext.diagnoseUncoveredAsyncSite(Ctx, E, + classification.getAsyncReason()); } } @@ -2524,7 +2662,8 @@ class CheckEffectsCoverage : public EffectsHandlingWalker if (CurContext.handlesAsync(ConditionalEffectKind::Conditional)) Ctx.Diags.diagnose(E->getAwaitLoc(), diag::no_async_in_await); else - CurContext.diagnoseUnhandledAsyncSite(Ctx.Diags, E, Context::Await); + CurContext.diagnoseUnhandledAsyncSite(Ctx.Diags, E, None, + /*forAwait=*/ true); } // Inform the parent of the walk that an 'await' exists here. @@ -2614,7 +2753,7 @@ class CheckEffectsCoverage : public EffectsHandlingWalker Flags.set(ContextFlags::HasAnyAsyncSite); if (!CurContext.handlesAsync(asyncKind)) - CurContext.diagnoseUnhandledAsyncSite(Ctx.Diags, S, Context::Unspecified); + CurContext.diagnoseUnhandledAsyncSite(Ctx.Diags, S, None); return ShouldRecurse; } diff --git a/lib/Sema/TypeCheckPropertyWrapper.cpp b/lib/Sema/TypeCheckPropertyWrapper.cpp index 9b72dfc7e0baf..261136ad743b7 100644 --- a/lib/Sema/TypeCheckPropertyWrapper.cpp +++ b/lib/Sema/TypeCheckPropertyWrapper.cpp @@ -98,6 +98,12 @@ static VarDecl *findValueProperty(ASTContext &ctx, NominalTypeDecl *nominal, break; } + // The property may not have any effects right now. + if (auto getter = var->getEffectfulGetAccessor()) { + getter->diagnose(diag::property_wrapper_effectful); + return nullptr; + } + return var; } diff --git a/lib/Sema/TypeCheckProtocol.cpp b/lib/Sema/TypeCheckProtocol.cpp index 7692cac945066..2448ca55bb863 100644 --- a/lib/Sema/TypeCheckProtocol.cpp +++ b/lib/Sema/TypeCheckProtocol.cpp @@ -488,6 +488,26 @@ matchWitnessDifferentiableAttr(DeclContext *dc, ValueDecl *req, return result; } +/// A property or subscript witness must have the same or fewer +/// effects specifiers than the protocol requirement. +/// +/// \returns None iff the witness satisfies the requirement's effects limit. +/// Otherwise, it returns the RequirementMatch that describes the +/// problem. +static Optional checkEffects(AbstractStorageDecl *witness, + AbstractStorageDecl *req) { + + if (!witness->isLessEffectfulThan(req, EffectKind::Async)) + return RequirementMatch(getStandinForAccessor(witness, AccessorKind::Get), + MatchKind::AsyncConflict); + + if (!witness->isLessEffectfulThan(req, EffectKind::Throws)) + return RequirementMatch(getStandinForAccessor(witness, AccessorKind::Get), + MatchKind::ThrowsConflict); + + return None; // OK +} + RequirementMatch swift::matchWitness( DeclContext *dc, ValueDecl *req, ValueDecl *witness, @@ -619,6 +639,10 @@ swift::matchWitness( MatchKind::MutatingConflict); } + // Check that the witness has no more effects than the requirement. + if (auto problem = checkEffects(witnessASD, reqASD)) + return problem.getValue(); + // Decompose the parameters for subscript declarations. decomposeFunctionType = isa(req); } else if (isa(witness)) { diff --git a/lib/Sema/TypeCheckStorage.cpp b/lib/Sema/TypeCheckStorage.cpp index 73f986c17a0f6..eb5418ddc622d 100644 --- a/lib/Sema/TypeCheckStorage.cpp +++ b/lib/Sema/TypeCheckStorage.cpp @@ -1899,6 +1899,7 @@ static AccessorDecl *createGetterPrototype(AbstractStorageDecl *storage, ctx, loc, /*AccessorKeywordLoc*/ loc, AccessorKind::Get, storage, staticLoc, StaticSpellingKind::None, + /*Async=*/false, /*AsyncLoc=*/SourceLoc(), /*Throws=*/false, /*ThrowsLoc=*/SourceLoc(), genericParams, getterParams, @@ -1950,6 +1951,7 @@ static AccessorDecl *createSetterPrototype(AbstractStorageDecl *storage, ctx, loc, /*AccessorKeywordLoc*/ SourceLoc(), AccessorKind::Set, storage, /*StaticLoc=*/SourceLoc(), StaticSpellingKind::None, + /*Async=*/false, /*AsyncLoc=*/SourceLoc(), /*Throws=*/false, /*ThrowsLoc=*/SourceLoc(), genericParams, params, Type(), @@ -2063,6 +2065,7 @@ createCoroutineAccessorPrototype(AbstractStorageDecl *storage, ctx, loc, /*AccessorKeywordLoc=*/SourceLoc(), kind, storage, /*StaticLoc=*/SourceLoc(), StaticSpellingKind::None, + /*Async=*/false, /*AsyncLoc=*/SourceLoc(), /*Throws=*/false, /*ThrowsLoc=*/SourceLoc(), genericParams, params, retTy, dc); accessor->setSynthesized(); diff --git a/lib/Serialization/Deserialization.cpp b/lib/Serialization/Deserialization.cpp index 5ecc06cc800bf..aab733b324c4f 100644 --- a/lib/Serialization/Deserialization.cpp +++ b/lib/Serialization/Deserialization.cpp @@ -3157,7 +3157,8 @@ class DeclDeserializer { decls_block::AccessorLayout::readRecord(scratch, contextID, isImplicit, isStatic, rawStaticSpelling, isObjC, rawMutModifier, - hasForcedStaticDispatch, throws, + hasForcedStaticDispatch, + async, throws, genericSigID, resultInterfaceTypeID, isIUO, @@ -3275,7 +3276,7 @@ class DeclDeserializer { } else { auto *accessor = AccessorDecl::createDeserialized( ctx, accessorKind, storage, staticSpelling.getValue(), - /*Throws=*/throws, genericParams, resultType, DC); + async, throws, genericParams, resultType, DC); accessor->setIsTransparent(isTransparent); fn = accessor; diff --git a/lib/Serialization/ModuleFormat.h b/lib/Serialization/ModuleFormat.h index d67ceae0ffe5b..11b6cc2e30b79 100644 --- a/lib/Serialization/ModuleFormat.h +++ b/lib/Serialization/ModuleFormat.h @@ -56,7 +56,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 = 606; // ossa module hash change +const uint16_t SWIFTMODULE_VERSION_MINOR = 607; // async / throws property decls /// A standard hash seed used for all string hashes in a serialized module. /// @@ -1378,6 +1378,7 @@ namespace decls_block { BCFixed<1>, // isObjC? SelfAccessKindField, // self access kind BCFixed<1>, // has forced static dispatch? + BCFixed<1>, // async? BCFixed<1>, // throws? GenericSignatureIDField, // generic environment TypeIDField, // result interface type diff --git a/lib/Serialization/Serialization.cpp b/lib/Serialization/Serialization.cpp index 5bfb5bf4614e6..12797db0d2d43 100644 --- a/lib/Serialization/Serialization.cpp +++ b/lib/Serialization/Serialization.cpp @@ -3724,6 +3724,7 @@ class Serializer::DeclSerializer : public DeclVisitor { uint8_t(getStableSelfAccessKind( fn->getSelfAccessKind())), fn->hasForcedStaticDispatch(), + fn->hasAsync(), fn->hasThrows(), S.addGenericSignatureRef( fn->getGenericSignature()), diff --git a/test/Concurrency/Runtime/effectful_properties.swift b/test/Concurrency/Runtime/effectful_properties.swift new file mode 100644 index 0000000000000..3196904fa1592 --- /dev/null +++ b/test/Concurrency/Runtime/effectful_properties.swift @@ -0,0 +1,128 @@ +// RUN: %target-run-simple-swift(-parse-as-library -Xfrontend -enable-experimental-concurrency %import-libdispatch) | %FileCheck %s + +// REQUIRES: executable_test +// REQUIRES: concurrency +// REQUIRES: libdispatch + +enum GeneralError : Error { + case UnknownBallKind + case Todo +} + +enum BallKind { + case MostLostV1 + case Chromehard + case PT5 + case KirksandLignature +} + +class Specs { + // obtains the number of dimples + subscript(_ bk : BallKind) -> Int { + get throws { + switch (bk) { + case .MostLostV1: + return 450 + case .Chromehard: + return 125 + default: + throw GeneralError.UnknownBallKind + } + } + } +} + +actor Database { + var currentData : Specs { + get async { + let handle = Task.runDetached { Specs() } + print("obtaining specs...") + return await handle.get() + } + } + + var hasNewData : Bool { + get throws { return true } + } +} + +protocol SphericalObject { + var name : String { get async throws } + var dimples : Int { get async throws } + var description : String { get async throws } +} + +class Ball : SphericalObject { + var name : String { get async throws { throw GeneralError.Todo } } + var dimples : Int { get async throws { throw GeneralError.Todo } } + var description : String { get async throws { throw GeneralError.Todo } } +} + +class GolfBall : Ball { + private static let db : Database = Database() + + private var _model : BallKind + private var _dimples : Int? + + init(_ bk : BallKind) { + _model = bk + } + + override var name : String { + return "golf ball" + } + + override var description : String { + get async throws { + return "this \(name) has \(await dimples) dimples" + } + } + + override var dimples : Int { + get async { + let newData = (try? await GolfBall.db.hasNewData) ?? false + + if newData || _dimples == nil { + let specs = await GolfBall.db.currentData + _dimples = (try? specs[_model]) ?? 0 + } + + return _dimples! + } + } +} + +// CHECK: obtaining specs... +// CHECK: this golf ball has 450 dimples +// CHECK: obtaining specs... +// CHECK: this golf ball has 125 dimples +// CHECK: obtaining specs... +// CHECK: this golf ball has 0 dimples +// CHECK: obtaining specs... +// CHECK: this golf ball has 0 dimples + +func printAsBall(_ b : Ball) async { + print(try! await b.description) +} + +func printAsAsSphericalObject(_ b : SphericalObject) async { + print(try! await b.description) +} + +@main struct RunIt { + static func main() async { + let balls : [(Bool, Ball)] = [ + (true, GolfBall(.MostLostV1)), + (false, GolfBall(.Chromehard)), + (true, GolfBall(.PT5)), + (false, GolfBall(.KirksandLignature)) + ] + for (useProtocol, ball) in balls { + if (useProtocol) { + await printAsAsSphericalObject(ball) + } else { + await printAsBall(ball) + } + } + } +} \ No newline at end of file diff --git a/test/Concurrency/actor_call_implicitly_async.swift b/test/Concurrency/actor_call_implicitly_async.swift index f0b07ba7ebb44..3af2a9e51314e 100644 --- a/test/Concurrency/actor_call_implicitly_async.swift +++ b/test/Concurrency/actor_call_implicitly_async.swift @@ -1,6 +1,22 @@ // RUN: %target-typecheck-verify-swift -enable-experimental-concurrency -warn-concurrency // REQUIRES: concurrency + +// some utilities +func thrower() throws {} +func asyncer() async {} + +func rethrower(_ f : @autoclosure () throws -> Any) rethrows -> Any { + return try f() +} + +func asAutoclosure(_ f : @autoclosure () -> Any) -> Any { return f() } + +// not a concurrency-safe type +class Box { + var counter : Int = 0 +} + actor BankAccount { private var curBalance : Int @@ -61,6 +77,73 @@ actor BankAccount { _ = deposit(withdraw(deposit(withdraw(balance())))) } + func testThrowing() throws {} + + var effPropA : Box { + get async { + await asyncer() + return Box() + } + } + + var effPropT : Box { + get throws { + try thrower() + return Box() + } + } + + var effPropAT : Int { + get async throws { + await asyncer() + try thrower() + return 0 + } + } + + // expected-note@+1 2 {{add 'async' to function 'effPropertiesFromInsideActorInstance()' to make it asynchronous}} + func effPropertiesFromInsideActorInstance() throws { + // expected-error@+1{{'async' property access in a function that does not support concurrency}} + _ = effPropA + + // expected-note@+4{{did you mean to handle error as optional value?}} + // expected-note@+3{{did you mean to use 'try'?}} + // expected-note@+2{{did you mean to disable error propagation?}} + // expected-error@+1{{property access can throw but is not marked with 'try'}} + _ = effPropT + + _ = try effPropT + + // expected-note@+6 {{did you mean to handle error as optional value?}} + // expected-note@+5 {{did you mean to use 'try'?}} + // expected-note@+4 {{did you mean to disable error propagation?}} + // expected-error@+3 {{property access can throw but is not marked with 'try'}} + // expected-note@+2 {{call is to 'rethrows' function, but argument function can throw}} + // expected-error@+1 {{call can throw but is not marked with 'try'}} + _ = rethrower(effPropT) + + // expected-note@+2 {{call is to 'rethrows' function, but argument function can throw}} + // expected-error@+1 {{call can throw but is not marked with 'try'}} + _ = rethrower(try effPropT) + + _ = try rethrower(effPropT) + _ = try rethrower(thrower()) + + _ = try rethrower(try effPropT) + _ = try rethrower(try thrower()) + + _ = rethrower(effPropA) // expected-error{{'async' property access in an autoclosure that does not support concurrency}} + + _ = asAutoclosure(effPropT) // expected-error{{property access can throw, but it is not marked with 'try' and it is executed in a non-throwing autoclosure}} + + // expected-note@+5{{did you mean to handle error as optional value?}} + // expected-note@+4{{did you mean to use 'try'?}} + // expected-note@+3{{did you mean to disable error propagation?}} + // expected-error@+2{{property access can throw but is not marked with 'try'}} + // expected-error@+1{{'async' property access in a function that does not support concurrency}} + _ = effPropAT + } + } // end actor func someAsyncFunc() async { @@ -88,6 +171,29 @@ func someAsyncFunc() async { a.testSelfBalance() // expected-error {{call is 'async' but is not marked with 'await'}} + await a.testThrowing() // expected-error {{call can throw, but it is not marked with 'try' and the error is not handled}} + + //////////// + // effectful properties from outside the actor instance + + // expected-warning@+2 {{cannot use property 'effPropA' with a non-sendable type 'Box' across actors}} + // expected-error@+1{{property access is 'async' but is not marked with 'await'}} {{7-7=await }} + _ = a.effPropA + + // expected-warning@+3 {{cannot use property 'effPropT' with a non-sendable type 'Box' across actors}} + // expected-error@+2{{property access can throw, but it is not marked with 'try' and the error is not handled}} + // expected-error@+1{{property access is 'async' but is not marked with 'await'}} {{7-7=await }} + _ = a.effPropT + + // expected-error@+2{{property access can throw, but it is not marked with 'try' and the error is not handled}} + // expected-error@+1{{property access is 'async' but is not marked with 'await'}} {{7-7=await }} + _ = a.effPropAT + + // (mostly) corrected ones + _ = await a.effPropA // expected-warning {{cannot use property 'effPropA' with a non-sendable type 'Box' across actors}} + _ = try! await a.effPropT // expected-warning {{cannot use property 'effPropT' with a non-sendable type 'Box' across actors}} + _ = try? await a.effPropAT + print("ok!") } @@ -255,3 +361,199 @@ actor Calculator { // expected-warning@-1{{cannot call function returning non-sendable type}} let _ = plusOne(2) } + +/////// +// Effectful properties isolated to a global actor + +@BananaActor +var effPropA : Int { + get async { + await asyncer() + try thrower() // expected-error{{errors thrown from here are not handled}} + return 0 + } +} + +@BananaActor +var effPropT : Int { // expected-note{{var declared here}} + get throws { + await asyncer() // expected-error{{'async' call in a function that does not support concurrency}} + try thrower() + return 0 + } +} + +@BananaActor +var effPropAT : Int { + get async throws { + await asyncer() + try thrower() + return 0 + } +} + +// expected-note@+1 2 {{add 'async' to function 'tryEffPropsFromBanana()' to make it asynchronous}} +@BananaActor func tryEffPropsFromBanana() throws { + // expected-error@+1{{'async' property access in a function that does not support concurrency}} + _ = effPropA + + // expected-note@+4{{did you mean to handle error as optional value?}} + // expected-note@+3{{did you mean to use 'try'?}} + // expected-note@+2{{did you mean to disable error propagation?}} + // expected-error@+1{{property access can throw but is not marked with 'try'}} + _ = effPropT + + _ = try effPropT + + // expected-note@+6 {{did you mean to handle error as optional value?}} + // expected-note@+5 {{did you mean to use 'try'?}} + // expected-note@+4 {{did you mean to disable error propagation?}} + // expected-error@+3 {{property access can throw but is not marked with 'try'}} + // expected-note@+2 {{call is to 'rethrows' function, but argument function can throw}} + // expected-error@+1 {{call can throw but is not marked with 'try'}} + _ = rethrower(effPropT) + + // expected-note@+2 {{call is to 'rethrows' function, but argument function can throw}} + // expected-error@+1 {{call can throw but is not marked with 'try'}} + _ = rethrower(try effPropT) + + _ = try rethrower(effPropT) + _ = try rethrower(thrower()) + + _ = try rethrower(try effPropT) + _ = try rethrower(try thrower()) + + _ = rethrower(effPropA) // expected-error{{'async' property access in an autoclosure that does not support concurrency}} + + _ = asAutoclosure(effPropT) // expected-error{{property access can throw, but it is not marked with 'try' and it is executed in a non-throwing autoclosure}} + + // expected-note@+5{{did you mean to handle error as optional value?}} + // expected-note@+4{{did you mean to use 'try'?}} + // expected-note@+3{{did you mean to disable error propagation?}} + // expected-error@+2{{property access can throw but is not marked with 'try'}} + // expected-error@+1{{'async' property access in a function that does not support concurrency}} + _ = effPropAT +} + + +// expected-note@+2 {{add '@BananaActor' to make global function 'tryEffPropsFromSync()' part of global actor 'BananaActor'}} +// expected-note@+1 2 {{add 'async' to function 'tryEffPropsFromSync()' to make it asynchronous}} +func tryEffPropsFromSync() { + _ = effPropA // expected-error{{'async' property access in a function that does not support concurrency}} + + // expected-error@+1 {{property access can throw, but it is not marked with 'try' and the error is not handled}} + _ = effPropT // expected-error{{var 'effPropT' isolated to global actor 'BananaActor' can not be referenced from this synchronous context}} + // NOTE: that we don't complain about async access on `effPropT` because it's not declared async, and we're not in an async context! + + // expected-error@+1 {{property access can throw, but it is not marked with 'try' and the error is not handled}} + _ = effPropAT // expected-error{{'async' property access in a function that does not support concurrency}} +} + +@OrangeActor func tryEffPropertiesFromGlobalActor() async throws { + // expected-error@+1{{property access is 'async' but is not marked with 'await'}} {{7-7=await }} + _ = effPropA + + // expected-note@+5{{did you mean to handle error as optional value?}} + // expected-note@+4{{did you mean to use 'try'?}} + // expected-note@+3{{did you mean to disable error propagation?}} + // expected-error@+2{{property access can throw but is not marked with 'try'}} + // expected-error@+1{{property access is 'async' but is not marked with 'await'}} {{7-7=await }} + _ = effPropT + + // expected-note@+5{{did you mean to handle error as optional value?}} + // expected-note@+4{{did you mean to use 'try'?}} + // expected-note@+3{{did you mean to disable error propagation?}} + // expected-error@+2{{property access can throw but is not marked with 'try'}} + // expected-error@+1{{property access is 'async' but is not marked with 'await'}} {{7-7=await }} + _ = effPropAT + + _ = await effPropA + _ = try? await effPropT + _ = try! await effPropAT +} + +///////////// +// check subscripts in actors + +actor SubscriptA { + subscript(_ i : Int) -> Int { + get async { + try thrower() // expected-error{{errors thrown from here are not handled}} + await asyncer() + return 0 + } + } + + func f() async { + _ = self[0] // expected-error{{subscript access is 'async' but is not marked with 'await'}} + } +} + +actor SubscriptT { + subscript(_ i : Int) -> Int { + get throws { + try thrower() + await asyncer() // expected-error{{'async' call in a function that does not support concurrency}} + return 0 + } + } + + func f() throws { + _ = try self[0] + + // expected-note@+6 {{did you mean to handle error as optional value?}} + // expected-note@+5 {{did you mean to use 'try'?}} + // expected-note@+4 {{did you mean to disable error propagation?}} + // expected-error@+3 {{subscript access can throw but is not marked with 'try'}} + // expected-note@+2 {{call is to 'rethrows' function, but argument function can throw}} + // expected-error@+1 {{call can throw but is not marked with 'try'}} + _ = rethrower(self[1]) + + // expected-note@+2 {{call is to 'rethrows' function, but argument function can throw}} + // expected-error@+1 {{call can throw but is not marked with 'try'}} + _ = rethrower(try self[1]) + + _ = try rethrower(self[1]) + _ = try rethrower(try self[1]) + } +} + +actor SubscriptAT { + subscript(_ i : Int) -> Int { + get async throws { + try thrower() + await asyncer() + return 0 + } + } + + func f() async throws { + _ = try await self[0] + } +} + +func tryTheActorSubscripts(a : SubscriptA, t : SubscriptT, at : SubscriptAT) async throws { + _ = a[0] // expected-error{{subscript access is 'async' but is not marked with 'await'}} + + _ = await a[0] + + // expected-note@+5{{did you mean to handle error as optional value?}} + // expected-note@+4{{did you mean to use 'try'?}} + // expected-note@+3{{did you mean to disable error propagation?}} + // expected-error@+2{{subscript access can throw but is not marked with 'try'}} + // expected-error@+1 {{subscript access is 'async' but is not marked with 'await'}} + _ = t[0] + + _ = try await t[0] + _ = try! await t[0] + _ = try? await t[0] + + // expected-note@+5{{did you mean to handle error as optional value?}} + // expected-note@+4{{did you mean to use 'try'?}} + // expected-note@+3{{did you mean to disable error propagation?}} + // expected-error@+2{{subscript access can throw but is not marked with 'try'}} + // expected-error@+1 {{subscript access is 'async' but is not marked with 'await'}} + _ = at[0] + + _ = try await at[0] +} \ No newline at end of file diff --git a/test/Misc/effectful_properties_keypath.swift b/test/Misc/effectful_properties_keypath.swift new file mode 100644 index 0000000000000..15218ef866432 --- /dev/null +++ b/test/Misc/effectful_properties_keypath.swift @@ -0,0 +1,43 @@ +// RUN: %target-typecheck-verify-swift -enable-experimental-concurrency + +// REQUIRES: concurrency +// REQUIRES: objc_interop + +enum E : Error { + case DoesNotExist +} + +class Vertex { + var marked : Bool = false + + // FIXME: we should suppress this note about adding @objc, since that's not allowed! + + // expected-note@+2 {{add '@objc' to expose this property to Objective-C}} + // expected-note@+1 5 {{property declared here}} + var parent : Vertex { + get throws { throw E.DoesNotExist } + } + + // expected-note@+1 2 {{subscript declared here}} + subscript(_ i : Int) -> Vertex? { + get async throws { + if i == 0 { + return try? parent + } + return Vertex() + } + } +} + +func asFunction(_ f : (Vertex) -> Bool) {} + +let _ = \Vertex.parent.parent.parent // expected-error 3 {{cannot form key path to property with 'throws' or 'async'}} +let _ = \Vertex.[3]?.marked // expected-error {{cannot form key path to subscript with 'throws' or 'async'}} + +asFunction(\Vertex.[0]!.marked) // expected-error {{cannot form key path to subscript with 'throws' or 'async'}} +asFunction(\Vertex.parent.marked) // expected-error {{cannot form key path to property with 'throws' or 'async'}} + +// expected-error@+2 {{argument of '#keyPath' refers to non-'@objc' property 'parent'}} +// expected-error@+1 {{cannot form key path to property with 'throws' or 'async'}} +let _ = #keyPath(Vertex.parent) + diff --git a/test/Parse/async.swift b/test/Parse/async.swift index 609b460607100..977644aa55e8a 100644 --- a/test/Parse/async.swift +++ b/test/Parse/async.swift @@ -42,7 +42,7 @@ class X { return 0 } - set async { // expected-error{{expected '{' to start setter definition}} + set async { // expected-error{{'set' accessor cannot have specifier 'async'}} } } } diff --git a/test/Parse/effectful_properties.swift b/test/Parse/effectful_properties.swift new file mode 100644 index 0000000000000..06989ea60e2c5 --- /dev/null +++ b/test/Parse/effectful_properties.swift @@ -0,0 +1,177 @@ +// RUN: %target-typecheck-verify-swift -enable-experimental-concurrency +// REQUIRES: concurrency + +struct MyProps { + var prop1 : Int { + get async { } + } + + var prop2 : Int { + get throws { } + } + + var prop3 : Int { + get async throws { } + } + + var prop1mut : Int { + mutating get async { } + } + + var prop2mut : Int { + mutating get throws { } + } + + var prop3mut : Int { + mutating get async throws { } + } +} + +struct X1 { + subscript(_ i : Int) -> Int { + get async {} + } +} + +class X2 { + subscript(_ i : Int) -> Int { + get throws {} + } +} + +struct X3 { + subscript(_ i : Int) -> Int { + get async throws {} + } +} + +struct BadSubscript1 { + subscript(_ i : Int) -> Int { + get async throws {} + set {} // expected-error {{'set' accessor is not allowed on property with 'get' accessor that is 'async' or 'throws'}} + } +} + +struct BadSubscript2 { + subscript(_ i : Int) -> Int { + get throws {} + + // expected-error@+2 {{'set' accessor is not allowed on property with 'get' accessor that is 'async' or 'throws'}} + // expected-error@+1 {{'set' accessor cannot have specifier 'throws'}} + set throws {} + } +} + +struct S { + var prop2 : Int { + mutating get async throws { 0 } + nonmutating set {} // expected-error {{'set' accessor is not allowed on property with 'get' accessor that is 'async' or 'throws'}} + } +} + +var prop3 : Bool { + // expected-error@+2 {{'_read' accessor is not allowed on property with 'get' accessor that is 'async' or 'throws'}} + // expected-error@+1 {{variable cannot provide both a 'read' accessor and a getter}} + _read { yield prop3 } + + // expected-note@+2 {{getter defined here}} + // expected-note@+1 2 {{previous definition of getter here}} + get throws { false } + get async { true } // expected-error{{variable already has a getter}} + + get {} // expected-error{{variable already has a getter}} +} + +enum E { + private(set) var prop4 : Double { + set {} // expected-error {{'set' accessor is not allowed on property with 'get' accessor that is 'async' or 'throws'}} + get async throws { 1.1 } + _modify { yield &prop4 } // expected-error {{'_modify' accessor is not allowed on property with 'get' accessor that is 'async' or 'throws'}} + } +} + +protocol P { + associatedtype T + var prop1 : T { get async throws } + var prop2 : T { get async throws set } // expected-error {{'set' accessor is not allowed on property with 'get' accessor that is 'async' or 'throws'}} + var prop3 : T { get throws set } // expected-error {{'set' accessor is not allowed on property with 'get' accessor that is 'async' or 'throws'}} + var prop4 : T { get async } + var prop5 : T { mutating get async throws } + var prop6 : T { mutating get throws } + var prop7 : T { mutating get async nonmutating set } // expected-error {{'set' accessor is not allowed on property with 'get' accessor that is 'async' or 'throws'}} +} + +/////////////////// +// invalid syntax + +var bad1 : Int { + get rethrows { 0 } // expected-error{{only function declarations may be marked 'rethrows'; did you mean 'throws'?}} + + // expected-error@+1 {{'set' accessor is not allowed on property with 'get' accessor that is 'async' or 'throws'}} + set rethrows { } // expected-error{{'set' accessor cannot have specifier 'rethrows'}} +} + +var bad2 : Int { + get reasync { 0 } // expected-error{{only function declarations may be marked 'reasync'; did you mean 'async'?}} + + // expected-error@+1 {{'set' accessor is not allowed on property with 'get' accessor that is 'async' or 'throws'}} + set reasync { } // expected-error{{'set' accessor cannot have specifier 'reasync'}} +} + +var bad3 : Int { + _read async { yield 0 } // expected-error{{'_read' accessor cannot have specifier 'async'}} + set(theValue) async { } // expected-error{{'set' accessor cannot have specifier 'async'}} +} + + +var bad4 : Int = 0 { + // expected-error@+4 {{'willSet' accessor cannot have specifier 'throws'}} + // expected-error@+3 {{'willSet' accessor cannot have specifier 'async'}} + // expected-error@+2 {{'willSet' accessor cannot have specifier 'rethrows'}} + // expected-error@+1 {{'willSet' accessor cannot have specifier 'reasync'}} + willSet(theValue) reasync rethrows async throws {} + + // expected-error@+2 {{expected '{' to start 'didSet' definition}} + // expected-error@+1 {{'didSet' accessor cannot have specifier 'throws'}} + didSet throws bogus {} +} + +var bad5 : Int { + get bogus rethrows {} // expected-error{{expected '{' to start getter definition}} +} + +var bad6 : Int { + // expected-error@+2{{expected '{' to start getter definition}} + // expected-error@+1 {{only function declarations may be marked 'rethrows'; did you mean 'throws'?}} + get rethrows -> Int { 0 } +} + +var bad7 : Double { + get throws async { 3.14 } // expected-error {{'async' must precede 'throws'}} +} + +var bad8 : Double { + get {} + // expected-error@+2 {{'_modify' accessor cannot have specifier 'async'}} + // expected-error@+1 {{'_modify' accessor cannot have specifier 'throws'}} + _modify throws async { yield &bad8 } +} + +protocol BadP { + // expected-error@+3 {{'set' accessor is not allowed on property with 'get' accessor that is 'async' or 'throws'}} + // expected-error@+2 {{only function declarations may be marked 'rethrows'; did you mean 'throws'?}} + // expected-error@+1 {{only function declarations may be marked 'reasync'; did you mean 'async'?}} + var prop1 : Int { get reasync rethrows set } + + var prop2 : Int { get bogus rethrows set } // expected-error{{expected get or set in a protocol property}} + + // expected-error@+2 {{only function declarations may be marked 'rethrows'; did you mean 'throws'?}} + // expected-error@+1 {{expected get or set in a protocol property}} + var prop3 : Int { get rethrows bogus set } + + // expected-error@+2 {{only function declarations may be marked 'reasync'; did you mean 'async'?}} + // expected-error@+1 {{expected get or set in a protocol property}} + var prop4 : Int { get reasync bogus set } + + var prop5 : Int { get throws async } // expected-error {{'async' must precede 'throws'}} +} \ No newline at end of file diff --git a/test/SILGen/effectful_properties.swift b/test/SILGen/effectful_properties.swift new file mode 100644 index 0000000000000..f3f5e7767c6f9 --- /dev/null +++ b/test/SILGen/effectful_properties.swift @@ -0,0 +1,68 @@ +// RUN: %target-swift-frontend -emit-silgen %s -module-name accessors -swift-version 5 -enable-experimental-concurrency | %FileCheck --enable-var-scope %s +// REQUIRES: concurrency + +class C { + // CHECK-DAG: sil hidden [ossa] @$s9accessors1CC16prop_asyncThrowsSivg : $@convention(method) @async (@guaranteed C) -> (Int, @error Error) { + var prop_asyncThrows : Int { + get async throws { 0 } + } + // CHECK-DAG: sil hidden [ossa] @$s9accessors1CC10prop_asyncSivg : $@convention(method) @async (@guaranteed C) -> Int { + var prop_async : Int { + get async { 1 } + } + // CHECK-DAG: sil hidden [ossa] @$s9accessors1CC11prop_throwsSivg : $@convention(method) (@guaranteed C) -> (Int, @error Error) { + var prop_throws : Int { + get throws { 2 } + } +} + +struct S { + // CHECK-DAG: sil hidden [ossa] @$s9accessors1SVyS2icig : $@convention(method) @async (Int, S) -> Int { + subscript(_ s : Int) -> Int { + get async { 0 } + } + // CHECK-DAG: sil hidden [ossa] @$s9accessors1SVySiSdcig : $@convention(method) (Double, S) -> (Int, @error Error) { + subscript(_ s : Double) -> Int { + get throws { 0 } + } +} + +enum E { + // CHECK-DAG: sil hidden [ossa] @$s9accessors1EOyS2icig : $@convention(method) @async (Int, E) -> (Int, @error Error) { + subscript(_ e : Int) -> Int { + get async throws { 0 } + } +} + +actor A { + // CHECK-DAG: sil hidden [transparent] [ossa] @$s9accessors1AC10normalPropSivg : $@convention(method) (@guaranteed A) -> Int { + var normalProp : Int = 0 + // CHECK-DAG: sil hidden [ossa] @$s9accessors1AC12computedPropSivg : $@convention(method) (@guaranteed A) -> Int { + var computedProp : Int { get { 0 } } + + // CHECK-LABEL: sil hidden [ossa] @$s9accessors1AC9asyncPropSivg : $@convention(method) @async (@guaranteed A) -> Int { + // CHECK: bb0([[SELF:%[0-9]+]] : @guaranteed $A): + // CHECK: hop_to_executor [[SELF]] : $A + // CHECK: } // end sil function '$s9accessors1AC9asyncPropSivg' + var asyncProp : Int { + get async { 0 } + } + +} + +// CHECK-LABEL: sil hidden [ossa] @$s9accessors19testImplicitlyAsync1aSiAA1AC_tYF : $@convention(thin) @async (@guaranteed A) -> Int { +// CHECK: hop_to_executor +// CHECK: apply {{%[0-9]+}}({{%[0-9]+}}) : $@convention(method) (@guaranteed A) -> Int +// CHECK: hop_to_executor +// CHECK: } // end sil function '$s9accessors19testImplicitlyAsync1aSiAA1AC_tYF' +func testImplicitlyAsync(a : A) async -> Int { + return await a.computedProp +} + + +// CHECK-LABEL: sil hidden [ossa] @$s9accessors15testNormalAsync1aSiAA1AC_tYF : $@convention(thin) @async (@guaranteed A) -> Int { +// CHECK: apply {{%[0-9]+}}({{%[0-9]+}}) : $@convention(method) @async (@guaranteed A) -> Int +// CHECK: } // end sil function '$s9accessors15testNormalAsync1aSiAA1AC_tYF' +func testNormalAsync(a : A) async -> Int { + return await a.asyncProp +} \ No newline at end of file diff --git a/test/Serialization/effectful_properties.swift b/test/Serialization/effectful_properties.swift new file mode 100644 index 0000000000000..8745142730b6a --- /dev/null +++ b/test/Serialization/effectful_properties.swift @@ -0,0 +1,88 @@ +// RUN: %empty-directory(%t) +// RUN: %target-swift-frontend -enable-experimental-concurrency -emit-module-path %t/a.swiftmodule -module-name a %s +// RUN: %target-swift-ide-test -print-module -module-to-print a -source-filename x -I %t | %FileCheck -check-prefix MODULE-CHECK %s +// RUN: %target-swift-frontend -enable-experimental-concurrency -emit-module-path %t/b.swiftmodule -module-name a %t/a.swiftmodule +// RUN: cmp -s %t/a.swiftmodule %t/b.swiftmodule + +// REQUIRES: concurrency + +/////////// +// This test checks for correct serialization & deserialization of +// effectful properties and subscripts + +// look for correct members in module's deserialization pretty-print: + +// MODULE-CHECK: actor A { +// MODULE-CHECK-NEXT: var normalProp: Int +// MODULE-CHECK-NEXT: var computedProp: Int { get } +// MODULE-CHECK-NEXT: var asyncProp: Int { get async } + +// MODULE-CHECK: class C { +// MODULE-CHECK-NEXT: var prop_asyncThrows: Int { get async throws } +// MODULE-CHECK-NEXT: var prop_async: Int { get async } +// MODULE-CHECK-NEXT: var prop_throws: Int { get throws } + +// MODULE-CHECK: enum E { +// MODULE-CHECK-NEXT: subscript(e: Int) -> Int { get async throws } + +// MODULE-CHECK: protocol P { +// MODULE-CHECK-NEXT: var propA: Int { get async } +// MODULE-CHECK-NEXT: var propT: Int { get throws } +// MODULE-CHECK-NEXT: var propAT: Int { get async throws } +// MODULE-CHECK-NEXT: subscript(p: Int) -> Int { get async } +// MODULE-CHECK-NEXT: subscript(p: Double) -> Int { get throws } +// MODULE-CHECK-NEXT: subscript(p: String) -> Int { get async throws } + +// MODULE-CHECK: struct S { +// MODULE-CHECK-NEXT: subscript(s: Int) -> Int { get async } +// MODULE-CHECK-NEXT: subscript(s: Double) -> Int { get throws } + +class C { + var prop_asyncThrows : Int { + get async throws { 0 } + } + + var prop_async : Int { + get async { 1 } + } + + var prop_throws : Int { + get throws { 2 } + } +} + +struct S { + subscript(_ s : Int) -> Int { + get async { 0 } + } + + subscript(_ s : Double) -> Int { + get throws { 0 } + } +} + +enum E { + subscript(_ e : Int) -> Int { + get async throws { 0 } + } +} + +actor A { + var normalProp : Int = 0 + var computedProp : Int { get { 0 } } + var asyncProp : Int { + get async { 0 } + } +} + +protocol P { + var propA : Int { get async } + var propT : Int { get throws } + var propAT : Int { get async throws } + + subscript(_ p : Int) -> Int { get async } + + subscript(_ p : Double) -> Int { get throws } + + subscript(_ p : String) -> Int { get async throws } +} diff --git a/test/decl/class/effectful_properties.swift b/test/decl/class/effectful_properties.swift new file mode 100644 index 0000000000000..72b75768d34a2 --- /dev/null +++ b/test/decl/class/effectful_properties.swift @@ -0,0 +1,96 @@ +// RUN: %target-typecheck-verify-swift -enable-experimental-concurrency + +// REQUIRES: concurrency + +enum E : Error { + case NotAvailable +} + +class GolfCourse { + var yards : Int { + get throws { throw E.NotAvailable } + } + var holes : Int { + get { 18 } + } + + var par : Int { + get async throws { 71 } + } + + subscript(_ i : Int) -> Int { + get throws { throw E.NotAvailable } + } +} + +class Presidio : GolfCourse { + private var yardsFromBackTees = 6481 + override var yards : Int { // removes effect & makes it mutable + get { + do { + return try super.yards + } catch { + return yardsFromBackTees + } + } + set { yardsFromBackTees = newValue } + } + + override var holes : Int { // expected-error {{cannot override non-'async' property with 'async' property}} + get async { 18 } + } + + override var par : Int { + get async { 72 } // removes the 'throws' effect + } + + override subscript(_ i : Int) -> Int { // removes effects + get { (try? super[i]) ?? 3 } + } +} + +class PresidioBackNine : Presidio { + override var par : Int { // expected-error{{cannot override non-'throws' property with 'throws' property}} + get throws { 36 } // attempts to put the 'throws' effect back + } + + override subscript(_ i : Int) -> Int { // expected-error{{cannot override non-'async' subscript with 'async' subscript}} + get async throws { 0 } + } +} + +func timeToPlay(gc : Presidio) async { + _ = gc.yards + _ = (gc as GolfCourse).yards // expected-error{{property access can throw, but it is not marked with 'try' and the error is not handled}} + _ = try? (gc as GolfCourse).yards + + // expected-error@+2 {{property access can throw, but it is not marked with 'try' and the error is not handled}} + // expected-error@+1 {{property access is 'async' but is not marked with 'await'}} + _ = (gc as GolfCourse).par + _ = try? await (gc as GolfCourse).par + + _ = await gc.par + + _ = gc[2] + _ = (gc as GolfCourse)[2] // expected-error{{subscript access can throw, but it is not marked with 'try' and the error is not handled}} + _ = try? (gc as GolfCourse)[2] +} + +class AcceptableDynamic { + dynamic var par : Int { + get async throws { 60 } + } + + dynamic subscript(_ i : Int) -> Int { + get throws { throw E.NotAvailable } + } +} + +// mainly just some sanity checks +class Misc { + // expected-error@+2 {{'lazy' cannot be used on a computed property}} + // expected-error@+1 {{lazy properties must have an initializer}} + lazy var someProp : Int { + get throws { 0 } + } +} diff --git a/test/decl/class/effectful_properties_objc.swift b/test/decl/class/effectful_properties_objc.swift new file mode 100644 index 0000000000000..6003a0a70a228 --- /dev/null +++ b/test/decl/class/effectful_properties_objc.swift @@ -0,0 +1,18 @@ +// RUN: %target-typecheck-verify-swift -enable-experimental-concurrency + +// REQUIRES: concurrency +// REQUIRES: objc_interop + +enum E : Error { + case NotAvailable +} + +class ProblematicObjC { + @objc var par : Int { // expected-error{{property with 'throws' or 'async' is not representable in Objective-C}} + get async throws { 60 } + } + + @objc subscript(_ i : Int) -> Int { // expected-error{{subscript with 'throws' or 'async' is not representable in Objective-C}} + get throws { throw E.NotAvailable } + } +} \ No newline at end of file diff --git a/test/decl/protocol/effectful_properties.swift b/test/decl/protocol/effectful_properties.swift new file mode 100644 index 0000000000000..ae6ce7fb363da --- /dev/null +++ b/test/decl/protocol/effectful_properties.swift @@ -0,0 +1,332 @@ +// RUN: %target-typecheck-verify-swift -enable-experimental-concurrency + +// REQUIRES: concurrency + +///// +// This test is focused on checking protocol conformance and constraint checking + +protocol None { + associatedtype V + // expected-note@+2 {{protocol requires property 'someProp' with type 'CA_N.V' (aka 'Int'); do you want to add a stub?}} + // expected-note@+1 2 {{protocol requires property 'someProp' with type 'Self.V'; do you want to add a stub?}} + var someProp : V { get } +} + +protocol T { + associatedtype V + // expected-note@+2 {{protocol requires property 'someProp' with type 'CAT_T.V' (aka 'Int'); do you want to add a stub?}} + // expected-note@+1 {{protocol requires property 'someProp' with type 'Self.V'; do you want to add a stub?}} + var someProp : V { get throws } +} + +protocol A { + associatedtype V + // expected-note@+1 2 {{protocol requires property 'someProp' with type 'Self.V'; do you want to add a stub?}} + var someProp : V { get async } +} + +protocol AT { + associatedtype V + var someProp : V { get async throws } +} + +///// +// exercise the space of conformances to a property with effects +// The naming scheme here is: +// CN_K +// where +// C = "conformer" +// N = candidate witness's effect abbreviation +// K = protocol requirement's effect abbreviation +// "effect abbreviation" = [ +// N -> , T -> throws, A -> async, AT -> async throws +// ] + +// First group here also demonstrates that stored or mutable properties can +// witness a protocol requirement that allows for effects. +class CN_N : None { typealias V = Int; var someProp : Int { get {0} } } +class CN_T : T { typealias V = Int; var someProp : Int = 0 } +class CN_T_v2 : T { typealias V = Int; var someProp : Int { get {0} } } +class CN_A : A { typealias V = Int; var someProp : Int { get {0} set {} } } +class CN_AT : AT { typealias V = Int; var someProp : Int { _read {yield 0} } } +// ------ ------ ------ ------ ------ ------ ------ ------ ------ ------ + +// Remaining conformances test the limit check for kinds of effects allowed + +// expected-note@+2 3 {{candidate throws, but protocol does not allow it}} +// expected-error@+1 {{type 'CT_N' does not conform to protocol 'None'}} +class CT_N : None { typealias V = Int; var someProp : Int { get throws {0} } } +class CT_T : T { typealias V = Int; var someProp : Int { get throws {0} } } + +// expected-note@+2 {{candidate throws, but protocol does not allow it}} +// expected-error@+1{{type 'CT_A' does not conform to protocol 'A'}} +class CT_A : A { typealias V = Int; var someProp : Int { get throws {0} } } +class CT_AT : AT { typealias V = Int; var someProp : Int { get throws {0} } } +// ------ ------ ------ ------ ------ ------ ------ ------ ------ ------ + +// expected-note@+2 3 {{candidate is 'async', but protocol requirement is not}} +// expected-error@+1 {{type 'CA_N' does not conform to protocol 'None'}} +struct CA_N : None { typealias V = Int; var someProp : Int { get async {0} } } + +// expected-note@+2 {{candidate is 'async', but protocol requirement is not}} +// expected-error@+1 {{type 'CA_T' does not conform to protocol 'T'}} +class CA_T : T { typealias V = Int; var someProp : Int { get async {0} } } + +struct CA_A : A { typealias V = Int; var someProp : Int { get async {0} } } +enum CA_AT : AT { typealias V = Int; var someProp : Int { get async {0} } } +// ------ ------ ------ ------ ------ ------ ------ ------ ------ ------ + +// I put these on separate lines to ensure diagnostics point to the right thing. + +// expected-error@+1 {{type 'CAT_N' does not conform to protocol 'None'}} +class CAT_N : None { typealias V = Int; + // expected-note@+2 {{candidate throws, but protocol does not allow it}} + // expected-note@+1 2 {{candidate is 'async', but protocol requirement is not}} + var someProp : Int { get async throws {0} } +} +// expected-error@+1 {{type 'CAT_T' does not conform to protocol 'T'}} +enum CAT_T : T { typealias V = Int; + // expected-note@+2 {{candidate throws, but protocol does not allow it}} + // expected-note@+1 2 {{candidate is 'async', but protocol requirement is not}} + var someProp : Int { get async throws {0} } +} +// expected-error@+1 {{type 'CAT_A' does not conform to protocol 'A'}} +class CAT_A : A { typealias V = Int; + // expected-note@+1 {{candidate throws, but protocol does not allow it}} + var someProp : Int { get async throws {0} } +} +class CAT_AT : AT { typealias V = Int; + var someProp : Int { get async throws {0} } +} + +// because the protocols above are generic over a type (i.e., +// have an associatedtype), we can't express the constraints +// below with just 'as'. + +func asNone(u : U) async throws { + _ = u.someProp +} + +func asAsync(u : U) async { + _ = u.someProp // expected-error {{property access is 'async' but is not marked with 'await'}} + + _ = await u.someProp +} + +func asThrows(u : U) throws { +// expected-note@+3 {{did you mean to handle error as optional value?}} +// expected-note@+2 {{did you mean to disable error propagation?}} +// expected-note@+1 {{did you mean to use 'try'?}} + _ = u.someProp // expected-error {{property access can throw but is not marked with 'try'}} + + _ = try u.someProp +} + +func asAsyncThrows(u : U) async throws { + // expected-note@+5 {{did you mean to handle error as optional value?}} + // expected-note@+4 {{did you mean to disable error propagation?}} + // expected-note@+3 {{did you mean to use 'try'?}} + // expected-error@+2 {{property access is 'async' but is not marked with 'await'}} + // expected-error@+1 {{property access can throw but is not marked with 'try'}} + _ = u.someProp + + _ = try await u.someProp +} + + +//////// +// specific conformance coverage for subscripts + +protocol NoneSub { + // expected-note@+1 3 {{protocol requires subscript with type '(Int) -> Bool'}} + subscript(_ i : Int) -> Bool { get } +} + +protocol AsyncSub { + // expected-note@+1 2 {{protocol requires subscript with type '(Int) -> Bool'}} + subscript(_ i : Int) -> Bool { get async } +} + +protocol ThrowsSub { + // expected-note@+1 2 {{protocol requires subscript with type '(Int) -> Bool'}} + subscript(_ i : Int) -> Bool { get throws } +} + +protocol AsyncThrowsSub { + subscript(_ i : Int) -> Bool { get async throws } +} + +// "S" stands for "subscript", but otherwise the convention above applies: +// Sx_y ==> witness x trying to conform to y. A = async, T = throws, AT = async throws +// I peppered some enums and structs in there for flavor. + +struct SN_N : NoneSub + { subscript(_ i : Int) -> Bool { get { true } }} +class SA_N : NoneSub // expected-error{{type 'SA_N' does not conform to protocol 'NoneSub'}} + { subscript(_ i : Int) -> Bool { get async { true } }} // expected-note{{candidate is 'async', but protocol requirement is not}} +class ST_N : NoneSub // expected-error{{type 'ST_N' does not conform to protocol 'NoneSub'}} + { subscript(_ i : Int) -> Bool { get throws { true } }} // expected-note{{candidate throws, but protocol does not allow it}} +class SAT_N : NoneSub // expected-error{{type 'SAT_N' does not conform to protocol 'NoneSub'}} + { subscript(_ i : Int) -> Bool { get async throws { true } }} // expected-note{{candidate is 'async', but protocol requirement is not}} + +class SN_A : AsyncSub + { subscript(_ i : Int) -> Bool { get { true } }} +struct SA_A : AsyncSub + { subscript(_ i : Int) -> Bool { get async { true } }} +enum ST_A : AsyncSub // expected-error{{type 'ST_A' does not conform to protocol 'AsyncSub'}} + { subscript(_ i : Int) -> Bool { get throws { true } }} // expected-note{{candidate throws, but protocol does not allow it}} +class SAT_A : AsyncSub // expected-error{{type 'SAT_A' does not conform to protocol 'AsyncSub'}} + { subscript(_ i : Int) -> Bool { get async throws { true } }} // expected-note{{candidate throws, but protocol does not allow it}} + +class SN_T : ThrowsSub + { subscript(_ i : Int) -> Bool { get { true } }} +class SA_T : ThrowsSub // expected-error{{type 'SA_T' does not conform to protocol 'ThrowsSub'}} + { subscript(_ i : Int) -> Bool { get async { true } }} // expected-note{{candidate is 'async', but protocol requirement is not}} +struct ST_T : ThrowsSub + { subscript(_ i : Int) -> Bool { get throws { true } }} +struct SAT_T : ThrowsSub // expected-error{{type 'SAT_T' does not conform to protocol 'ThrowsSub'}} + { subscript(_ i : Int) -> Bool { get async throws { true } }} // expected-note{{candidate is 'async', but protocol requirement is not}} + +class SN_AT : AsyncThrowsSub + { subscript(_ i : Int) -> Bool { get { true } }} +enum SA_AT : AsyncThrowsSub + { subscript(_ i : Int) -> Bool { get async { true } }} +class ST_AT : AsyncThrowsSub + { subscript(_ i : Int) -> Bool { get throws { true } }} +struct SAT_AT : AsyncThrowsSub + { subscript(_ i : Int) -> Bool { get async throws { true } }} + + +//////// +// protocol composition & inheritance + +func composed1(u : U) async throws { + // expected-note@+4 {{did you mean to handle error as optional value?}} + // expected-note@+3 {{did you mean to disable error propagation?}} + // expected-note@+2 {{did you mean to use 'try'?}} + // expected-error@+1 {{property access can throw but is not marked with 'try'}} + _ = u.someProp + + // FIXME: this ^ should raise property access is 'async' but is not marked with 'await' + + _ = try u.someProp +} + +func composed2(u : U) async { + _ = u.someProp + // FIXME: this ^ should raise "property access is 'async' but is not marked with 'await'"" + + _ = await u.someProp // expected-warning {{no 'async' operations occur within 'await' expression}} +} + +func composed3(u : U) throws { + // expected-note@+3 {{did you mean to handle error as optional value?}} + // expected-note@+2 {{did you mean to disable error propagation?}} + // expected-note@+1 {{did you mean to use 'try'?}} + _ = u.someProp // expected-error {{property access can throw but is not marked with 'try'}} + + _ = try u.someProp +} + +func composed4(u : U) { + _ = u.someProp // expected-error {{property access can throw, but it is not marked with 'try' and the error is not handled}} + + _ = try! u.someProp +} + + +///////////////// +// redefining the protocols to make sure the fix-its are matched + +protocol NoEffects { +// expected-note@+1 2 {{protocol requires property 'someProp' with type 'Int'; do you want to add a stub?}} + var someProp : Int { get } +} + +protocol Throws { + // expected-note@+1 2 {{protocol requires property 'someProp' with type 'Int'; do you want to add a stub?}} + var someProp : Int { get throws } +} + +protocol Async { + // expected-note@+1 3 {{protocol requires property 'someProp' with type 'Int'; do you want to add a stub?}} + var someProp : Int { get async } +} + +protocol AsyncThrowsByInheritance : Async, Throws {} +protocol AsyncByInheritance : Async, NoEffects {} +protocol ThrowsByInheritance : Throws, NoEffects {} + +extension CN_N : AsyncByInheritance, ThrowsByInheritance, AsyncThrowsByInheritance {} +extension CN_T : AsyncByInheritance, ThrowsByInheritance, AsyncThrowsByInheritance {} +extension CN_A : AsyncByInheritance, ThrowsByInheritance, AsyncThrowsByInheritance {} +extension CN_AT : AsyncByInheritance, ThrowsByInheritance, AsyncThrowsByInheritance {} + +// ----- ----- + +extension CT_N : Throws {} + +// expected-error@+1 {{type 'CT_N' does not conform to protocol 'NoEffects'}} +extension CT_N : ThrowsByInheritance {} + +// expected-error@+1 {{type 'CT_N' does not conform to protocol 'Async'}} +extension CT_N : AsyncByInheritance {} + +// ----- ----- + +extension CA_N : Async {} + +// expected-error@+1 {{type 'CA_N' does not conform to protocol 'NoEffects'}} +extension CA_N : AsyncByInheritance {} + +// expected-error@+1 {{type 'CA_N' does not conform to protocol 'Throws'}} +extension CA_N : ThrowsByInheritance {} + +// ----- ----- + +// expected-error@+1 {{type 'CAT_N' does not conform to protocol 'Async'}} +extension CAT_N : Async {} + +// expected-error@+1 {{type 'CAT_N' does not conform to protocol 'Throws'}} +extension CAT_N : Throws {} + +// expected-error@+2 {{type 'CAT_T' does not conform to protocol 'Async'}} +// expected-error@+1 {{type 'CAT_T' does not conform to protocol 'Throws'}} +extension CAT_T : AsyncThrowsByInheritance {} + + +struct S : Async, Throws { + var someProp : Int { 3 } +} + +func play(s : S) async throws { + _ = s.someProp + _ = await (s as Async).someProp + _ = try (s as Throws).someProp +} + +////////// +/// Check protocol overrides. Cannot override with more effects. + +protocol HammeredDulcimer { + subscript(_ note : Int) -> Int { get } + var bridges : Int { get async throws } +} + +protocol Santur : HammeredDulcimer { + override subscript(_ note : Int) -> Int { get throws } // expected-error{{cannot override non-'throws' subscript with 'throws' subscript}} + override var bridges : Int { get throws } +} + +protocol Santoor : Santur { + override var bridges : Int { get async throws } // expected-error{{cannot override non-'async' property with 'async' property}} +} + +protocol Yangqin : HammeredDulcimer { + override var bridges : Int { get async throws } // same effects are OK +} + +protocol Hackbrett : HammeredDulcimer { + override var bridges : Int { get } // no effects are OK + override subscript(_ note : Int) -> Int { get async throws } // expected-error {{cannot override non-'async' subscript with 'async' subscript}} +} \ No newline at end of file diff --git a/test/decl/protocol/effectful_properties_objc.swift b/test/decl/protocol/effectful_properties_objc.swift new file mode 100644 index 0000000000000..6cc8e9481eade --- /dev/null +++ b/test/decl/protocol/effectful_properties_objc.swift @@ -0,0 +1,15 @@ +// RUN: %target-typecheck-verify-swift -enable-experimental-concurrency + +// REQUIRES: concurrency +// REQUIRES: objc_interop + +///////// +// check for disallowed attributes in protocols +@objc protocol Tea { + var temperature : Double { get throws } // expected-error{{property with 'throws' or 'async' is not representable in Objective-C}} + subscript(_ d : Double) -> Bool { get async throws } // expected-error{{subscript with 'throws' or 'async' is not representable in Objective-C}} + + // NOTE: this seems counter-intuitive, but TSPL says @nonobjc applies to + // members that are representable in ObjC, and this is not representable. + @nonobjc var sugar : Bool { get async } // expected-error{{property with 'throws' or 'async' is not representable in Objective-C}} +} \ No newline at end of file diff --git a/test/decl/var/effectful_properties_global.swift b/test/decl/var/effectful_properties_global.swift new file mode 100644 index 0000000000000..a6d1eb1d537cb --- /dev/null +++ b/test/decl/var/effectful_properties_global.swift @@ -0,0 +1,41 @@ +// RUN: %target-typecheck-verify-swift -enable-experimental-concurrency + +var intAsyncProp : Int { + get async { 0 } +} + +var intThrowsProp : Int { // expected-note 2 {{var declared here}} + get throws { 0 } +} + +var asyncThrowsProp : Int { + // expected-warning@+1 {{reference to var 'intThrowsProp' is not concurrency-safe because it involves shared mutable state}} + get async throws { try await intAsyncProp + intThrowsProp } +} + +func hello() async { + // expected-warning@+1 {{reference to var 'intThrowsProp' is not concurrency-safe because it involves shared mutable state}} + _ = intThrowsProp // expected-error{{property access can throw, but it is not marked with 'try' and the error is not handled}} + + _ = intAsyncProp // expected-error{{property access is 'async' but is not marked with 'await'}} +} + +class C { + var counter : Int = 0 +} + +var refTypeThrowsProp : C { // expected-note {{var declared here}} + get throws { return C() } +} + +var refTypeAsyncProp : C { + get async { return C() } +} + +func salam() async { + // expected-warning@+1 {{reference to var 'refTypeThrowsProp' is not concurrency-safe because it involves shared mutable state}} + _ = refTypeThrowsProp // expected-error{{property access can throw, but it is not marked with 'try' and the error is not handled}} + + + _ = refTypeAsyncProp // expected-error {{property access is 'async' but is not marked with 'await'}} +} \ No newline at end of file diff --git a/test/decl/var/effectful_property_wrapper.swift b/test/decl/var/effectful_property_wrapper.swift new file mode 100644 index 0000000000000..d7fa3dd1e8002 --- /dev/null +++ b/test/decl/var/effectful_property_wrapper.swift @@ -0,0 +1,41 @@ +// RUN: %target-typecheck-verify-swift -enable-experimental-concurrency + +// Currently, we don't support having property wrappers that are effectful. +// Eventually we'd like to add this. + +@propertyWrapper +struct Abstraction { + private var value : T + + init(_ initial : T) { self.value = initial } + + var wrappedValue : T { + get throws { return value } // expected-error{{property wrappers currently cannot define an 'async' or 'throws' accessor}} + } + + // its OK to have effectul props that are not `wrappedValue` + + var prop1 : T { + get async { value } + } + var prop2 : T { + get throws { value } + } + var prop3 : T { + get async throws { value } + } +} + +@propertyWrapper +struct NeedlessIntWrapper { + var wrappedValue : Int { + get {} + } +} + +struct S { + // expected-error@+1 {{property wrapper cannot be applied to a computed property}} + @NeedlessIntWrapper var throwingProp : Int { + get throws { 0 } + } +} \ No newline at end of file