Field Accessors #238
Description
This is a suggestion for a different way to design the instructions for getting/setting fields, which for lack of a better term I'll refer to as "field accessors".
Design
After processing the types section of the module, we'll get two lists: the list of types, and the list of field accessors. When processing a (nominal) type in the types section, for every field of the type a corresponding field accessor is automatically added to the field-accessor list. The type of the field accessor depends on the qualities of the field, e.g. what is its value type, and is the field writable? The "receiver" type of the field accessor is whatever (nominal) type it was generated from.
So, if one defines a nominal type, say $Foo
, with a mutable i32
field followed by an immutable i64
field, then there will also be two field accessors, say $x
and $y
, where the type of $x
is "receiver type is $Foo
, value type is i32
, and mutability is mutable
" and the type of $y
is "receiver type is $Foo
, value type is i64
, and mutability is immutable
". The text format could provider room for declaring the field-accessor names $x
and $y
, which get bound to their corresponding indices in the generated field-accessor list.
Then the instructions for getting and setting a field simply include a field-accessor index, rather than both a type and an index. The type of the corresponding field accessor determines the receiver type that must be on the stack and the value type that either is returned (for getting) or must be on the stack (for setting). For setting, the field accessor must also be mutable.
To clarify, this is not the same as the field members described in the Post-MVP. Field members in the Post-MVP are essentially the first-class variant on field accessors.
Motivation
Short-term reasons to use field accessors:
- Primary: Reduces code size without introducing any type-checking complexity: the get/set instructions now have only one index rather than two.
- The names section can give a name to each field accessor, which is helpful for debugging
- I also wouldn't be surprised if it's more straightforward/efficient to compile and interpret.
Longer-term reasons to use field accessors:
- Another way to reduce code size is to simply synthesize the receiver's type rather than explicitly specify its index in the get/set instruction, but that runs into type-checking complexity issues when combined with extensions like multiple upper-bounds (as described in Complications with multiple upper bounds #160), whereas field accessors retain efficient type-checking even in the presence of such extensions.
- Field accessors make it easy for one to import just the fields of another module's types that it actually depends on, and similarly to give other modules access to just the public fields of one's own types without having to also give them access to private fields.
Accompanying Extension
Field accessors would also facilitate another extension or variation. When one defines a nominal type that extends another nominal type, we could avoid repeating the fields of the other nominal type and simply state the new fields of the new type. In the short term, this would reduce the size of the types section. In the long term, this would facilitate separate compilation of subclasses (particularly in the presence of private fields of the superclass).
Whether we actually want this accompanying extension should be discussed later and somewhere else; I just mention it here as another motivation for field accessors.
Transfer
Although I came up with this for nominal types, the idea likely transfers to structural types.