i have an outstanding draft PR that i've been trying to progress recently in which i'm looking to add diagnostics for situations like the following (example copied from this issue):
class ClosureWrapper {
var closure: () -> Void = {}
init() {
self.closure = {
let otherClosure: () -> Void = { [weak self] in
self?.closure = {}
}
}
}
deinit { print("deinit") }
}
var instance: ClosureWrapper? = ClosureWrapper()
instance = nil // lost, but not deinitialized
i'd like to emit a warning diagnostic in such cases along the lines of:
test.swift:8:52: warning: 'weak' capture list item 'self' implicitly captured as strong reference in outer scope
5 | var closure: () -> Void = {}
6 | init() {
7 | self.closure = {
| `- note: 'self' implicitly captured here
8 | let otherClosure: () -> Void = { [weak self] in
| `- warning: 'weak' capture list item 'self' implicitly captured as strong reference in outer scope
9 | self?.closure = {}
10 | }
the intent of the programmer in such cases is presumably to avoid strongly capturing the reference by using a weak capture list item, but since the capture list item is nested, its presence induces a strong capture in the outer closure.
in order to emit an appropriate warning, we need to be able to adequately characterize the situation we're looking to diagnose. colloquially it's something like:
- there's a 'weakified'[1] capture list item nested in an escaping closure
- there are no other 'explicit' references to the 'weakified' capture's referent
to try and make this somewhat more precise, here's the algorithm as currently implemented in the PR:
đ Click for some pseudocode
- use an ASTWalker to walk expressions in
MiscDiagnostics.cpp
- when visiting a
Decl
(pre)- check if the decl is a closure list item with the properties:
- it is nested in a parent escaping closure
- it has weak/unowned reference ownership
- it is initialized via a
DeclRefExpr
whose referencedDecl
has strong reference ownership
- if the
Decl
meets these criteria, append aWeakToStrongCaptureItemInfo
entry that contains:- the
VarDecl
of the capture item - the
ValueDecl
of the item's referent - the
DeclRefExpr
from the capture binding - the
ReferenceOwnership
of the capture list item
- the
- check if the decl is a closure list item with the properties:
- when visiting an
Expr
(pre)- if the node is an
AbstractClosureExpr
- push it onto a stack tracking the current closure context
- note whether it is an escaping closure
- if the node is a
DeclRefExpr
- add it to a map from the current closure context to a set of all
DeclRefExprs
(transitively) contained within this closure context
- add it to a map from the current closure context to a set of all
- if the node is an
- when visiting an
Expr
(post)- if the node is an
AbstractClosureExpr
- pop the top closure of our stack
- if the stack isn't empty, add the DREs from the popped context into the new top-of-stack
- if the node is an
- when visiting a
- after collecting data from the
ASTWalker
, attempt to diagnose by doing the following:- for each recorded
WeakToStrongCaptureItemInfo
- walk up the
DeclContext
hierarchy starting from that of the capture item to that of its referent - when we encounter a
DeclContext
that is anAbstractClosureExpr
:- if the context is both escaping and has none of its own capture list items that reference the original item's referent, record it as a candidate for the diagnostic
- for each
DeclRefExpr
associated with this closure context, check if it:- refers to the capture item's referent
- is not an expression from the initializer of a 'weakified' capture list item
- if these properties hold, then the reference is assumed to be 'explicit' and imply an intentional strong capture of the 'weakified' capture item, so we will not diagnose it
- conversely, if they do not, then we continue the search for additional references
- once we finish the
DeclContext
walk, if we've found no 'explicit' references, then we emit the diagnostic warning
- walk up the
- for each recorded
i'd appreciate input on any of the following (or, really, anything that comes to mind on this subject):
- does the logic generally seem correct for the intent of the diagnostic?
- does the current implementation algorithm seem reasonable?
- it's almost certainly not the most efficient... but also perhaps not the easiest to follow.
- i can imagine an alternate approach where we walk the AST multiple times; once to find things we may want to diagnose, and then again (perhaps repeatedly) to see if we should actually do so.
- are there cases in which this will produce false positives or negatives?
- e.g. local functions â i haven't thought too much about how these should be handled, or their impact on the current approach.
thanks in advance for your time and consideration!
i.e. a capture of a strong variable bound to a less-than-strong capture list item. alternative jargon suggestions welcome... âŠī¸