After thinking about it, I think that Opt is probably the right place to do this.
(I suspect you thought of a lot of this, and that it just took me some time to wrap my head around what you were intending).
What we need to define is some sort of Accessor functions, which can be stored as Expression.Opt, or which are possibly stored in an Opt.Def. This would look something like this:
data Accessor =
ADTAccess Int Expr --get the nth field of an ADT
We would transform Let statements as follows:
let
(x,y) =
f arg1 arg2
in
body
--translated into
let
_v0 = f arg1 arg2
x = (ADTAccess _v0 )
y = (ADTAccess 2 _v1)
A record access could be translated into the usual dot notation:
let
{x,y} =
f arg1 arg2
in
body
--translated into
let
_v0 = f arg1 arg2
x = _v0.x
y = _v0.y
Possible benefits of this approach:
-
If a variable is only used once, we could inline-away the extra assignment, and just use the accessor
-
If a pattern match is irrefutable, there's an easy way to express in optimization that the match is unneeded.
case somePattern of P x y -> f x y -- If P is only constructor, this becomes let x = (ADTAccess 0 somePattern) y = (ADTAccess 1 somePattern) in f x y
We need a special AST Expr variant for pattern access. In particular, we need to make sure that the user does not have access to it, because it is inherently not type-safe.