Pattern Boldness
At the moment, mutagen only considers
top-level idents in function arguments (e.g.foo(x: X, y: Y)
), but function
arguments are actually patterns, so we could have foo((x, y): (X, Y))
or
bar(Bar { bla, bazz } : Bar)
. For now, this means we have no type information
for either of those examples.
(Note that this only applies to structs for now, as enums cannot be destructured infallibly)
So what can we do? We can try to destructure the patterns, but then what should happen with the types (we need those to check if args are interchangeable)? We need to add the additional information on where in that type the pattern is, that’s what!
Since we have tuples, tuple structs and “normal” structs as well as references, we need three ways of addressing:
/// an (partial) occurrence of a pattern position in the given type
#[derive(Clone, Copy, Eq, Hash, Ord, PartialEq, PartialOrd)]
enum TyOcc {
Field(Ident),
Index(usize),
Deref
}
Now we can encode a pattern’s type+position as (&Ty, Vec<TyOcc>)
and
compare them. This means we can make a
and c
as well as b
and d
in
fn bar(Foo(a, b), Foo(c, d))
interchangeable, even if we don’t know what a
Foo
is. In this case, both would have the type/occ tuple (&Foo, Index(0))
and (&Foo, Index(1))
, respectively.
We could extend this scheme to look into structs we see to store their content
types, which would allow us to e.g. know that Foo.0
is a Bar
, which might
be our return type, but our users most likely keep the scope of mutations
small, so we don’t normally expect to encounter any types anyway. At least with
tuples and slices, we know the constitutent types.
So we walk the patterns and types in lock-step and try our best to keep both in
sync, adding TyOcc
s wherever we fail. Once we have any occurrence marked, we
are no longer able to tell anything about the types in play, so we will have to
bail there. To understand why this is needed, consider the following function
declaration: fn bad(Doodad { &mut badum, bar } = Doodad)
. If we try to strip
the &mut
from the type of badum
, we fail (Worse, we might succeed in some
scenarios, ending up with wrong types). So we best be conservative and give
badum
the tuple (&Doodad, vec![Field(badum), Deref])
.
Even with this simple scheme, we get a fair amount of code just to “walk the walk”. This is the price we who work with the syntax pay to allow you users to write beautiful low-level code. Of course, this would be much easier with languages like Java or Brainfuck (do I sense a strawman argument here?), but I set out to mutate Rust code, I better deal with the complexity. You’re welcome!