Skip to content

Commit 2c2d087

Browse files
committed
Sema: More accurate VarDeclUsageChecker analysis with local functions
We used to take all the captures of a local function and treat them all as read and write usages of vars from an outer scope. Instead, let's refactor the analysis to walk into local functions.
1 parent 93f0faf commit 2c2d087

10 files changed

+54
-68
lines changed

lib/Sema/MiscDiagnostics.cpp

Lines changed: 41 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -2250,13 +2250,10 @@ class VarDeclUsageChecker : public ASTWalker {
22502250

22512251
/// This is a mapping from an OpaqueValue to the expression that initialized
22522252
/// it.
2253-
llvm::SmallDenseMap<OpaqueValueExpr*, Expr*> OpaqueValueMap;
2253+
llvm::SmallDenseMap<OpaqueValueExpr *, Expr *> OpaqueValueMap;
22542254

2255-
/// The getter associated with a setter function declaration.
2256-
const VarDecl *AssociatedGetter = nullptr;
2257-
2258-
/// The first reference to the associated getter.
2259-
const Expr *AssociatedGetterRefExpr = nullptr;
2255+
/// The first reference to the given property.
2256+
llvm::SmallDenseMap<VarDecl *, Expr *> AssociatedGetterRefExpr;
22602257

22612258
/// This is a mapping from VarDecls to the if/while/guard statement that they
22622259
/// occur in, when they are in a pattern in a StmtCondition.
@@ -2272,36 +2269,8 @@ class VarDeclUsageChecker : public ASTWalker {
22722269
void operator=(const VarDeclUsageChecker &) = delete;
22732270

22742271
public:
2275-
VarDeclUsageChecker(AbstractFunctionDecl *AFD)
2276-
: Diags(AFD->getASTContext().Diags) {
2277-
// If this AFD is a setter, track the parameter and the getter for
2278-
// the containing property so if newValue isn't used but the getter is used
2279-
// an error can be reported.
2280-
if (auto FD = dyn_cast<AccessorDecl>(AFD)) {
2281-
if (FD->getAccessorKind() == AccessorKind::Set) {
2282-
if (auto getter = dyn_cast<VarDecl>(FD->getStorage())) {
2283-
auto arguments = FD->getParameters();
2284-
VarDecls[arguments->get(0)] = RK_Defined;
2285-
AssociatedGetter = getter;
2286-
}
2287-
}
2288-
}
2289-
}
2290-
22912272
VarDeclUsageChecker(DiagnosticEngine &Diags) : Diags(Diags) {}
22922273

2293-
VarDeclUsageChecker(VarDecl *vd) : Diags(vd->getASTContext().Diags) {
2294-
// Track a specific VarDecl
2295-
VarDecls[vd] = RK_Defined;
2296-
if (auto *childVd = vd->getCorrespondingCaseBodyVariable().getPtrOrNull()) {
2297-
VarDecls[childVd] = RK_Defined;
2298-
}
2299-
}
2300-
2301-
void suppressDiagnostics() {
2302-
sawError = true; // set this flag so that no diagnostics will be emitted on delete.
2303-
}
2304-
23052274
// After we have scanned the entire region, diagnose variables that could be
23062275
// declared with a narrower usage kind.
23072276
~VarDeclUsageChecker() override;
@@ -2404,23 +2373,31 @@ class VarDeclUsageChecker : public ASTWalker {
24042373
return false;
24052374

24062375
if (auto *afd = dyn_cast<AbstractFunctionDecl>(D)) {
2407-
// If this is a nested function with a capture list, mark any captured
2408-
// variables.
2376+
// If this AFD is a setter, track the parameter and the getter for
2377+
// the containing property so if newValue isn't used but the getter is used
2378+
// an error can be reported.
2379+
if (auto FD = dyn_cast<AccessorDecl>(afd)) {
2380+
if (FD->getAccessorKind() == AccessorKind::Set) {
2381+
if (isa<VarDecl>(FD->getStorage())) {
2382+
auto arguments = FD->getParameters();
2383+
VarDecls[arguments->get(0)] = RK_Defined;
2384+
}
2385+
}
2386+
}
2387+
24092388
if (afd->isBodyTypeChecked()) {
2389+
// FIXME: We don't actually need captures here anymore, but this happens
2390+
// to be the place where they get computed. Move this somewhere else.
24102391
TypeChecker::computeCaptures(afd);
2411-
for (const auto &capture : afd->getCaptureInfo().getCaptures())
2412-
addMark(capture.getDecl(), RK_Read|RK_Written);
2413-
} else {
2414-
// If the body hasn't been type checked yet, be super-conservative and
2415-
// mark all variables as used. This can be improved later, e.g. by
2416-
// walking the untype-checked body to look for things that could
2417-
// possibly be used.
2418-
VarDecls.clear();
2392+
return true;
24192393
}
2420-
2421-
// Don't walk into it though, it may not even be type checked yet.
2394+
2395+
// Don't walk into a body that has not yet been type checked. This should
2396+
// only occur for top-level code.
2397+
VarDecls.clear();
24222398
return false;
24232399
}
2400+
24242401
if (auto *TLCD = dyn_cast<TopLevelCodeDecl>(D)) {
24252402
// If this is a TopLevelCodeDecl, scan for global variables
24262403
auto *body = TLCD->getBody();
@@ -2682,9 +2659,11 @@ VarDeclUsageChecker::~VarDeclUsageChecker() {
26822659
if (auto param = dyn_cast<ParamDecl>(var)) {
26832660
auto FD = dyn_cast<AccessorDecl>(param->getDeclContext());
26842661
if (FD && FD->getAccessorKind() == AccessorKind::Set) {
2685-
auto getter = dyn_cast<VarDecl>(FD->getStorage());
2686-
if ((access & RK_Read) == 0 && AssociatedGetter == getter) {
2687-
if (auto DRE = AssociatedGetterRefExpr) {
2662+
auto VD = dyn_cast<VarDecl>(FD->getStorage());
2663+
if ((access & RK_Read) == 0) {
2664+
auto found = AssociatedGetterRefExpr.find(VD);
2665+
if (found != AssociatedGetterRefExpr.end()) {
2666+
auto *DRE = found->second;
26882667
Diags.diagnose(DRE->getLoc(), diag::unused_setter_parameter,
26892668
var->getName());
26902669
Diags.diagnose(DRE->getLoc(), diag::fixit_for_unused_setter_parameter,
@@ -3028,16 +3007,14 @@ std::pair<bool, Expr *> VarDeclUsageChecker::walkToExprPre(Expr *E) {
30283007

30293008
// If the Expression is a read of a getter, track for diagnostics
30303009
if (auto VD = dyn_cast<VarDecl>(DRE->getDecl())) {
3031-
if (AssociatedGetter == VD && AssociatedGetterRefExpr == nullptr)
3032-
AssociatedGetterRefExpr = DRE;
3010+
AssociatedGetterRefExpr.insert(std::make_pair(VD, DRE));
30333011
}
30343012
}
30353013
// If the Expression is a member reference, see if it is a read of the getter
30363014
// to track for diagnostics.
30373015
if (auto *MRE = dyn_cast<MemberRefExpr>(E)) {
30383016
if (auto VD = dyn_cast<VarDecl>(MRE->getMember().getDecl())) {
3039-
if (AssociatedGetter == VD && AssociatedGetterRefExpr == nullptr)
3040-
AssociatedGetterRefExpr = MRE;
3017+
AssociatedGetterRefExpr.insert(std::make_pair(VD, MRE));
30413018
markBaseOfStorageUse(MRE->getBase(), MRE->getMember(), RK_Read);
30423019
return { false, E };
30433020
}
@@ -3124,18 +3101,22 @@ performTopLevelDeclDiagnostics(TopLevelCodeDecl *TLCD) {
31243101
}
31253102

31263103
/// Perform diagnostics for func/init/deinit declarations.
3127-
void swift::performAbstractFuncDeclDiagnostics(AbstractFunctionDecl *AFD,
3128-
BraceStmt *body) {
3129-
assert(body && "Need a body to check");
3130-
3104+
void swift::performAbstractFuncDeclDiagnostics(AbstractFunctionDecl *AFD) {
31313105
// Don't produce these diagnostics for implicitly generated code.
31323106
if (AFD->getLoc().isInvalid() || AFD->isImplicit() || AFD->isInvalid())
31333107
return;
31343108

31353109
// Check for unused variables, as well as variables that are could be
3136-
// declared as constants.
3137-
body->walk(VarDeclUsageChecker(AFD));
3138-
3110+
// declared as constants. Skip local functions though, since they will
3111+
// be checked as part of their parent function or TopLevelCodeDecl.
3112+
if (!AFD->getDeclContext()->isLocalContext()) {
3113+
auto &ctx = AFD->getDeclContext()->getASTContext();
3114+
VarDeclUsageChecker checker(ctx.Diags);
3115+
AFD->walk(checker);
3116+
}
3117+
3118+
auto *body = AFD->getBody();
3119+
31393120
// If the function has an opaque return type, check the return expressions
31403121
// to determine the underlying type.
31413122
if (auto opaqueResultTy = AFD->getOpaqueResultTypeDecl()) {

lib/Sema/MiscDiagnostics.h

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,8 +40,7 @@ void performSyntacticExprDiagnostics(const Expr *E, const DeclContext *DC,
4040
/// Emit diagnostics for a given statement.
4141
void performStmtDiagnostics(ASTContext &ctx, const Stmt *S);
4242

43-
void performAbstractFuncDeclDiagnostics(AbstractFunctionDecl *AFD,
44-
BraceStmt *body);
43+
void performAbstractFuncDeclDiagnostics(AbstractFunctionDecl *AFD);
4544

4645
/// Perform diagnostics on the top level code declaration.
4746
void performTopLevelDeclDiagnostics(TopLevelCodeDecl *TLCD);

lib/Sema/TypeCheckStmt.cpp

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1972,12 +1972,13 @@ TypeCheckFunctionBodyUntilRequest::evaluate(Evaluator &evaluator,
19721972
}
19731973
}
19741974

1975+
// Wire up the function body now.
1976+
AFD->setBody(body, AbstractFunctionDecl::BodyKind::TypeChecked);
1977+
19751978
// If nothing went wrong yet, perform extra checking.
19761979
if (!hadError && endTypeCheckLoc.isInvalid())
1977-
performAbstractFuncDeclDiagnostics(AFD, body);
1980+
performAbstractFuncDeclDiagnostics(AFD);
19781981

1979-
// Wire up the function body now.
1980-
AFD->setBody(body, AbstractFunctionDecl::BodyKind::TypeChecked);
19811982
return hadError;
19821983
}
19831984

test/SILGen/c_function_pointers.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
// RUN: %target-swift-emit-silgen -verify %s | %FileCheck %s
22

33
if true {
4-
var x = 0
4+
var x = 0 // expected-warning {{variable 'x' was never mutated; consider changing to 'let' constant}}
55
func local() -> Int { return 0 }
66
func localWithContext() -> Int { return x }
77
func transitiveWithoutContext() -> Int { return local() }

test/SILGen/capture_order.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ public func captureBeforeDefUnownedVar(obj: AnyObject) -> () -> AnyObject? {
4747
}
4848
let closure = getter
4949
unowned var unownedObj: AnyObject = obj // expected-note{{captured value declared here}}
50+
// expected-warning@-1 {{variable 'unownedObj' was never mutated; consider changing to 'let' constant}}
5051
return closure
5152
}
5253

test/SILGen/nested_types_referencing_nested_functions.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ do {
3636
// Invalid case
3737
do {
3838
var x = 123 // expected-note {{captured value declared here}}
39+
// expected-warning@-1 {{variable 'x' was never mutated; consider changing to 'let' constant}}
3940

4041
func local() {
4142
// expected-error@-1 {{closure captures 'x' before it is declared}}

test/SILGen/statements.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -525,6 +525,7 @@ func defer_in_closure_in_generic<T>(_ x: T) {
525525
// CHECK-LABEL: sil hidden [ossa] @$s10statements13defer_mutableyySiF
526526
func defer_mutable(_ x: Int) {
527527
var x = x
528+
// expected-warning@-1 {{variable 'x' was never mutated; consider changing to 'let' constant}}
528529
// CHECK: [[BOX:%.*]] = alloc_box ${ var Int }
529530
// CHECK-NEXT: project_box [[BOX]]
530531
// CHECK-NOT: [[BOX]]

test/decl/var/properties.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -930,7 +930,7 @@ class ObservingPropertiesNotMutableInWillSet {
930930
}
931931

932932
func localCase() {
933-
var localProperty: Int = 42 {
933+
var localProperty: Int = 42 { // expected-warning {{variable 'localProperty' was written to, but never read}}
934934
willSet {
935935
localProperty = 19 // expected-warning {{attempting to store to property 'localProperty' within its own willSet}}
936936
}

test/decl/var/usage.swift

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -426,9 +426,10 @@ func testLocalFunc() {
426426
// expected-warning@-1 {{initialization of variable 'unusedVar' was never used; consider replacing with assignment to '_' or removing it}}
427427

428428
var notMutatedVar = 0
429-
// FIXME: All captures are read/write for the purposes of this analysis.
429+
// expected-warning@-1 {{variable 'notMutatedVar' was never mutated; consider changing to 'let' constant}}
430430

431431
var mutatedVar = 0
432+
// expected-warning@-1 {{variable 'mutatedVar' was written to, but never read}}
432433

433434
func localFunc() {
434435
_ = notMutatedVar

test/stmt/statements.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ func invalid_semi() {
1515

1616
func nested1(_ x: Int) {
1717
var y : Int
18+
// expected-warning@-1 {{variable 'y' was never mutated; consider changing to 'let' constant}}
1819

1920
func nested2(_ z: Int) -> Int {
2021
return x+y+z

0 commit comments

Comments
 (0)