use daml_parser::ast::{Span as ParserSpan, Type};
use serde::Serialize;
use std::path::{Path, PathBuf};
#[derive(Debug, Clone, Serialize, PartialEq, Eq)]
pub struct Span {
pub file: PathBuf,
pub line: usize,
pub column: usize,
}
#[derive(Debug, Clone, Serialize, PartialEq, Eq)]
pub struct SourceSpan {
pub file: PathBuf,
pub line: usize,
pub column: usize,
pub start: usize,
pub end: usize,
pub byte_start: usize,
pub byte_end: usize,
}
pub(crate) struct SourceMap<'a> {
file: &'a Path,
source: &'a str,
line_starts: Vec<usize>,
byte_to_utf16: Vec<usize>,
}
impl<'a> SourceMap<'a> {
pub(crate) fn new(file: &'a Path, source: &'a str) -> Self {
let mut line_starts = vec![0];
for (idx, byte) in source.bytes().enumerate() {
if byte == b'\n' {
line_starts.push(idx + 1);
}
}
let mut byte_to_utf16 = vec![0; source.len() + 1];
let mut utf16 = 0usize;
let mut prev = 0usize;
for (idx, ch) in source.char_indices() {
for slot in byte_to_utf16.iter_mut().take(idx).skip(prev) {
*slot = utf16;
}
let char_end = idx + ch.len_utf8();
for slot in byte_to_utf16.iter_mut().take(char_end).skip(idx) {
*slot = utf16;
}
utf16 += ch.len_utf16();
prev = char_end;
}
for slot in byte_to_utf16.iter_mut().take(source.len() + 1).skip(prev) {
*slot = utf16;
}
Self {
file,
source,
line_starts,
byte_to_utf16,
}
}
fn line_column_at_byte(&self, byte: usize) -> (usize, usize) {
let byte = byte.min(self.source.len());
let line_idx = match self.line_starts.binary_search(&byte) {
Ok(idx) => idx,
Err(idx) => idx.saturating_sub(1),
};
let line_start = self.line_starts[line_idx];
(
line_idx + 1,
self.source[line_start..byte].chars().count() + 1,
)
}
fn source_span(&self, span: ParserSpan) -> SourceSpan {
let byte_start = span.start.min(self.source.len());
let byte_end = span.end.min(self.source.len()).max(byte_start);
let (line, column) = self.line_column_at_byte(byte_start);
SourceSpan {
file: self.file.to_path_buf(),
line,
column,
start: self.byte_to_utf16[byte_start],
end: self.byte_to_utf16[byte_end],
byte_start,
byte_end,
}
}
}
#[derive(Debug, Clone, Serialize, PartialEq)]
pub enum TypeNode {
Con {
qualifier: Option<String>,
name: String,
span: SourceSpan,
},
App {
head: Box<Self>,
args: Vec<Self>,
span: SourceSpan,
},
List {
inner: Box<Self>,
span: SourceSpan,
},
Tuple {
items: Vec<Self>,
span: SourceSpan,
},
Fun {
param: Box<Self>,
result: Box<Self>,
span: SourceSpan,
},
Var {
name: String,
span: SourceSpan,
},
Unit {
span: SourceSpan,
},
Constrained {
body: Box<Self>,
span: SourceSpan,
},
}
impl TypeNode {
pub(crate) fn from_type(t: &Type, source_map: &SourceMap<'_>) -> Self {
let span = || source_map.source_span(t.span());
match t {
Type::Con {
qualifier, name, ..
} => Self::Con {
qualifier: qualifier.clone(),
name: name.clone(),
span: span(),
},
Type::App(head, args, _) => Self::App {
head: Box::new(Self::from_type(head, source_map)),
args: args
.iter()
.map(|arg| Self::from_type(arg, source_map))
.collect(),
span: span(),
},
Type::List(inner, _) => Self::List {
inner: Box::new(Self::from_type(inner, source_map)),
span: span(),
},
Type::Tuple(items, _) => Self::Tuple {
items: items
.iter()
.map(|item| Self::from_type(item, source_map))
.collect(),
span: span(),
},
Type::Fun(param, result, _) => Self::Fun {
param: Box::new(Self::from_type(param, source_map)),
result: Box::new(Self::from_type(result, source_map)),
span: span(),
},
Type::Var(name, _) => Self::Var {
name: name.clone(),
span: span(),
},
Type::Unit(_) => Self::Unit { span: span() },
Type::Constrained(body, _) => Self::Constrained {
body: Box::new(Self::from_type(body, source_map)),
span: span(),
},
}
}
}
#[derive(Debug, Clone, Serialize, PartialEq)]
pub enum DamlType {
Party,
Text,
Decimal,
Int,
Bool,
Date,
Time,
ContractId(Box<Self>),
List(Box<Self>),
Optional(Box<Self>),
TextMap(Box<Self>),
Map(Box<Self>, Box<Self>),
Named(String),
Unit,
Unknown,
}
impl DamlType {
pub fn from_type(t: &Type) -> Self {
match t {
Type::Con {
qualifier, name, ..
} => Self::con(qualifier.as_deref(), name),
Type::App(head, args, _) => match head.as_ref() {
Type::Con {
qualifier, name, ..
} => Self::apply(qualifier.as_deref(), name, args),
_ => Self::Unknown,
},
Type::List(inner, _) => Self::List(Box::new(Self::from_type(inner))),
Type::Unit(_) => Self::Unit,
Type::Constrained(body, _) => Self::from_type(body),
Type::Tuple(_, _) | Type::Fun(_, _, _) | Type::Var(_, _) => Self::Unknown,
}
}
fn named(qualifier: Option<&str>, name: &str) -> Self {
Self::Named(qualifier.map_or_else(|| name.to_string(), |q| format!("{q}.{name}")))
}
fn con(qualifier: Option<&str>, name: &str) -> Self {
match name {
"Party" => Self::Party,
"Text" => Self::Text,
"Decimal" => Self::Decimal,
"Int" => Self::Int,
"Bool" => Self::Bool,
"Date" => Self::Date,
"Time" => Self::Time,
"Numeric" => Self::Decimal,
_ => Self::named(qualifier, name),
}
}
fn apply(qualifier: Option<&str>, name: &str, args: &[Type]) -> Self {
let first = || args.first().map(Self::from_type).unwrap_or(Self::Unknown);
match name {
"ContractId" => Self::ContractId(Box::new(first())),
"Optional" => Self::Optional(Box::new(first())),
"TextMap" => Self::TextMap(Box::new(first())),
"Numeric" => Self::Decimal,
"Map" | "GenMap" => {
let k = first();
let v = args.get(1).map(Self::from_type).unwrap_or(Self::Unknown);
Self::Map(Box::new(k), Box::new(v))
}
"Set" => Self::List(Box::new(first())),
_ => Self::named(qualifier, name),
}
}
pub const fn is_decimal(&self) -> bool {
matches!(self, Self::Decimal)
}
pub const fn is_text(&self) -> bool {
matches!(self, Self::Text)
}
pub const fn is_textmap(&self) -> bool {
matches!(self, Self::TextMap(_))
}
pub const fn is_list(&self) -> bool {
matches!(self, Self::List(_))
}
pub const fn is_map(&self) -> bool {
matches!(self, Self::Map(_, _))
}
pub fn is_unbounded(&self) -> bool {
match self {
Self::Optional(inner) => inner.is_unbounded(),
_ => self.is_text() || self.is_textmap() || self.is_list() || self.is_map(),
}
}
}
#[derive(Debug, Clone, Copy, Serialize, PartialEq, Eq)]
pub struct SrcPos {
pub line: usize,
pub column: usize,
}
#[derive(Debug, Clone, Serialize, PartialEq)]
pub enum Expr {
Var {
name: String,
qualifier: Option<String>,
span: SrcPos,
},
Con {
name: String,
qualifier: Option<String>,
span: SrcPos,
},
Lit {
kind: String,
value: String,
span: SrcPos,
},
App {
func: Box<Self>,
args: Vec<Self>,
span: SrcPos,
},
BinOp {
op: String,
lhs: Box<Self>,
rhs: Box<Self>,
span: SrcPos,
},
Neg {
expr: Box<Self>,
span: SrcPos,
},
Lambda {
params: Vec<String>,
body: Box<Self>,
span: SrcPos,
},
If {
cond: Box<Self>,
then_branch: Box<Self>,
else_branch: Box<Self>,
span: SrcPos,
},
Case {
scrutinee: Box<Self>,
alts: Vec<CaseAlt>,
span: SrcPos,
},
DoBlock {
statements: Vec<Statement>,
span: SrcPos,
},
LetIn {
bindings: Vec<LetBinding>,
body: Box<Self>,
span: SrcPos,
},
Record {
base: Box<Self>,
fields: Vec<RecordField>,
span: SrcPos,
},
Tuple {
items: Vec<Self>,
span: SrcPos,
},
List {
items: Vec<Self>,
span: SrcPos,
},
Unknown {
raw: String,
span: SrcPos,
},
}
#[derive(Debug, Clone, Serialize, PartialEq)]
pub struct CaseAlt {
pub pattern: String,
pub body: Expr,
}
#[derive(Debug, Clone, Serialize, PartialEq)]
pub struct LetBinding {
pub name: String,
pub value: Expr,
}
#[derive(Debug, Clone, Serialize, PartialEq)]
pub struct RecordField {
pub name: String,
pub value: Option<Expr>,
}
impl Expr {
pub(crate) fn conjuncts(&self) -> Vec<&Self> {
fn go<'a>(e: &'a Expr, out: &mut Vec<&'a Expr>) {
match e {
Expr::BinOp { op, lhs, rhs, .. } if op == "&&" => {
go(lhs, out);
go(rhs, out);
}
_ => out.push(e),
}
}
let mut out = Vec::new();
go(self, &mut out);
out
}
pub(crate) fn ref_string(&self) -> Option<String> {
match self {
Self::Var {
name, qualifier, ..
}
| Self::Con {
name, qualifier, ..
} => Some(
qualifier
.as_ref()
.map_or_else(|| name.clone(), |q| format!("{}.{}", q, name)),
),
Self::BinOp { op, lhs, rhs, .. } if op == "." => {
Some(format!("{}.{}", lhs.ref_string()?, rhs.ref_string()?))
}
_ => None,
}
}
pub(crate) fn refers_to(&self, name: &str) -> bool {
self.ref_string()
.is_some_and(|s| s == name || strip_implicit_self(&s) == strip_implicit_self(name))
}
pub(crate) fn is_zero_lit(&self) -> bool {
match self {
Self::Lit { kind, value, .. } if kind == "Int" || kind == "Decimal" => {
let t = value.trim();
!t.is_empty() && t.bytes().all(|b| b == b'0' || b == b'.') && t.contains('0')
}
_ => false,
}
}
pub(crate) fn is_nonzero_numeric_lit(&self) -> bool {
matches!(self, Self::Lit { kind, .. } if kind == "Int" || kind == "Decimal")
&& !self.is_zero_lit()
}
pub(crate) fn is_nonzero_numeric_divisor(&self) -> bool {
match self {
Self::Neg { expr, .. } => expr.is_nonzero_numeric_divisor(),
_ => self.is_nonzero_numeric_lit(),
}
}
pub(crate) fn is_nonneg_numeric_lit(&self) -> bool {
matches!(self, Self::Lit { kind, .. } if kind == "Int" || kind == "Decimal")
}
pub(crate) fn render_text(&self) -> String {
match self {
Self::Var { .. } | Self::Con { .. } => self.ref_string().unwrap_or_default(),
Self::Lit { value, .. } => value.clone(),
Self::Neg { expr, .. } => format!("-{}", expr.render_text()),
Self::BinOp { op, lhs, rhs, .. } if op == "." => {
format!("{}.{}", lhs.render_text(), rhs.render_text())
}
Self::BinOp { op, lhs, rhs, .. } => {
format!("{} {} {}", lhs.render_text(), op, rhs.render_text())
}
Self::App { func, args, .. } => {
let mut s = func.render_text();
for a in args {
s.push(' ');
s.push_str(&a.render_text());
}
s
}
Self::Tuple { items, .. } => format!("({})", render_join(items, ", ")),
Self::List { items, .. } => format!("[{}]", render_join(items, ", ")),
Self::Unknown { raw, .. } => raw.clone(),
_ => "…".to_string(),
}
}
pub(crate) fn app_head(&self) -> &Self {
match self {
Self::App { func, .. } => func.app_head(),
other => other,
}
}
}
fn render_join(items: &[Expr], sep: &str) -> String {
items
.iter()
.map(|i| i.render_text())
.collect::<Vec<_>>()
.join(sep)
}
fn strip_implicit_self(s: &str) -> &str {
s.strip_prefix("this.")
.or_else(|| s.strip_prefix("self."))
.unwrap_or(s)
}
pub(crate) fn is_nonneg_bound(c: &Expr, name: &str) -> bool {
match c {
Expr::BinOp { op, lhs, rhs, .. } => match op.as_str() {
">" | ">=" => lhs.refers_to(name) && rhs.is_nonneg_numeric_lit(),
"<" | "<=" => rhs.refers_to(name) && lhs.is_nonneg_numeric_lit(),
"==" => {
(lhs.refers_to(name) && rhs.is_nonzero_numeric_lit())
|| (rhs.refers_to(name) && lhs.is_nonzero_numeric_lit())
}
_ => false,
},
_ => false,
}
}
pub(crate) fn is_nonzero_bound(c: &Expr, name: &str) -> bool {
match c {
Expr::BinOp { op, lhs, rhs, .. } => match op.as_str() {
">" => lhs.refers_to(name) && rhs.is_zero_lit(),
"<" => rhs.refers_to(name) && lhs.is_zero_lit(),
"/=" | "!=" => {
(lhs.refers_to(name) && rhs.is_zero_lit())
|| (rhs.refers_to(name) && lhs.is_zero_lit())
}
_ => false,
},
_ => false,
}
}
pub(crate) fn is_strict_positive_bound(c: &Expr, name: &str) -> bool {
match c {
Expr::BinOp { op, lhs, rhs, .. } => match op.as_str() {
">" => lhs.refers_to(name) && rhs.is_nonneg_numeric_lit(),
">=" => lhs.refers_to(name) && rhs.is_nonzero_numeric_lit(),
"<" => rhs.refers_to(name) && lhs.is_nonneg_numeric_lit(),
"<=" => rhs.refers_to(name) && lhs.is_nonzero_numeric_lit(),
_ => false,
},
_ => false,
}
}
pub(crate) fn expr_guarantees_strict_positive(cond: &Expr, name: &str) -> bool {
cond.conjuncts()
.iter()
.any(|c| is_strict_positive_bound(c, name))
}
fn is_size_upper_bound(c: &Expr, name: &str, fields: &[String]) -> bool {
match c {
Expr::BinOp { op, lhs, rhs, .. } => match op.as_str() {
"<" | "<=" => is_size_app(lhs, name) && is_const_size_bound(rhs, fields),
">" | ">=" => is_size_app(rhs, name) && is_const_size_bound(lhs, fields),
"==" => {
(is_size_app(lhs, name) && is_const_size_bound(rhs, fields))
|| (is_size_app(rhs, name) && is_const_size_bound(lhs, fields))
}
_ => false,
},
_ => false,
}
}
fn is_const_size_bound(e: &Expr, fields: &[String]) -> bool {
if e.is_nonneg_numeric_lit() {
return true;
}
match e.ref_string() {
Some(_) => !fields.iter().any(|f| e.refers_to(f)),
None => false,
}
}
fn is_size_call(func: &Expr, args: &[Expr], name: &str) -> bool {
matches!(func, Expr::Var { name: f, .. } if f == "length" || f == "size")
&& args.len() == 1
&& args[0].refers_to(name)
}
fn is_size_app(e: &Expr, name: &str) -> bool {
match e {
Expr::App { func, args, .. } => is_size_call(func, args, name),
Expr::BinOp { op, lhs, rhs, .. } if op == "." => match lhs.as_ref() {
Expr::App { func, args, .. }
if matches!(func.as_ref(), Expr::Var { name: f, .. } if f == "length" || f == "size")
&& args.len() == 1 =>
{
let base = args[0].ref_string();
let field = rhs.ref_string();
match (base, field) {
(Some(base), Some(field)) => {
format!("{base}.{field}") == name
|| (matches!(base.as_str(), "this" | "self") && rhs.refers_to(name))
}
_ => false,
}
}
_ => false,
},
_ => false,
}
}
pub(crate) fn expr_guarantees_nonzero(cond: &Expr, name: &str) -> bool {
cond.conjuncts().iter().any(|c| is_nonzero_bound(c, name))
}
fn is_null_app(e: &Expr, name: &str) -> bool {
matches!(e, Expr::App { func, args, .. }
if matches!(func.as_ref(), Expr::Var { name: f, .. } if f == "null")
&& args.len() == 1
&& args[0].refers_to(name))
|| matches!(e, Expr::BinOp { op, lhs, rhs, .. } if op == "$"
&& matches!(lhs.as_ref(), Expr::Var { name: f, .. } if f == "null")
&& rhs.refers_to(name))
}
fn is_nonempty_bound(c: &Expr, name: &str) -> bool {
match c {
Expr::BinOp { op, lhs, rhs, .. } => match op.as_str() {
">" => is_size_app(lhs, name) && rhs.is_zero_lit(),
">=" => is_size_app(lhs, name) && rhs.is_nonzero_numeric_lit(),
"<" => is_size_app(rhs, name) && lhs.is_zero_lit(),
"<=" => is_size_app(rhs, name) && lhs.is_nonzero_numeric_lit(),
"/=" | "!=" => {
(is_size_app(lhs, name) && rhs.is_zero_lit())
|| (is_size_app(rhs, name) && lhs.is_zero_lit())
}
"$" => {
matches!(lhs.as_ref(), Expr::Var { name: f, .. } if f == "not")
&& is_null_app(rhs, name)
}
_ => false,
},
Expr::App { func, args, .. } => {
matches!(func.as_ref(), Expr::Var { name: f, .. } if f == "not")
&& args.len() == 1
&& is_null_app(&args[0], name)
}
_ => false,
}
}
pub(crate) fn expr_guarantees_nonempty(cond: &Expr, name: &str) -> bool {
cond.conjuncts().iter().any(|c| is_nonempty_bound(c, name))
}
pub(crate) fn expr_has_size_upper_bound(cond: &Expr, name: &str) -> bool {
cond.conjuncts().iter().any(|c| match c {
Expr::BinOp { op, lhs, rhs, .. } => match op.as_str() {
"<" | "<=" => is_size_app(lhs, name) && is_ceiling_operand(rhs),
">" | ">=" => is_size_app(rhs, name) && is_ceiling_operand(lhs),
_ => false,
},
_ => false,
})
}
fn is_ceiling_operand(e: &Expr) -> bool {
e.is_nonneg_numeric_lit() || e.ref_string().is_some()
}
pub(crate) fn statement_exprs(s: &Statement) -> Vec<&Expr> {
match s {
Statement::Let { value, .. } => vec![value],
Statement::Assert { condition_expr, .. } => vec![condition_expr],
Statement::Fetch { cid, .. } => vec![cid],
Statement::Archive { cid, .. } => vec![cid],
Statement::Create { argument, .. } => vec![argument],
Statement::Exercise { cid, argument, .. } => argument
.as_ref()
.map_or_else(|| vec![cid], |a| vec![cid, a]),
Statement::Other { expr, .. } => vec![expr],
Statement::Branch { scrutinee, .. } => scrutinee.iter().collect(),
Statement::TryCatch { .. } => vec![],
}
}
pub(crate) fn child_exprs(e: &Expr) -> Vec<&Expr> {
match e {
Expr::App { func, args, .. } => {
let mut v = vec![func.as_ref()];
v.extend(args.iter());
v
}
Expr::BinOp { lhs, rhs, .. } => vec![lhs, rhs],
Expr::Neg { expr, .. } => vec![expr],
Expr::Lambda { body, .. } => vec![body],
Expr::If {
cond,
then_branch,
else_branch,
..
} => vec![cond, then_branch, else_branch],
Expr::Case {
scrutinee, alts, ..
} => {
let mut v = vec![scrutinee.as_ref()];
v.extend(alts.iter().map(|a| &a.body));
v
}
Expr::LetIn { bindings, body, .. } => {
let mut v: Vec<&Expr> = bindings.iter().map(|b| &b.value).collect();
v.push(body);
v
}
Expr::Record { base, fields, .. } => {
let mut v = vec![base.as_ref()];
v.extend(fields.iter().filter_map(|f| f.value.as_ref()));
v
}
Expr::Tuple { items, .. } | Expr::List { items, .. } => items.iter().collect(),
Expr::Var { .. }
| Expr::Con { .. }
| Expr::Lit { .. }
| Expr::DoBlock { .. }
| Expr::Unknown { .. } => vec![],
}
}
pub(crate) fn walk_body_exprs<'a>(stmts: &'a [Statement], f: &mut impl FnMut(&'a Expr)) {
for s in stmts {
for e in statement_exprs(s) {
walk_expr(e, f);
}
match s {
Statement::TryCatch {
try_body,
catch_body,
..
} => {
walk_body_exprs(try_body, f);
walk_body_exprs(catch_body, f);
}
Statement::Branch { arms, .. } => {
for arm in arms {
walk_body_exprs(&arm.body, f);
}
}
_ => {}
}
}
}
pub(crate) fn for_each_subexpr<'a>(e: &'a Expr, f: &mut impl FnMut(&'a Expr)) {
walk_expr(e, f);
}
fn walk_expr<'a>(e: &'a Expr, f: &mut impl FnMut(&'a Expr)) {
f(e);
if let Expr::DoBlock { statements, .. } = e {
walk_body_exprs(statements, f);
}
for c in child_exprs(e) {
walk_expr(c, f);
}
}
pub(crate) fn walk_unconditional_stmts<'a>(
stmts: &'a [Statement],
f: &mut impl FnMut(&'a Statement),
) {
for s in stmts {
f(s);
if let Statement::TryCatch {
try_body,
catch_body,
..
} = s
{
walk_unconditional_stmts(try_body, f);
walk_unconditional_stmts(catch_body, f);
}
}
}
pub(crate) fn walk_body_stmts<'a>(stmts: &'a [Statement], g: &mut impl FnMut(&'a Statement)) {
for s in stmts {
g(s);
match s {
Statement::TryCatch {
try_body,
catch_body,
..
} => {
walk_body_stmts(try_body, g);
walk_body_stmts(catch_body, g);
}
Statement::Branch { arms, .. } => {
for arm in arms {
walk_body_stmts(&arm.body, g);
}
}
_ => {}
}
for e in statement_exprs(s) {
walk_nested_do_stmts(e, g);
}
}
}
fn walk_nested_do_stmts<'a>(e: &'a Expr, g: &mut impl FnMut(&'a Statement)) {
if let Expr::DoBlock { statements, .. } = e {
walk_body_stmts(statements, g);
}
for c in child_exprs(e) {
walk_nested_do_stmts(c, g);
}
}
#[derive(Debug, Clone, Serialize, PartialEq)]
pub struct Field {
pub name: String,
pub type_: Option<TypeNode>,
#[serde(skip_serializing)]
pub daml_type: DamlType,
pub span: Span,
}
#[derive(Debug, Clone, Serialize)]
pub struct Template {
pub name: String,
pub fields: Vec<Field>,
pub signatory_exprs: Vec<Expr>,
pub observer_exprs: Vec<Expr>,
pub ensure_clause: Option<EnsureClause>,
pub key_expr: Option<Expr>,
pub key_type: Option<TypeNode>,
pub maintainer_exprs: Vec<Expr>,
pub choices: Vec<Choice>,
pub interface_instances: Vec<InterfaceInstance>,
pub span: Span,
}
#[derive(Debug, Clone, Serialize)]
pub struct InterfaceInstance {
pub interface_name: String,
pub methods: Vec<String>,
pub span: Span,
}
#[derive(Debug, Clone, Serialize)]
pub struct EnsureClause {
pub expr: Expr,
pub span: Span,
}
impl EnsureClause {
pub fn has_positive_bound(&self, field_name: &str) -> bool {
self.expr
.conjuncts()
.iter()
.any(|c| is_nonneg_bound(c, field_name))
}
pub fn has_size_bound(&self, field_name: &str, fields: &[String]) -> bool {
self.expr
.conjuncts()
.iter()
.any(|c| is_size_upper_bound(c, field_name, fields))
}
pub fn guarantees_nonzero(&self, name: &str) -> bool {
expr_guarantees_nonzero(&self.expr, name)
}
}
#[derive(Debug, Clone, Serialize)]
pub struct Choice {
pub name: String,
pub consuming: bool,
pub controller_exprs: Vec<Expr>,
pub observer_exprs: Vec<Expr>,
pub parameters: Vec<Field>,
pub return_type: Option<TypeNode>,
pub body: Vec<Statement>,
pub span: Span,
}
#[derive(Debug, Clone, Serialize, PartialEq)]
pub enum Statement {
Let {
name: String,
value: Expr,
span: SrcPos,
},
Assert {
condition_expr: Expr,
span: SrcPos,
},
Fetch {
cid: Expr,
binder: Option<String>,
span: SrcPos,
},
Archive {
cid: Expr,
span: SrcPos,
},
Create {
template_name: String,
argument: Expr,
binder: Option<String>,
span: SrcPos,
},
Exercise {
choice_name: String,
cid: Expr,
argument: Option<Expr>,
binder: Option<String>,
span: SrcPos,
},
TryCatch {
try_body: Vec<Self>,
catch_body: Vec<Self>,
span: SrcPos,
},
Branch {
scrutinee: Option<Expr>,
arms: Vec<BranchArm>,
span: SrcPos,
},
Other {
raw: String,
expr: Expr,
binder: Option<String>,
span: SrcPos,
},
}
#[derive(Debug, Clone, Serialize, PartialEq)]
pub struct BranchArm {
pub pattern: Option<String>,
pub body: Vec<Statement>,
}
#[derive(Debug, Clone, Serialize)]
pub struct Function {
pub name: String,
pub type_signature: Option<TypeNode>,
pub body: Vec<Statement>,
pub span: Span,
}
#[derive(Debug, Clone, Serialize)]
pub struct Import {
pub module_name: String,
pub qualified: bool,
pub alias: Option<String>,
pub span: Span,
}
#[derive(Debug, Clone, Serialize)]
pub struct InterfaceMethod {
pub name: String,
pub type_: Option<TypeNode>,
pub span: Span,
}
#[derive(Debug, Clone, Serialize)]
pub struct Interface {
pub name: String,
pub requires: Vec<String>,
pub viewtype: Option<String>,
pub methods: Vec<InterfaceMethod>,
pub choices: Vec<Choice>,
pub span: Span,
}
#[derive(Debug, Clone, Serialize)]
pub struct DamlModule {
pub ir_version: u32,
pub name: String,
pub file: PathBuf,
pub source: String,
pub imports: Vec<Import>,
pub templates: Vec<Template>,
pub interfaces: Vec<Interface>,
pub functions: Vec<Function>,
}
#[cfg(test)]
mod tests {
use super::*;
use daml_parser::ast::Span as AstSpan;
fn con(name: &str) -> Type {
Type::Con {
qualifier: None,
name: name.to_string(),
span: AstSpan::default(),
}
}
fn app(head: Type, args: Vec<Type>) -> Type {
Type::App(Box::new(head), args, AstSpan::default())
}
fn var(name: &str) -> Type {
Type::Var(name.to_string(), AstSpan::default())
}
fn unit() -> Type {
Type::Unit(AstSpan::default())
}
fn tuple(items: Vec<Type>) -> Type {
Type::Tuple(items, AstSpan::default())
}
fn fun(param: Type, result: Type) -> Type {
Type::Fun(Box::new(param), Box::new(result), AstSpan::default())
}
fn qualified_con(qualifier: &str, name: &str) -> Type {
Type::Con {
qualifier: Some(qualifier.into()),
name: name.into(),
span: AstSpan::default(),
}
}
#[test]
fn nested_contractid_maps_through() {
let ty = app(
con("Optional"),
vec![app(con("ContractId"), vec![con("Foo")])],
);
match DamlType::from_type(&ty) {
DamlType::Optional(inner) => match *inner {
DamlType::ContractId(c) => {
assert!(matches!(*c, DamlType::Named(ref n) if n == "Foo"))
}
other => panic!("expected ContractId inside Optional, got {:?}", other),
},
other => panic!("expected Optional, got {:?}", other),
}
assert!(matches!(
DamlType::from_type(&app(con("ContractId"), vec![con("Foo")])),
DamlType::ContractId(_)
));
assert!(matches!(DamlType::from_type(&unit()), DamlType::Unit));
}
#[test]
fn numeric_family_is_decimal() {
assert!(DamlType::from_type(&con("Numeric")).is_decimal());
assert!(DamlType::from_type(&app(con("Numeric"), vec![var("n")])).is_decimal());
assert!(DamlType::from_type(&con("Decimal")).is_decimal());
assert!(!DamlType::from_type(&con("NumericThing")).is_decimal());
}
#[test]
fn map_and_set_are_unbounded() {
assert!(
DamlType::from_type(&app(con("Map"), vec![con("Text"), con("Int")])).is_unbounded()
);
assert!(
DamlType::from_type(&app(con("GenMap"), vec![con("Party"), con("Int")])).is_unbounded()
);
assert!(DamlType::from_type(&app(con("Set"), vec![con("Party")])).is_unbounded());
assert!(DamlType::from_type(&app(
qualified_con("DA.Map", "Map"),
vec![con("Text"), con("Int")]
))
.is_unbounded());
assert!(DamlType::from_type(&app(
qualified_con("Map", "Map"),
vec![con("Text"), con("Int")]
))
.is_unbounded());
assert!(
DamlType::from_type(&app(qualified_con("Set", "Set"), vec![con("Int")])).is_unbounded()
);
assert!(!DamlType::from_type(&con("MapView")).is_unbounded());
}
#[test]
fn named_keeps_qualifier() {
let qualified = qualified_con("Lib.Mod", "Asset");
assert_eq!(
DamlType::from_type(&qualified),
DamlType::Named("Lib.Mod.Asset".to_string())
);
assert_eq!(
DamlType::from_type(&con("Asset")),
DamlType::Named("Asset".to_string())
);
}
#[test]
fn tuple_type_is_unknown() {
assert!(matches!(
DamlType::from_type(&tuple(vec![con("Int"), con("Text")])),
DamlType::Unknown
));
}
#[test]
fn function_type_is_unknown_not_named() {
let arrow = fun(con("Int"), con("Int"));
assert!(matches!(DamlType::from_type(&arrow), DamlType::Unknown));
}
}