Skip to content

Commit d3b5996

Browse files
authored
[Sema] Diagnose explicit access to a lazy variable's underlying storage (#33144)
1 parent b378baa commit d3b5996

File tree

4 files changed

+82
-0
lines changed

4 files changed

+82
-0
lines changed

include/swift/AST/DiagnosticsSema.def

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3009,6 +3009,8 @@ ERROR(lazy_must_be_property,none,
30093009
"lazy is only valid for members of a struct or class", ())
30103010
ERROR(lazy_not_strong,none,
30113011
"lazy properties cannot be %0", (ReferenceOwnership))
3012+
ERROR(lazy_var_storage_access,none,
3013+
"access to the underlying storage of a lazy property is not allowed", ())
30123014

30133015
// Debugger function attribute.
30143016
ERROR(attr_for_debugger_support_only,none,

lib/Sema/MiscDiagnostics.cpp

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4390,6 +4390,53 @@ static void maybeDiagnoseCallToKeyValueObserveMethod(const Expr *E,
43904390
const_cast<Expr *>(E)->walk(Walker);
43914391
}
43924392

4393+
static void diagnoseExplicitUseOfLazyVariableStorage(const Expr *E,
4394+
const DeclContext *DC) {
4395+
4396+
class ExplicitLazyVarStorageAccessFinder : public ASTWalker {
4397+
const ASTContext &C;
4398+
4399+
public:
4400+
ExplicitLazyVarStorageAccessFinder(ASTContext &ctx) : C(ctx) {}
4401+
4402+
void tryDiagnoseExplicitLazyStorageVariableUse(MemberRefExpr *MRE) {
4403+
if (MRE->isImplicit()) {
4404+
return;
4405+
}
4406+
auto VD = dyn_cast<VarDecl>(MRE->getMember().getDecl());
4407+
if (!VD) {
4408+
return;
4409+
}
4410+
auto sourceFileKind = VD->getDeclContext()->getParentSourceFile();
4411+
if (!sourceFileKind) {
4412+
return;
4413+
}
4414+
if (sourceFileKind->Kind != SourceFileKind::Library &&
4415+
sourceFileKind->Kind != SourceFileKind::Main) {
4416+
return;
4417+
}
4418+
if (VD->isLazyStorageProperty()) {
4419+
C.Diags.diagnose(MRE->getLoc(), diag::lazy_var_storage_access);
4420+
}
4421+
}
4422+
4423+
std::pair<bool, Expr *> walkToExprPre(Expr *E) override {
4424+
if (!E || isa<ErrorExpr>(E) || !E->getType())
4425+
return {false, E};
4426+
4427+
if (auto *MRE = dyn_cast<MemberRefExpr>(E)) {
4428+
tryDiagnoseExplicitLazyStorageVariableUse(MRE);
4429+
return {false, E};
4430+
}
4431+
4432+
return {true, E};
4433+
}
4434+
};
4435+
4436+
ExplicitLazyVarStorageAccessFinder Walker(DC->getASTContext());
4437+
const_cast<Expr *>(E)->walk(Walker);
4438+
}
4439+
43934440
//===----------------------------------------------------------------------===//
43944441
// High-level entry points.
43954442
//===----------------------------------------------------------------------===//
@@ -4405,6 +4452,7 @@ void swift::performSyntacticExprDiagnostics(const Expr *E,
44054452
diagnoseImplicitSelfUseInClosure(E, DC);
44064453
diagnoseUnintendedOptionalBehavior(E, DC);
44074454
maybeDiagnoseCallToKeyValueObserveMethod(E, DC);
4455+
diagnoseExplicitUseOfLazyVariableStorage(E, DC);
44084456
if (!ctx.isSwiftVersionAtLeast(5))
44094457
diagnoseDeprecatedWritableKeyPath(E, DC);
44104458
if (!ctx.LangOpts.DisableAvailabilityChecking)

localization/diagnostics/en.yaml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1993,6 +1993,9 @@
19931993
msg: >-
19941994
cannot declare entity named %0; the '$' prefix is reserved for
19951995
implicitly-synthesized declarations
1996+
- id: lazy_var_storage_access
1997+
msg: >-
1998+
access to the underlying storage of a lazy property is not allowed
19961999
19972000
- id: anon_closure_arg_not_in_closure
19982001
msg: >-

test/decl/var/lazy_properties.swift

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -189,3 +189,32 @@ class ReferenceStaticInLazyProperty {
189189
static var i = 42
190190
static func f() -> Int { return 0 }
191191
}
192+
193+
// Explicit access to the lazy variable storage
194+
class LazyVarContainer {
195+
lazy var foo: Int = {
196+
return 0
197+
}()
198+
199+
func accessLazyStorage() {
200+
$__lazy_storage_$_foo = nil // expected-error {{access to the underlying storage of a lazy property is not allowed}}
201+
print($__lazy_storage_$_foo!) // expected-error {{access to the underlying storage of a lazy property is not allowed}}
202+
_ = $__lazy_storage_$_foo == nil // expected-error {{access to the underlying storage of a lazy property is not allowed}}
203+
}
204+
}
205+
206+
// Make sure we can still access a synthesized variable with the same name as a lazy storage variable
207+
// i.e. $__lazy_storage_$_{property_name} when using property wrapper where the property name is
208+
// '__lazy_storage_$_{property_name}'.
209+
@propertyWrapper
210+
struct Wrapper {
211+
var wrappedValue: Int { 1 }
212+
var projectedValue: Int { 1 }
213+
}
214+
215+
struct PropertyWrapperContainer {
216+
@Wrapper var __lazy_storage_$_foo
217+
func test() {
218+
_ = $__lazy_storage_$_foo // This is okay.
219+
}
220+
}

0 commit comments

Comments
 (0)