Hey folks, I've been taking a pass at prototyping support for implicit self in [weak self]
closures (discussed briefly on the Evolution forums earlier this year):
{ [weak self] in
guard let self = self else { return }
// implicit self should be allowed, since self has been unwrapped
// e.g. this is equivalent `self.dismiss()`:
dismiss()
}
Permitting this specific example to compile is somewhat trivial, and I was able to get this to work without much trouble.
I've run into a snag, though, with figuring out whether or not self
has been unwrapped. For example:
{ [weak self] in
// implicit self should not be allowed, since self has not been unwrapped
dismiss()
guard let self = self else { return }
// implicit self should be allowed, since self has been unwrapped
dismiss()
}
After type checking, both of these CallExpr
s have a the same implicit DeclRefExpr
, with a non-optional type:
(declref_expr implicit type='TypeContainingClosure' function_ref=unapplied)
// type is:
(class_type decl=TypeContainingClosure...)
Full closure expression
(lldb) p closureExpr->dump()
(closure_expr type='() -> ()' location=/Users/cal/swift-samples/weak.swift:2:35 range=[/Users/cal/swift-samples/weak.swift:2:35 - line:6:3] discriminator=0 escaping
(parameter_list range=[/Users/cal/swift-samples/weak.swift:2:35 - line:2:35])
(brace_stmt range=[/Users/cal/swift-samples/weak.swift:2:35 - line:6:3]
(call_expr type='()' location=/Users/cal/swift-samples/weak.swift:3:5 range=[/Users/cal/swift-samples/weak.swift:3:5 - line:3:13]
(dot_syntax_call_expr implicit type='() -> ()' location=/Users/cal/swift-samples/weak.swift:3:5 range=[/Users/cal/swift-samples/weak.swift:3:5 - line:3:5]
(declref_expr type='(View) -> () -> ()' location=/Users/cal/swift-samples/weak.swift:3:5 range=[/Users/cal/swift-samples/weak.swift:3:5 - line:3:5] decl=weak.(file).View.dismiss()@/Users/cal/swift-samples/weak.swift:8:8 function_ref=single)
(argument_list implicit
(argument
(declref_expr implicit type='View' location=/Users/cal/swift-samples/weak.swift:3:5 range=[/Users/cal/swift-samples/weak.swift:3:5 - line:3:5] decl=weak.(file).View.pattern binding initializer.self@/Users/cal/swift-samples/weak.swift:2:12 function_ref=unapplied))
))
(argument_list))
(guard_stmt range=[/Users/cal/swift-samples/weak.swift:4:5 - line:4:41]
(pattern
(pattern_optional_some implicit type='View?'
(pattern_let implicit type='View'
(pattern_named type='View' 'self')))
(load_expr implicit type='View?' location=/Users/cal/swift-samples/weak.swift:4:22 range=[/Users/cal/swift-samples/weak.swift:4:22 - line:4:22]
(declref_expr type='@lvalue View?' location=/Users/cal/swift-samples/weak.swift:4:22 range=[/Users/cal/swift-samples/weak.swift:4:22 - line:4:22] decl=weak.(file).View.pattern binding initializer.self@/Users/cal/swift-samples/weak.swift:2:43 function_ref=unapplied)))
(brace_stmt range=[/Users/cal/swift-samples/weak.swift:4:32 - line:4:41]
(return_stmt range=[/Users/cal/swift-samples/weak.swift:4:34 - line:4:34])))
(call_expr type='()' location=/Users/cal/swift-samples/weak.swift:5:5 range=[/Users/cal/swift-samples/weak.swift:5:5 - line:5:13]
(dot_syntax_call_expr implicit type='() -> ()' location=/Users/cal/swift-samples/weak.swift:5:5 range=[/Users/cal/swift-samples/weak.swift:5:5 - line:5:5]
(declref_expr type='(View) -> () -> ()' location=/Users/cal/swift-samples/weak.swift:5:5 range=[/Users/cal/swift-samples/weak.swift:5:5 - line:5:5] decl=weak.(file).View.dismiss()@/Users/cal/swift-samples/weak.swift:8:8 function_ref=single)
(argument_list implicit
(argument
(declref_expr implicit type='View' location=/Users/cal/swift-samples/weak.swift:5:5 range=[/Users/cal/swift-samples/weak.swift:5:5 - line:5:5] decl=weak.(file).View.pattern binding initializer.self@/Users/cal/swift-samples/weak.swift:2:12 function_ref=unapplied))
))
(argument_list))))
With the type information currently available, there isn't an obvious way to reject the first call (where self
is actually not unwrapped yet).
My thought process here is that we'd probably need to update type checking for implicit self references, so the type is set to an (optional_type (class_type ...))
in the first case but a (class_type ...)
in the second case.
Does that sound like the right approach? If so, pointers on how to get started with this would be very helpful.
Would I need to add a new constraint to represent this in the constraint system? Or is this something that can be looked up in somewhere like TypeChecker::lookupUnqualified
? (I think this eventually calls into PatternBindingInitializer::getImplicitSelfDecl()
, which populates the type of the self
reference using the DeclContext
, but it doesn't seem like we'd have enough context here to know if self
has actually been unwrapped yet).