Approaches for rejecting implicit self when self is optional?

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 CallExprs 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).

4 Likes

I was able to get this to work! (fix here)

Basically, it was always creating a DeclRefExpr with a non-optional type for implicit self calls. I updated this to an UnresolvedDeclRefExpr referencing self (without a type at this point), and the type checker was able to figure things out from there -- it's correctly populated as an optional if self hasn't been unwrapped yet.

1 Like