As I’ve been working on implementing PBetterLocus I’ve run into an issue with PType Inference. There currently seem to be no facilities for converting between values of two distinct PTypes that share the same virtual type.
In the course of thinking about this, I’ve developed an interface that I think seems reasonable at least for staged code:
abstract class PLocusCode {
// Add these two methods
// This one takes a region because it may need to allocate
final def toCanonical(mb: EmitMethodBuilder[_], region: Value[Region]
): PCanonicalLocusCode = {
val npt = PCanonicalLocus(rg, pt.required)
this match {
case _: PCanonicalLocusCode =>
this
case pc =>
val code = EmitCodeBuilder.scopedCode[Long](mb) { cb =>
val pv = pc.memoize(cb, "locus_val")
// PCanonicalLocus' fields are required so this naked
// allocation is fine
val addr = cb.newLocal("addr",
npt.representation.allocate(region))
cb += pv.contig(mb).store(mb, region, npt.contigAddr(addr))
cb += Region.storeInt(npt.positionAddr(addr), pv.position())
addr
}
new PCanonicalLocusCode(newPType, code)
}
}
// This one doesn't because it doesn't need to allocate
final def toBetter(mb: EmitMethodBuilder[_]): PBetterLocusCode = {
val newPtype = PBetterLocusCode(pt.rg, pt.required)
this match {
case _: PBetterLocusCode =>
this
case pc =>
val code = EmitCodeBuilder.scopedCode[Long](mb) { cb =>
val rg = mb.getReferenceGenome(pt.rg)
val pv = pc.memoize(cb, "locus_val")
val contigIdx = rgc.invoke[String, Int]("contigIndex",
pv.contig(mb).loadString())
(contigIdx.toL << 32) | pv.position().toL
}
new PBetterLocusCode(newPType, code)
}
}
}
// And then on PCode
class PCode {
def convert(toPt: PType,
mb: EmitMethodBuilder[_],
region: Value[Region]
): PCode {
// FIXME requiredness??
assert(toPt isOfType pt)
toPt match {
case _: PCanonicalLocus => asLocus.toCanonical(mb, region)
case _: PBetterLocus => asLocus.toBetter(mb)
// ...
}
}
}
Thoughts? This seems to be extensible in a way that won’t make it onerous, like adding cases for all the copyFrom* methods on PType.