Skip to content

Commit bae76c8

Browse files
authored
Merge pull request #36430 from kavon/effectful-properties
2 parents 58f03e8 + 8ba98a9 commit bae76c8

36 files changed

+1974
-144
lines changed

include/swift/AST/Decl.h

Lines changed: 35 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4521,6 +4521,34 @@ class AbstractStorageDecl : public ValueDecl {
45214521
return {};
45224522
}
45234523

4524+
/// This is the primary mechanism by which we can easily determine whether
4525+
/// this storage decl has any effects.
4526+
///
4527+
/// \returns the getter decl iff this decl has only one accessor that is
4528+
/// a 'get' with an effect (i.e., 'async', 'throws', or both).
4529+
/// Otherwise returns nullptr.
4530+
AccessorDecl *getEffectfulGetAccessor() const;
4531+
4532+
/// Performs a "limit check" on an effect possibly exhibited by this storage
4533+
/// decl with respect to some other storage decl that serves as the "limit."
4534+
/// This check says that \c this is less effectful than \c other if
4535+
/// \c this either does not exhibit the effect, or if it does, then \c other
4536+
/// also exhibits the effect. Thus, it is conceptually equivalent to
4537+
/// a less-than-or-equal (≤) check like so:
4538+
///
4539+
/// \verbatim
4540+
///
4541+
/// this->hasEffect(E) ≤ other->hasEffect(E)
4542+
///
4543+
/// \endverbatim
4544+
///
4545+
/// \param kind the single effect we are performing a check for.
4546+
///
4547+
/// \returns true iff \c this decl either does not exhibit the effect,
4548+
/// or \c other also exhibits the effect.
4549+
bool isLessEffectfulThan(AbstractStorageDecl const* other,
4550+
EffectKind kind) const;
4551+
45244552
/// Return an accessor that this storage is expected to have, synthesizing
45254553
/// one if necessary. Note that will always synthesize one, even if the
45264554
/// accessor is not part of the expected opaque set for the storage, so use
@@ -6327,16 +6355,18 @@ class AccessorDecl final : public FuncDecl {
63276355
AccessorDecl(SourceLoc declLoc, SourceLoc accessorKeywordLoc,
63286356
AccessorKind accessorKind, AbstractStorageDecl *storage,
63296357
SourceLoc staticLoc, StaticSpellingKind staticSpelling,
6330-
bool throws, SourceLoc throwsLoc,
6358+
bool async, SourceLoc asyncLoc, bool throws, SourceLoc throwsLoc,
63316359
bool hasImplicitSelfDecl, GenericParamList *genericParams,
63326360
DeclContext *parent)
63336361
: FuncDecl(DeclKind::Accessor,
63346362
staticLoc, staticSpelling, /*func loc*/ declLoc,
63356363
/*name*/ Identifier(), /*name loc*/ declLoc,
6336-
/*Async=*/false, SourceLoc(), throws, throwsLoc,
6364+
async, asyncLoc, throws, throwsLoc,
63376365
hasImplicitSelfDecl, genericParams, parent),
63386366
AccessorKeywordLoc(accessorKeywordLoc),
63396367
Storage(storage) {
6368+
assert(!async || accessorKind == AccessorKind::Get
6369+
&& "only get accessors can be async");
63406370
Bits.AccessorDecl.AccessorKind = unsigned(accessorKind);
63416371
}
63426372

@@ -6347,6 +6377,7 @@ class AccessorDecl final : public FuncDecl {
63476377
AbstractStorageDecl *storage,
63486378
SourceLoc staticLoc,
63496379
StaticSpellingKind staticSpelling,
6380+
bool async, SourceLoc asyncLoc,
63506381
bool throws, SourceLoc throwsLoc,
63516382
GenericParamList *genericParams,
63526383
DeclContext *parent,
@@ -6365,7 +6396,7 @@ class AccessorDecl final : public FuncDecl {
63656396
AccessorKind accessorKind,
63666397
AbstractStorageDecl *storage,
63676398
StaticSpellingKind staticSpelling,
6368-
bool throws,
6399+
bool async, bool throws,
63696400
GenericParamList *genericParams,
63706401
Type fnRetType, DeclContext *parent);
63716402

@@ -6375,6 +6406,7 @@ class AccessorDecl final : public FuncDecl {
63756406
AbstractStorageDecl *storage,
63766407
SourceLoc staticLoc,
63776408
StaticSpellingKind staticSpelling,
6409+
bool async, SourceLoc asyncLoc,
63786410
bool throws, SourceLoc throwsLoc,
63796411
GenericParamList *genericParams,
63806412
ParameterList *parameterList,

include/swift/AST/DiagnosticsParse.def

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -278,6 +278,12 @@ ERROR(expected_lbrace_accessor,PointsToFirstBadToken,
278278
ERROR(expected_accessor_kw,none,
279279
"expected 'get', 'set', 'willSet', or 'didSet' keyword to "
280280
"start an accessor definition",())
281+
ERROR(invalid_accessor_specifier,none,
282+
"'%0' accessor cannot have specifier '%1'",
283+
(StringRef, StringRef))
284+
ERROR(invalid_accessor_with_effectful_get,none,
285+
"'%0' accessor is not allowed on property with 'get' accessor that is 'async' or 'throws'",
286+
(StringRef))
281287
ERROR(missing_getter,none,
282288
"%select{variable|subscript}0 with %1 must also have a getter",
283289
(unsigned, StringRef))

include/swift/AST/DiagnosticsSema.def

Lines changed: 35 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1584,6 +1584,10 @@ WARNING(enum_frozen_nonpublic,none,
15841584
ERROR(getset_init,none,
15851585
"variable with getter/setter cannot have an initial value", ())
15861586

1587+
ERROR(effectful_not_representable_objc,none,
1588+
"%0 with 'throws' or 'async' is not representable in Objective-C",
1589+
(DescriptiveDeclKind))
1590+
15871591
ERROR(unimplemented_static_var,none,
15881592
"%select{ERROR|static|class}1 stored properties not supported"
15891593
"%select{ in this context| in generic types| in classes| in protocol extensions}0"
@@ -2600,6 +2604,10 @@ ERROR(override_class_declaration_in_extension,none,
26002604
ERROR(override_throws,none,
26012605
"cannot override non-throwing %select{method|initializer}0 with "
26022606
"throwing %select{method|initializer}0", (bool))
2607+
// TODO: this really could be merged with the above.
2608+
ERROR(override_with_more_effects,none,
2609+
"cannot override non-'%1' %0 with '%1' %0",
2610+
(DescriptiveDeclKind, StringRef))
26032611
ERROR(override_throws_objc,none,
26042612
"overriding a throwing @objc %select{method|initializer}0 with "
26052613
"a non-throwing %select{method|initializer}0 is not supported", (bool))
@@ -4150,21 +4158,21 @@ ERROR(isa_collection_downcast_pattern_value_unimplemented,none,
41504158
ERROR(try_unhandled,none,
41514159
"errors thrown from here are not handled", ())
41524160
ERROR(throwing_call_unhandled,none,
4153-
"call can throw, but the error is not handled", ())
4161+
"%0 can throw, but the error is not handled", (StringRef))
41544162
ERROR(tryless_throwing_call_unhandled,none,
4155-
"call can throw, but it is not marked with 'try' and "
4156-
"the error is not handled", ())
4163+
"%0 can throw, but it is not marked with 'try' and "
4164+
"the error is not handled", (StringRef))
41574165
ERROR(throw_in_nonthrowing_function,none,
41584166
"error is not handled because the enclosing function "
41594167
"is not declared 'throws'", ())
41604168

41614169
ERROR(throwing_call_in_rethrows_function,none,
4162-
"call can throw, but the error is not handled; a function declared "
4163-
"'rethrows' may only throw if its parameter does", ())
4170+
"%0 can throw, but the error is not handled; a function declared "
4171+
"'rethrows' may only throw if its parameter does", (StringRef))
41644172
ERROR(tryless_throwing_call_in_rethrows_function,none,
4165-
"call can throw, but it is not marked with 'try' and "
4173+
"%0 can throw, but it is not marked with 'try' and "
41664174
"the error is not handled; a function declared "
4167-
"'rethrows' may only throw if its parameter does", ())
4175+
"'rethrows' may only throw if its parameter does", (StringRef))
41684176
ERROR(throw_in_rethrows_function,none,
41694177
"a function declared 'rethrows' may only throw if its parameter does", ())
41704178
NOTE(because_rethrows_argument_throws,none,
@@ -4176,11 +4184,11 @@ NOTE(because_rethrows_conformance_throws,none,
41764184
"call is to 'rethrows' function, but a conformance has a throwing witness", ())
41774185

41784186
ERROR(throwing_call_in_nonthrowing_autoclosure,none,
4179-
"call can throw, but it is executed in a non-throwing "
4180-
"autoclosure",())
4187+
"%0 can throw, but it is executed in a non-throwing "
4188+
"autoclosure",(StringRef))
41814189
ERROR(tryless_throwing_call_in_nonthrowing_autoclosure,none,
4182-
"call can throw, but it is not marked with 'try' and "
4183-
"it is executed in a non-throwing autoclosure",())
4190+
"%0 can throw, but it is not marked with 'try' and "
4191+
"it is executed in a non-throwing autoclosure",(StringRef))
41844192
ERROR(throw_in_nonthrowing_autoclosure,none,
41854193
"error is not handled because it is thrown in a non-throwing "
41864194
"autoclosure", ())
@@ -4189,17 +4197,17 @@ ERROR(try_unhandled_in_nonexhaustive_catch,none,
41894197
"errors thrown from here are not handled because the "
41904198
"enclosing catch is not exhaustive", ())
41914199
ERROR(throwing_call_in_nonexhaustive_catch,none,
4192-
"call can throw, but the enclosing catch is not exhaustive", ())
4200+
"%0 can throw, but the enclosing catch is not exhaustive", (StringRef))
41934201
ERROR(tryless_throwing_call_in_nonexhaustive_catch,none,
4194-
"call can throw, but it is not marked with 'try' and "
4195-
"the enclosing catch is not exhaustive", ())
4202+
"%0 can throw, but it is not marked with 'try' and "
4203+
"the enclosing catch is not exhaustive", (StringRef))
41964204
ERROR(throw_in_nonexhaustive_catch,none,
41974205
"error is not handled because the enclosing catch is not exhaustive", ())
41984206

4199-
ERROR(throwing_call_in_illegal_context,none,
4200-
"call can throw, but errors cannot be thrown out of "
4207+
ERROR(throwing_op_in_illegal_context,none,
4208+
"%1 can throw, but errors cannot be thrown out of "
42014209
"%select{<<ERROR>>|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",
4202-
(unsigned))
4210+
(unsigned, StringRef))
42034211
ERROR(throw_in_illegal_context,none,
42044212
"errors cannot be thrown out of "
42054213
"%select{<<ERROR>>|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,
42134221
"call can throw but is not marked with 'try'", ())
42144222
ERROR(throwing_async_let_without_try,none,
42154223
"reading 'async let' can throw but is not marked with 'try'", ())
4224+
ERROR(throwing_prop_access_without_try,none,
4225+
"property access can throw but is not marked with 'try'", ())
4226+
ERROR(throwing_subscript_access_without_try,none,
4227+
"subscript access can throw but is not marked with 'try'", ())
42164228
NOTE(note_forgot_try,none,
42174229
"did you mean to use 'try'?", ())
42184230
NOTE(note_error_to_optional,none,
@@ -4382,6 +4394,9 @@ ERROR(actor_isolated_from_escaping_closure,none,
43824394
ERROR(actor_isolated_keypath_component,none,
43834395
"cannot form key path to actor-isolated %0 %1",
43844396
(DescriptiveDeclKind, DeclName))
4397+
ERROR(effectful_keypath_component,none,
4398+
"cannot form key path to %0 with 'throws' or 'async'",
4399+
(DescriptiveDeclKind))
43854400
ERROR(local_function_executed_concurrently,none,
43864401
"concurrently-executed %0 %1 must be marked as '@Sendable'",
43874402
(DescriptiveDeclKind, DeclName))
@@ -5615,6 +5630,9 @@ ERROR(property_wrapper_let, none,
56155630
ERROR(property_wrapper_computed, none,
56165631
"property wrapper cannot be applied to a computed property",
56175632
())
5633+
ERROR(property_wrapper_effectful,none,
5634+
"property wrappers currently cannot define an 'async' or 'throws' accessor",
5635+
())
56185636

56195637
ERROR(property_with_wrapper_conflict_attribute,none,
56205638
"property %0 with a wrapper cannot also be "

include/swift/AST/StorageImpl.h

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,8 @@ enum class AccessorKind {
4545
#define ACCESSOR(ID) ID,
4646
#define LAST_ACCESSOR(ID) Last = ID
4747
#include "swift/AST/AccessorKinds.def"
48+
#undef ACCESSOR
49+
#undef LAST_ACCESSOR
4850
};
4951

5052
const unsigned NumAccessorKinds = unsigned(AccessorKind::Last) + 1;
@@ -54,6 +56,22 @@ static inline IntRange<AccessorKind> allAccessorKinds() {
5456
AccessorKind(NumAccessorKinds));
5557
}
5658

59+
/// \returns a user-readable string name for the accessor kind
60+
static inline StringRef accessorKindName(AccessorKind ak) {
61+
switch(ak) {
62+
63+
#define ACCESSOR(ID) ID
64+
#define SINGLETON_ACCESSOR(ID, KEYWORD) \
65+
case AccessorKind::ID: \
66+
return #KEYWORD;
67+
68+
#include "swift/AST/AccessorKinds.def"
69+
70+
#undef ACCESSOR_KEYWORD
71+
#undef SINGLETON_ACCESSOR
72+
}
73+
}
74+
5775
/// Whether an access to storage is for reading, writing, or both.
5876
enum class AccessKind : uint8_t {
5977
/// The access is just to read the current value.

include/swift/Parse/Parser.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1158,6 +1158,12 @@ class Parser {
11581158
bool hasInitializer,
11591159
const DeclAttributes &Attributes,
11601160
SmallVectorImpl<Decl *> &Decls);
1161+
ParserStatus parseGetEffectSpecifier(ParsedAccessors &accessors,
1162+
SourceLoc &asyncLoc,
1163+
SourceLoc &throwsLoc,
1164+
bool &hasEffectfulGet,
1165+
AccessorKind currentKind,
1166+
SourceLoc const& currentLoc);
11611167

11621168
void consumeAbstractFunctionBody(AbstractFunctionDecl *AFD,
11631169
const DeclAttributes &Attrs);

lib/AST/ASTPrinter.cpp

Lines changed: 53 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1018,6 +1018,24 @@ static StaticSpellingKind getCorrectStaticSpelling(const Decl *D) {
10181018
}
10191019
}
10201020

1021+
static bool hasAsyncGetter(const AbstractStorageDecl *ASD) {
1022+
if (auto getter = ASD->getAccessor(AccessorKind::Get)) {
1023+
assert(!getter->getAttrs().hasAttribute<ReasyncAttr>());
1024+
return getter->hasAsync();
1025+
}
1026+
1027+
return false;
1028+
}
1029+
1030+
static bool hasThrowsGetter(const AbstractStorageDecl *ASD) {
1031+
if (auto getter = ASD->getAccessor(AccessorKind::Get)) {
1032+
assert(!getter->getAttrs().hasAttribute<RethrowsAttr>());
1033+
return getter->hasThrows();
1034+
}
1035+
1036+
return false;
1037+
}
1038+
10211039
static bool hasMutatingGetter(const AbstractStorageDecl *ASD) {
10221040
return ASD->getAccessor(AccessorKind::Get) && ASD->isGetterMutating();
10231041
}
@@ -1925,6 +1943,15 @@ void PrintAST::printAccessors(const AbstractStorageDecl *ASD) {
19251943
return;
19261944
}
19271945

1946+
// prints with a space prefixed
1947+
auto printWithSpace = [&](StringRef word) {
1948+
Printer << " ";
1949+
Printer.printKeyword(word, Options);
1950+
};
1951+
1952+
const bool asyncGet = hasAsyncGetter(ASD);
1953+
const bool throwsGet = hasThrowsGetter(ASD);
1954+
19281955
// We sometimes want to print the accessors abstractly
19291956
// instead of listing out how they're actually implemented.
19301957
bool inProtocol = isa<ProtocolDecl>(ASD->getDeclContext());
@@ -1935,27 +1962,27 @@ void PrintAST::printAccessors(const AbstractStorageDecl *ASD) {
19351962
bool nonmutatingSetter = hasNonMutatingSetter(ASD);
19361963

19371964
// We're about to print something like this:
1938-
// { mutating? get (nonmutating? set)? }
1965+
// { mutating? get async? throws? (nonmutating? set)? }
19391966
// But don't print "{ get set }" if we don't have to.
19401967
if (!inProtocol && !Options.PrintGetSetOnRWProperties &&
1941-
settable && !mutatingGetter && !nonmutatingSetter) {
1968+
settable && !mutatingGetter && !nonmutatingSetter
1969+
&& !asyncGet && !throwsGet) {
19421970
return;
19431971
}
19441972

19451973
Printer << " {";
1946-
if (mutatingGetter) {
1947-
Printer << " ";
1948-
Printer.printKeyword("mutating", Options);
1949-
}
1950-
Printer << " ";
1951-
Printer.printKeyword("get", Options);
1974+
if (mutatingGetter) printWithSpace("mutating");
1975+
1976+
printWithSpace("get");
1977+
1978+
if (asyncGet) printWithSpace("async");
1979+
1980+
if (throwsGet) printWithSpace("throws");
1981+
19521982
if (settable) {
1953-
if (nonmutatingSetter) {
1954-
Printer << " ";
1955-
Printer.printKeyword("nonmutating", Options);
1956-
}
1957-
Printer << " ";
1958-
Printer.printKeyword("set", Options);
1983+
if (nonmutatingSetter) printWithSpace("nonmutating");
1984+
1985+
printWithSpace("set");
19591986
}
19601987
Printer << " }";
19611988
return;
@@ -1983,7 +2010,8 @@ void PrintAST::printAccessors(const AbstractStorageDecl *ASD) {
19832010
!Options.PrintGetSetOnRWProperties &&
19842011
!Options.FunctionDefinitions &&
19852012
!ASD->isGetterMutating() &&
1986-
!ASD->getAccessor(AccessorKind::Set)->isExplicitNonMutating()) {
2013+
!ASD->getAccessor(AccessorKind::Set)->isExplicitNonMutating() &&
2014+
!asyncGet && !throwsGet) {
19872015
return;
19882016
}
19892017

@@ -1999,7 +2027,15 @@ void PrintAST::printAccessors(const AbstractStorageDecl *ASD) {
19992027
if (!PrintAccessorBody) {
20002028
Printer << " ";
20012029
printMutabilityModifiersIfNeeded(Accessor);
2030+
20022031
Printer.printKeyword(getAccessorLabel(Accessor->getAccessorKind()), Options);
2032+
2033+
// handle any effects specifiers
2034+
if (Accessor->getAccessorKind() == AccessorKind::Get) {
2035+
if (asyncGet) printWithSpace("async");
2036+
if (throwsGet) printWithSpace("throws");
2037+
}
2038+
20032039
} else {
20042040
{
20052041
IndentRAII IndentMore(*this);
@@ -2017,7 +2053,8 @@ void PrintAST::printAccessors(const AbstractStorageDecl *ASD) {
20172053
bool isOnlyGetter = impl.getReadImpl() == ReadImplKind::Get &&
20182054
ASD->getAccessor(AccessorKind::Get);
20192055
bool isGetterMutating = ASD->supportsMutation() || ASD->isGetterMutating();
2020-
if (isOnlyGetter && !isGetterMutating && PrintAccessorBody &&
2056+
bool hasEffects = asyncGet || throwsGet;
2057+
if (isOnlyGetter && !isGetterMutating && !hasEffects && PrintAccessorBody &&
20212058
Options.FunctionBody && Options.CollapseSingleGetterProperty) {
20222059
Options.FunctionBody(ASD->getAccessor(AccessorKind::Get), Printer);
20232060
indent();

0 commit comments

Comments
 (0)