use heck::{ToSnakeCase, ToUpperCamelCase};
use proc_macro2::{Ident, Span, TokenStream};
use quote::quote;
use crate::ir::{
Choice, Crate, DamlType, DataType, Enum, Interface, Module, NamedModule, PackageModule, Record,
Template, Variant,
};
use crate::map::rust_type;
#[must_use]
pub(crate) fn data_type(data_type: &DataType) -> TokenStream {
match data_type {
DataType::Record(record) => record_items(record),
DataType::Variant(variant) => variant_items(variant),
DataType::Enum(enumeration) => enum_items(enumeration),
DataType::InterfaceMarker(name) => interface_marker(name),
}
}
#[must_use]
fn interface_marker(name: &str) -> TokenStream {
let name = type_ident(name);
let doc = format!("Marker for the Daml interface `{name}` (held via `ContractId`).");
quote! {
#[doc = #doc]
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct #name;
}
}
#[must_use]
pub(crate) fn module_items(module: &Module) -> TokenStream {
let data_types = module.data_types.iter().map(data_type);
let templates = module.templates.iter().map(template);
let interfaces = module.interfaces.iter().map(interface);
quote! {
#(#data_types)*
#(#templates)*
#(#interfaces)*
}
}
#[must_use]
pub(crate) fn crate_items(krate: &Crate) -> TokenStream {
let packages = krate.packages.iter().map(package_module);
quote! {
#(#packages)*
}
}
fn package_module(package: &PackageModule) -> TokenStream {
let name = ident(&package.name);
let modules = package.modules.iter().map(named_module);
quote! {
pub mod #name {
#(#modules)*
}
}
}
fn named_module(module: &NamedModule) -> TokenStream {
let name = ident(&module.name);
let items = module_items(&module.module);
quote! {
pub mod #name {
use canton_daml as rt;
#items
}
}
}
fn renamed_doc(rust_name: &Ident, daml_name: &str, kind: &str) -> TokenStream {
if rust_name.to_string().trim_start_matches("r#") == daml_name {
return TokenStream::new();
}
let doc = format!("Daml {kind} `{daml_name}`.");
quote!(#[doc = #doc])
}
#[must_use]
pub(crate) fn record_struct(record: &Record) -> TokenStream {
let name = type_ident(&record.name);
let generics = generics(&record.type_params);
let fields = record.fields.iter().map(|field| {
let field_name = field_ident(&field.label);
let ty = rust_type(&field.ty);
let label = &field.label;
let doc = renamed_doc(&field_name, label, "field");
quote! {
#doc
#[serde(rename = #label)]
pub #field_name: #ty,
}
});
let phantom = phantom_field(&record.type_params, record.fields.iter().map(|f| &f.ty));
quote! {
#[derive(Clone, Debug, PartialEq, Eq, rt::serde::Serialize, rt::serde::Deserialize)]
#[serde(crate = "rt::serde")]
pub struct #name #generics {
#(#fields)*
#phantom
}
}
}
fn phantom_field<'a>(
type_params: &'a [String],
field_types: impl Iterator<Item = &'a DamlType>,
) -> TokenStream {
let unused = unused_params(type_params, field_types);
if unused.is_empty() {
return quote!();
}
let params = unused.iter().map(|param| type_var_ident(param));
quote! {
#[doc(hidden)]
#[serde(skip)]
pub _phantom: ::core::marker::PhantomData<(#(#params,)*)>,
}
}
fn phantom_init<'a>(
type_params: &'a [String],
field_types: impl Iterator<Item = &'a DamlType>,
) -> TokenStream {
if unused_params(type_params, field_types).is_empty() {
quote!()
} else {
quote! { _phantom: ::core::marker::PhantomData, }
}
}
fn unused_params<'a>(
type_params: &'a [String],
field_types: impl Iterator<Item = &'a DamlType>,
) -> Vec<&'a String> {
let mut used = std::collections::BTreeSet::new();
for ty in field_types {
collect_type_vars(ty, &mut used);
}
type_params
.iter()
.filter(|param| !used.contains(*param))
.collect()
}
fn collect_type_vars(ty: &DamlType, out: &mut std::collections::BTreeSet<String>) {
match ty {
DamlType::Var(name) => {
out.insert(name.clone());
}
DamlType::ContractId(inner)
| DamlType::List(inner)
| DamlType::Optional(inner)
| DamlType::TextMap(inner)
| DamlType::Boxed(inner) => collect_type_vars(inner, out),
DamlType::GenMap(key, value) => {
collect_type_vars(key, out);
collect_type_vars(value, out);
}
DamlType::Ref(reference) => {
for arg in &reference.args {
collect_type_vars(arg, out);
}
}
_ => {}
}
}
#[must_use]
pub(crate) fn record_items(record: &Record) -> TokenStream {
let structure = record_struct(record);
let codecs = record_codecs(record);
quote! {
#structure
#codecs
}
}
fn record_codecs(record: &Record) -> TokenStream {
let name = type_ident(&record.name);
let to_fields = record.fields.iter().map(|field| {
let label = &field.label;
let ident = field_ident(&field.label);
quote! { (#label, rt::ToValue::to_value(&self.#ident)), }
});
let from_fields = record.fields.iter().enumerate().map(|(index, field)| {
let label = &field.label;
let ident = field_ident(&field.label);
if matches!(field.ty, DamlType::Optional(_)) {
quote! { #ident: rt::optional_field(value, #index, #label).map_err(|e| e.at(#label))?, }
} else {
quote! {
#ident: rt::FromValue::from_value(rt::required_field(value, #index, #label)?)
.map_err(|e| e.at(#label))?,
}
}
});
let used = used_params(record.fields.iter().map(|f| &f.ty));
let (impl_generics, ty, to_where) =
codec_header(&name, &record.type_params, &used, "e!(rt::ToValue));
let (_, _, from_where) =
codec_header(&name, &record.type_params, &used, "e!(rt::FromValue));
let phantom = phantom_init(&record.type_params, record.fields.iter().map(|f| &f.ty));
let value_binding = ident(if record.fields.is_empty() {
"_value"
} else {
"value"
});
quote! {
impl #impl_generics rt::ToValue for #ty #to_where {
fn to_value(&self) -> rt::Value {
rt::record(::std::vec![#(#to_fields)*])
}
}
impl #impl_generics rt::FromValue for #ty #from_where {
fn from_value(#value_binding: &rt::Value) -> ::core::result::Result<Self, rt::ValueError> {
::core::result::Result::Ok(Self { #(#from_fields)* #phantom })
}
}
}
}
fn codec_header(
name: &Ident,
type_params: &[String],
used: &std::collections::BTreeSet<String>,
trait_bound: &TokenStream,
) -> (TokenStream, TokenStream, TokenStream) {
if type_params.is_empty() {
return (TokenStream::new(), quote!(#name), TokenStream::new());
}
let params = type_params
.iter()
.map(|param| type_var_ident(param))
.collect::<Vec<_>>();
let impl_generics = quote!(<#(#params),*>);
let ty = quote!(#name<#(#params),*>);
let bounded = type_params
.iter()
.filter(|param| used.contains(*param))
.map(|param| type_var_ident(param))
.collect::<Vec<_>>();
let where_clause = if bounded.is_empty() {
TokenStream::new()
} else {
quote!(where #(#bounded: #trait_bound),*)
};
(impl_generics, ty, where_clause)
}
fn used_params<'a>(
types: impl Iterator<Item = &'a DamlType>,
) -> std::collections::BTreeSet<String> {
let mut used = std::collections::BTreeSet::new();
for ty in types {
collect_type_vars(ty, &mut used);
}
used
}
#[must_use]
pub(crate) fn variant_enum(variant: &Variant) -> TokenStream {
let name = type_ident(&variant.name);
let generics = generics(&variant.type_params);
let constructors = variant.constructors.iter().map(|ctor| {
let ctor_name = type_ident(&ctor.name);
let label = &ctor.name;
let doc = renamed_doc(&ctor_name, label, "constructor");
let payload = ctor
.payload
.as_ref()
.map_or_else(|| quote!(rt::Unit), rust_type);
quote! {
#doc
#[serde(rename = #label)]
#ctor_name(#payload),
}
});
quote! {
#[derive(Clone, Debug, PartialEq, Eq, rt::serde::Serialize, rt::serde::Deserialize)]
#[serde(crate = "rt::serde", tag = "tag", content = "value")]
pub enum #name #generics {
#(#constructors)*
}
}
}
#[must_use]
pub(crate) fn variant_items(variant: &Variant) -> TokenStream {
let structure = variant_enum(variant);
let codecs = variant_codecs(variant);
quote! {
#structure
#codecs
}
}
fn variant_codecs(variant: &Variant) -> TokenStream {
let name = type_ident(&variant.name);
let type_name = &variant.name;
let to_arms = variant.constructors.iter().map(|ctor| {
let ctor_name = type_ident(&ctor.name);
let label = &ctor.name;
quote! { #name::#ctor_name(inner) => rt::variant_value(#label, rt::ToValue::to_value(inner)), }
});
let from_arms = variant.constructors.iter().map(|ctor| {
let ctor_name = type_ident(&ctor.name);
let label = &ctor.name;
quote! {
#label => ::core::result::Result::Ok(#name::#ctor_name(
rt::FromValue::from_value(payload).map_err(|e| e.at(#label))?,
)),
}
});
let used = used_params(
variant
.constructors
.iter()
.filter_map(|c| c.payload.as_ref()),
);
let (impl_generics, ty, to_where) =
codec_header(&name, &variant.type_params, &used, "e!(rt::ToValue));
let (_, _, from_where) =
codec_header(&name, &variant.type_params, &used, "e!(rt::FromValue));
quote! {
impl #impl_generics rt::ToValue for #ty #to_where {
fn to_value(&self) -> rt::Value {
match self {
#(#to_arms)*
}
}
}
impl #impl_generics rt::FromValue for #ty #from_where {
fn from_value(value: &rt::Value) -> ::core::result::Result<Self, rt::ValueError> {
let (constructor, payload) = rt::variant_parts(value)?;
match constructor {
#(#from_arms)*
other => ::core::result::Result::Err(rt::unexpected_constructor(#type_name, other)),
}
}
}
}
}
#[must_use]
pub(crate) fn enum_type(enumeration: &Enum) -> TokenStream {
let name = type_ident(&enumeration.name);
let constructors = enumeration.constructors.iter().map(|ctor| {
let ctor_name = type_ident(ctor);
let label = ctor;
let doc = renamed_doc(&ctor_name, label, "constructor");
quote! {
#doc
#[serde(rename = #label)]
#ctor_name,
}
});
quote! {
#[derive(
Clone, Copy, Debug, PartialEq, Eq, rt::serde::Serialize, rt::serde::Deserialize,
)]
#[serde(crate = "rt::serde")]
pub enum #name {
#(#constructors)*
}
}
}
#[must_use]
pub(crate) fn enum_items(enumeration: &Enum) -> TokenStream {
let structure = enum_type(enumeration);
let codecs = enum_codecs(enumeration);
quote! {
#structure
#codecs
}
}
fn enum_codecs(enumeration: &Enum) -> TokenStream {
let name = type_ident(&enumeration.name);
let type_name = &enumeration.name;
let to_arms = enumeration.constructors.iter().map(|ctor| {
let ctor_name = type_ident(ctor);
let label = ctor;
quote! { #name::#ctor_name => #label, }
});
let from_arms = enumeration.constructors.iter().map(|ctor| {
let ctor_name = type_ident(ctor);
let label = ctor;
quote! { #label => ::core::result::Result::Ok(#name::#ctor_name), }
});
quote! {
impl rt::ToValue for #name {
fn to_value(&self) -> rt::Value {
rt::enum_value(match self { #(#to_arms)* })
}
}
impl rt::FromValue for #name {
fn from_value(value: &rt::Value) -> ::core::result::Result<Self, rt::ValueError> {
match rt::enum_constructor(value)? {
#(#from_arms)*
other => ::core::result::Result::Err(rt::unexpected_constructor(#type_name, other)),
}
}
}
}
}
fn template_doc(template: &Template) -> TokenStream {
let mut lines = vec![
format!(
"The Daml template `{}:{}`.",
template.module_name, template.name
),
String::new(),
format!(
"Submit with `rt::create_command`; its on-ledger id is `#{}:{}:{}`.",
template.package_name, template.module_name, template.name,
),
];
if !template.choices.is_empty() {
lines.extend([
String::new(),
"# Choices".to_string(),
String::new(),
"Exercise with `rt::exercise_command`:".to_string(),
String::new(),
]);
for choice in &template.choices {
let consuming = if choice.consuming {
"consuming"
} else {
"non-consuming"
};
lines.push(format!("- `{}` — {consuming}", choice.name));
}
}
if template.key.is_some() {
lines.extend([
String::new(),
"Keyed: also exercisable with `rt::exercise_by_key_command`.".to_string(),
]);
}
quote!(#(#[doc = #lines])*)
}
#[must_use]
pub(crate) fn template(template: &Template) -> TokenStream {
let payload = record_items(&Record {
name: template.name.clone(),
type_params: Vec::new(),
fields: template.fields.clone(),
});
let self_ty = type_ident(&template.name);
let doc = template_doc(template);
let contract_impl = contract_impl(
&self_ty,
&template.package_id,
&template.package_name,
&template.module_name,
&template.name,
);
let key_impl = template.key.as_ref().map_or_else(TokenStream::new, |key| {
let key_ty = rust_type(key);
quote! {
impl rt::WithKey for #self_ty {
type Key = #key_ty;
}
}
});
let choices = choice_impls(&self_ty, &template.name, &template.choices);
let record_fields = template.fields.iter().map(|field| {
let label = &field.label;
let ident = field_ident(&field.label);
quote! { (#label, rt::ToValue::to_value(&self.#ident)), }
});
quote! {
#doc
#payload
#contract_impl
impl rt::Template for #self_ty {
fn to_record(&self) -> rt::Record {
rt::record_fields(::std::vec![#(#record_fields)*])
}
}
#key_impl
#choices
}
}
#[must_use]
pub(crate) fn interface(interface: &Interface) -> TokenStream {
let self_ty = type_ident(&interface.name);
let contract_impl = contract_impl(
&self_ty,
&interface.package_id,
&interface.package_name,
&interface.module_name,
&interface.name,
);
let view_ty = interface
.view
.as_ref()
.map_or_else(|| quote!(rt::Unit), rust_type);
let choices = choice_impls(&self_ty, &interface.name, &interface.choices);
quote! {
#contract_impl
impl rt::Interface for #self_ty {
type View = #view_ty;
}
#choices
}
}
fn contract_impl(
self_ty: &Ident,
package_id: &str,
package_name: &str,
module_name: &str,
entity_name: &str,
) -> TokenStream {
quote! {
impl rt::Contract for #self_ty {
const PACKAGE_ID: &'static str = #package_id;
const PACKAGE_NAME: &'static str = #package_name;
const MODULE_NAME: &'static str = #module_name;
const ENTITY_NAME: &'static str = #entity_name;
}
}
}
fn choice_impls(self_ty: &Ident, owner: &str, choices: &[Choice]) -> TokenStream {
let impls = choices.iter().map(|choice| {
let argument = rust_type(&choice.argument);
let returns = rust_type(&choice.returns);
let choice_name = &choice.name;
let consuming = choice.consuming;
let doc = format!(
"The `{}` choice on [`{}`] ({}).",
choice.name,
owner,
if choice.consuming {
"consuming"
} else {
"non-consuming"
}
);
quote! {
#[doc = #doc]
impl rt::Choice<#self_ty> for #argument {
type Return = #returns;
const NAME: &'static str = #choice_name;
const CONSUMING: bool = #consuming;
}
}
});
quote! { #(#impls)* }
}
fn generics(type_params: &[String]) -> TokenStream {
if type_params.is_empty() {
return TokenStream::new();
}
let params = type_params.iter().map(|param| type_var_ident(param));
quote!(<#(#params),*>)
}
#[must_use]
pub(crate) fn type_ident(name: &str) -> Ident {
ident(name)
}
#[must_use]
pub(crate) fn type_var_ident(name: &str) -> Ident {
ident(&name.to_upper_camel_case())
}
#[must_use]
pub(crate) fn type_path(segments: &[String]) -> TokenStream {
let parts = segments.iter().map(|segment| match segment.as_str() {
"crate" | "self" | "super" | "Self" => segment.parse::<TokenStream>().unwrap_or_default(),
crate::lower::EXTERNAL_CRATE_ROOT => TokenStream::new(),
other => {
let id = ident(other);
quote!(#id)
}
});
quote!(#(#parts)::*)
}
fn field_ident(label: &str) -> Ident {
ident(&label.to_snake_case())
}
fn ident(name: &str) -> Ident {
match name {
"crate" | "self" | "Self" | "super" => Ident::new(&format!("{name}_"), Span::call_site()),
_ if is_keyword(name) => Ident::new_raw(name, Span::call_site()),
_ if name.is_empty() || name.starts_with(|c: char| c.is_ascii_digit()) => {
Ident::new(&format!("_{name}"), Span::call_site())
}
_ => Ident::new(name, Span::call_site()),
}
}
fn is_keyword(name: &str) -> bool {
matches!(
name,
"as" | "break"
| "const"
| "continue"
| "crate"
| "dyn"
| "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"
| "async"
| "await"
| "abstract"
| "become"
| "box"
| "do"
| "final"
| "macro"
| "override"
| "priv"
| "typeof"
| "unsized"
| "virtual"
| "yield"
| "try"
| "gen"
)
}