Small standalone POC project for exercising the current comptime
implementation in the sibling ../rescript checkout.
This POC is intended to run against
github.com/mununki/rescript on the poc-comptime branch.
The public surface used here is intentionally small:
%comptime(...)on top-levelletbindingstype t = %comptime(...)for generated type aliasesreflect()field.name,field.typ,field.get(value)item.index,item.typ,item.get(value)constructor.name,constructor.payload,constructor.unpack(value),constructor.make(payload)- anonymous module witnesses such as
module({type t = user})for type-level reflection
The sample includes generic:
makeJsonEncodermakeJsonDecodermakeCopymakeAllCasesmakeVariantFromRecordmakeOptionalRecordmakeRecordFromVariant
and applies them to:
- records
- tuples
- ordinary variants
listresult- generated type aliases derived from existing reflected types
The source tree is split by use case:
src/EncoderSamples.res:makeJsonEncodersrc/DecoderSamples.res:makeJsonDecodersrc/CopySamples.res:makeCopysrc/AllCasesSamples.res:makeAllCasessrc/VariantFromRecordSamples.res:makeVariantFromRecordsrc/OptionalRecordSamples.res:makeOptionalRecordsrc/RecordFromVariantSamples.res:makeRecordFromVariantsrc/ComptimeValues.res: direct compile-time evaluation examples
Each sample module logs its own values at top level, so you can inspect a single example directly with Node:
node src/EncoderSamples.mjs
node src/DecoderSamples.mjs
node src/CopySamples.mjs
node src/AllCasesSamples.mjs
node src/VariantFromRecordSamples.mjs
node src/OptionalRecordSamples.mjs
node src/RecordFromVariantSamples.mjs
node src/ComptimeValues.mjs- The compiler repo lives at
/Users/mununki/github/mununki/rescript - That checkout is
github.com/mununki/rescripton thepoc-comptimebranch - That checkout has already been rebuilt after the
comptimechanges
From the compiler checkout:
cd /Users/mununki/github/mununki/rescript
opam exec -- dune build @install
node scripts/copyExes.js --compilerThen in this POC project:
cd /Users/mununki/github/mununki/rescript-comptime-poc
pnpm install
pnpm build
pnpm testThe project uses local link: dependencies, so node_modules/rescript points
at the sibling compiler checkout.
When a comptime helper needs to return an actual record value, the intended surface stays close to ordinary ReScript record syntax:
let finish = (_obj, builder) => Some(builder)
let addField = (next, field) =>
(obj, r) =>
switch Dict.get(obj, field.name) {
| Some(valueJson) =>
switch decodeByType(field.typ, valueJson) {
| Some(value) => next(obj, {...r, field: value})
| None => None
}
| None => None
}
let seed = {}
Array.reduceRight(fields, finish, addField)(obj, seed)This keeps the decoder-style "build a record from reflected fields" path close to ordinary record update syntax. The compiler still lowers it to its existing internal builder representation.
The POC includes a generated type alias that turns a record into a variant
without introducing a separate Type.* builder API:
type userFieldValue = %comptime(
{
let makeVariantFromFields = fields =>
Variant({
constructors:
fields->Array.map(field =>
Constructor({
name: field.name->String.capitalize,
payload: Single(field.typ),
})
),
})
let makeVariantFromRecord = _witness =>
switch reflect() {
| Record({fields}) => makeVariantFromFields(fields)
| _ => failwith("userFieldValue only supports records")
}
makeVariantFromRecord(module({type t = user}))
}
)Additional generated-type samples:
type r0 = {name: string, age: int}
type optionalValueR0 = %comptime({
let makeOptionRecord = _witness =>
switch reflect() {
| Record({fields}) =>
Record({
fields:
fields->Array.map(field => {
name: field.name,
typ: Option(field.typ),
}),
})
| _ => failwith("makeOptionRecord only supports records")
}
makeOptionRecord(module({type t = r0}))
})
type optionalFieldR0 = %comptime({
let makeOptionalFieldRecord = _witness =>
switch reflect() {
| Record({fields}) =>
Record({
fields:
fields->Array.map(field => {
name: field.name,
typ: Optional(field.typ),
}),
})
| _ => failwith("makeOptionalFieldRecord only supports records")
}
makeOptionalFieldRecord(module({type t = r0}))
})
type v0 =
| Name(string)
| Age(int)
type r0 = %comptime({
let makeRecordFromVariant = _witness =>
switch reflect() {
| Variant({constructors}) =>
Record({
fields:
constructors->Array.map(constructor => {
name: switch constructor.name {
| "Name" => "name"
| "Age" => "age"
| _ => failwith("unsupported constructor")
},
typ: switch constructor.payload {
| Single(desc) => desc
| _ => failwith("single-payload constructors only")
},
}),
})
| _ => failwith("makeRecordFromVariant only supports variants")
}
makeRecordFromVariant(module({type t = v0}))
})three and greeting are valid %comptime(...) examples, but their final JS is
not proof by itself because ordinary compiler optimizations can also
constant-fold simple expressions.
The direct proof is the commented example in src/ComptimeValues.res:
let broken: int = %comptime(failwith("ran during compilation"))If you uncomment it and run:
pnpm buildthe build fails during compilation, before JS is emitted. That demonstrates
that %comptime(...) is being evaluated by the compiler.