I'm working on implementation for https://github.com/nickolas-pohilets/swift-evolution/blob/master/proposals/NNNN-closures-as-structs.md. I'm pretty new to the compiler codebase, and still feeling a bit lost, but slowly making my way through.
I would appreciate some feedback on my implementation plan so far and some help with filling in the gaps.
Proposal describes being able to satisfy potential any protocol requirement with closure body. I'm not sure this is solvable in general case, so for now I've limited my scope only to a single
Implementation consists of two parts:
- Detect if closure should be transformed at all, and if it should - what protocols it should conform to, which protocol requirement should be satisfied by the closure body, and types for associated types used in the signature of the protocol requirement.
- Do the AST re-writing.
This is mostly about constraint solver.
I'm planning to introduce a new type, something like
ClosureAsStructType. This type should be used as a placeholder for the type of the structure created. If closure got assigned this type after type-checking - that means it should be re-writen. After re-writing this type will disappear. But depending on where re-writing will happen, it may escape from the constraint solver. Do I understand correctly that in order to be
UNCHECKED_TYPE it should not escape from constraint solver and be allocated in the constraint solver area?
Current logic always assigns
FunctionType to a closure. I'm planning to assign a fresh type variable to it, and create a disjunction of two constraints equality constraints - either to
FunctionType, or to a
ClosureAsStructType. Or should I keep
FunctionType assigned to a closure, and assign type variable to something else?
I suspect assigning a fresh type variable to every closure may cause a performance regression. But for now I'm concerned about getting something working, and will return to performance optimisations later.
I'm planning to update the score calculation system, so that overloads where closure literal matches with function type should win against overloads where closure get-s re-written. That's mostly for backward compatibility and can be reversed later. If we ever make function types true protocols, should be prioritise them differently compared to hand-written protocols? Or should we prioritise between generic vs existential types? Looks like currently existentials win for regular protocols (but I suspect there might be nuances), which is consistent with current decision to prefer function types to closure-as-struct.
When processing protocol conformance constraint against a
ClosureAsStructType, constraint is always solved (unless protocol requires a class), but matched protocol is recorded for the future processing. I should be able to backtrack matched protocols, so looks like it should be stored in the
ConstraintSystem and reverted by the
I don't know yet how to handle protocols that don't have suitable requirement or where requirement is ambiguous. Probably they should be silently ignored if there are other overloads available.
To validate protocol requirements, I need to know that I've seen all the protocol requirements. How can I ensure that in the constraint solver?
Also, I need to match
FunctionType of the closure with the type of the protocol requirement. I think I can handle this by creating a bunch of constraints of existing kinds when processing protocol requirements. But maybe this should be a new kind of constraint between
I need to keep track of association between
ClosureAsStructType and related
FunctionType of the closure. Can I store reference to
FunctionType inside the
ClosureAsStructType , or should I use locators or some other mechanism?
I don't yet fully understand how
SolverStep's work. I'll keep reading the code, but maybe there are any other sources I could check? https://github.com/apple/swift/blob/master/docs/TypeChecker.rst does not seem to cover this.
First of all, what would be the place to do the re-writing? Should it happen when applying constraint solver solution or later?
I'll need to re-write closure expression into something which is an expression, but may contain function declaration inside. Any alternatives to wrapping all this into an immediately invoked closure? Can I even construct an AST for closure with return type that is declared inside the closure body? Alternatively I can sacrifice locality of the re-writing and put all the declarations for anonymous structs in the top of the parent scope and re-write closure expressions into sole structure instance construction.
Re-writing calls to
super may a bit challenging. Will
SuperRefExpr work fine, if it's
Self is not an actual
self of the struct, but a property in the struct?