Open
Description
We could have a flow-sensitive ISL-typed builder.
Consider the following:
pub struct TypeDefinitionBuilder<V: IslVersion, T: IslType> {
pub(crate) constraints: Vec<AnyConstraint>,
open_content: Vec<(Symbol, IonData<Element>)>,
isl_version: PhantomData<V>,
isl_type: PhantomData<T>,
}
We could create a set of empty types and marker traits:
enum UnknownType
enum NumberType
enum DecimalType
enum IntegerType
enum FloatType
trait CouldBeDecimalType {}
impl CouldBeDecimalType for DecimalType {}
impl CouldBeDecimalType for UnknownType {}
impl CouldBeDecimalType for NumberType {}
And then use generic builder method implementations that can transition the builder's type-state.
impl <V: IslVersion, T: CouldBeDecimalType> TypeDefinitionBuilder<V, T> {
pub fn precision(self, range: Range<usize>) -> TypeDefinitionBuilder<V, DecimalType> {
let { mut constraints, open_content, isl_version, _ } = self;
constraints.push(AnyConstraint::from(PrecisionConstraint::new(range)));
TypeDefinitionBuilder { constraints, open_content, isl_version, isl_type: Phantom::<DecimalType>::default() }
}
}
That would allow things like this to compile successfully:
let my_type = TypeBuilder::new()
.annotations(Closed, ["foo"])
.precision(0..=10)
.exponent(-2)
.build();
But it would cause a compilation error for cases like this:
let my_type = TypeBuilder::new()
.annotations(Closed, ["foo"])
.precision(0..=10)
.exponent(-2)
.timestamp_offset(["+00:00"])
.build();
This is probably not suitable for all use cases, so we would probably need to add a flow-sensitive builder as an option that complements rather than replaces a naive builder.