mirror of
https://github.com/roc-lang/roc.git
synced 2025-10-02 00:01:16 +00:00
2121 lines
68 KiB
Text
2121 lines
68 KiB
Text
app "rust-glue"
|
|
packages { pf: "../platform/main.roc" }
|
|
imports [
|
|
pf.Types.{ Types }, pf.Shape.{ Shape, RocFn }, pf.File.{ File }, pf.TypeId.{ TypeId },
|
|
"../static/Cargo.toml" as rocAppCargoToml : Str,
|
|
"../../roc_std/Cargo.toml" as rocStdCargoToml : Str,
|
|
"../../roc_std/src/lib.rs" as rocStdLib : Str,
|
|
"../../roc_std/src/roc_box.rs" as rocStdBox : Str,
|
|
"../../roc_std/src/roc_list.rs" as rocStdList : Str,
|
|
"../../roc_std/src/roc_dict.rs" as rocStdDict : Str,
|
|
"../../roc_std/src/roc_set.rs" as rocStdSet : Str,
|
|
"../../roc_std/src/roc_str.rs" as rocStdStr : Str,
|
|
"../../roc_std/src/storage.rs" as rocStdStorage : Str,
|
|
]
|
|
provides [makeGlue] to pf
|
|
|
|
makeGlue : List Types -> Result (List File) Str
|
|
makeGlue = \typesByArch ->
|
|
modFileContent =
|
|
List.walk typesByArch "" \content, types ->
|
|
arch = (Types.target types).architecture
|
|
archStr = archName arch
|
|
|
|
Str.concat
|
|
content
|
|
"""
|
|
#[cfg(target_arch = "\(archStr)")]
|
|
mod \(archStr);
|
|
#[cfg(target_arch = "\(archStr)")]
|
|
pub use \(archStr)::*;
|
|
|
|
"""
|
|
|
|
typesByArch
|
|
|> List.map convertTypesToFile
|
|
|> List.append { name: "roc_app/src/lib.rs", content: modFileContent }
|
|
|> List.concat staticFiles
|
|
|> Ok
|
|
|
|
## These are always included, and don't depend on the specifics of the app.
|
|
staticFiles : List File
|
|
staticFiles =
|
|
[
|
|
{ name: "roc_app/Cargo.toml", content: rocAppCargoToml },
|
|
{ name: "roc_std/Cargo.toml", content: rocStdCargoToml },
|
|
{ name: "roc_std/src/lib.rs", content: rocStdLib },
|
|
{ name: "roc_std/src/roc_box.rs", content: rocStdBox },
|
|
{ name: "roc_std/src/roc_list.rs", content: rocStdList },
|
|
{ name: "roc_std/src/roc_dict.rs", content: rocStdDict },
|
|
{ name: "roc_std/src/roc_set.rs", content: rocStdSet },
|
|
{ name: "roc_std/src/roc_str.rs", content: rocStdStr },
|
|
{ name: "roc_std/src/storage.rs", content: rocStdStorage },
|
|
]
|
|
|
|
convertTypesToFile : Types -> File
|
|
convertTypesToFile = \types ->
|
|
content =
|
|
Types.walkShapes types fileHeader \buf, type, id ->
|
|
when type is
|
|
Struct { name, fields } ->
|
|
generateStruct buf types id name fields Public
|
|
|
|
TagUnionPayload { name, fields } ->
|
|
generateStruct buf types id name (nameTagUnionPayloadFields fields) Public
|
|
|
|
TagUnion (Enumeration { name, tags, size }) ->
|
|
generateEnumeration buf types type name tags size
|
|
|
|
TagUnion (NonRecursive { name, tags, discriminantSize, discriminantOffset }) ->
|
|
if !(List.isEmpty tags) then
|
|
generateNonRecursiveTagUnion buf types id name tags discriminantSize discriminantOffset
|
|
else
|
|
buf
|
|
|
|
TagUnion (Recursive { name, tags, discriminantSize, discriminantOffset }) ->
|
|
if !(List.isEmpty tags) then
|
|
generateRecursiveTagUnion buf types id name tags discriminantSize discriminantOffset None
|
|
else
|
|
buf
|
|
|
|
TagUnion (NullableWrapped { name, indexOfNullTag, tags, discriminantSize, discriminantOffset }) ->
|
|
# TODO: generate this as `TypeName(*mut u8)` if the payload contains functions / unsized types
|
|
generateRecursiveTagUnion buf types id name tags discriminantSize discriminantOffset (Some indexOfNullTag)
|
|
|
|
TagUnion (NullableUnwrapped { name, nullTag, nonNullTag, nonNullPayload, whichTagIsNull }) ->
|
|
generateNullableUnwrapped buf types id name nullTag nonNullTag nonNullPayload whichTagIsNull
|
|
|
|
TagUnion (SingleTagStruct { name, tagName, payload }) ->
|
|
generateSingleTagStruct buf types name tagName payload
|
|
|
|
TagUnion (NonNullableUnwrapped { name, tagName, payload }) ->
|
|
generateNonNullableUnwrapped buf types name tagName payload 0 0 None
|
|
|
|
Function rocFn ->
|
|
if rocFn.isToplevel then
|
|
buf
|
|
else
|
|
generateFunction buf types rocFn
|
|
|
|
RecursivePointer _ ->
|
|
# This is recursively pointing to a type that should already have been added,
|
|
# so no extra work needs to happen.
|
|
buf
|
|
|
|
Unit
|
|
| Unsized
|
|
| EmptyTagUnion
|
|
| Num _
|
|
| Bool
|
|
| RocResult _ _
|
|
| RocStr
|
|
| RocDict _ _
|
|
| RocSet _
|
|
| RocList _
|
|
| RocBox _ ->
|
|
# These types don't need to be declared in Rust.
|
|
# TODO: Eventually we want to generate roc_std. So these types will need to be emitted.
|
|
buf
|
|
|
|
arch = (Types.target types).architecture
|
|
archStr = archName arch
|
|
|
|
{
|
|
name: "roc_app/src/\(archStr).rs",
|
|
content: content |> generateEntryPoints types,
|
|
}
|
|
|
|
generateEntryPoints : Str, Types -> Str
|
|
generateEntryPoints = \buf, types ->
|
|
List.walk (Types.entryPoints types) buf \accum, T name id -> generateEntryPoint accum types name id
|
|
|
|
generateEntryPoint : Str, Types, Str, TypeId -> Str
|
|
generateEntryPoint = \buf, types, name, id ->
|
|
publicSignature =
|
|
when Types.shape types id is
|
|
Function rocFn ->
|
|
arguments =
|
|
toArgStr rocFn.args types \argId, _shape, index ->
|
|
type = typeName types argId
|
|
indexStr = Num.toStr index
|
|
|
|
"arg\(indexStr): \(type)"
|
|
|
|
ret = typeName types rocFn.ret
|
|
|
|
"(\(arguments)) -> \(ret)"
|
|
|
|
_ ->
|
|
ret = typeName types id
|
|
"() -> \(ret)"
|
|
|
|
externSignature =
|
|
when Types.shape types id is
|
|
Function rocFn ->
|
|
arguments =
|
|
toArgStr rocFn.args types \argId, shape, _index ->
|
|
type = typeName types argId
|
|
|
|
if canDeriveCopy types shape then
|
|
"_: \(type)"
|
|
else
|
|
"_: &mut core::mem::ManuallyDrop<\(type)>"
|
|
|
|
ret = typeName types rocFn.ret
|
|
"(_: *mut \(ret), \(arguments))"
|
|
|
|
_ ->
|
|
ret = typeName types id
|
|
"(_: *mut \(ret))"
|
|
|
|
externArguments =
|
|
when Types.shape types id is
|
|
Function rocFn ->
|
|
toArgStr rocFn.args types \_argId, shape, index ->
|
|
indexStr = Num.toStr index
|
|
|
|
if canDeriveCopy types shape then
|
|
"arg\(indexStr)"
|
|
else
|
|
"&mut core::mem::ManuallyDrop::new(arg\(indexStr))"
|
|
|
|
_ ->
|
|
""
|
|
|
|
"""
|
|
\(buf)
|
|
|
|
pub fn \(name)\(publicSignature) {
|
|
extern "C" {
|
|
fn roc__\(name)_1_exposed_generic\(externSignature);
|
|
}
|
|
|
|
let mut ret = core::mem::MaybeUninit::uninit();
|
|
|
|
unsafe {
|
|
roc__\(name)_1_exposed_generic(ret.as_mut_ptr(), \(externArguments));
|
|
|
|
ret.assume_init()
|
|
}
|
|
}
|
|
"""
|
|
|
|
generateFunction : Str, Types, RocFn -> Str
|
|
generateFunction = \buf, types, rocFn ->
|
|
name = rocFn.functionName
|
|
externName = rocFn.externName
|
|
|
|
lambdaSet = typeName types rocFn.lambdaSet
|
|
|
|
publicArguments =
|
|
toArgStr rocFn.args types \argId, _shape, index ->
|
|
type = typeName types argId
|
|
indexStr = Num.toStr index
|
|
|
|
"arg\(indexStr): \(type)"
|
|
|
|
externDefArguments =
|
|
withoutUnit =
|
|
toArgStr rocFn.args types \argId, _shape, index ->
|
|
type = typeName types argId
|
|
indexStr = Num.toStr index
|
|
|
|
"arg\(indexStr): *const \(type)"
|
|
|
|
if Str.isEmpty withoutUnit then
|
|
# These always have a first argument that's a pointer, even if it's to nothing.
|
|
"arg0: *const ()"
|
|
else
|
|
withoutUnit
|
|
|
|
externCallArguments =
|
|
withoutUnit =
|
|
toArgStr rocFn.args types \_argId, _shape, index ->
|
|
indexStr = Num.toStr index
|
|
|
|
"&arg\(indexStr)"
|
|
|
|
if Str.isEmpty withoutUnit then
|
|
# These always have a first argument that's a pointer, even if it's to nothing.
|
|
"&()"
|
|
else
|
|
withoutUnit
|
|
|
|
publicComma = if Str.isEmpty publicArguments then "" else ", "
|
|
|
|
ret = typeName types rocFn.ret
|
|
|
|
"""
|
|
\(buf)
|
|
|
|
#[repr(C)]
|
|
#[derive(Debug, Clone)]
|
|
pub struct \(name) {
|
|
closure_data: \(lambdaSet),
|
|
}
|
|
|
|
impl \(name) {
|
|
pub fn force_thunk(self\(publicComma)\(publicArguments)) -> \(ret) {
|
|
extern "C" {
|
|
fn \(externName)(\(externDefArguments), closure_data: *mut u8, output: *mut \(ret));
|
|
}
|
|
|
|
let mut output = core::mem::MaybeUninit::uninit();
|
|
let closure_ptr =
|
|
(&mut core::mem::ManuallyDrop::new(self.closure_data)) as *mut _ as *mut u8;
|
|
|
|
unsafe {
|
|
\(externName)(\(externCallArguments), closure_ptr, output.as_mut_ptr());
|
|
|
|
output.assume_init()
|
|
}
|
|
}
|
|
}
|
|
"""
|
|
|
|
generateStruct : Str, Types, TypeId, _, _, _ -> Str
|
|
generateStruct = \buf, types, id, name, structFields, visibility ->
|
|
escapedName = escapeKW name
|
|
repr =
|
|
length =
|
|
when structFields is
|
|
HasClosure fields -> List.len fields
|
|
HasNoClosure fields -> List.len fields
|
|
if length <= 1 then
|
|
"transparent"
|
|
else
|
|
"C"
|
|
|
|
pub =
|
|
when visibility is
|
|
Public -> "pub "
|
|
Private -> ""
|
|
|
|
structType = Types.shape types id
|
|
|
|
buf
|
|
|> generateDeriveStr types structType IncludeDebug
|
|
|> Str.concat "#[repr(\(repr))]\n\(pub)struct \(escapedName) {\n"
|
|
|> generateStructFields types Public structFields
|
|
|> Str.concat "}\n\n"
|
|
|
|
generateStructFields = \buf, types, visibility, structFields ->
|
|
when structFields is
|
|
HasNoClosure fields ->
|
|
List.walk fields buf (generateStructFieldWithoutClosure types visibility)
|
|
|
|
HasClosure fields ->
|
|
List.walk fields buf (generateStructFieldWithoutClosure types visibility)
|
|
|
|
generateStructFieldWithoutClosure = \types, visibility ->
|
|
\accum, { name: fieldName, id } ->
|
|
typeStr = typeName types id
|
|
escapedFieldName = escapeKW fieldName
|
|
|
|
pub =
|
|
when visibility is
|
|
Public -> "pub"
|
|
Private -> ""
|
|
|
|
Str.concat accum "\(indent)\(pub) \(escapedFieldName): \(typeStr),\n"
|
|
|
|
nameTagUnionPayloadFields = \payloadFields ->
|
|
# Tag union payloads have numbered fields, so we prefix them
|
|
# with an "f" because Rust doesn't allow struct fields to be numbers.
|
|
when payloadFields is
|
|
HasNoClosure fields ->
|
|
renamedFields = List.map fields \{ name, id } -> { name: "f\(name)", id }
|
|
HasNoClosure renamedFields
|
|
|
|
HasClosure fields ->
|
|
renamedFields = List.map fields \{ name, id, accessors } -> { name: "f\(name)", id, accessors }
|
|
HasClosure renamedFields
|
|
|
|
generateEnumeration = \buf, types, enumType, name, tags, tagBytes ->
|
|
escapedName = escapeKW name
|
|
|
|
reprBits = tagBytes * 8 |> Num.toStr
|
|
|
|
buf
|
|
|> generateDeriveStr types enumType ExcludeDebug
|
|
|> Str.concat "#[repr(u\(reprBits))]\npub enum \(escapedName) {\n"
|
|
|> \b -> walkWithIndex tags b generateEnumTags
|
|
|>
|
|
# Enums require a custom debug impl to ensure naming is identical on all platforms.
|
|
Str.concat
|
|
"""
|
|
}
|
|
|
|
impl core::fmt::Debug for \(escapedName) {
|
|
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
|
|
match self {
|
|
|
|
"""
|
|
|> \b -> List.walk tags b (generateEnumTagsDebug name)
|
|
|> Str.concat "\(indent)\(indent)}\n\(indent)}\n}\n\n"
|
|
|
|
generateEnumTags = \accum, index, name ->
|
|
indexStr = Num.toStr index
|
|
|
|
Str.concat accum "\(indent)\(name) = \(indexStr),\n"
|
|
|
|
generateEnumTagsDebug = \name ->
|
|
\accum, tagName ->
|
|
Str.concat accum "\(indent)\(indent)\(indent)Self::\(tagName) => f.write_str(\"\(name)::\(tagName)\"),\n"
|
|
|
|
deriveCloneTagUnion : Str, Str, List { name : Str, payload : [Some TypeId, None] } -> Str
|
|
deriveCloneTagUnion = \buf, tagUnionType, tags ->
|
|
clones =
|
|
List.walk tags "" \accum, { name: tagName } ->
|
|
"""
|
|
\(accum)
|
|
\(tagName) => union_\(tagUnionType) {
|
|
\(tagName): self.payload.\(tagName).clone(),
|
|
},
|
|
"""
|
|
|
|
"""
|
|
\(buf)
|
|
|
|
impl Clone for \(tagUnionType) {
|
|
fn clone(&self) -> Self {
|
|
use discriminant_\(tagUnionType)::*;
|
|
|
|
let payload = unsafe {
|
|
match self.discriminant {\(clones)
|
|
}
|
|
};
|
|
|
|
Self {
|
|
discriminant: self.discriminant,
|
|
payload,
|
|
}
|
|
}
|
|
}
|
|
"""
|
|
|
|
deriveDebugTagUnion : Str, Types, Str, List { name : Str, payload : [Some TypeId, None] } -> Str
|
|
deriveDebugTagUnion = \buf, types, tagUnionType, tags ->
|
|
checks =
|
|
List.walk tags "" \accum, { name: tagName, payload } ->
|
|
type =
|
|
when payload is
|
|
Some id -> typeName types id
|
|
None -> "()"
|
|
|
|
"""
|
|
\(accum)
|
|
\(tagName) => {
|
|
let field: &\(type) = &self.payload.\(tagName);
|
|
f.debug_tuple("\(tagUnionType)::\(tagName)").field(field).finish()
|
|
},
|
|
"""
|
|
|
|
"""
|
|
\(buf)
|
|
|
|
impl core::fmt::Debug for \(tagUnionType) {
|
|
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
|
|
use discriminant_\(tagUnionType)::*;
|
|
|
|
unsafe {
|
|
match self.discriminant {\(checks)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
"""
|
|
|
|
deriveEqTagUnion : Str, Types, Shape, Str -> Str
|
|
deriveEqTagUnion = \buf, types, shape, tagUnionType ->
|
|
if canSupportEqHashOrd types shape then
|
|
"""
|
|
\(buf)
|
|
|
|
impl Eq for \(tagUnionType) {}
|
|
"""
|
|
else
|
|
buf
|
|
|
|
derivePartialEqTagUnion : Str, Types, Shape, Str, List { name : Str, payload : [Some TypeId, None] } -> Str
|
|
derivePartialEqTagUnion = \buf, types, shape, tagUnionType, tags ->
|
|
if canSupportPartialEqOrd types shape then
|
|
checks =
|
|
List.walk tags "" \accum, { name: tagName } ->
|
|
"""
|
|
\(accum)
|
|
\(tagName) => self.payload.\(tagName) == other.payload.\(tagName),
|
|
"""
|
|
|
|
"""
|
|
\(buf)
|
|
|
|
impl PartialEq for \(tagUnionType) {
|
|
fn eq(&self, other: &Self) -> bool {
|
|
use discriminant_\(tagUnionType)::*;
|
|
|
|
if self.discriminant != other.discriminant {
|
|
return false;
|
|
}
|
|
|
|
unsafe {
|
|
match self.discriminant {\(checks)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
"""
|
|
else
|
|
buf
|
|
|
|
deriveOrdTagUnion : Str, Types, Shape, Str -> Str
|
|
deriveOrdTagUnion = \buf, types, shape, tagUnionType ->
|
|
if canSupportEqHashOrd types shape then
|
|
"""
|
|
\(buf)
|
|
|
|
impl Ord for \(tagUnionType) {
|
|
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
|
|
self.partial_cmp(other).unwrap()
|
|
}
|
|
}
|
|
"""
|
|
else
|
|
buf
|
|
|
|
derivePartialOrdTagUnion : Str, Types, Shape, Str, List { name : Str, payload : [Some TypeId, None] } -> Str
|
|
derivePartialOrdTagUnion = \buf, types, shape, tagUnionType, tags ->
|
|
if canSupportPartialEqOrd types shape then
|
|
checks =
|
|
List.walk tags "" \accum, { name: tagName } ->
|
|
"""
|
|
\(accum)
|
|
\(tagName) => self.payload.\(tagName).partial_cmp(&other.payload.\(tagName)),
|
|
"""
|
|
|
|
"""
|
|
\(buf)
|
|
|
|
impl PartialOrd for \(tagUnionType) {
|
|
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
|
|
use discriminant_\(tagUnionType)::*;
|
|
|
|
use std::cmp::Ordering::*;
|
|
|
|
match self.discriminant.cmp(&other.discriminant) {
|
|
Less => Option::Some(Less),
|
|
Greater => Option::Some(Greater),
|
|
Equal => unsafe {
|
|
match self.discriminant {\(checks)
|
|
}
|
|
},
|
|
}
|
|
}
|
|
}
|
|
"""
|
|
else
|
|
buf
|
|
|
|
deriveHashTagUnion : Str, Types, Shape, Str, List { name : Str, payload : [Some TypeId, None] } -> Str
|
|
deriveHashTagUnion = \buf, types, shape, tagUnionType, tags ->
|
|
if canSupportEqHashOrd types shape then
|
|
checks =
|
|
List.walk tags "" \accum, { name: tagName } ->
|
|
"""
|
|
\(accum)
|
|
\(tagName) => self.payload.\(tagName).hash(state),
|
|
"""
|
|
|
|
"""
|
|
\(buf)
|
|
|
|
impl core::hash::Hash for \(tagUnionType) {
|
|
fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
|
|
use discriminant_\(tagUnionType)::*;
|
|
|
|
unsafe {
|
|
match self.discriminant {\(checks)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
"""
|
|
else
|
|
buf
|
|
|
|
generateConstructorFunctions : Str, Types, Str, List { name : Str, payload : [Some TypeId, None] } -> Str
|
|
generateConstructorFunctions = \buf, types, tagUnionType, tags ->
|
|
buf
|
|
|> Str.concat "\n\nimpl \(tagUnionType) {"
|
|
|> \b -> List.walk tags b \accum, r -> generateConstructorFunction accum types tagUnionType r.name r.payload
|
|
|> Str.concat "\n}\n\n"
|
|
|
|
generateConstructorFunction : Str, Types, Str, Str, [Some TypeId, None] -> Str
|
|
generateConstructorFunction = \buf, types, tagUnionType, name, optPayload ->
|
|
when optPayload is
|
|
None ->
|
|
"""
|
|
\(buf)
|
|
|
|
pub fn \(name)() -> Self {
|
|
Self {
|
|
discriminant: discriminant_\(tagUnionType)::\(name),
|
|
payload: union_\(tagUnionType) {
|
|
\(name): (),
|
|
}
|
|
}
|
|
}
|
|
"""
|
|
|
|
Some payloadId ->
|
|
payloadType = typeName types payloadId
|
|
shape = Types.shape types payloadId
|
|
|
|
new =
|
|
if canDeriveCopy types shape then
|
|
"payload"
|
|
else
|
|
"core::mem::ManuallyDrop::new(payload)"
|
|
|
|
"""
|
|
\(buf)
|
|
|
|
pub fn \(name)(payload: \(payloadType)) -> Self {
|
|
Self {
|
|
discriminant: discriminant_\(tagUnionType)::\(name),
|
|
payload: union_\(tagUnionType) {
|
|
\(name): \(new),
|
|
}
|
|
}
|
|
}
|
|
"""
|
|
|
|
generateDestructorFunctions : Str, Types, Str, List { name : Str, payload : [Some TypeId, None] } -> Str
|
|
generateDestructorFunctions = \buf, types, tagUnionType, tags ->
|
|
buf
|
|
|> Str.concat "\n\nimpl \(tagUnionType) {"
|
|
|> \b -> List.walk tags b \accum, r -> generateDestructorFunction accum types tagUnionType r.name r.payload
|
|
|> Str.concat "\n}\n\n"
|
|
|
|
generateDestructorFunction : Str, Types, Str, Str, [Some TypeId, None] -> Str
|
|
generateDestructorFunction = \buf, types, tagUnionType, name, optPayload ->
|
|
when optPayload is
|
|
None ->
|
|
"""
|
|
\(buf)
|
|
|
|
pub fn is_\(name)(&self) -> bool {
|
|
matches!(self.discriminant, discriminant_\(tagUnionType)::\(name))
|
|
}
|
|
"""
|
|
|
|
Some payloadId ->
|
|
payloadType = typeName types payloadId
|
|
shape = Types.shape types payloadId
|
|
|
|
take =
|
|
if canDeriveCopy types shape then
|
|
"unsafe { self.payload.\(name) }"
|
|
else
|
|
"unsafe { core::mem::ManuallyDrop::take(&mut self.payload.\(name)) }"
|
|
|
|
"""
|
|
\(buf)
|
|
|
|
pub fn unwrap_\(name)(mut self) -> \(payloadType) {
|
|
debug_assert_eq!(self.discriminant, discriminant_\(tagUnionType)::\(name));
|
|
\(take)
|
|
}
|
|
|
|
pub fn is_\(name)(&self) -> bool {
|
|
matches!(self.discriminant, discriminant_\(tagUnionType)::\(name))
|
|
}
|
|
"""
|
|
|
|
generateNonRecursiveTagUnion : Str, Types, TypeId, Str, List { name : Str, payload : [Some TypeId, None] }, U32, U32 -> Str
|
|
generateNonRecursiveTagUnion = \buf, types, id, name, tags, discriminantSize, discriminantOffset ->
|
|
escapedName = escapeKW name
|
|
discriminantName = "discriminant_\(escapedName)"
|
|
unionName = "union_\(escapedName)"
|
|
discriminantOffsetStr = Num.toStr discriminantOffset
|
|
tagNames = List.map tags \{ name: n } -> n
|
|
selfMut = "self"
|
|
|
|
max = \a, b -> if a >= b then a else b
|
|
|
|
alignOfUnion =
|
|
List.walk tags 1 \accum, { payload } ->
|
|
when payload is
|
|
Some payloadId -> max accum (Types.alignment types payloadId)
|
|
None -> accum
|
|
|
|
alignOfUnionStr = Num.toStr alignOfUnion
|
|
|
|
sizeOfUnionStr =
|
|
List.walk tags 1 \accum, { payload } ->
|
|
when payload is
|
|
Some payloadId -> max accum (Types.size types payloadId)
|
|
None -> accum
|
|
|> nextMultipleOf alignOfUnion
|
|
|> Num.toStr
|
|
|
|
sizeOfSelf = Num.toStr (Types.size types id)
|
|
alignOfSelf = Num.toStr (Types.alignment types id)
|
|
shape = Types.shape types id
|
|
|
|
# TODO: this value can be different than the alignment of `id`
|
|
align =
|
|
List.walk tags 1 \accum, { payload } ->
|
|
when payload is
|
|
Some payloadId -> max accum (Types.alignment types payloadId)
|
|
None -> accum
|
|
|> Num.toStr
|
|
|
|
buf
|
|
|> generateDiscriminant types discriminantName tagNames discriminantSize
|
|
|> Str.concat "#[repr(C, align(\(align)))]\npub union \(unionName) {\n"
|
|
|> \b -> List.walk tags b (generateUnionField types)
|
|
|> Str.concat
|
|
"""
|
|
}
|
|
|
|
const _SIZE_CHECK_\(unionName): () = assert!(core::mem::size_of::<\(unionName)>() == \(sizeOfUnionStr));
|
|
const _ALIGN_CHECK_\(unionName): () = assert!(core::mem::align_of::<\(unionName)>() == \(alignOfUnionStr));
|
|
|
|
const _SIZE_CHECK_\(escapedName): () = assert!(core::mem::size_of::<\(escapedName)>() == \(sizeOfSelf));
|
|
const _ALIGN_CHECK_\(escapedName): () = assert!(core::mem::align_of::<\(escapedName)>() == \(alignOfSelf));
|
|
|
|
impl \(escapedName) {
|
|
\(discriminantDocComment)
|
|
pub fn discriminant(&self) -> \(discriminantName) {
|
|
unsafe {
|
|
let bytes = core::mem::transmute::<&Self, &[u8; core::mem::size_of::<Self>()]>(self);
|
|
|
|
core::mem::transmute::<u8, \(discriminantName)>(*bytes.as_ptr().add(\(discriminantOffsetStr)))
|
|
}
|
|
}
|
|
|
|
/// Internal helper
|
|
fn set_discriminant(&mut self, discriminant: \(discriminantName)) {
|
|
let discriminant_ptr: *mut \(discriminantName) = (self as *mut \(escapedName)).cast();
|
|
|
|
unsafe {
|
|
*(discriminant_ptr.add(\(discriminantOffsetStr))) = discriminant;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
"""
|
|
|> Str.concat
|
|
"""
|
|
#[repr(C)]
|
|
pub struct \(escapedName) {
|
|
payload: union_\(escapedName),
|
|
discriminant: discriminant_\(escapedName),
|
|
}
|
|
"""
|
|
|> deriveCloneTagUnion escapedName tags
|
|
|> deriveDebugTagUnion types escapedName tags
|
|
|> deriveEqTagUnion types shape escapedName
|
|
|> derivePartialEqTagUnion types shape escapedName tags
|
|
|> deriveOrdTagUnion types shape escapedName
|
|
|> derivePartialOrdTagUnion types shape escapedName tags
|
|
|> deriveHashTagUnion types shape escapedName tags
|
|
|> generateDestructorFunctions types escapedName tags
|
|
|> generateConstructorFunctions types escapedName tags
|
|
|> \b ->
|
|
type = Types.shape types id
|
|
if cannotSupportCopy types type then
|
|
# A custom drop impl is only needed when we can't derive copy.
|
|
b
|
|
|> Str.concat
|
|
"""
|
|
impl Drop for \(escapedName) {
|
|
fn drop(&mut self) {
|
|
// Drop the payloads
|
|
|
|
"""
|
|
|> generateTagUnionDropPayload types selfMut tags discriminantName discriminantSize 2
|
|
|> Str.concat
|
|
"""
|
|
}
|
|
}
|
|
|
|
|
|
"""
|
|
else
|
|
b
|
|
|
|
generateNonNullableUnwrapped = \buf, types, name, tagName, payload, discriminantSize, _discriminantOffset, _nullTagIndex ->
|
|
escapedName = escapeKW name
|
|
discriminantName = "discriminant_\(escapedName)"
|
|
|
|
payloadFields =
|
|
when Types.shape types payload is
|
|
TagUnionPayload { fields } ->
|
|
when fields is
|
|
HasNoClosure xs -> List.map xs .id
|
|
HasClosure xs -> List.map xs .id
|
|
|
|
_ ->
|
|
[]
|
|
|
|
payloadFieldNames =
|
|
commaSeparated "" payloadFields \_, i ->
|
|
n = Num.toStr i
|
|
"f\(n)"
|
|
|
|
constructorArguments =
|
|
commaSeparated "" payloadFields \id, i ->
|
|
n = Num.toStr i
|
|
type = typeName types id
|
|
"f\(n): \(type)"
|
|
|
|
debugFields =
|
|
payloadFields
|
|
|> List.mapWithIndex \_, i ->
|
|
n = Num.toStr i
|
|
".field(&node.f\(n))"
|
|
|> Str.joinWith ""
|
|
|
|
buf1 = buf |> generateDiscriminant types discriminantName [tagName] discriminantSize
|
|
|
|
"""
|
|
\(buf1)
|
|
|
|
#[repr(transparent)]
|
|
#[derive(Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
|
|
pub struct \(escapedName)(roc_std::RocBox<\(name)_\(tagName)>);
|
|
|
|
impl \(escapedName) {
|
|
pub fn \(tagName)(\(constructorArguments)) -> Self {
|
|
let payload = \(name)_\(tagName) { \(payloadFieldNames) };
|
|
|
|
Self(roc_std::RocBox::new(payload))
|
|
}
|
|
}
|
|
|
|
impl core::fmt::Debug for \(escapedName) {
|
|
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
|
|
let node = &self.0;
|
|
f.debug_tuple("\(escapedName)::\(tagName)")\(debugFields).finish()
|
|
}
|
|
}
|
|
"""
|
|
|
|
generateRecursiveTagUnion = \buf, types, id, tagUnionName, tags, discriminantSize, _discriminantOffset, nullTagIndex ->
|
|
escapedName = escapeKW tagUnionName
|
|
discriminantName = "discriminant_\(escapedName)"
|
|
tagNames = List.map tags \{ name: n } -> n
|
|
# self = "(&*self.union_pointer())"
|
|
# selfMut = "(&mut *self.union_pointer())"
|
|
# other = "(&*other.union_pointer())"
|
|
unionName = "union_\(escapedName)"
|
|
|
|
discriminants =
|
|
tagNames
|
|
|> Str.joinWith ", "
|
|
|> \b -> "[ \(b) ]"
|
|
|
|
nullTagId =
|
|
when nullTagIndex is
|
|
Some index ->
|
|
n = Num.toStr index
|
|
"discriminants[\(n)]"
|
|
|
|
None ->
|
|
"""
|
|
unreachable!("this pointer cannot be NULL")
|
|
"""
|
|
|
|
isFunction = \{ name: tagName, payload: optPayload }, index ->
|
|
payloadFields =
|
|
when optPayload is
|
|
Some payload ->
|
|
when Types.shape types payload is
|
|
TagUnionPayload { fields } ->
|
|
when fields is
|
|
HasNoClosure xs -> List.map xs .id
|
|
HasClosure xs -> List.map xs .id
|
|
|
|
_ ->
|
|
[]
|
|
|
|
None ->
|
|
[]
|
|
|
|
payloadFieldNames =
|
|
commaSeparated "" payloadFields \_, i ->
|
|
n = Num.toStr i
|
|
"f\(n)"
|
|
|
|
constructorArguments =
|
|
commaSeparated "" payloadFields \payloadId, i ->
|
|
n = Num.toStr i
|
|
type = typeName types payloadId
|
|
"f\(n): \(type)"
|
|
|
|
fixManuallyDrop =
|
|
when optPayload is
|
|
Some payload ->
|
|
shape = Types.shape types payload
|
|
|
|
if canDeriveCopy types shape then
|
|
"payload"
|
|
else
|
|
"core::mem::ManuallyDrop::new(payload)"
|
|
|
|
None ->
|
|
"payload"
|
|
|
|
if Some (Num.intCast index) == nullTagIndex then
|
|
"""
|
|
pub fn is_\(tagName)(&self) -> bool {
|
|
matches!(self.discriminant(), discriminant_\(escapedName)::\(tagName))
|
|
}
|
|
|
|
pub fn \(tagName)(\(constructorArguments)) -> Self {
|
|
Self(std::ptr::null_mut())
|
|
}
|
|
"""
|
|
else
|
|
"""
|
|
pub fn is_\(tagName)(&self) -> bool {
|
|
matches!(self.discriminant(), discriminant_\(escapedName)::\(tagName))
|
|
}
|
|
|
|
pub fn \(tagName)(\(constructorArguments)) -> Self {
|
|
let tag_id = discriminant_\(escapedName)::\(tagName);
|
|
|
|
let payload = \(escapedName)_\(tagName) { \(payloadFieldNames) } ;
|
|
|
|
let union_payload = union_\(escapedName) { \(tagName): \(fixManuallyDrop) };
|
|
|
|
let ptr = unsafe { roc_std::RocBox::leak(roc_std::RocBox::new(union_payload)) };
|
|
|
|
Self((ptr as usize | tag_id as usize) as *mut _)
|
|
}
|
|
|
|
pub fn get_\(tagName)(mut self) -> \(escapedName)_\(tagName) {
|
|
debug_assert!(self.is_\(tagName)());
|
|
|
|
unsafe { core::mem::ManuallyDrop::take(&mut self.ptr_read_union().\(tagName)) }
|
|
}
|
|
"""
|
|
|
|
constructors =
|
|
tags
|
|
|> List.mapWithIndex isFunction
|
|
|> Str.joinWith "\n\n"
|
|
|
|
cloneCase = \{ name: tagName }, index ->
|
|
if Some (Num.intCast index) == nullTagIndex then
|
|
"""
|
|
\(tagName) => Self::\(tagName)(),
|
|
"""
|
|
else
|
|
"""
|
|
\(tagName) => {
|
|
let tag_id = discriminant_\(escapedName)::\(tagName);
|
|
|
|
let payload_union = unsafe { self.ptr_read_union() };
|
|
let payload = union_\(escapedName) {
|
|
\(tagName): unsafe { payload_union.\(tagName).clone() },
|
|
};
|
|
|
|
let ptr = unsafe { roc_std::RocBox::leak(roc_std::RocBox::new(payload)) };
|
|
|
|
Self((ptr as usize | tag_id as usize) as *mut _)
|
|
},
|
|
"""
|
|
|
|
cloneCases =
|
|
tags
|
|
|> List.mapWithIndex cloneCase
|
|
|> Str.joinWith "\n"
|
|
|
|
partialEqCase = \{ name: tagName }, index ->
|
|
if Some (Num.intCast index) == nullTagIndex then
|
|
"""
|
|
\(tagName) => true,
|
|
"""
|
|
else
|
|
"""
|
|
\(tagName) => {
|
|
let payload_union1 = unsafe { self.ptr_read_union() };
|
|
let payload_union2 = unsafe { other.ptr_read_union() };
|
|
|
|
unsafe {
|
|
payload_union1.\(tagName) == payload_union2.\(tagName)
|
|
}
|
|
},
|
|
"""
|
|
|
|
partialEqCases =
|
|
tags
|
|
|> List.mapWithIndex partialEqCase
|
|
|> Str.joinWith "\n"
|
|
|
|
partialEqImpl =
|
|
if canSupportPartialEqOrd types (Types.shape types id) then
|
|
"""
|
|
impl PartialEq for \(escapedName) {
|
|
fn eq(&self, other: &Self) -> bool {
|
|
use discriminant_\(escapedName)::*;
|
|
|
|
if self.discriminant() != other.discriminant() {
|
|
return false;
|
|
}
|
|
|
|
match self.discriminant() {
|
|
\(partialEqCases)
|
|
}
|
|
}
|
|
}
|
|
|
|
impl Eq for \(escapedName) {}
|
|
"""
|
|
else
|
|
""
|
|
|
|
debugCase = \{ name: tagName, payload: optPayload }, index ->
|
|
if Some (Num.intCast index) == nullTagIndex then
|
|
"""
|
|
\(tagName) => f.debug_tuple("\(escapedName)::\(tagName)").finish(),
|
|
"""
|
|
else
|
|
payloadFields =
|
|
when optPayload is
|
|
Some payload ->
|
|
when Types.shape types payload is
|
|
TagUnionPayload { fields } ->
|
|
when fields is
|
|
HasNoClosure xs -> List.map xs .id
|
|
HasClosure xs -> List.map xs .id
|
|
|
|
_ ->
|
|
[]
|
|
|
|
None ->
|
|
[]
|
|
|
|
debugFields =
|
|
payloadFields
|
|
|> List.mapWithIndex \_, i ->
|
|
n = Num.toStr i
|
|
".field(&payload_union.\(tagName).f\(n))"
|
|
|> Str.joinWith ""
|
|
|
|
"""
|
|
\(tagName) => {
|
|
let payload_union = unsafe { self.ptr_read_union() };
|
|
|
|
unsafe {
|
|
f.debug_tuple("\(escapedName)::\(tagName)")\(debugFields).finish()
|
|
}
|
|
},
|
|
"""
|
|
|
|
debugCases =
|
|
tags
|
|
|> List.mapWithIndex debugCase
|
|
|> Str.joinWith "\n"
|
|
|
|
hashCase = \{ name: tagName }, index ->
|
|
if Some (Num.intCast index) == nullTagIndex then
|
|
"""
|
|
\(tagName) => {}
|
|
"""
|
|
else
|
|
"""
|
|
\(tagName) => {
|
|
let payload_union = unsafe { self.ptr_read_union() };
|
|
unsafe { payload_union.\(tagName).hash(state) };
|
|
},
|
|
"""
|
|
|
|
hashCases =
|
|
tags
|
|
|> List.mapWithIndex hashCase
|
|
|> Str.joinWith "\n"
|
|
|
|
|
|
|
|
hashImpl =
|
|
if canSupportPartialEqOrd types (Types.shape types id) then
|
|
"""
|
|
impl core::hash::Hash for \(escapedName) {
|
|
fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
|
|
use discriminant_\(escapedName)::*;
|
|
|
|
self.discriminant().hash(state);
|
|
|
|
match self.discriminant() {
|
|
\(hashCases)
|
|
}
|
|
}
|
|
}
|
|
"""
|
|
else
|
|
""
|
|
|
|
partialOrdCase = \{ name: tagName }, index ->
|
|
if Some (Num.intCast index) == nullTagIndex then
|
|
"""
|
|
\(tagName) => std::cmp::Ordering::Equal,
|
|
"""
|
|
else
|
|
"""
|
|
\(tagName) => {
|
|
let payload_union1 = unsafe { self.ptr_read_union() };
|
|
let payload_union2 = unsafe { other.ptr_read_union() };
|
|
|
|
unsafe {
|
|
payload_union1.\(tagName).cmp(&payload_union2.\(tagName))
|
|
}
|
|
},
|
|
"""
|
|
|
|
partialOrdCases =
|
|
tags
|
|
|> List.mapWithIndex partialOrdCase
|
|
|> Str.joinWith "\n"
|
|
|
|
partialOrdImpl =
|
|
if canSupportPartialEqOrd types (Types.shape types id) then
|
|
"""
|
|
impl PartialOrd for \(escapedName) {
|
|
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
|
|
Some(<Self as Ord>::cmp(self, other))
|
|
}
|
|
}
|
|
|
|
impl Ord for \(escapedName) {
|
|
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
|
|
use discriminant_\(escapedName)::*;
|
|
|
|
use std::cmp::Ordering::*;
|
|
|
|
match self.discriminant().cmp(&other.discriminant()) {
|
|
Less => Less,
|
|
Greater => Greater,
|
|
Equal => unsafe {
|
|
match self.discriminant() {
|
|
\(partialOrdCases)
|
|
}
|
|
},
|
|
}
|
|
}
|
|
}
|
|
"""
|
|
else
|
|
""
|
|
|
|
sizeOfSelf = Num.toStr (Types.size types id)
|
|
alignOfSelf = Num.toStr (Types.alignment types id)
|
|
|
|
buf
|
|
|> generateDiscriminant types discriminantName tagNames discriminantSize
|
|
|> Str.concat
|
|
"""
|
|
#[repr(transparent)]
|
|
pub struct \(escapedName)(*mut \(unionName));
|
|
|
|
const _SIZE_CHECK_\(escapedName): () = assert!(core::mem::size_of::<\(escapedName)>() == \(sizeOfSelf));
|
|
const _ALIGN_CHECK_\(escapedName): () = assert!(core::mem::align_of::<\(escapedName)>() == \(alignOfSelf));
|
|
|
|
impl \(escapedName) {
|
|
pub fn discriminant(&self) -> discriminant_\(escapedName) {
|
|
let discriminants = {
|
|
use \(discriminantName)::*;
|
|
|
|
\(discriminants)
|
|
};
|
|
|
|
if self.0.is_null() {
|
|
\(nullTagId)
|
|
} else {
|
|
match std::mem::size_of::<usize>() {
|
|
4 => discriminants[self.0 as usize & 0b011],
|
|
8 => discriminants[self.0 as usize & 0b111],
|
|
_ => unreachable!(),
|
|
}
|
|
}
|
|
}
|
|
|
|
unsafe fn ptr_read_union(&self) -> core::mem::ManuallyDrop<union_\(escapedName)> {
|
|
debug_assert!(!self.0.is_null());
|
|
|
|
let mask = match std::mem::size_of::<usize>() {
|
|
4 => !0b011usize,
|
|
8 => !0b111usize,
|
|
_ => unreachable!(),
|
|
};
|
|
|
|
let ptr = ((self.0 as usize) & mask) as *mut union_\(escapedName);
|
|
|
|
core::mem::ManuallyDrop::new(unsafe { std::ptr::read(ptr) })
|
|
}
|
|
|
|
\(constructors)
|
|
}
|
|
|
|
impl Clone for \(escapedName) {
|
|
fn clone(&self) -> Self {
|
|
use discriminant_\(escapedName)::*;
|
|
|
|
let discriminant = self.discriminant();
|
|
|
|
match discriminant {
|
|
\(cloneCases)
|
|
}
|
|
}
|
|
}
|
|
|
|
\(partialEqImpl)
|
|
|
|
\(hashImpl)
|
|
|
|
\(partialOrdImpl)
|
|
|
|
|
|
impl core::fmt::Debug for \(escapedName) {
|
|
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
|
|
use discriminant_\(escapedName)::*;
|
|
|
|
match self.discriminant() {
|
|
\(debugCases)
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
#[repr(C)]
|
|
union \(unionName) {
|
|
"""
|
|
|> \b -> List.walk tags b (generateUnionField types)
|
|
|> generateTagUnionSizer types id tags
|
|
|> Str.concat "}\n\n"
|
|
|
|
generateTagUnionDropPayload = \buf, types, selfMut, tags, discriminantName, discriminantSize, indents ->
|
|
if discriminantSize == 0 then
|
|
when List.first tags is
|
|
Ok { name } ->
|
|
# There's only one tag, so there's no discriminant and no need to match;
|
|
# just drop the pointer.
|
|
buf
|
|
|> writeIndents indents
|
|
|> Str.concat "unsafe { core::mem::ManuallyDrop::drop(&mut core::ptr::read(self.pointer).\(name)); }"
|
|
|
|
Err ListWasEmpty ->
|
|
crash "unreachable"
|
|
else
|
|
buf
|
|
|> writeTagImpls tags discriminantName indents \name, payload ->
|
|
when payload is
|
|
Some id if cannotSupportCopy types (Types.shape types id) ->
|
|
"unsafe { core::mem::ManuallyDrop::drop(&mut \(selfMut).payload.\(name)) },"
|
|
|
|
_ ->
|
|
# If it had no payload, or if the payload had no pointers,
|
|
# there's nothing to clean up, so do `=> {}` for the branch.
|
|
"{}"
|
|
|
|
writeIndents = \buf, indents ->
|
|
if indents <= 0 then
|
|
buf
|
|
else
|
|
buf
|
|
|> Str.concat indent
|
|
|> writeIndents (indents - 1)
|
|
|
|
writeTagImpls = \buf, tags, discriminantName, indents, f ->
|
|
buf
|
|
|> writeIndents indents
|
|
|> Str.concat "match self.discriminant() {\n"
|
|
|> \b -> List.walk tags b \accum, { name, payload } ->
|
|
branchStr = f name payload
|
|
accum
|
|
|> writeIndents (indents + 1)
|
|
|> Str.concat "\(discriminantName)::\(name) => \(branchStr)\n"
|
|
|> writeIndents indents
|
|
|> Str.concat "}\n"
|
|
|
|
generateTagUnionSizer : Str, Types, TypeId, _ -> Str
|
|
generateTagUnionSizer = \buf, types, id, tags ->
|
|
if List.len tags > 1 then
|
|
# When there's a discriminant (so, multiple tags) and there is
|
|
# no alignment padding after the largest variant,
|
|
# the compiler will make extra room for the discriminant.
|
|
# We need that to be reflected in the overall size of the enum,
|
|
# so add an extra variant with the appropriate size.
|
|
#
|
|
# (Do this even if theoretically shouldn't be necessary, since
|
|
# there's no runtime cost and it more explicitly syncs the
|
|
# union's size with what we think it should be.)
|
|
size = getSizeRoundedToAlignment types id
|
|
sizeStr = Num.toStr size
|
|
|
|
Str.concat buf "\(indent)_sizer: [u8; \(sizeStr)],\n"
|
|
else
|
|
buf
|
|
|
|
generateDiscriminant = \buf, types, name, tags, size ->
|
|
if size > 0 then
|
|
enumType =
|
|
TagUnion
|
|
(
|
|
Enumeration {
|
|
name,
|
|
tags,
|
|
size,
|
|
}
|
|
)
|
|
|
|
buf
|
|
|> generateEnumeration types enumType name tags size
|
|
else
|
|
buf
|
|
|
|
generateUnionField = \types ->
|
|
\accum, { name: fieldName, payload } ->
|
|
escapedFieldName = escapeKW fieldName
|
|
|
|
when payload is
|
|
Some id ->
|
|
typeStr = typeName types id
|
|
|
|
type = Types.shape types id
|
|
fullTypeStr =
|
|
if cannotSupportCopy types type then
|
|
# types with pointers need ManuallyDrop
|
|
# because rust unions don't (and can't)
|
|
# know how to drop them automatically!
|
|
"core::mem::ManuallyDrop<\(typeStr)>"
|
|
else
|
|
typeStr
|
|
|
|
Str.concat accum "\(indent)\(escapedFieldName): \(fullTypeStr),\n"
|
|
|
|
None ->
|
|
# use unit as the payload
|
|
Str.concat accum "\(indent)\(escapedFieldName): (),\n"
|
|
|
|
commaSeparated : Str, List a, (a, Nat -> Str) -> Str
|
|
commaSeparated = \buf, items, step ->
|
|
length = List.len items
|
|
List.walk items { buf, count: 0 } \accum, item ->
|
|
if accum.count + 1 == length then
|
|
{ buf: Str.concat accum.buf (step item accum.count), count: length }
|
|
else
|
|
{ buf: Str.concat accum.buf (step item accum.count) |> Str.concat ", ", count: accum.count + 1 }
|
|
|> .buf
|
|
|
|
generateNullableUnwrapped : Str, Types, TypeId, Str, Str, Str, TypeId, [FirstTagIsNull, SecondTagIsNull] -> Str
|
|
generateNullableUnwrapped = \buf, types, tagUnionid, name, nullTag, nonNullTag, nonNullPayload, whichTagIsNull ->
|
|
payloadFields =
|
|
when Types.shape types nonNullPayload is
|
|
TagUnionPayload { fields } ->
|
|
when fields is
|
|
HasNoClosure xs -> List.map xs .id
|
|
HasClosure xs -> List.map xs .id
|
|
|
|
_ ->
|
|
[]
|
|
|
|
payloadFieldNames =
|
|
commaSeparated "" payloadFields \_, i ->
|
|
n = Num.toStr i
|
|
"f\(n)"
|
|
|
|
constructorArguments =
|
|
commaSeparated "" payloadFields \id, i ->
|
|
n = Num.toStr i
|
|
type = typeName types id
|
|
"f\(n): \(type)"
|
|
|
|
debugFields =
|
|
payloadFields
|
|
|> List.mapWithIndex \_, i ->
|
|
n = Num.toStr i
|
|
".field(&node.f\(n))"
|
|
|> Str.joinWith ""
|
|
|
|
discriminant =
|
|
when whichTagIsNull is
|
|
FirstTagIsNull ->
|
|
"""
|
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
|
pub enum discriminant_\(name) {
|
|
\(nullTag) = 0,
|
|
\(nonNullTag) = 1,
|
|
}
|
|
"""
|
|
|
|
SecondTagIsNull ->
|
|
"""
|
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
|
pub enum discriminant_\(name) {
|
|
\(nonNullTag) = 0,
|
|
\(nullTag) = 1,
|
|
}
|
|
"""
|
|
|
|
sizeOfSelf = Num.toStr (Types.size types tagUnionid)
|
|
alignOfSelf = Num.toStr (Types.alignment types tagUnionid)
|
|
|
|
"""
|
|
\(buf)
|
|
|
|
#[derive(PartialOrd, Ord)]
|
|
#[repr(C)]
|
|
pub struct \(name)(*mut \(name)_\(nonNullTag));
|
|
|
|
\(discriminant)
|
|
|
|
const _SIZE_CHECK_\(name): () = assert!(core::mem::size_of::<\(name)>() == \(sizeOfSelf));
|
|
const _ALIGN_CHECK_\(name): () = assert!(core::mem::align_of::<\(name)>() == \(alignOfSelf));
|
|
|
|
impl \(name) {
|
|
pub fn \(nullTag)() -> Self {
|
|
Self(core::ptr::null_mut())
|
|
}
|
|
|
|
pub fn \(nonNullTag)(\(constructorArguments)) -> Self {
|
|
let payload = \(name)_\(nonNullTag) { \(payloadFieldNames) };
|
|
|
|
let ptr = unsafe { roc_std::RocBox::leak(roc_std::RocBox::new(payload)) };
|
|
|
|
Self(ptr)
|
|
}
|
|
|
|
pub fn discriminant(&self) -> discriminant_\(name) {
|
|
if self.is_\(nullTag)() {
|
|
discriminant_\(name)::\(nullTag)
|
|
} else {
|
|
discriminant_\(name)::\(nonNullTag)
|
|
}
|
|
}
|
|
|
|
pub fn is_\(nullTag)(&self) -> bool {
|
|
self.0.is_null()
|
|
}
|
|
|
|
pub fn is_\(nonNullTag)(&self) -> bool {
|
|
!self.0.is_null()
|
|
}
|
|
}
|
|
|
|
impl core::fmt::Debug for \(name) {
|
|
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
|
|
if self.is_\(nullTag)() {
|
|
f.debug_tuple("\(name)::\(nullTag)").finish()
|
|
} else {
|
|
let node = core::mem::ManuallyDrop::new(unsafe { std::ptr::read(self.0) });
|
|
f.debug_tuple("\(name)::\(nonNullTag)")\(debugFields).finish()
|
|
}
|
|
}
|
|
}
|
|
|
|
impl Clone for \(name) {
|
|
fn clone(&self) -> Self {
|
|
if self.is_\(nullTag)() {
|
|
Self::\(nullTag)()
|
|
} else {
|
|
use std::ops::Deref;
|
|
|
|
let node_ref = core::mem::ManuallyDrop::new(unsafe { std::ptr::read(self.0) });
|
|
let payload : \(name)_\(nonNullTag) = (node_ref.deref()).clone();
|
|
|
|
let ptr = unsafe { roc_std::RocBox::leak(roc_std::RocBox::new(payload)) };
|
|
|
|
Self(ptr)
|
|
}
|
|
}
|
|
}
|
|
|
|
impl PartialEq for \(name) {
|
|
fn eq(&self, other: &Self) -> bool {
|
|
if self.discriminant() != other.discriminant() {
|
|
return false;
|
|
}
|
|
|
|
if self.is_\(nullTag)() {
|
|
return true;
|
|
}
|
|
|
|
let payload1 = core::mem::ManuallyDrop::new(unsafe { std::ptr::read(self.0) });
|
|
let payload2 = core::mem::ManuallyDrop::new(unsafe { std::ptr::read(other.0) });
|
|
|
|
payload1 == payload2
|
|
}
|
|
}
|
|
|
|
impl Eq for \(name) {}
|
|
|
|
impl core::hash::Hash for \(name) {
|
|
fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
|
|
self.discriminant().hash(state);
|
|
|
|
if self.is_\(nonNullTag)() {
|
|
let payload = core::mem::ManuallyDrop::new(unsafe { std::ptr::read(self.0) });
|
|
payload.hash(state);
|
|
}
|
|
}
|
|
}
|
|
"""
|
|
|
|
generateSingleTagStruct = \buf, types, name, tagName, payload ->
|
|
# Store single-tag unions as structs rather than enums,
|
|
# because they have only one alternative. However, still
|
|
# offer the usual tag union APIs.
|
|
escapedName = escapeKW name
|
|
repr =
|
|
length =
|
|
when payload is
|
|
HasClosure fields -> List.len fields
|
|
HasNoClosure fields -> List.len fields
|
|
if length <= 1 then
|
|
"transparent"
|
|
else
|
|
"C"
|
|
|
|
when payload is
|
|
HasNoClosure fields ->
|
|
asStructFields =
|
|
List.mapWithIndex fields \{ id }, index ->
|
|
indexStr = Num.toStr index
|
|
|
|
{ name: "f\(indexStr)", id }
|
|
|> HasNoClosure
|
|
asStructType =
|
|
Struct {
|
|
name,
|
|
fields: asStructFields,
|
|
}
|
|
|
|
buf
|
|
|> generateDeriveStr types asStructType ExcludeDebug
|
|
|> Str.concat "#[repr(\(repr))]\npub struct \(escapedName) "
|
|
|> \b ->
|
|
if List.isEmpty fields then
|
|
generateZeroElementSingleTagStruct b escapedName tagName
|
|
else
|
|
generateMultiElementSingleTagStruct b types escapedName tagName fields asStructFields
|
|
|
|
HasClosure _ ->
|
|
Str.concat buf "\\TODO: SingleTagStruct with closures"
|
|
|
|
generateMultiElementSingleTagStruct = \buf, types, name, tagName, payloadFields, asStructFields ->
|
|
buf
|
|
|> Str.concat "{\n"
|
|
|> generateStructFields types Private asStructFields
|
|
|> Str.concat "}\n\n"
|
|
|> Str.concat
|
|
"""
|
|
impl \(name) {
|
|
|
|
"""
|
|
|> \b ->
|
|
fieldTypes =
|
|
payloadFields
|
|
|> List.map \{ id } ->
|
|
typeName types id
|
|
args =
|
|
fieldTypes
|
|
|> List.mapWithIndex \fieldTypeName, index ->
|
|
indexStr = Num.toStr index
|
|
|
|
"f\(indexStr): \(fieldTypeName)"
|
|
fields =
|
|
payloadFields
|
|
|> List.mapWithIndex \_, index ->
|
|
indexStr = Num.toStr index
|
|
|
|
"f\(indexStr)"
|
|
|
|
fieldAccesses =
|
|
fields
|
|
|> List.map \field ->
|
|
"self.\(field)"
|
|
|
|
{
|
|
b,
|
|
args,
|
|
fields,
|
|
fieldTypes,
|
|
fieldAccesses,
|
|
}
|
|
|> \{ b, args, fields, fieldTypes, fieldAccesses } ->
|
|
argsStr = Str.joinWith args ", "
|
|
fieldsStr = Str.joinWith fields "\n\(indent)\(indent)\(indent)"
|
|
|
|
{
|
|
b: Str.concat
|
|
b
|
|
"""
|
|
\(indent)/// A tag named ``\(tagName)``, with the given payload.
|
|
\(indent)pub fn \(tagName)(\(argsStr)) -> Self {
|
|
\(indent) Self {
|
|
\(indent) \(fieldsStr)
|
|
\(indent) }
|
|
\(indent)}
|
|
|
|
|
|
""",
|
|
fieldTypes,
|
|
fieldAccesses,
|
|
}
|
|
|> \{ b, fieldTypes, fieldAccesses } ->
|
|
retType = asRustTuple fieldTypes
|
|
retExpr = asRustTuple fieldAccesses
|
|
|
|
{
|
|
b: Str.concat
|
|
b
|
|
"""
|
|
\(indent)/// Since `\(name)` only has one tag (namely, `\(tagName)`),
|
|
\(indent)/// convert it to `\(tagName)`'s payload.
|
|
\(indent)pub fn into_\(tagName)(self) -> \(retType) {
|
|
\(indent) \(retExpr)
|
|
\(indent)}
|
|
|
|
|
|
""",
|
|
fieldTypes,
|
|
fieldAccesses,
|
|
}
|
|
|> \{ b, fieldTypes, fieldAccesses } ->
|
|
retType =
|
|
fieldTypes
|
|
|> List.map \ft -> "&\(ft)"
|
|
|> asRustTuple
|
|
retExpr =
|
|
fieldAccesses
|
|
|> List.map \fa -> "&\(fa)"
|
|
|> asRustTuple
|
|
|
|
Str.concat
|
|
b
|
|
"""
|
|
\(indent)/// Since `\(name)` only has one tag (namely, `\(tagName)`),
|
|
\(indent)/// convert it to `\(tagName)`'s payload.
|
|
\(indent)pub fn as_\(tagName)(&self) -> \(retType) {
|
|
\(indent) \(retExpr)
|
|
\(indent)}
|
|
|
|
"""
|
|
|> Str.concat
|
|
"""
|
|
}
|
|
|
|
|
|
impl core::fmt::Debug for \(name) {
|
|
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
|
|
f.debug_tuple("\(name)::\(tagName)")
|
|
|
|
"""
|
|
|> \b ->
|
|
payloadFields
|
|
|> List.mapWithIndex \_, index ->
|
|
indexStr = Num.toStr index
|
|
|
|
"\(indent)\(indent)\(indent)\(indent).field(&self.f\(indexStr))\n"
|
|
|> List.walk b Str.concat
|
|
|> Str.concat
|
|
"""
|
|
.finish()
|
|
}
|
|
}
|
|
|
|
|
|
"""
|
|
|
|
asRustTuple = \list ->
|
|
# If there is 1 element in the list we just return it
|
|
# Otherwise, we make a proper tuple string.
|
|
joined = Str.joinWith list ", "
|
|
|
|
if List.len list == 1 then
|
|
joined
|
|
else
|
|
"(\(joined))"
|
|
|
|
generateZeroElementSingleTagStruct = \buf, name, tagName ->
|
|
# A single tag with no payload is a zero-sized unit type, so
|
|
# represent it as a zero-sized struct (e.g. "struct Foo()").
|
|
buf
|
|
|> Str.concat "();\n\n"
|
|
|> Str.concat
|
|
"""
|
|
impl \(name) {
|
|
/// A tag named \(tagName), which has no payload.
|
|
pub const \(tagName): Self = Self();
|
|
|
|
/// Other `into_` methods return a payload, but since \(tagName) tag
|
|
/// has no payload, this does nothing and is only here for completeness.
|
|
pub fn into_\(tagName)(self) {
|
|
()
|
|
}
|
|
|
|
/// Other `as_` methods return a payload, but since \(tagName) tag
|
|
/// has no payload, this does nothing and is only here for completeness.
|
|
pub fn as_\(tagName)(&self) {
|
|
()
|
|
}
|
|
}
|
|
|
|
impl core::fmt::Debug for \(name) {
|
|
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
|
|
f.write_str("\(name)::\(tagName)")
|
|
}
|
|
}
|
|
|
|
|
|
"""
|
|
|
|
generateDeriveStr = \buf, types, type, includeDebug ->
|
|
condWrite = \b, cond, str ->
|
|
if cond then
|
|
Str.concat b str
|
|
else
|
|
b
|
|
|
|
deriveDebug =
|
|
when includeDebug is
|
|
IncludeDebug -> Bool.true
|
|
ExcludeDebug -> Bool.false
|
|
|
|
buf
|
|
|> Str.concat "#[derive(Clone, "
|
|
|> condWrite (!(cannotSupportCopy types type)) "Copy, "
|
|
|> condWrite (!(cannotSupportDefault types type)) "Default, "
|
|
|> condWrite deriveDebug "Debug, "
|
|
|> condWrite (canSupportPartialEqOrd types type) "PartialEq, PartialOrd, "
|
|
|> condWrite (canSupportEqHashOrd types type) "Eq, Ord, Hash, "
|
|
|> Str.concat ")]\n"
|
|
|
|
canSupportEqHashOrd : Types, Shape -> Bool
|
|
canSupportEqHashOrd = \types, type ->
|
|
!(hasFloat types type) && (canSupportPartialEqOrd types type)
|
|
|
|
canSupportPartialEqOrd : Types, Shape -> Bool
|
|
canSupportPartialEqOrd = \types, type ->
|
|
when type is
|
|
Function rocFn ->
|
|
runtimeRepresentation = Types.shape types rocFn.lambdaSet
|
|
canSupportPartialEqOrd types runtimeRepresentation
|
|
|
|
Unsized -> Bool.false
|
|
Unit | EmptyTagUnion | Bool | Num _ | TagUnion (Enumeration _) -> Bool.true
|
|
RocStr -> Bool.true
|
|
RocList inner | RocSet inner | RocBox inner ->
|
|
innerType = Types.shape types inner
|
|
canSupportPartialEqOrd types innerType
|
|
|
|
RocDict k v ->
|
|
kType = Types.shape types k
|
|
vType = Types.shape types v
|
|
|
|
canSupportPartialEqOrd types kType && canSupportPartialEqOrd types vType
|
|
|
|
TagUnion (Recursive { tags }) ->
|
|
List.all tags \{ payload } ->
|
|
when payload is
|
|
None -> Bool.true
|
|
Some id -> canSupportPartialEqOrd types (Types.shape types id)
|
|
|
|
TagUnion (NullableWrapped { tags }) ->
|
|
List.all tags \{ payload } ->
|
|
when payload is
|
|
None -> Bool.true
|
|
Some id -> canSupportPartialEqOrd types (Types.shape types id)
|
|
|
|
TagUnion (NonNullableUnwrapped { payload }) ->
|
|
canSupportPartialEqOrd types (Types.shape types payload)
|
|
|
|
TagUnion (NullableUnwrapped { nonNullPayload }) ->
|
|
canSupportPartialEqOrd types (Types.shape types nonNullPayload)
|
|
|
|
RecursivePointer _ -> Bool.true
|
|
TagUnion (SingleTagStruct { payload: HasNoClosure fields }) ->
|
|
List.all fields \{ id } -> canSupportPartialEqOrd types (Types.shape types id)
|
|
|
|
TagUnion (SingleTagStruct { payload: HasClosure _ }) ->
|
|
Bool.false
|
|
|
|
TagUnion (NonRecursive { tags }) ->
|
|
List.all tags \{ payload } ->
|
|
when payload is
|
|
Some id -> canSupportPartialEqOrd types (Types.shape types id)
|
|
None -> Bool.true
|
|
|
|
RocResult okId errId ->
|
|
okShape = Types.shape types okId
|
|
errShape = Types.shape types errId
|
|
|
|
canSupportPartialEqOrd types okShape && canSupportPartialEqOrd types errShape
|
|
|
|
Struct { fields: HasNoClosure fields } | TagUnionPayload { fields: HasNoClosure fields } ->
|
|
List.all fields \{ id } -> canSupportPartialEqOrd types (Types.shape types id)
|
|
|
|
Struct { fields: HasClosure fields } | TagUnionPayload { fields: HasClosure fields } ->
|
|
List.all fields \{ id } -> canSupportPartialEqOrd types (Types.shape types id)
|
|
|
|
cannotSupportCopy : Types, Shape -> Bool
|
|
cannotSupportCopy = \types, type ->
|
|
!(canDeriveCopy types type)
|
|
|
|
canDeriveCopy : Types, Shape -> Bool
|
|
canDeriveCopy = \types, type ->
|
|
when type is
|
|
Function rocFn ->
|
|
runtimeRepresentation = Types.shape types rocFn.lambdaSet
|
|
canDeriveCopy types runtimeRepresentation
|
|
|
|
# unsized values are heap-allocated
|
|
Unsized -> Bool.false
|
|
Unit | EmptyTagUnion | Bool | Num _ | TagUnion (Enumeration _) -> Bool.true
|
|
RocStr | RocList _ | RocDict _ _ | RocSet _ | RocBox _ | TagUnion (NullableUnwrapped _) | TagUnion (NullableWrapped _) | TagUnion (Recursive _) | TagUnion (NonNullableUnwrapped _) | RecursivePointer _ -> Bool.false
|
|
TagUnion (SingleTagStruct { payload: HasNoClosure fields }) ->
|
|
List.all fields \{ id } -> canDeriveCopy types (Types.shape types id)
|
|
|
|
TagUnion (SingleTagStruct { payload: HasClosure fields }) ->
|
|
List.all fields \{ id } -> canDeriveCopy types (Types.shape types id)
|
|
|
|
TagUnion (NonRecursive { tags }) ->
|
|
List.all tags \{ payload } ->
|
|
when payload is
|
|
Some id -> canDeriveCopy types (Types.shape types id)
|
|
None -> Bool.true
|
|
|
|
RocResult okId errId ->
|
|
canDeriveCopy types (Types.shape types okId)
|
|
&& canDeriveCopy types (Types.shape types errId)
|
|
|
|
Struct { fields: HasNoClosure fields } | TagUnionPayload { fields: HasNoClosure fields } ->
|
|
List.all fields \{ id } -> canDeriveCopy types (Types.shape types id)
|
|
|
|
Struct { fields: HasClosure fields } | TagUnionPayload { fields: HasClosure fields } ->
|
|
List.all fields \{ id } -> canDeriveCopy types (Types.shape types id)
|
|
|
|
cannotSupportDefault = \types, type ->
|
|
when type is
|
|
Unit | Unsized | EmptyTagUnion | TagUnion _ | RocResult _ _ | RecursivePointer _ | Function _ -> Bool.true
|
|
RocStr | Bool | Num _ -> Bool.false
|
|
RocList id | RocSet id | RocBox id ->
|
|
cannotSupportDefault types (Types.shape types id)
|
|
|
|
TagUnionPayload { fields: HasClosure _ } -> Bool.true
|
|
|
|
RocDict keyId valId ->
|
|
cannotSupportCopy types (Types.shape types keyId)
|
|
|| cannotSupportCopy types (Types.shape types valId)
|
|
|
|
Struct { fields: HasClosure _ } -> Bool.true
|
|
Struct { fields: HasNoClosure fields } | TagUnionPayload { fields: HasNoClosure fields } ->
|
|
List.any fields \{ id } -> cannotSupportDefault types (Types.shape types id)
|
|
|
|
hasFloat = \types, type ->
|
|
hasFloatHelp types type (Set.empty {})
|
|
|
|
hasFloatHelp = \types, type, doNotRecurse ->
|
|
# TODO: is doNotRecurse problematic? Do we need an updated doNotRecurse for calls up the tree?
|
|
# I think there is a change it really only matters for RecursivePointer, so it may be fine.
|
|
# Otherwise we need to deal with threading through updates to doNotRecurse
|
|
when type is
|
|
Num kind ->
|
|
when kind is
|
|
F32 | F64 -> Bool.true
|
|
_ -> Bool.false
|
|
|
|
Unit | Unsized | EmptyTagUnion | RocStr | Bool | TagUnion (Enumeration _) | Function _ -> Bool.false
|
|
RocList id | RocSet id | RocBox id ->
|
|
hasFloatHelp types (Types.shape types id) doNotRecurse
|
|
|
|
RocDict id0 id1 | RocResult id0 id1 ->
|
|
hasFloatHelp types (Types.shape types id0) doNotRecurse
|
|
|| hasFloatHelp types (Types.shape types id1) doNotRecurse
|
|
|
|
Struct { fields: HasNoClosure fields } | TagUnionPayload { fields: HasNoClosure fields } ->
|
|
List.any fields \{ id } -> hasFloatHelp types (Types.shape types id) doNotRecurse
|
|
|
|
Struct { fields: HasClosure fields } | TagUnionPayload { fields: HasClosure fields } ->
|
|
List.any fields \{ id } -> hasFloatHelp types (Types.shape types id) doNotRecurse
|
|
|
|
TagUnion (SingleTagStruct { payload: HasNoClosure fields }) ->
|
|
List.any fields \{ id } -> hasFloatHelp types (Types.shape types id) doNotRecurse
|
|
|
|
TagUnion (SingleTagStruct { payload: HasClosure fields }) ->
|
|
List.any fields \{ id } -> hasFloatHelp types (Types.shape types id) doNotRecurse
|
|
|
|
TagUnion (Recursive { tags }) ->
|
|
List.any tags \{ payload } ->
|
|
when payload is
|
|
Some id -> hasFloatHelp types (Types.shape types id) doNotRecurse
|
|
None -> Bool.false
|
|
|
|
TagUnion (NonRecursive { tags }) ->
|
|
List.any tags \{ payload } ->
|
|
when payload is
|
|
Some id -> hasFloatHelp types (Types.shape types id) doNotRecurse
|
|
None -> Bool.false
|
|
|
|
TagUnion (NullableWrapped { tags }) ->
|
|
List.any tags \{ payload } ->
|
|
when payload is
|
|
Some id -> hasFloatHelp types (Types.shape types id) doNotRecurse
|
|
None -> Bool.false
|
|
|
|
TagUnion (NonNullableUnwrapped { payload }) ->
|
|
if Set.contains doNotRecurse payload then
|
|
Bool.false
|
|
else
|
|
nextDoNotRecurse = Set.insert doNotRecurse payload
|
|
|
|
hasFloatHelp types (Types.shape types payload) nextDoNotRecurse
|
|
|
|
TagUnion (NullableUnwrapped { nonNullPayload }) ->
|
|
if Set.contains doNotRecurse nonNullPayload then
|
|
Bool.false
|
|
else
|
|
nextDoNotRecurse = Set.insert doNotRecurse nonNullPayload
|
|
|
|
hasFloatHelp types (Types.shape types nonNullPayload) nextDoNotRecurse
|
|
|
|
RecursivePointer payload ->
|
|
if Set.contains doNotRecurse payload then
|
|
Bool.false
|
|
else
|
|
nextDoNotRecurse = Set.insert doNotRecurse payload
|
|
|
|
hasFloatHelp types (Types.shape types payload) nextDoNotRecurse
|
|
|
|
typeName = \types, id ->
|
|
when Types.shape types id is
|
|
Unit -> "()"
|
|
Unsized -> "roc_std::RocList<u8>"
|
|
EmptyTagUnion -> "std::convert::Infallible"
|
|
RocStr -> "roc_std::RocStr"
|
|
Bool -> "bool"
|
|
Num U8 -> "u8"
|
|
Num U16 -> "u16"
|
|
Num U32 -> "u32"
|
|
Num U64 -> "u64"
|
|
Num U128 -> "u128"
|
|
Num I8 -> "i8"
|
|
Num I16 -> "i16"
|
|
Num I32 -> "i32"
|
|
Num I64 -> "i64"
|
|
Num I128 -> "i128"
|
|
Num F32 -> "f32"
|
|
Num F64 -> "f64"
|
|
Num Dec -> "roc_std:RocDec"
|
|
RocDict key value ->
|
|
keyName = typeName types key
|
|
valueName = typeName types value
|
|
|
|
"roc_std::RocDict<\(keyName), \(valueName)>"
|
|
|
|
RocSet elem ->
|
|
elemName = typeName types elem
|
|
|
|
"roc_std::RocSet<\(elemName)>"
|
|
|
|
RocList elem ->
|
|
elemName = typeName types elem
|
|
|
|
"roc_std::RocList<\(elemName)>"
|
|
|
|
RocBox elem ->
|
|
elemName = typeName types elem
|
|
|
|
"roc_std::RocBox<\(elemName)>"
|
|
|
|
RocResult ok err ->
|
|
okName = typeName types ok
|
|
errName = typeName types err
|
|
|
|
"roc_std::RocResult<\(okName), \(errName)>"
|
|
|
|
RecursivePointer content ->
|
|
typeName types content
|
|
|
|
Struct { name } -> escapeKW name
|
|
TagUnionPayload { name } -> escapeKW name
|
|
TagUnion (NonRecursive { name }) -> escapeKW name
|
|
TagUnion (Recursive { name }) -> escapeKW name
|
|
TagUnion (Enumeration { name }) -> escapeKW name
|
|
TagUnion (NullableWrapped { name }) -> escapeKW name
|
|
TagUnion (NullableUnwrapped { name }) -> escapeKW name
|
|
TagUnion (NonNullableUnwrapped { name }) -> escapeKW name
|
|
TagUnion (SingleTagStruct { name }) -> escapeKW name
|
|
Function { functionName } -> escapeKW functionName
|
|
|
|
getSizeRoundedToAlignment = \types, id ->
|
|
alignment = Types.alignment types id
|
|
|
|
Types.size types id
|
|
|> roundUpToAlignment alignment
|
|
|
|
roundUpToAlignment = \width, alignment ->
|
|
when alignment is
|
|
0 -> width
|
|
1 -> width
|
|
_ ->
|
|
if width % alignment > 0 then
|
|
width + alignment - (width % alignment)
|
|
else
|
|
width
|
|
|
|
walkWithIndex = \list, originalState, f ->
|
|
stateWithId =
|
|
List.walk list { id: 0nat, state: originalState } \{ id, state }, elem ->
|
|
nextState = f state id elem
|
|
|
|
{ id: id + 1, state: nextState }
|
|
|
|
stateWithId.state
|
|
|
|
archName = \arch ->
|
|
when arch is
|
|
Aarch32 ->
|
|
"arm"
|
|
|
|
Aarch64 ->
|
|
"aarch64"
|
|
|
|
Wasm32 ->
|
|
"wasm32"
|
|
|
|
X86x32 ->
|
|
"x86"
|
|
|
|
X86x64 ->
|
|
"x86_64"
|
|
|
|
fileHeader =
|
|
"""
|
|
// ⚠️ GENERATED CODE ⚠️ - this entire file was generated by the `roc glue` CLI command
|
|
|
|
#![allow(unused_unsafe)]
|
|
#![allow(dead_code)]
|
|
#![allow(unused_mut)]
|
|
#![allow(non_snake_case)]
|
|
#![allow(non_camel_case_types)]
|
|
#![allow(non_upper_case_globals)]
|
|
#![allow(clippy::undocumented_unsafe_blocks)]
|
|
#![allow(clippy::redundant_static_lifetimes)]
|
|
#![allow(clippy::unused_unit)]
|
|
#![allow(clippy::missing_safety_doc)]
|
|
#![allow(clippy::let_and_return)]
|
|
#![allow(clippy::missing_safety_doc)]
|
|
#![allow(clippy::redundant_static_lifetimes)]
|
|
#![allow(clippy::needless_borrow)]
|
|
#![allow(clippy::clone_on_copy)]
|
|
|
|
|
|
|
|
"""
|
|
|
|
indent = " "
|
|
discriminantDocComment = "/// Returns which variant this tag union holds. Note that this never includes a payload!"
|
|
|
|
reservedKeywords = Set.fromList [
|
|
"try",
|
|
"abstract",
|
|
"become",
|
|
"box",
|
|
"do",
|
|
"final",
|
|
"macro",
|
|
"override",
|
|
"priv",
|
|
"typeof",
|
|
"unsized",
|
|
"virtual",
|
|
"yield",
|
|
"async",
|
|
"await",
|
|
"dyn",
|
|
"as",
|
|
"break",
|
|
"const",
|
|
"continue",
|
|
"crate",
|
|
"else",
|
|
"enum",
|
|
"extern",
|
|
"false",
|
|
"fn",
|
|
"for",
|
|
"if",
|
|
"impl",
|
|
"in",
|
|
"let",
|
|
"loop",
|
|
"match",
|
|
"mod",
|
|
"move",
|
|
"mut",
|
|
"pub",
|
|
"ref",
|
|
"return",
|
|
"self",
|
|
"Self",
|
|
"static",
|
|
"struct",
|
|
"super",
|
|
"trait",
|
|
"true",
|
|
"type",
|
|
"unsafe",
|
|
"use",
|
|
"where",
|
|
"while",
|
|
]
|
|
|
|
escapeKW = \input ->
|
|
# use a raw identifier for this, to prevent a syntax error due to using a reserved keyword.
|
|
# https://doc.rust-lang.org/rust-by-example/compatibility/raw_identifiers.html
|
|
# another design would be to add an underscore after it; this is an experiment!
|
|
if Set.contains reservedKeywords input then
|
|
"r#\(input)"
|
|
else
|
|
input
|
|
|
|
nextMultipleOf = \lhs, rhs ->
|
|
when lhs % rhs is
|
|
0 -> lhs
|
|
r -> lhs + (rhs - r)
|
|
|
|
|
|
isUnit : Shape -> Bool
|
|
isUnit = \shape ->
|
|
when shape is
|
|
Unit -> Bool.true
|
|
_ -> Bool.false
|
|
|
|
toArgStr : List TypeId, Types, (TypeId, Shape, Nat -> Str) -> Str
|
|
toArgStr = \args, types, fmt ->
|
|
answer = List.walk args { state: "", index: 0 } \{ state, index }, argId ->
|
|
newState =
|
|
shape = Types.shape types argId
|
|
|
|
# Drop `()` args; they aren't FFI-safe, and nothing will get passed anyway.
|
|
if isUnit shape then
|
|
state
|
|
else
|
|
argStr = fmt argId shape index
|
|
|
|
if Str.isEmpty state then
|
|
argStr # Don't prepend a comma if this is the first one
|
|
else
|
|
state
|
|
|> Str.concat ", "
|
|
|> Str.concat argStr
|
|
|
|
{ state: newState, index: index + 1 }
|
|
|
|
answer.state
|