Skip to content

Commit 724f8c2

Browse files
authored
[Typechecker] Implement SE-0268 Refine didSet Semantics (#26632)
1 parent deb58e0 commit 724f8c2

29 files changed

+476
-133
lines changed

include/swift/AST/Decl.h

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -428,16 +428,15 @@ class alignas(1 << DeclAlignInBits) Decl {
428428
HasTopLevelLocalContextCaptures : 1
429429
);
430430

431-
SWIFT_INLINE_BITFIELD(AccessorDecl, FuncDecl, 4+1+1,
432-
/// The kind of accessor this is.
433-
AccessorKind : 4,
431+
SWIFT_INLINE_BITFIELD(AccessorDecl, FuncDecl, 4 + 1 + 1,
432+
/// The kind of accessor this is.
433+
AccessorKind : 4,
434434

435-
/// Whether the accessor is transparent.
436-
IsTransparent : 1,
435+
/// Whether the accessor is transparent.
436+
IsTransparent : 1,
437437

438-
/// Whether we have computed the above.
439-
IsTransparentComputed : 1
440-
);
438+
/// Whether we have computed the above.
439+
IsTransparentComputed : 1);
441440

442441
SWIFT_INLINE_BITFIELD(ConstructorDecl, AbstractFunctionDecl, 3+1+1,
443442
/// The body initialization kind (+1), or zero if not yet computed.
@@ -6502,6 +6501,11 @@ class AccessorDecl final : public FuncDecl {
65026501
return isGetter() && getAccessorKeywordLoc().isInvalid();
65036502
}
65046503

6504+
/// Is this accessor a "simple" didSet? A "simple" didSet does not
6505+
/// use the implicit oldValue parameter in its body or does not have
6506+
/// an explicit parameter in its parameter list.
6507+
bool isSimpleDidSet() const;
6508+
65056509
void setIsTransparent(bool transparent) {
65066510
Bits.AccessorDecl.IsTransparent = transparent;
65076511
Bits.AccessorDecl.IsTransparentComputed = 1;

include/swift/AST/KnownIdentifiers.def

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,7 @@ IDENTIFIER(objectAtIndexedSubscript)
9898
IDENTIFIER(objectForKeyedSubscript)
9999
IDENTIFIER(ObjectiveC)
100100
IDENTIFIER_(ObjectiveCType)
101+
IDENTIFIER(oldValue)
101102
IDENTIFIER(Optional)
102103
IDENTIFIER_(OptionalNilComparisonType)
103104
IDENTIFIER(parameter)

include/swift/AST/StorageImpl.h

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -222,6 +222,12 @@ enum class ReadWriteImplKind {
222222

223223
/// There's a modify coroutine.
224224
Modify,
225+
226+
/// We have a didSet which doesn't use the oldValue
227+
StoredWithSimpleDidSet,
228+
229+
/// We have a didSet which doesn't use the oldValue
230+
InheritedWithSimpleDidSet,
225231
};
226232
enum { NumReadWriteImplKindBits = 4 };
227233

@@ -266,12 +272,14 @@ class StorageImplInfo {
266272

267273
case WriteImplKind::StoredWithObservers:
268274
assert(readImpl == ReadImplKind::Stored);
269-
assert(readWriteImpl == ReadWriteImplKind::MaterializeToTemporary);
275+
assert(readWriteImpl == ReadWriteImplKind::MaterializeToTemporary ||
276+
readWriteImpl == ReadWriteImplKind::StoredWithSimpleDidSet);
270277
return;
271278

272279
case WriteImplKind::InheritedWithObservers:
273280
assert(readImpl == ReadImplKind::Inherited);
274-
assert(readWriteImpl == ReadWriteImplKind::MaterializeToTemporary);
281+
assert(readWriteImpl == ReadWriteImplKind::MaterializeToTemporary ||
282+
readWriteImpl == ReadWriteImplKind::InheritedWithSimpleDidSet);
275283
return;
276284

277285
case WriteImplKind::Set:

include/swift/AST/TypeCheckRequests.h

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2301,6 +2301,27 @@ class ResolveTypeEraserTypeRequest
23012301
void cacheResult(Type value) const;
23022302
};
23032303

2304+
/// Determines whether this is a "simple" didSet i.e one that either does not
2305+
/// use the implicit oldValue parameter in the body or does not take an explicit
2306+
/// parameter (ex: 'didSet(oldValue)').
2307+
class SimpleDidSetRequest
2308+
: public SimpleRequest<SimpleDidSetRequest, bool(AccessorDecl *),
2309+
RequestFlags::Cached> {
2310+
public:
2311+
using SimpleRequest::SimpleRequest;
2312+
2313+
private:
2314+
friend SimpleRequest;
2315+
2316+
// Evaluation.
2317+
bool evaluate(Evaluator &evaluator, AccessorDecl *decl) const;
2318+
2319+
public:
2320+
bool isCached() const {
2321+
return std::get<0>(getStorage())->getAccessorKind() == AccessorKind::DidSet;
2322+
}
2323+
};
2324+
23042325
// Allow AnyValue to compare two Type values, even though Type doesn't
23052326
// support ==.
23062327
template<>

include/swift/AST/TypeCheckerTypeIDZone.def

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -249,3 +249,5 @@ SWIFT_REQUEST(TypeChecker, ClosureHasExplicitResultRequest,
249249
SWIFT_REQUEST(TypeChecker, LookupAllConformancesInContextRequest,
250250
ProtocolConformanceLookupResult(const DeclContext *),
251251
Uncached, NoLocationInfo)
252+
SWIFT_REQUEST(TypeChecker, SimpleDidSetRequest,
253+
bool(AccessorDecl *), Cached, NoLocationInfo)

lib/AST/ASTDumper.cpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -264,6 +264,10 @@ StringRef swift::getReadWriteImplKindName(ReadWriteImplKind kind) {
264264
return "materialize_to_temporary";
265265
case ReadWriteImplKind::Modify:
266266
return "modify_coroutine";
267+
case ReadWriteImplKind::StoredWithSimpleDidSet:
268+
return "stored_simple_didset";
269+
case ReadWriteImplKind::InheritedWithSimpleDidSet:
270+
return "inherited_simple_didset";
267271
}
268272
llvm_unreachable("bad kind");
269273
}

lib/AST/Decl.cpp

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2094,6 +2094,16 @@ getDirectReadWriteAccessStrategy(const AbstractStorageDecl *storage) {
20942094
case ReadWriteImplKind::Modify:
20952095
return AccessStrategy::getAccessor(AccessorKind::Modify,
20962096
/*dispatch*/ false);
2097+
case ReadWriteImplKind::StoredWithSimpleDidSet:
2098+
case ReadWriteImplKind::InheritedWithSimpleDidSet:
2099+
if (storage->requiresOpaqueModifyCoroutine()) {
2100+
return AccessStrategy::getAccessor(AccessorKind::Modify,
2101+
/*dispatch*/ false);
2102+
} else {
2103+
return AccessStrategy::getMaterializeToTemporary(
2104+
getDirectReadAccessStrategy(storage),
2105+
getDirectWriteAccessStrategy(storage));
2106+
}
20972107
case ReadWriteImplKind::MaterializeToTemporary:
20982108
return AccessStrategy::getMaterializeToTemporary(
20992109
getDirectReadAccessStrategy(storage),
@@ -7328,6 +7338,12 @@ bool AccessorDecl::isExplicitNonMutating() const {
73287338
!getDeclContext()->getDeclaredInterfaceType()->hasReferenceSemantics();
73297339
}
73307340

7341+
bool AccessorDecl::isSimpleDidSet() const {
7342+
auto mutableThis = const_cast<AccessorDecl *>(this);
7343+
return evaluateOrDefault(getASTContext().evaluator,
7344+
SimpleDidSetRequest{mutableThis}, false);
7345+
}
7346+
73317347
StaticSpellingKind FuncDecl::getCorrectStaticSpelling() const {
73327348
assert(getDeclContext()->isTypeContext());
73337349
if (!isStatic())

lib/AST/TypeCheckRequests.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -908,7 +908,7 @@ void EnumRawValuesRequest::diagnoseCycle(DiagnosticEngine &diags) const {
908908
}
909909

910910
void EnumRawValuesRequest::noteCycleStep(DiagnosticEngine &diags) const {
911-
911+
912912
}
913913

914914
//----------------------------------------------------------------------------//

lib/SIL/IR/TypeLowering.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2483,6 +2483,8 @@ TypeConverter::getLoweredLocalCaptures(SILDeclRef fn) {
24832483
collectAccessorCaptures(AccessorKind::MutableAddress);
24842484
break;
24852485
case ReadWriteImplKind::Modify:
2486+
case ReadWriteImplKind::StoredWithSimpleDidSet:
2487+
case ReadWriteImplKind::InheritedWithSimpleDidSet:
24862488
collectAccessorCaptures(AccessorKind::Modify);
24872489
break;
24882490
}

lib/Sema/TypeCheckDecl.cpp

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1118,6 +1118,69 @@ EnumRawValuesRequest::evaluate(Evaluator &eval, EnumDecl *ED,
11181118
return std::make_tuple<>();
11191119
}
11201120

1121+
bool SimpleDidSetRequest::evaluate(Evaluator &evaluator,
1122+
AccessorDecl *decl) const {
1123+
1124+
class OldValueFinder : public ASTWalker {
1125+
const ParamDecl *OldValueParam;
1126+
bool foundOldValueRef = false;
1127+
1128+
public:
1129+
OldValueFinder(const ParamDecl *param) : OldValueParam(param) {}
1130+
1131+
virtual std::pair<bool, Expr *> walkToExprPre(Expr *E) override {
1132+
if (!E)
1133+
return {true, E};
1134+
if (auto DRE = dyn_cast<DeclRefExpr>(E)) {
1135+
if (auto decl = DRE->getDecl()) {
1136+
if (decl == OldValueParam) {
1137+
foundOldValueRef = true;
1138+
return {false, nullptr};
1139+
}
1140+
}
1141+
}
1142+
1143+
return {true, E};
1144+
}
1145+
1146+
bool didFindOldValueRef() { return foundOldValueRef; }
1147+
};
1148+
1149+
// If this is not a didSet accessor, bail out.
1150+
if (decl->getAccessorKind() != AccessorKind::DidSet) {
1151+
return false;
1152+
}
1153+
1154+
// didSet must have a single parameter.
1155+
if (decl->getParameters()->size() != 1) {
1156+
return false;
1157+
}
1158+
1159+
auto param = decl->getParameters()->get(0);
1160+
// If this parameter is not implicit, then it means it has been explicitly
1161+
// provided by the user (i.e. 'didSet(oldValue)'). This means we cannot
1162+
// consider this a "simple" didSet because we have to fetch the oldValue
1163+
// regardless of whether it's referenced in the body or not.
1164+
if (!param->isImplicit()) {
1165+
return false;
1166+
}
1167+
1168+
// If we find a reference to the implicit 'oldValue' parameter, then it is
1169+
// not a "simple" didSet because we need to fetch it.
1170+
auto walker = OldValueFinder(param);
1171+
decl->getBody()->walk(walker);
1172+
auto hasOldValueRef = walker.didFindOldValueRef();
1173+
if (!hasOldValueRef) {
1174+
// If the body does not refer to implicit 'oldValue', it means we can
1175+
// consider this as a "simple" didSet. Let's also erase the implicit
1176+
// oldValue as it is never used.
1177+
auto &ctx = decl->getASTContext();
1178+
decl->setParameters(ParameterList::createEmpty(ctx));
1179+
return true;
1180+
}
1181+
return false;
1182+
}
1183+
11211184
const ConstructorDecl *
11221185
swift::findNonImplicitRequiredInit(const ConstructorDecl *CD) {
11231186
while (CD->isImplicit()) {
@@ -1577,6 +1640,12 @@ static ParamDecl *getOriginalParamFromAccessor(AbstractStorageDecl *storage,
15771640
case AccessorKind::DidSet:
15781641
case AccessorKind::WillSet:
15791642
case AccessorKind::Set:
1643+
if (accessor->isSimpleDidSet()) {
1644+
// If this is a "simple" didSet, there won't be
1645+
// a parameter.
1646+
return nullptr;
1647+
}
1648+
15801649
if (param == accessorParams->get(0)) {
15811650
// This is the 'newValue' parameter.
15821651
return nullptr;
@@ -2190,6 +2259,13 @@ InterfaceTypeRequest::evaluate(Evaluator &eval, ValueDecl *D) const {
21902259
case DeclKind::Accessor:
21912260
case DeclKind::Constructor:
21922261
case DeclKind::Destructor: {
2262+
// If this is a didSet, then we need to check whether the body references
2263+
// the implicit 'oldValue' parameter or not, in order to correctly
2264+
// compute the interface type.
2265+
if (auto AD = dyn_cast<AccessorDecl>(D)) {
2266+
(void)AD->isSimpleDidSet();
2267+
}
2268+
21932269
auto *AFD = cast<AbstractFunctionDecl>(D);
21942270

21952271
auto sig = AFD->getGenericSignature();

0 commit comments

Comments
 (0)