use shape_ast::ast::expr_helpers::{
AssignExpr, AsyncLetExpr, BlockExpr, BlockItem, ComprehensionClause, ComptimeForExpr, ForExpr,
FromQueryExpr, IfExpr, JoinBranch, JoinExpr, LetExpr, ListComprehension, LoopExpr, MatchArm,
MatchExpr, QueryClause, WhileExpr,
};
use shape_ast::ast::expressions::{EnumConstructorPayload, Expr, ObjectEntry};
use shape_ast::ast::functions::{FunctionDef, FunctionParameter};
use shape_ast::ast::patterns::{
DecompositionBinding, DestructurePattern, ObjectPatternField, Pattern, PatternConstructorFields,
};
use shape_ast::ast::statements::{ForInit, IfStatement, Statement, WhileLoop};
use shape_ast::ast::type_path::TypePath;
use shape_ast::ast::types::{
ExtendStatement, FunctionParam, MethodDef, ObjectTypeField, TypeAnnotation,
};
use shape_value::v2::ConcreteType;
use std::collections::HashMap;
use crate::compiler::monomorphization::type_resolution::ComptimeConstValue;
pub const SUBSTITUTION_NOT_INTEGRATED: &str = "monomorphization::substitution is integrated";
pub fn concrete_to_annotation(ct: &ConcreteType) -> TypeAnnotation {
match ct {
ConcreteType::F64 => TypeAnnotation::Basic("number".into()),
ConcreteType::F32 => TypeAnnotation::Basic("f32".into()),
ConcreteType::Char => TypeAnnotation::Basic("char".into()),
ConcreteType::I64 => TypeAnnotation::Basic("int".into()),
ConcreteType::I32 => TypeAnnotation::Basic("i32".into()),
ConcreteType::I16 => TypeAnnotation::Basic("i16".into()),
ConcreteType::I8 => TypeAnnotation::Basic("i8".into()),
ConcreteType::U64 => TypeAnnotation::Basic("u64".into()),
ConcreteType::U32 => TypeAnnotation::Basic("u32".into()),
ConcreteType::U16 => TypeAnnotation::Basic("u16".into()),
ConcreteType::U8 => TypeAnnotation::Basic("u8".into()),
ConcreteType::Bool => TypeAnnotation::Basic("bool".into()),
ConcreteType::String => TypeAnnotation::Basic("string".into()),
ConcreteType::Decimal => TypeAnnotation::Basic("decimal".into()),
ConcreteType::BigInt => TypeAnnotation::Basic("bigint".into()),
ConcreteType::DateTime => TypeAnnotation::Basic("DateTime".into()),
ConcreteType::Array(elem) => TypeAnnotation::Generic {
name: TypePath::simple("Array"),
args: vec![concrete_to_annotation(elem)],
},
ConcreteType::HashMap(k, v) => TypeAnnotation::Generic {
name: TypePath::simple("HashMap"),
args: vec![concrete_to_annotation(k), concrete_to_annotation(v)],
},
ConcreteType::Option(inner) => TypeAnnotation::Generic {
name: TypePath::simple("Option"),
args: vec![concrete_to_annotation(inner)],
},
ConcreteType::Result(ok, err) => TypeAnnotation::Generic {
name: TypePath::simple("Result"),
args: vec![concrete_to_annotation(ok), concrete_to_annotation(err)],
},
ConcreteType::Tuple(elems) => {
TypeAnnotation::Tuple(elems.iter().map(concrete_to_annotation).collect())
}
ConcreteType::Pointer(inner) => TypeAnnotation::Generic {
name: TypePath::simple("ptr"),
args: vec![concrete_to_annotation(inner)],
},
ConcreteType::Struct(id) => match id.name_str() {
Some(name) => TypeAnnotation::Reference(TypePath::simple(name.to_string())),
None => {
TypeAnnotation::Reference(TypePath::simple(format!("__mono_struct_{}", id.layout.0)))
}
},
ConcreteType::Enum(id) => match id.name_str() {
Some(name) => TypeAnnotation::Reference(TypePath::simple(name.to_string())),
None => {
TypeAnnotation::Reference(TypePath::simple(format!("__mono_enum_{}", id.layout.0)))
}
},
ConcreteType::Closure(id) => {
TypeAnnotation::Reference(TypePath::simple(format!("__mono_closure_{}", id.0)))
}
ConcreteType::Function(id) => {
TypeAnnotation::Reference(TypePath::simple(format!("__mono_fn_{}", id.0)))
}
ConcreteType::HashSet(elem) => TypeAnnotation::Generic {
name: TypePath::simple("HashSet"),
args: vec![concrete_to_annotation(elem)],
},
ConcreteType::Deque(elem) => TypeAnnotation::Generic {
name: TypePath::simple("Deque"),
args: vec![concrete_to_annotation(elem)],
},
ConcreteType::PriorityQueue => {
TypeAnnotation::Reference(TypePath::simple("PriorityQueue"))
}
ConcreteType::Channel(elem) => TypeAnnotation::Generic {
name: TypePath::simple("Channel"),
args: vec![concrete_to_annotation(elem)],
},
ConcreteType::Mutex(inner) => TypeAnnotation::Generic {
name: TypePath::simple("Mutex"),
args: vec![concrete_to_annotation(inner)],
},
ConcreteType::Atomic => TypeAnnotation::Reference(TypePath::simple("Atomic")),
ConcreteType::Lazy(inner) => TypeAnnotation::Generic {
name: TypePath::simple("Lazy"),
args: vec![concrete_to_annotation(inner)],
},
ConcreteType::Void => TypeAnnotation::Void,
}
}
pub fn substitute_type_annotation(
ann: &TypeAnnotation,
subs: &HashMap<String, ConcreteType>,
) -> TypeAnnotation {
match ann {
TypeAnnotation::Reference(path) => {
if !path.is_qualified() {
if let Some(ct) = subs.get(path.as_str()) {
return concrete_to_annotation(ct);
}
}
TypeAnnotation::Reference(path.clone())
}
TypeAnnotation::Basic(name) => {
if let Some(ct) = subs.get(name) {
concrete_to_annotation(ct)
} else {
TypeAnnotation::Basic(name.clone())
}
}
TypeAnnotation::Array(inner) => {
TypeAnnotation::Array(Box::new(substitute_type_annotation(inner, subs)))
}
TypeAnnotation::Tuple(items) => TypeAnnotation::Tuple(
items
.iter()
.map(|t| substitute_type_annotation(t, subs))
.collect(),
),
TypeAnnotation::Object(fields) => TypeAnnotation::Object(
fields
.iter()
.map(|f| ObjectTypeField {
name: f.name.clone(),
optional: f.optional,
type_annotation: substitute_type_annotation(&f.type_annotation, subs),
annotations: f.annotations.clone(),
})
.collect(),
),
TypeAnnotation::Function { params, returns } => TypeAnnotation::Function {
params: params
.iter()
.map(|p| FunctionParam {
name: p.name.clone(),
optional: p.optional,
type_annotation: substitute_type_annotation(&p.type_annotation, subs),
})
.collect(),
returns: Box::new(substitute_type_annotation(returns, subs)),
},
TypeAnnotation::Union(items) => TypeAnnotation::Union(
items
.iter()
.map(|t| substitute_type_annotation(t, subs))
.collect(),
),
TypeAnnotation::Intersection(items) => TypeAnnotation::Intersection(
items
.iter()
.map(|t| substitute_type_annotation(t, subs))
.collect(),
),
TypeAnnotation::Generic { name, args } => TypeAnnotation::Generic {
name: name.clone(),
args: args
.iter()
.map(|a| substitute_type_annotation(a, subs))
.collect(),
},
TypeAnnotation::Void => TypeAnnotation::Void,
TypeAnnotation::Never => TypeAnnotation::Never,
TypeAnnotation::Null => TypeAnnotation::Null,
TypeAnnotation::Undefined => TypeAnnotation::Undefined,
TypeAnnotation::Dyn(paths) => TypeAnnotation::Dyn(paths.clone()),
}
}
pub fn mono_key_from_subs(subs: &HashMap<String, ConcreteType>) -> String {
let mut entries: Vec<(&String, &ConcreteType)> = subs.iter().collect();
entries.sort_by(|a, b| a.0.cmp(b.0));
entries
.iter()
.map(|(_, ct)| ct.mono_key())
.collect::<Vec<_>>()
.join("_")
}
pub fn substitute_function_def(
def: &FunctionDef,
subs: &HashMap<String, ConcreteType>,
) -> FunctionDef {
let mut cloned = def.clone();
cloned.params = def
.params
.iter()
.map(|p| substitute_function_parameter(p, subs))
.collect();
cloned.return_type = def
.return_type
.as_ref()
.map(|t| substitute_type_annotation(t, subs));
cloned.body = def
.body
.iter()
.map(|s| substitute_statement(s, subs))
.collect();
synthesize_empty_array_result_annotation(&mut cloned);
cloned.name = format!("{}::{}", def.name, mono_key_from_subs(subs));
cloned.type_params = None;
cloned
}
fn synthesize_empty_array_result_annotation(def: &mut FunctionDef) {
let elem_annotation: TypeAnnotation = match def.return_type.as_ref() {
Some(TypeAnnotation::Generic { name, args }) if name.as_str() == "Array" && args.len() == 1 => {
args[0].clone()
}
Some(TypeAnnotation::Generic { name, args }) if name.as_str() == "Vec" && args.len() == 1 => {
args[0].clone()
}
Some(TypeAnnotation::Array(inner)) => (**inner).clone(),
_ => return,
};
let Some(Statement::Expression(Expr::Identifier(terminal_name, _), _)) = def.body.last() else {
return;
};
let terminal_name = terminal_name.clone();
for stmt in def.body.iter_mut() {
let Statement::VariableDecl(decl, _) = stmt else {
continue;
};
let Some(decl_name) = decl.pattern.as_identifier() else {
continue;
};
if decl_name != terminal_name {
continue;
}
if decl.type_annotation.is_some() {
continue;
}
let is_empty_array = matches!(
decl.value.as_ref(),
Some(Expr::Array(items, _)) if items.is_empty()
);
if !is_empty_array {
continue;
}
decl.type_annotation = Some(TypeAnnotation::Generic {
name: TypePath::simple("Array"),
args: vec![elem_annotation.clone()],
});
return;
}
}
pub fn substitute_function_def_with_consts(
def: &FunctionDef,
type_subs: &HashMap<String, ConcreteType>,
const_subs: &HashMap<String, ComptimeConstValue>,
mono_key: &str,
) -> FunctionDef {
let mut cloned = def.clone();
cloned.params = def
.params
.iter()
.map(|p| {
let mut np = substitute_function_parameter(p, type_subs);
np.default_value = p
.default_value
.as_ref()
.map(|e| substitute_const_in_expr(e, const_subs));
np
})
.collect();
cloned.return_type = def
.return_type
.as_ref()
.map(|t| substitute_type_annotation(t, type_subs));
cloned.body = def
.body
.iter()
.map(|s| {
let s = substitute_statement(s, type_subs);
substitute_const_in_statement(&s, const_subs)
})
.collect();
synthesize_empty_array_result_annotation(&mut cloned);
cloned.name = format!("{}::{}", def.name, strip_fn_name_prefix(&def.name, mono_key));
cloned.type_params = None;
cloned
}
fn strip_fn_name_prefix<'a>(fn_name: &str, mono_key: &'a str) -> &'a str {
let prefix = format!("{}::", fn_name);
mono_key.strip_prefix(&prefix).unwrap_or(mono_key)
}
fn substitute_const_in_statement(
stmt: &Statement,
const_subs: &HashMap<String, ComptimeConstValue>,
) -> Statement {
if const_subs.is_empty() {
return stmt.clone();
}
match stmt {
Statement::Return(expr, span) => Statement::Return(
expr.as_ref()
.map(|e| substitute_const_in_expr(e, const_subs)),
*span,
),
Statement::Break(span) => Statement::Break(*span),
Statement::Continue(span) => Statement::Continue(*span),
Statement::VariableDecl(decl, span) => {
let mut new_decl = decl.clone();
new_decl.value = decl
.value
.as_ref()
.map(|e| substitute_const_in_expr(e, const_subs));
Statement::VariableDecl(new_decl, *span)
}
Statement::Assignment(assign, span) => {
let mut new_assign = assign.clone();
new_assign.value = substitute_const_in_expr(&assign.value, const_subs);
Statement::Assignment(new_assign, *span)
}
Statement::Expression(expr, span) => {
Statement::Expression(substitute_const_in_expr(expr, const_subs), *span)
}
Statement::For(for_loop, span) => {
let mut new_loop = for_loop.clone();
new_loop.init = match &for_loop.init {
ForInit::ForIn { pattern, iter } => ForInit::ForIn {
pattern: pattern.clone(),
iter: substitute_const_in_expr(iter, const_subs),
},
ForInit::ForC {
init,
condition,
update,
} => ForInit::ForC {
init: Box::new(substitute_const_in_statement(init, const_subs)),
condition: substitute_const_in_expr(condition, const_subs),
update: substitute_const_in_expr(update, const_subs),
},
};
new_loop.body = for_loop
.body
.iter()
.map(|s| substitute_const_in_statement(s, const_subs))
.collect();
Statement::For(new_loop, *span)
}
Statement::While(while_loop, span) => Statement::While(
WhileLoop {
condition: substitute_const_in_expr(&while_loop.condition, const_subs),
body: while_loop
.body
.iter()
.map(|s| substitute_const_in_statement(s, const_subs))
.collect(),
},
*span,
),
Statement::If(if_stmt, span) => Statement::If(
IfStatement {
condition: substitute_const_in_expr(&if_stmt.condition, const_subs),
then_body: if_stmt
.then_body
.iter()
.map(|s| substitute_const_in_statement(s, const_subs))
.collect(),
else_body: if_stmt.else_body.as_ref().map(|body| {
body.iter()
.map(|s| substitute_const_in_statement(s, const_subs))
.collect()
}),
},
*span,
),
Statement::Extend(ext, span) => {
let mut new_ext = ext.clone();
new_ext.methods = ext
.methods
.iter()
.map(|m| {
let mut cm = m.clone();
cm.body = m
.body
.iter()
.map(|s| substitute_const_in_statement(s, const_subs))
.collect();
cm.when_clause = m
.when_clause
.as_ref()
.map(|e| Box::new(substitute_const_in_expr(e, const_subs)));
cm
})
.collect();
Statement::Extend(new_ext, *span)
}
Statement::RemoveTarget(span) => Statement::RemoveTarget(*span),
Statement::SetParamType {
param_name,
type_annotation,
span,
} => Statement::SetParamType {
param_name: param_name.clone(),
type_annotation: type_annotation.clone(),
span: *span,
},
Statement::SetParamValue {
param_name,
expression,
span,
} => Statement::SetParamValue {
param_name: param_name.clone(),
expression: substitute_const_in_expr(expression, const_subs),
span: *span,
},
Statement::SetReturnType {
type_annotation,
span,
} => Statement::SetReturnType {
type_annotation: type_annotation.clone(),
span: *span,
},
Statement::SetReturnExpr { expression, span } => Statement::SetReturnExpr {
expression: substitute_const_in_expr(expression, const_subs),
span: *span,
},
Statement::ReplaceBody { body, span } => Statement::ReplaceBody {
body: body
.iter()
.map(|s| substitute_const_in_statement(s, const_subs))
.collect(),
span: *span,
},
Statement::ReplaceBodyExpr { expression, span } => Statement::ReplaceBodyExpr {
expression: substitute_const_in_expr(expression, const_subs),
span: *span,
},
Statement::ReplaceModuleExpr { expression, span } => Statement::ReplaceModuleExpr {
expression: substitute_const_in_expr(expression, const_subs),
span: *span,
},
}
}
fn substitute_const_in_expr(
expr: &Expr,
const_subs: &HashMap<String, ComptimeConstValue>,
) -> Expr {
if const_subs.is_empty() {
return expr.clone();
}
match expr {
Expr::Identifier(name, span) => {
if let Some(value) = const_subs.get(name) {
if let Some(lit) = const_value_to_literal(value, *span) {
return lit;
}
}
expr.clone()
}
Expr::Literal(_, _)
| Expr::DataRef(_, _)
| Expr::DataDateTimeRef(_, _)
| Expr::TimeRef(_, _)
| Expr::DateTime(_, _)
| Expr::PatternRef(_, _)
| Expr::Duration(_, _)
| Expr::Continue(_)
| Expr::Unit(_)
| Expr::TableRows(_, _) => expr.clone(),
Expr::DataRelativeAccess {
reference,
index,
span,
} => Expr::DataRelativeAccess {
reference: Box::new(substitute_const_in_expr(reference, const_subs)),
index: index.clone(),
span: *span,
},
Expr::PropertyAccess {
object,
property,
optional,
span,
} => Expr::PropertyAccess {
object: Box::new(substitute_const_in_expr(object, const_subs)),
property: property.clone(),
optional: *optional,
span: *span,
},
Expr::IndexAccess {
object,
index,
end_index,
span,
} => Expr::IndexAccess {
object: Box::new(substitute_const_in_expr(object, const_subs)),
index: Box::new(substitute_const_in_expr(index, const_subs)),
end_index: end_index
.as_ref()
.map(|e| Box::new(substitute_const_in_expr(e, const_subs))),
span: *span,
},
Expr::BinaryOp {
left,
op,
right,
span,
} => Expr::BinaryOp {
left: Box::new(substitute_const_in_expr(left, const_subs)),
op: op.clone(),
right: Box::new(substitute_const_in_expr(right, const_subs)),
span: *span,
},
Expr::FuzzyComparison {
left,
op,
right,
tolerance,
span,
} => Expr::FuzzyComparison {
left: Box::new(substitute_const_in_expr(left, const_subs)),
op: op.clone(),
right: Box::new(substitute_const_in_expr(right, const_subs)),
tolerance: tolerance.clone(),
span: *span,
},
Expr::UnaryOp { op, operand, span } => Expr::UnaryOp {
op: op.clone(),
operand: Box::new(substitute_const_in_expr(operand, const_subs)),
span: *span,
},
Expr::FunctionCall {
name,
args,
named_args,
span,
} => Expr::FunctionCall {
name: name.clone(),
args: args
.iter()
.map(|a| substitute_const_in_expr(a, const_subs))
.collect(),
named_args: named_args
.iter()
.map(|(k, v)| (k.clone(), substitute_const_in_expr(v, const_subs)))
.collect(),
span: *span,
},
Expr::QualifiedFunctionCall {
namespace,
function,
args,
named_args,
span,
} => Expr::QualifiedFunctionCall {
namespace: namespace.clone(),
function: function.clone(),
args: args
.iter()
.map(|a| substitute_const_in_expr(a, const_subs))
.collect(),
named_args: named_args
.iter()
.map(|(k, v)| (k.clone(), substitute_const_in_expr(v, const_subs)))
.collect(),
span: *span,
},
Expr::EnumConstructor {
enum_name,
variant,
payload,
span,
} => Expr::EnumConstructor {
enum_name: enum_name.clone(),
variant: variant.clone(),
payload: match payload {
EnumConstructorPayload::Unit => EnumConstructorPayload::Unit,
EnumConstructorPayload::Tuple(args) => EnumConstructorPayload::Tuple(
args.iter()
.map(|a| substitute_const_in_expr(a, const_subs))
.collect(),
),
EnumConstructorPayload::Struct(fields) => EnumConstructorPayload::Struct(
fields
.iter()
.map(|(k, v)| (k.clone(), substitute_const_in_expr(v, const_subs)))
.collect(),
),
},
span: *span,
},
Expr::Conditional {
condition,
then_expr,
else_expr,
span,
} => Expr::Conditional {
condition: Box::new(substitute_const_in_expr(condition, const_subs)),
then_expr: Box::new(substitute_const_in_expr(then_expr, const_subs)),
else_expr: else_expr
.as_ref()
.map(|e| Box::new(substitute_const_in_expr(e, const_subs))),
span: *span,
},
Expr::Object(entries, span) => Expr::Object(
entries
.iter()
.map(|e| match e {
ObjectEntry::Field {
key,
value,
type_annotation,
} => ObjectEntry::Field {
key: key.clone(),
value: substitute_const_in_expr(value, const_subs),
type_annotation: type_annotation.clone(),
},
ObjectEntry::Spread(inner) => {
ObjectEntry::Spread(substitute_const_in_expr(inner, const_subs))
}
})
.collect(),
*span,
),
Expr::Array(items, span) => Expr::Array(
items
.iter()
.map(|i| substitute_const_in_expr(i, const_subs))
.collect(),
*span,
),
Expr::ListComprehension(comp, span) => Expr::ListComprehension(
Box::new(ListComprehension {
element: Box::new(substitute_const_in_expr(&comp.element, const_subs)),
clauses: comp
.clauses
.iter()
.map(|c| ComprehensionClause {
pattern: c.pattern.clone(),
iterable: Box::new(substitute_const_in_expr(&c.iterable, const_subs)),
filter: c
.filter
.as_ref()
.map(|f| Box::new(substitute_const_in_expr(f, const_subs))),
})
.collect(),
}),
*span,
),
Expr::Block(block, span) => {
let new_items = block
.items
.iter()
.map(|item| match item {
BlockItem::VariableDecl(decl) => {
let mut new_decl = decl.clone();
new_decl.value = decl
.value
.as_ref()
.map(|e| substitute_const_in_expr(e, const_subs));
BlockItem::VariableDecl(new_decl)
}
BlockItem::Assignment(assign) => {
let mut new_assign = assign.clone();
new_assign.value = substitute_const_in_expr(&assign.value, const_subs);
BlockItem::Assignment(new_assign)
}
BlockItem::Statement(s) => {
BlockItem::Statement(substitute_const_in_statement(s, const_subs))
}
BlockItem::Expression(e) => {
BlockItem::Expression(substitute_const_in_expr(e, const_subs))
}
})
.collect();
Expr::Block(BlockExpr { items: new_items }, *span)
}
Expr::TypeAssertion {
expr,
type_annotation,
meta_param_overrides,
span,
} => Expr::TypeAssertion {
expr: Box::new(substitute_const_in_expr(expr, const_subs)),
type_annotation: type_annotation.clone(),
meta_param_overrides: meta_param_overrides.clone(),
span: *span,
},
Expr::InstanceOf {
expr,
type_annotation,
span,
} => Expr::InstanceOf {
expr: Box::new(substitute_const_in_expr(expr, const_subs)),
type_annotation: type_annotation.clone(),
span: *span,
},
Expr::FunctionExpr {
params,
return_type,
body,
span,
} => Expr::FunctionExpr {
params: params
.iter()
.map(|p| {
let mut np = p.clone();
np.default_value = p
.default_value
.as_ref()
.map(|e| substitute_const_in_expr(e, const_subs));
np
})
.collect(),
return_type: return_type.clone(),
body: body
.iter()
.map(|s| substitute_const_in_statement(s, const_subs))
.collect(),
span: *span,
},
Expr::Spread(inner, span) => {
Expr::Spread(Box::new(substitute_const_in_expr(inner, const_subs)), *span)
}
Expr::If(if_expr, span) => Expr::If(
Box::new(IfExpr {
condition: Box::new(substitute_const_in_expr(&if_expr.condition, const_subs)),
then_branch: Box::new(substitute_const_in_expr(&if_expr.then_branch, const_subs)),
else_branch: if_expr
.else_branch
.as_ref()
.map(|e| Box::new(substitute_const_in_expr(e, const_subs))),
}),
*span,
),
Expr::While(while_expr, span) => Expr::While(
Box::new(WhileExpr {
condition: Box::new(substitute_const_in_expr(&while_expr.condition, const_subs)),
body: Box::new(substitute_const_in_expr(&while_expr.body, const_subs)),
}),
*span,
),
Expr::For(for_expr, span) => Expr::For(
Box::new(ForExpr {
pattern: for_expr.pattern.clone(),
iterable: Box::new(substitute_const_in_expr(&for_expr.iterable, const_subs)),
body: Box::new(substitute_const_in_expr(&for_expr.body, const_subs)),
is_async: for_expr.is_async,
}),
*span,
),
Expr::Loop(loop_expr, span) => Expr::Loop(
Box::new(LoopExpr {
body: Box::new(substitute_const_in_expr(&loop_expr.body, const_subs)),
}),
*span,
),
Expr::Let(let_expr, span) => Expr::Let(
Box::new(LetExpr {
pattern: let_expr.pattern.clone(),
type_annotation: let_expr.type_annotation.clone(),
value: let_expr
.value
.as_ref()
.map(|v| Box::new(substitute_const_in_expr(v, const_subs))),
body: Box::new(substitute_const_in_expr(&let_expr.body, const_subs)),
}),
*span,
),
Expr::Assign(assign_expr, span) => Expr::Assign(
Box::new(AssignExpr {
target: Box::new(substitute_const_in_expr(&assign_expr.target, const_subs)),
value: Box::new(substitute_const_in_expr(&assign_expr.value, const_subs)),
}),
*span,
),
Expr::Break(value, span) => Expr::Break(
value
.as_ref()
.map(|e| Box::new(substitute_const_in_expr(e, const_subs))),
*span,
),
Expr::Return(value, span) => Expr::Return(
value
.as_ref()
.map(|e| Box::new(substitute_const_in_expr(e, const_subs))),
*span,
),
Expr::MethodCall {
receiver,
method,
args,
named_args,
optional,
span,
} => Expr::MethodCall {
receiver: Box::new(substitute_const_in_expr(receiver, const_subs)),
method: method.clone(),
args: args
.iter()
.map(|a| substitute_const_in_expr(a, const_subs))
.collect(),
named_args: named_args
.iter()
.map(|(k, v)| (k.clone(), substitute_const_in_expr(v, const_subs)))
.collect(),
optional: *optional,
span: *span,
},
Expr::Match(match_expr, span) => Expr::Match(
Box::new(MatchExpr {
scrutinee: Box::new(substitute_const_in_expr(&match_expr.scrutinee, const_subs)),
arms: match_expr
.arms
.iter()
.map(|arm| MatchArm {
pattern: arm.pattern.clone(),
guard: arm
.guard
.as_ref()
.map(|g| Box::new(substitute_const_in_expr(g, const_subs))),
body: Box::new(substitute_const_in_expr(&arm.body, const_subs)),
pattern_span: arm.pattern_span,
})
.collect(),
}),
*span,
),
Expr::Range {
start,
end,
kind,
span,
} => Expr::Range {
start: start
.as_ref()
.map(|e| Box::new(substitute_const_in_expr(e, const_subs))),
end: end
.as_ref()
.map(|e| Box::new(substitute_const_in_expr(e, const_subs))),
kind: *kind,
span: *span,
},
Expr::TimeframeContext {
timeframe,
expr,
span,
} => Expr::TimeframeContext {
timeframe: timeframe.clone(),
expr: Box::new(substitute_const_in_expr(expr, const_subs)),
span: *span,
},
Expr::TryOperator(inner, span) => {
Expr::TryOperator(Box::new(substitute_const_in_expr(inner, const_subs)), *span)
}
Expr::UsingImpl {
expr,
impl_name,
span,
} => Expr::UsingImpl {
expr: Box::new(substitute_const_in_expr(expr, const_subs)),
impl_name: impl_name.clone(),
span: *span,
},
Expr::SimulationCall { name, params, span } => Expr::SimulationCall {
name: name.clone(),
params: params
.iter()
.map(|(k, v)| (k.clone(), substitute_const_in_expr(v, const_subs)))
.collect(),
span: *span,
},
Expr::WindowExpr(w, span) => Expr::WindowExpr(w.clone(), *span),
Expr::FromQuery(q, span) => Expr::FromQuery(
Box::new(FromQueryExpr {
variable: q.variable.clone(),
source: Box::new(substitute_const_in_expr(&q.source, const_subs)),
clauses: q
.clauses
.iter()
.map(|clause| match clause {
QueryClause::Where(e) => {
QueryClause::Where(Box::new(substitute_const_in_expr(e, const_subs)))
}
QueryClause::OrderBy(specs) => QueryClause::OrderBy(specs.clone()),
QueryClause::GroupBy {
element,
key,
into_var,
} => QueryClause::GroupBy {
element: Box::new(substitute_const_in_expr(element, const_subs)),
key: Box::new(substitute_const_in_expr(key, const_subs)),
into_var: into_var.clone(),
},
QueryClause::Join {
variable,
source,
left_key,
right_key,
into_var,
} => QueryClause::Join {
variable: variable.clone(),
source: Box::new(substitute_const_in_expr(source, const_subs)),
left_key: Box::new(substitute_const_in_expr(left_key, const_subs)),
right_key: Box::new(substitute_const_in_expr(right_key, const_subs)),
into_var: into_var.clone(),
},
QueryClause::Let { variable, value } => QueryClause::Let {
variable: variable.clone(),
value: Box::new(substitute_const_in_expr(value, const_subs)),
},
})
.collect(),
select: Box::new(substitute_const_in_expr(&q.select, const_subs)),
}),
*span,
),
Expr::StructLiteral {
type_name,
fields,
span,
} => Expr::StructLiteral {
type_name: type_name.clone(),
fields: fields
.iter()
.map(|(k, v)| (k.clone(), substitute_const_in_expr(v, const_subs)))
.collect(),
span: *span,
},
Expr::Await(inner, span) => {
Expr::Await(Box::new(substitute_const_in_expr(inner, const_subs)), *span)
}
Expr::Join(join, span) => Expr::Join(
Box::new(JoinExpr {
kind: join.kind,
branches: join
.branches
.iter()
.map(|b| JoinBranch {
label: b.label.clone(),
expr: substitute_const_in_expr(&b.expr, const_subs),
annotations: b.annotations.clone(),
})
.collect(),
span: join.span,
}),
*span,
),
Expr::Annotated {
annotation,
target,
span,
} => Expr::Annotated {
annotation: annotation.clone(),
target: Box::new(substitute_const_in_expr(target, const_subs)),
span: *span,
},
Expr::AsyncLet(async_let, span) => Expr::AsyncLet(
Box::new(AsyncLetExpr {
name: async_let.name.clone(),
expr: Box::new(substitute_const_in_expr(&async_let.expr, const_subs)),
span: async_let.span,
}),
*span,
),
Expr::AsyncScope(inner, span) => Expr::AsyncScope(
Box::new(substitute_const_in_expr(inner, const_subs)),
*span,
),
Expr::Comptime(stmts, span) => Expr::Comptime(
stmts
.iter()
.map(|s| substitute_const_in_statement(s, const_subs))
.collect(),
*span,
),
Expr::ComptimeFor(comp_for, span) => Expr::ComptimeFor(
Box::new(ComptimeForExpr {
variable: comp_for.variable.clone(),
iterable: Box::new(substitute_const_in_expr(&comp_for.iterable, const_subs)),
body: comp_for
.body
.iter()
.map(|s| substitute_const_in_statement(s, const_subs))
.collect(),
}),
*span,
),
Expr::Reference {
expr,
is_mutable,
span,
} => Expr::Reference {
expr: Box::new(substitute_const_in_expr(expr, const_subs)),
is_mutable: *is_mutable,
span: *span,
},
}
}
fn const_value_to_literal(value: &ComptimeConstValue, span: shape_ast::ast::Span) -> Option<Expr> {
use shape_ast::ast::Literal;
match value {
ComptimeConstValue::Int(i) => Some(Expr::Literal(Literal::Int(*i), span)),
ComptimeConstValue::Bool(b) => Some(Expr::Literal(Literal::Bool(*b), span)),
ComptimeConstValue::Number(f) => Some(Expr::Literal(Literal::Number(*f), span)),
ComptimeConstValue::String(s) => Some(Expr::Literal(Literal::String(s.clone()), span)),
}
}
fn substitute_function_parameter(
p: &FunctionParameter,
subs: &HashMap<String, ConcreteType>,
) -> FunctionParameter {
FunctionParameter {
pattern: substitute_destructure_pattern(&p.pattern, subs),
is_const: p.is_const,
is_reference: p.is_reference,
is_mut_reference: p.is_mut_reference,
is_out: p.is_out,
type_annotation: p
.type_annotation
.as_ref()
.map(|t| substitute_type_annotation(t, subs)),
default_value: p.default_value.as_ref().map(|e| substitute_expr(e, subs)),
}
}
fn substitute_destructure_pattern(
pat: &DestructurePattern,
subs: &HashMap<String, ConcreteType>,
) -> DestructurePattern {
match pat {
DestructurePattern::Identifier(name, span) => {
DestructurePattern::Identifier(name.clone(), *span)
}
DestructurePattern::Array(items) => DestructurePattern::Array(
items
.iter()
.map(|p| substitute_destructure_pattern(p, subs))
.collect(),
),
DestructurePattern::Object(fields) => DestructurePattern::Object(
fields
.iter()
.map(|f| ObjectPatternField {
key: f.key.clone(),
pattern: substitute_destructure_pattern(&f.pattern, subs),
})
.collect(),
),
DestructurePattern::Rest(inner) => {
DestructurePattern::Rest(Box::new(substitute_destructure_pattern(inner, subs)))
}
DestructurePattern::Decomposition(bindings) => DestructurePattern::Decomposition(
bindings
.iter()
.map(|b| DecompositionBinding {
name: b.name.clone(),
type_annotation: substitute_type_annotation(&b.type_annotation, subs),
span: b.span,
})
.collect(),
),
}
}
fn substitute_pattern(pat: &Pattern, subs: &HashMap<String, ConcreteType>) -> Pattern {
match pat {
Pattern::Typed {
name,
type_annotation,
} => Pattern::Typed {
name: name.clone(),
type_annotation: substitute_type_annotation(type_annotation, subs),
},
Pattern::Array(items) => {
Pattern::Array(items.iter().map(|p| substitute_pattern(p, subs)).collect())
}
Pattern::Object(fields) => Pattern::Object(
fields
.iter()
.map(|(k, p)| (k.clone(), substitute_pattern(p, subs)))
.collect(),
),
Pattern::Constructor {
enum_name,
variant,
fields,
} => Pattern::Constructor {
enum_name: enum_name.clone(),
variant: variant.clone(),
fields: match fields {
PatternConstructorFields::Unit => PatternConstructorFields::Unit,
PatternConstructorFields::Tuple(pats) => PatternConstructorFields::Tuple(
pats.iter().map(|p| substitute_pattern(p, subs)).collect(),
),
PatternConstructorFields::Struct(pairs) => PatternConstructorFields::Struct(
pairs
.iter()
.map(|(k, p)| (k.clone(), substitute_pattern(p, subs)))
.collect(),
),
},
},
Pattern::Identifier(_) | Pattern::Literal(_) | Pattern::Wildcard => pat.clone(),
}
}
fn substitute_statement(stmt: &Statement, subs: &HashMap<String, ConcreteType>) -> Statement {
match stmt {
Statement::Return(expr, span) => {
Statement::Return(expr.as_ref().map(|e| substitute_expr(e, subs)), *span)
}
Statement::Break(span) => Statement::Break(*span),
Statement::Continue(span) => Statement::Continue(*span),
Statement::VariableDecl(decl, span) => {
let mut new_decl = decl.clone();
new_decl.pattern = substitute_destructure_pattern(&decl.pattern, subs);
new_decl.type_annotation = decl
.type_annotation
.as_ref()
.map(|t| substitute_type_annotation(t, subs));
new_decl.value = decl.value.as_ref().map(|e| substitute_expr(e, subs));
Statement::VariableDecl(new_decl, *span)
}
Statement::Assignment(assign, span) => {
let mut new_assign = assign.clone();
new_assign.pattern = substitute_destructure_pattern(&assign.pattern, subs);
new_assign.value = substitute_expr(&assign.value, subs);
Statement::Assignment(new_assign, *span)
}
Statement::Expression(expr, span) => {
Statement::Expression(substitute_expr(expr, subs), *span)
}
Statement::For(for_loop, span) => {
let mut new_loop = for_loop.clone();
new_loop.init = match &for_loop.init {
ForInit::ForIn { pattern, iter } => ForInit::ForIn {
pattern: substitute_destructure_pattern(pattern, subs),
iter: substitute_expr(iter, subs),
},
ForInit::ForC {
init,
condition,
update,
} => ForInit::ForC {
init: Box::new(substitute_statement(init, subs)),
condition: substitute_expr(condition, subs),
update: substitute_expr(update, subs),
},
};
new_loop.body = for_loop
.body
.iter()
.map(|s| substitute_statement(s, subs))
.collect();
Statement::For(new_loop, *span)
}
Statement::While(while_loop, span) => Statement::While(
WhileLoop {
condition: substitute_expr(&while_loop.condition, subs),
body: while_loop
.body
.iter()
.map(|s| substitute_statement(s, subs))
.collect(),
},
*span,
),
Statement::If(if_stmt, span) => Statement::If(
IfStatement {
condition: substitute_expr(&if_stmt.condition, subs),
then_body: if_stmt
.then_body
.iter()
.map(|s| substitute_statement(s, subs))
.collect(),
else_body: if_stmt.else_body.as_ref().map(|body| {
body.iter()
.map(|s| substitute_statement(s, subs))
.collect()
}),
},
*span,
),
Statement::Extend(ext, span) => Statement::Extend(substitute_extend(ext, subs), *span),
Statement::RemoveTarget(span) => Statement::RemoveTarget(*span),
Statement::SetParamType {
param_name,
type_annotation,
span,
} => Statement::SetParamType {
param_name: param_name.clone(),
type_annotation: substitute_type_annotation(type_annotation, subs),
span: *span,
},
Statement::SetParamValue {
param_name,
expression,
span,
} => Statement::SetParamValue {
param_name: param_name.clone(),
expression: substitute_expr(expression, subs),
span: *span,
},
Statement::SetReturnType {
type_annotation,
span,
} => Statement::SetReturnType {
type_annotation: substitute_type_annotation(type_annotation, subs),
span: *span,
},
Statement::SetReturnExpr { expression, span } => Statement::SetReturnExpr {
expression: substitute_expr(expression, subs),
span: *span,
},
Statement::ReplaceBody { body, span } => Statement::ReplaceBody {
body: body
.iter()
.map(|s| substitute_statement(s, subs))
.collect(),
span: *span,
},
Statement::ReplaceBodyExpr { expression, span } => Statement::ReplaceBodyExpr {
expression: substitute_expr(expression, subs),
span: *span,
},
Statement::ReplaceModuleExpr { expression, span } => Statement::ReplaceModuleExpr {
expression: substitute_expr(expression, subs),
span: *span,
},
}
}
fn substitute_extend(
ext: &ExtendStatement,
subs: &HashMap<String, ConcreteType>,
) -> ExtendStatement {
ExtendStatement {
type_name: ext.type_name.clone(),
methods: ext
.methods
.iter()
.map(|m| substitute_method_def(m, subs))
.collect(),
}
}
fn substitute_method_def(m: &MethodDef, subs: &HashMap<String, ConcreteType>) -> MethodDef {
let mut cloned = m.clone();
cloned.params = m
.params
.iter()
.map(|p| substitute_function_parameter(p, subs))
.collect();
cloned.return_type = m
.return_type
.as_ref()
.map(|t| substitute_type_annotation(t, subs));
cloned.body = m
.body
.iter()
.map(|s| substitute_statement(s, subs))
.collect();
cloned.when_clause = m
.when_clause
.as_ref()
.map(|e| Box::new(substitute_expr(e, subs)));
cloned
}
fn substitute_expr(expr: &Expr, subs: &HashMap<String, ConcreteType>) -> Expr {
match expr {
Expr::Literal(_, _)
| Expr::Identifier(_, _)
| Expr::DataRef(_, _)
| Expr::DataDateTimeRef(_, _)
| Expr::TimeRef(_, _)
| Expr::DateTime(_, _)
| Expr::PatternRef(_, _)
| Expr::Duration(_, _)
| Expr::Continue(_)
| Expr::Unit(_)
| Expr::TableRows(_, _) => expr.clone(),
Expr::DataRelativeAccess {
reference,
index,
span,
} => Expr::DataRelativeAccess {
reference: Box::new(substitute_expr(reference, subs)),
index: index.clone(),
span: *span,
},
Expr::PropertyAccess {
object,
property,
optional,
span,
} => Expr::PropertyAccess {
object: Box::new(substitute_expr(object, subs)),
property: property.clone(),
optional: *optional,
span: *span,
},
Expr::IndexAccess {
object,
index,
end_index,
span,
} => Expr::IndexAccess {
object: Box::new(substitute_expr(object, subs)),
index: Box::new(substitute_expr(index, subs)),
end_index: end_index
.as_ref()
.map(|e| Box::new(substitute_expr(e, subs))),
span: *span,
},
Expr::BinaryOp {
left,
op,
right,
span,
} => Expr::BinaryOp {
left: Box::new(substitute_expr(left, subs)),
op: op.clone(),
right: Box::new(substitute_expr(right, subs)),
span: *span,
},
Expr::FuzzyComparison {
left,
op,
right,
tolerance,
span,
} => Expr::FuzzyComparison {
left: Box::new(substitute_expr(left, subs)),
op: op.clone(),
right: Box::new(substitute_expr(right, subs)),
tolerance: tolerance.clone(),
span: *span,
},
Expr::UnaryOp { op, operand, span } => Expr::UnaryOp {
op: op.clone(),
operand: Box::new(substitute_expr(operand, subs)),
span: *span,
},
Expr::FunctionCall {
name,
args,
named_args,
span,
} => Expr::FunctionCall {
name: name.clone(),
args: args.iter().map(|a| substitute_expr(a, subs)).collect(),
named_args: named_args
.iter()
.map(|(k, v)| (k.clone(), substitute_expr(v, subs)))
.collect(),
span: *span,
},
Expr::QualifiedFunctionCall {
namespace,
function,
args,
named_args,
span,
} => Expr::QualifiedFunctionCall {
namespace: namespace.clone(),
function: function.clone(),
args: args.iter().map(|a| substitute_expr(a, subs)).collect(),
named_args: named_args
.iter()
.map(|(k, v)| (k.clone(), substitute_expr(v, subs)))
.collect(),
span: *span,
},
Expr::EnumConstructor {
enum_name,
variant,
payload,
span,
} => Expr::EnumConstructor {
enum_name: enum_name.clone(),
variant: variant.clone(),
payload: match payload {
EnumConstructorPayload::Unit => EnumConstructorPayload::Unit,
EnumConstructorPayload::Tuple(args) => EnumConstructorPayload::Tuple(
args.iter().map(|a| substitute_expr(a, subs)).collect(),
),
EnumConstructorPayload::Struct(fields) => EnumConstructorPayload::Struct(
fields
.iter()
.map(|(k, v)| (k.clone(), substitute_expr(v, subs)))
.collect(),
),
},
span: *span,
},
Expr::Conditional {
condition,
then_expr,
else_expr,
span,
} => Expr::Conditional {
condition: Box::new(substitute_expr(condition, subs)),
then_expr: Box::new(substitute_expr(then_expr, subs)),
else_expr: else_expr
.as_ref()
.map(|e| Box::new(substitute_expr(e, subs))),
span: *span,
},
Expr::Object(entries, span) => Expr::Object(
entries
.iter()
.map(|e| match e {
ObjectEntry::Field {
key,
value,
type_annotation,
} => ObjectEntry::Field {
key: key.clone(),
value: substitute_expr(value, subs),
type_annotation: type_annotation
.as_ref()
.map(|t| substitute_type_annotation(t, subs)),
},
ObjectEntry::Spread(inner) => ObjectEntry::Spread(substitute_expr(inner, subs)),
})
.collect(),
*span,
),
Expr::Array(items, span) => Expr::Array(
items.iter().map(|i| substitute_expr(i, subs)).collect(),
*span,
),
Expr::ListComprehension(comp, span) => Expr::ListComprehension(
Box::new(ListComprehension {
element: Box::new(substitute_expr(&comp.element, subs)),
clauses: comp
.clauses
.iter()
.map(|c| ComprehensionClause {
pattern: substitute_destructure_pattern(&c.pattern, subs),
iterable: Box::new(substitute_expr(&c.iterable, subs)),
filter: c
.filter
.as_ref()
.map(|f| Box::new(substitute_expr(f, subs))),
})
.collect(),
}),
*span,
),
Expr::Block(block, span) => {
let new_items = block
.items
.iter()
.map(|item| match item {
BlockItem::VariableDecl(decl) => {
let mut new_decl = decl.clone();
new_decl.pattern = substitute_destructure_pattern(&decl.pattern, subs);
new_decl.type_annotation = decl
.type_annotation
.as_ref()
.map(|t| substitute_type_annotation(t, subs));
new_decl.value = decl.value.as_ref().map(|e| substitute_expr(e, subs));
BlockItem::VariableDecl(new_decl)
}
BlockItem::Assignment(assign) => {
let mut new_assign = assign.clone();
new_assign.pattern =
substitute_destructure_pattern(&assign.pattern, subs);
new_assign.value = substitute_expr(&assign.value, subs);
BlockItem::Assignment(new_assign)
}
BlockItem::Statement(s) => BlockItem::Statement(substitute_statement(s, subs)),
BlockItem::Expression(e) => BlockItem::Expression(substitute_expr(e, subs)),
})
.collect();
Expr::Block(BlockExpr { items: new_items }, *span)
}
Expr::TypeAssertion {
expr,
type_annotation,
meta_param_overrides,
span,
} => Expr::TypeAssertion {
expr: Box::new(substitute_expr(expr, subs)),
type_annotation: substitute_type_annotation(type_annotation, subs),
meta_param_overrides: meta_param_overrides.clone(),
span: *span,
},
Expr::InstanceOf {
expr,
type_annotation,
span,
} => Expr::InstanceOf {
expr: Box::new(substitute_expr(expr, subs)),
type_annotation: substitute_type_annotation(type_annotation, subs),
span: *span,
},
Expr::FunctionExpr {
params,
return_type,
body,
span,
} => Expr::FunctionExpr {
params: params
.iter()
.map(|p| substitute_function_parameter(p, subs))
.collect(),
return_type: return_type
.as_ref()
.map(|t| substitute_type_annotation(t, subs)),
body: body.iter().map(|s| substitute_statement(s, subs)).collect(),
span: *span,
},
Expr::Spread(inner, span) => {
Expr::Spread(Box::new(substitute_expr(inner, subs)), *span)
}
Expr::If(if_expr, span) => Expr::If(
Box::new(IfExpr {
condition: Box::new(substitute_expr(&if_expr.condition, subs)),
then_branch: Box::new(substitute_expr(&if_expr.then_branch, subs)),
else_branch: if_expr
.else_branch
.as_ref()
.map(|e| Box::new(substitute_expr(e, subs))),
}),
*span,
),
Expr::While(while_expr, span) => Expr::While(
Box::new(WhileExpr {
condition: Box::new(substitute_expr(&while_expr.condition, subs)),
body: Box::new(substitute_expr(&while_expr.body, subs)),
}),
*span,
),
Expr::For(for_expr, span) => Expr::For(
Box::new(ForExpr {
pattern: substitute_pattern(&for_expr.pattern, subs),
iterable: Box::new(substitute_expr(&for_expr.iterable, subs)),
body: Box::new(substitute_expr(&for_expr.body, subs)),
is_async: for_expr.is_async,
}),
*span,
),
Expr::Loop(loop_expr, span) => Expr::Loop(
Box::new(LoopExpr {
body: Box::new(substitute_expr(&loop_expr.body, subs)),
}),
*span,
),
Expr::Let(let_expr, span) => Expr::Let(
Box::new(LetExpr {
pattern: substitute_pattern(&let_expr.pattern, subs),
type_annotation: let_expr
.type_annotation
.as_ref()
.map(|t| substitute_type_annotation(t, subs)),
value: let_expr
.value
.as_ref()
.map(|v| Box::new(substitute_expr(v, subs))),
body: Box::new(substitute_expr(&let_expr.body, subs)),
}),
*span,
),
Expr::Assign(assign_expr, span) => Expr::Assign(
Box::new(AssignExpr {
target: Box::new(substitute_expr(&assign_expr.target, subs)),
value: Box::new(substitute_expr(&assign_expr.value, subs)),
}),
*span,
),
Expr::Break(value, span) => Expr::Break(
value.as_ref().map(|e| Box::new(substitute_expr(e, subs))),
*span,
),
Expr::Return(value, span) => Expr::Return(
value.as_ref().map(|e| Box::new(substitute_expr(e, subs))),
*span,
),
Expr::MethodCall {
receiver,
method,
args,
named_args,
optional,
span,
} => Expr::MethodCall {
receiver: Box::new(substitute_expr(receiver, subs)),
method: method.clone(),
args: args.iter().map(|a| substitute_expr(a, subs)).collect(),
named_args: named_args
.iter()
.map(|(k, v)| (k.clone(), substitute_expr(v, subs)))
.collect(),
optional: *optional,
span: *span,
},
Expr::Match(match_expr, span) => Expr::Match(
Box::new(MatchExpr {
scrutinee: Box::new(substitute_expr(&match_expr.scrutinee, subs)),
arms: match_expr
.arms
.iter()
.map(|arm| MatchArm {
pattern: substitute_pattern(&arm.pattern, subs),
guard: arm.guard.as_ref().map(|g| Box::new(substitute_expr(g, subs))),
body: Box::new(substitute_expr(&arm.body, subs)),
pattern_span: arm.pattern_span,
})
.collect(),
}),
*span,
),
Expr::Range {
start,
end,
kind,
span,
} => Expr::Range {
start: start.as_ref().map(|e| Box::new(substitute_expr(e, subs))),
end: end.as_ref().map(|e| Box::new(substitute_expr(e, subs))),
kind: *kind,
span: *span,
},
Expr::TimeframeContext {
timeframe,
expr,
span,
} => Expr::TimeframeContext {
timeframe: timeframe.clone(),
expr: Box::new(substitute_expr(expr, subs)),
span: *span,
},
Expr::TryOperator(inner, span) => {
Expr::TryOperator(Box::new(substitute_expr(inner, subs)), *span)
}
Expr::UsingImpl {
expr,
impl_name,
span,
} => Expr::UsingImpl {
expr: Box::new(substitute_expr(expr, subs)),
impl_name: impl_name.clone(),
span: *span,
},
Expr::SimulationCall { name, params, span } => Expr::SimulationCall {
name: name.clone(),
params: params
.iter()
.map(|(k, v)| (k.clone(), substitute_expr(v, subs)))
.collect(),
span: *span,
},
Expr::WindowExpr(w, span) => Expr::WindowExpr(w.clone(), *span),
Expr::FromQuery(q, span) => Expr::FromQuery(
Box::new(FromQueryExpr {
variable: q.variable.clone(),
source: Box::new(substitute_expr(&q.source, subs)),
clauses: q
.clauses
.iter()
.map(|clause| match clause {
QueryClause::Where(e) => {
QueryClause::Where(Box::new(substitute_expr(e, subs)))
}
QueryClause::OrderBy(specs) => QueryClause::OrderBy(specs.clone()),
QueryClause::GroupBy {
element,
key,
into_var,
} => QueryClause::GroupBy {
element: Box::new(substitute_expr(element, subs)),
key: Box::new(substitute_expr(key, subs)),
into_var: into_var.clone(),
},
QueryClause::Join {
variable,
source,
left_key,
right_key,
into_var,
} => QueryClause::Join {
variable: variable.clone(),
source: Box::new(substitute_expr(source, subs)),
left_key: Box::new(substitute_expr(left_key, subs)),
right_key: Box::new(substitute_expr(right_key, subs)),
into_var: into_var.clone(),
},
QueryClause::Let { variable, value } => QueryClause::Let {
variable: variable.clone(),
value: Box::new(substitute_expr(value, subs)),
},
})
.collect(),
select: Box::new(substitute_expr(&q.select, subs)),
}),
*span,
),
Expr::StructLiteral {
type_name,
fields,
span,
} => Expr::StructLiteral {
type_name: type_name.clone(),
fields: fields
.iter()
.map(|(k, v)| (k.clone(), substitute_expr(v, subs)))
.collect(),
span: *span,
},
Expr::Await(inner, span) => Expr::Await(Box::new(substitute_expr(inner, subs)), *span),
Expr::Join(join, span) => Expr::Join(
Box::new(JoinExpr {
kind: join.kind,
branches: join
.branches
.iter()
.map(|b| JoinBranch {
label: b.label.clone(),
expr: substitute_expr(&b.expr, subs),
annotations: b.annotations.clone(),
})
.collect(),
span: join.span,
}),
*span,
),
Expr::Annotated {
annotation,
target,
span,
} => Expr::Annotated {
annotation: annotation.clone(),
target: Box::new(substitute_expr(target, subs)),
span: *span,
},
Expr::AsyncLet(async_let, span) => Expr::AsyncLet(
Box::new(AsyncLetExpr {
name: async_let.name.clone(),
expr: Box::new(substitute_expr(&async_let.expr, subs)),
span: async_let.span,
}),
*span,
),
Expr::AsyncScope(inner, span) => {
Expr::AsyncScope(Box::new(substitute_expr(inner, subs)), *span)
}
Expr::Comptime(stmts, span) => Expr::Comptime(
stmts
.iter()
.map(|s| substitute_statement(s, subs))
.collect(),
*span,
),
Expr::ComptimeFor(comp_for, span) => Expr::ComptimeFor(
Box::new(ComptimeForExpr {
variable: comp_for.variable.clone(),
iterable: Box::new(substitute_expr(&comp_for.iterable, subs)),
body: comp_for
.body
.iter()
.map(|s| substitute_statement(s, subs))
.collect(),
}),
*span,
),
Expr::Reference {
expr,
is_mutable,
span,
} => Expr::Reference {
expr: Box::new(substitute_expr(expr, subs)),
is_mutable: *is_mutable,
span: *span,
},
}
}
pub fn inline_closure_body_into_specialization(
specialized: &mut shape_ast::ast::FunctionDef,
closure_param_name: &str,
closure_params: &[String],
closure_body: &[shape_ast::ast::Statement],
capture_names: &[String],
closure_param_annotations: &[Option<shape_ast::ast::TypeAnnotation>],
) -> shape_ast::error::Result<()> {
let _ = capture_names;
if std::env::var_os("SHAPE_JIT_DEBUG").is_some() {
PHASEC_TRACE_FN_CALL_TOTAL.with(|c| c.set(0));
PHASEC_TRACE_FN_CALL_MATCH.with(|c| c.set(0));
PHASEC_TRACE_METHOD_CALL.with(|c| c.set(0));
PHASEC_TRACE_FOR_STMT.with(|c| c.set(0));
eprintln!(
"[phaseC-empirical] specialization fn={} body stmt count BEFORE inline = {}",
specialized.name,
specialized.body.len(),
);
for (i, s) in specialized.body.iter().enumerate() {
eprintln!(
"[phaseC-empirical] pre-body[{}] discriminant={}",
i,
statement_discriminant(s),
);
}
}
specialized.body = specialized
.body
.iter()
.map(|s| {
inline_closure_calls_in_statement(
s,
closure_param_name,
closure_params,
closure_body,
closure_param_annotations,
)
})
.collect();
if std::env::var_os("SHAPE_JIT_DEBUG").is_some() {
eprintln!(
"[phaseC-empirical] specialization fn={} body stmt count AFTER inline = {} \
fn_call_total={} fn_call_match={} method_call={} for_stmt={}",
specialized.name,
specialized.body.len(),
PHASEC_TRACE_FN_CALL_TOTAL.with(|c| c.get()),
PHASEC_TRACE_FN_CALL_MATCH.with(|c| c.get()),
PHASEC_TRACE_METHOD_CALL.with(|c| c.get()),
PHASEC_TRACE_FOR_STMT.with(|c| c.get()),
);
for (i, s) in specialized.body.iter().enumerate() {
eprintln!(
"[phaseC-empirical] post-body[{}] discriminant={}",
i,
statement_discriminant(s),
);
}
}
Ok(())
}
thread_local! {
static PHASEC_TRACE_FN_CALL_TOTAL: std::cell::Cell<usize> = const { std::cell::Cell::new(0) };
static PHASEC_TRACE_FN_CALL_MATCH: std::cell::Cell<usize> = const { std::cell::Cell::new(0) };
static PHASEC_TRACE_METHOD_CALL: std::cell::Cell<usize> = const { std::cell::Cell::new(0) };
static PHASEC_TRACE_FOR_STMT: std::cell::Cell<usize> = const { std::cell::Cell::new(0) };
}
fn statement_discriminant(s: &shape_ast::ast::Statement) -> &'static str {
use shape_ast::ast::Statement;
match s {
Statement::Return(..) => "Return",
Statement::Expression(..) => "Expression",
Statement::VariableDecl(..) => "VariableDecl",
Statement::Assignment(..) => "Assignment",
Statement::For(..) => "For",
Statement::While(..) => "While",
Statement::If(..) => "If",
Statement::Break(..) => "Break",
Statement::Continue(..) => "Continue",
Statement::Extend(..) => "Extend",
_ => "Other",
}
}
fn inline_closure_calls_in_statement(
stmt: &shape_ast::ast::Statement,
closure_param_name: &str,
closure_params: &[String],
closure_body: &[shape_ast::ast::Statement],
closure_param_annotations: &[Option<shape_ast::ast::TypeAnnotation>],
) -> shape_ast::ast::Statement {
use shape_ast::ast::{Statement, statements::{ForInit, ForLoop, IfStatement, WhileLoop}};
match stmt {
Statement::Return(expr, span) => Statement::Return(
expr.as_ref()
.map(|e| inline_closure_calls_in_expr(e, closure_param_name, closure_params, closure_body, closure_param_annotations)),
*span,
),
Statement::Expression(expr, span) => Statement::Expression(
inline_closure_calls_in_expr(expr, closure_param_name, closure_params, closure_body, closure_param_annotations),
*span,
),
Statement::VariableDecl(decl, span) => {
let mut new_decl = decl.clone();
new_decl.value = decl.value.as_ref().map(|e| {
inline_closure_calls_in_expr(e, closure_param_name, closure_params, closure_body, closure_param_annotations)
});
Statement::VariableDecl(new_decl, *span)
}
Statement::Assignment(assignment, span) => {
let mut new_assign = assignment.clone();
new_assign.value = inline_closure_calls_in_expr(
&assignment.value,
closure_param_name,
closure_params,
closure_body,
closure_param_annotations,
);
Statement::Assignment(new_assign, *span)
}
Statement::For(for_loop, span) => {
if std::env::var_os("SHAPE_JIT_DEBUG").is_some() {
PHASEC_TRACE_FOR_STMT.with(|c| c.set(c.get() + 1));
eprintln!(
"[phaseC-empirical] For statement encountered (body_stmts={})",
for_loop.body.len(),
);
}
let new_init = match &for_loop.init {
ForInit::ForIn { pattern, iter } => ForInit::ForIn {
pattern: pattern.clone(),
iter: inline_closure_calls_in_expr(
iter,
closure_param_name,
closure_params,
closure_body,
closure_param_annotations,
),
},
ForInit::ForC { init, condition, update } => ForInit::ForC {
init: Box::new(inline_closure_calls_in_statement(
init,
closure_param_name,
closure_params,
closure_body,
closure_param_annotations,
)),
condition: inline_closure_calls_in_expr(
condition,
closure_param_name,
closure_params,
closure_body,
closure_param_annotations,
),
update: inline_closure_calls_in_expr(
update,
closure_param_name,
closure_params,
closure_body,
closure_param_annotations,
),
},
};
Statement::For(
ForLoop {
init: new_init,
body: for_loop
.body
.iter()
.map(|s| {
inline_closure_calls_in_statement(
s,
closure_param_name,
closure_params,
closure_body,
closure_param_annotations,
)
})
.collect(),
is_async: for_loop.is_async,
},
*span,
)
}
Statement::While(wl, span) => Statement::While(
WhileLoop {
condition: inline_closure_calls_in_expr(
&wl.condition,
closure_param_name,
closure_params,
closure_body,
closure_param_annotations,
),
body: wl
.body
.iter()
.map(|s| {
inline_closure_calls_in_statement(
s,
closure_param_name,
closure_params,
closure_body,
closure_param_annotations,
)
})
.collect(),
},
*span,
),
Statement::If(ifs, span) => Statement::If(
IfStatement {
condition: inline_closure_calls_in_expr(
&ifs.condition,
closure_param_name,
closure_params,
closure_body,
closure_param_annotations,
),
then_body: ifs
.then_body
.iter()
.map(|s| {
inline_closure_calls_in_statement(
s,
closure_param_name,
closure_params,
closure_body,
closure_param_annotations,
)
})
.collect(),
else_body: ifs.else_body.as_ref().map(|body| {
body.iter()
.map(|s| {
inline_closure_calls_in_statement(
s,
closure_param_name,
closure_params,
closure_body,
closure_param_annotations,
)
})
.collect()
}),
},
*span,
),
other => other.clone(),
}
}
fn inline_closure_calls_in_expr(
expr: &shape_ast::ast::Expr,
closure_param_name: &str,
closure_params: &[String],
closure_body: &[shape_ast::ast::Statement],
closure_param_annotations: &[Option<shape_ast::ast::TypeAnnotation>],
) -> shape_ast::ast::Expr {
use shape_ast::ast::Expr;
if let Expr::FunctionCall { name, args, .. } = expr {
if std::env::var_os("SHAPE_JIT_DEBUG").is_some() {
PHASEC_TRACE_FN_CALL_TOTAL.with(|c| c.set(c.get() + 1));
let matched = name == closure_param_name;
if matched {
PHASEC_TRACE_FN_CALL_MATCH.with(|c| c.set(c.get() + 1));
}
eprintln!(
"[phaseC-empirical] FunctionCall name={:?} closure_param={:?} matched={}",
name, closure_param_name, matched,
);
}
if name == closure_param_name {
let rewritten_args: Vec<Expr> = args
.iter()
.map(|a| {
inline_closure_calls_in_expr(
a,
closure_param_name,
closure_params,
closure_body,
closure_param_annotations,
)
})
.collect();
return build_inlined_closure_block(closure_params, &rewritten_args, closure_body, closure_param_annotations);
}
}
let rec = |e: &Expr| {
inline_closure_calls_in_expr(e, closure_param_name, closure_params, closure_body, closure_param_annotations)
};
let rec_box = |e: &Box<Expr>| Box::new(rec(e));
let rec_vec = |v: &Vec<Expr>| v.iter().map(rec).collect();
match expr {
Expr::BinaryOp { left, op, right, span } => Expr::BinaryOp {
left: rec_box(left),
op: op.clone(),
right: rec_box(right),
span: *span,
},
Expr::UnaryOp { op, operand, span } => Expr::UnaryOp {
op: op.clone(),
operand: rec_box(operand),
span: *span,
},
Expr::FunctionCall { name, args, named_args, span } => Expr::FunctionCall {
name: name.clone(),
args: args.iter().map(rec).collect(),
named_args: named_args
.iter()
.map(|(k, v)| (k.clone(), rec(v)))
.collect(),
span: *span,
},
Expr::MethodCall { receiver, method, args, named_args, optional, span } => {
if std::env::var_os("SHAPE_JIT_DEBUG").is_some() {
PHASEC_TRACE_METHOD_CALL.with(|c| c.set(c.get() + 1));
eprintln!(
"[phaseC-empirical] MethodCall method={:?} args_count={}",
method, args.len(),
);
}
Expr::MethodCall {
receiver: rec_box(receiver),
method: method.clone(),
args: rec_vec(args),
named_args: named_args
.iter()
.map(|(k, v)| (k.clone(), rec(v)))
.collect(),
optional: *optional,
span: *span,
}
}
Expr::PropertyAccess { object, property, optional, span } => Expr::PropertyAccess {
object: rec_box(object),
property: property.clone(),
optional: *optional,
span: *span,
},
Expr::IndexAccess { object, index, end_index, span } => Expr::IndexAccess {
object: rec_box(object),
index: rec_box(index),
end_index: end_index.as_ref().map(|e| rec_box(e)),
span: *span,
},
Expr::Array(items, span) => Expr::Array(rec_vec(items), *span),
Expr::Assign(assign, span) => {
use shape_ast::ast::expr_helpers::AssignExpr;
Expr::Assign(
Box::new(AssignExpr {
target: rec_box(&assign.target),
value: rec_box(&assign.value),
}),
*span,
)
}
Expr::Return(Some(e), span) => Expr::Return(Some(rec_box(e)), *span),
Expr::If(ifexpr, span) => {
use shape_ast::ast::expr_helpers::IfExpr;
Expr::If(
Box::new(IfExpr {
condition: rec_box(&ifexpr.condition),
then_branch: rec_box(&ifexpr.then_branch),
else_branch: ifexpr.else_branch.as_ref().map(|e| rec_box(e)),
}),
*span,
)
}
Expr::Block(block, span) => {
use shape_ast::ast::expr_helpers::{BlockExpr, BlockItem};
let items = block
.items
.iter()
.map(|item| match item {
BlockItem::Expression(e) => BlockItem::Expression(rec(e)),
BlockItem::Statement(s) => BlockItem::Statement(
inline_closure_calls_in_statement(
s,
closure_param_name,
closure_params,
closure_body,
closure_param_annotations,
),
),
BlockItem::VariableDecl(decl) => {
let mut new_decl = decl.clone();
new_decl.value = decl.value.as_ref().map(rec);
BlockItem::VariableDecl(new_decl)
}
BlockItem::Assignment(assignment) => {
let mut na = assignment.clone();
na.value = rec(&assignment.value);
BlockItem::Assignment(na)
}
})
.collect();
Expr::Block(BlockExpr { items }, *span)
}
Expr::For(for_expr, span) => {
use shape_ast::ast::expr_helpers::ForExpr;
Expr::For(
Box::new(ForExpr {
pattern: for_expr.pattern.clone(),
iterable: rec_box(&for_expr.iterable),
body: rec_box(&for_expr.body),
is_async: for_expr.is_async,
}),
*span,
)
}
Expr::While(while_expr, span) => {
use shape_ast::ast::expr_helpers::WhileExpr;
Expr::While(
Box::new(WhileExpr {
condition: rec_box(&while_expr.condition),
body: rec_box(&while_expr.body),
}),
*span,
)
}
Expr::Loop(loop_expr, span) => {
use shape_ast::ast::expr_helpers::LoopExpr;
Expr::Loop(
Box::new(LoopExpr {
body: rec_box(&loop_expr.body),
}),
*span,
)
}
Expr::Let(let_expr, span) => {
use shape_ast::ast::expr_helpers::LetExpr;
Expr::Let(
Box::new(LetExpr {
pattern: let_expr.pattern.clone(),
type_annotation: let_expr.type_annotation.clone(),
value: let_expr.value.as_ref().map(|v| rec_box(v)),
body: rec_box(&let_expr.body),
}),
*span,
)
}
Expr::Match(match_expr, span) => {
use shape_ast::ast::expr_helpers::{MatchArm, MatchExpr};
Expr::Match(
Box::new(MatchExpr {
scrutinee: rec_box(&match_expr.scrutinee),
arms: match_expr
.arms
.iter()
.map(|arm| MatchArm {
pattern: arm.pattern.clone(),
guard: arm.guard.as_ref().map(|g| rec_box(g)),
body: rec_box(&arm.body),
pattern_span: arm.pattern_span,
})
.collect(),
}),
*span,
)
}
Expr::Break(value, span) => Expr::Break(value.as_ref().map(|e| rec_box(e)), *span),
Expr::TryOperator(inner, span) => Expr::TryOperator(rec_box(inner), *span),
Expr::Await(inner, span) => Expr::Await(rec_box(inner), *span),
Expr::AsyncScope(inner, span) => Expr::AsyncScope(rec_box(inner), *span),
Expr::Spread(inner, span) => Expr::Spread(rec_box(inner), *span),
other => other.clone(),
}
}
fn build_inlined_closure_block(
closure_params: &[String],
call_args: &[shape_ast::ast::Expr],
closure_body: &[shape_ast::ast::Statement],
closure_param_annotations: &[Option<shape_ast::ast::TypeAnnotation>],
) -> shape_ast::ast::Expr {
use shape_ast::ast::expr_helpers::{BlockExpr, BlockItem};
use shape_ast::ast::{DestructurePattern, Expr, Statement, VarKind, VariableDecl};
let span = shape_ast::ast::Span::default();
let mut items: Vec<BlockItem> = Vec::new();
for (idx, (pname, aexpr)) in closure_params.iter().zip(call_args.iter()).enumerate() {
let type_annotation = closure_param_annotations
.get(idx)
.and_then(|opt| opt.clone());
let decl = VariableDecl {
kind: VarKind::Let,
is_mut: false,
pattern: DestructurePattern::Identifier(pname.clone(), span),
type_annotation,
value: Some(aexpr.clone()),
ownership: Default::default(),
};
items.push(BlockItem::Statement(Statement::VariableDecl(decl, span)));
}
let last_idx = closure_body.len().saturating_sub(1);
for (i, stmt) in closure_body.iter().enumerate() {
if i == last_idx {
match stmt {
shape_ast::ast::Statement::Expression(expr, _) => {
items.push(BlockItem::Expression(expr.clone()));
continue;
}
shape_ast::ast::Statement::Return(Some(expr), _) => {
items.push(BlockItem::Expression(expr.clone()));
continue;
}
_ => {}
}
}
items.push(BlockItem::Statement(stmt.clone()));
}
Expr::Block(BlockExpr { items }, span)
}
#[cfg(test)]
mod tests {
use super::*;
use shape_ast::ast::expressions::Expr;
use shape_ast::ast::functions::FunctionParameter;
use shape_ast::ast::patterns::DestructurePattern;
use shape_ast::ast::span::Span;
use shape_ast::ast::statements::Statement;
use shape_ast::ast::types::{TypeAnnotation, TypeParam};
fn ident_param(name: &str, ty: TypeAnnotation) -> FunctionParameter {
FunctionParameter {
pattern: DestructurePattern::Identifier(name.into(), Span::default()),
is_const: false,
is_reference: false,
is_mut_reference: false,
is_out: false,
type_annotation: Some(ty),
default_value: None,
}
}
fn type_param(name: &str) -> TypeParam {
TypeParam::Type {
name: name.into(),
span: Span::default(),
doc_comment: None,
default_type: None,
trait_bounds: vec![],
}
}
fn ref_t(name: &str) -> TypeAnnotation {
TypeAnnotation::Reference(TypePath::simple(name))
}
fn identity_fn() -> FunctionDef {
FunctionDef {
name: "id".into(),
name_span: Span::default(),
declaring_module_path: None,
doc_comment: None,
type_params: Some(vec![type_param("T")]),
params: vec![ident_param("x", ref_t("T"))],
return_type: Some(ref_t("T")),
where_clause: None,
body: vec![Statement::Return(
Some(Expr::Identifier("x".into(), Span::default())),
Span::default(),
)],
annotations: vec![],
is_async: false,
is_comptime: false,
}
}
#[test]
fn concrete_to_annotation_primitives() {
assert_eq!(
concrete_to_annotation(&ConcreteType::F64),
TypeAnnotation::Basic("number".into())
);
assert_eq!(
concrete_to_annotation(&ConcreteType::I64),
TypeAnnotation::Basic("int".into())
);
assert_eq!(
concrete_to_annotation(&ConcreteType::Bool),
TypeAnnotation::Basic("bool".into())
);
assert_eq!(
concrete_to_annotation(&ConcreteType::String),
TypeAnnotation::Basic("string".into())
);
assert_eq!(
concrete_to_annotation(&ConcreteType::I32),
TypeAnnotation::Basic("i32".into())
);
assert_eq!(
concrete_to_annotation(&ConcreteType::U8),
TypeAnnotation::Basic("u8".into())
);
assert_eq!(
concrete_to_annotation(&ConcreteType::Void),
TypeAnnotation::Void
);
}
#[test]
fn concrete_to_annotation_composites() {
let arr = ConcreteType::Array(Box::new(ConcreteType::F64));
match concrete_to_annotation(&arr) {
TypeAnnotation::Generic { name, args } => {
assert_eq!(name.as_str(), "Array");
assert_eq!(args.len(), 1);
assert_eq!(args[0], TypeAnnotation::Basic("number".into()));
}
other => panic!("expected Generic, got {:?}", other),
}
let map = ConcreteType::HashMap(
Box::new(ConcreteType::String),
Box::new(ConcreteType::I64),
);
match concrete_to_annotation(&map) {
TypeAnnotation::Generic { name, args } => {
assert_eq!(name.as_str(), "HashMap");
assert_eq!(args.len(), 2);
assert_eq!(args[0], TypeAnnotation::Basic("string".into()));
assert_eq!(args[1], TypeAnnotation::Basic("int".into()));
}
other => panic!("expected Generic, got {:?}", other),
}
let tup = ConcreteType::Tuple(vec![ConcreteType::I64, ConcreteType::F64]);
match concrete_to_annotation(&tup) {
TypeAnnotation::Tuple(items) => {
assert_eq!(items.len(), 2);
assert_eq!(items[0], TypeAnnotation::Basic("int".into()));
assert_eq!(items[1], TypeAnnotation::Basic("number".into()));
}
other => panic!("expected Tuple, got {:?}", other),
}
}
#[test]
fn substitute_simple_reference() {
let mut subs = HashMap::new();
subs.insert("T".to_string(), ConcreteType::I64);
let ann = ref_t("T");
let out = substitute_type_annotation(&ann, &subs);
assert_eq!(out, TypeAnnotation::Basic("int".into()));
let other = ref_t("MyType");
let out2 = substitute_type_annotation(&other, &subs);
assert_eq!(out2, ref_t("MyType"));
}
#[test]
fn substitute_does_not_touch_qualified_paths() {
let mut subs = HashMap::new();
subs.insert("T".to_string(), ConcreteType::I64);
let qualified =
TypeAnnotation::Reference(TypePath::from_segments(vec!["mod".into(), "T".into()]));
let out = substitute_type_annotation(&qualified, &subs);
assert_eq!(out, qualified);
}
#[test]
fn substitute_nested_array_of_t() {
let mut subs = HashMap::new();
subs.insert("T".to_string(), ConcreteType::I64);
let ann = TypeAnnotation::Generic {
name: TypePath::simple("Array"),
args: vec![ref_t("T")],
};
let out = substitute_type_annotation(&ann, &subs);
match out {
TypeAnnotation::Generic { name, args } => {
assert_eq!(name.as_str(), "Array");
assert_eq!(args, vec![TypeAnnotation::Basic("int".into())]);
}
other => panic!("expected Generic, got {:?}", other),
}
}
#[test]
fn substitute_function_t_to_i64() {
let mut subs = HashMap::new();
subs.insert("T".to_string(), ConcreteType::I64);
let func = identity_fn();
let mono = substitute_function_def(&func, &subs);
assert_eq!(mono.params.len(), 1);
assert_eq!(
mono.params[0].type_annotation,
Some(TypeAnnotation::Basic("int".into()))
);
assert_eq!(
mono.return_type,
Some(TypeAnnotation::Basic("int".into()))
);
assert!(mono.type_params.is_none());
assert_eq!(mono.name, "id::i64");
}
#[test]
fn substitute_map_t_u_to_number_string() {
let func = FunctionDef {
name: "map".into(),
name_span: Span::default(),
declaring_module_path: None,
doc_comment: None,
type_params: Some(vec![type_param("T"), type_param("U")]),
params: vec![
ident_param(
"arr",
TypeAnnotation::Generic {
name: TypePath::simple("Array"),
args: vec![ref_t("T")],
},
),
ident_param(
"f",
TypeAnnotation::Function {
params: vec![FunctionParam {
name: None,
optional: false,
type_annotation: ref_t("T"),
}],
returns: Box::new(ref_t("U")),
},
),
],
return_type: Some(TypeAnnotation::Generic {
name: TypePath::simple("Array"),
args: vec![ref_t("U")],
}),
where_clause: None,
body: vec![],
annotations: vec![],
is_async: false,
is_comptime: false,
};
let mut subs = HashMap::new();
subs.insert("T".to_string(), ConcreteType::F64);
subs.insert("U".to_string(), ConcreteType::String);
let mono = substitute_function_def(&func, &subs);
match &mono.params[0].type_annotation {
Some(TypeAnnotation::Generic { name, args }) => {
assert_eq!(name.as_str(), "Array");
assert_eq!(args, &vec![TypeAnnotation::Basic("number".into())]);
}
other => panic!("expected Generic Array, got {:?}", other),
}
match &mono.params[1].type_annotation {
Some(TypeAnnotation::Function { params, returns }) => {
assert_eq!(params.len(), 1);
assert_eq!(
params[0].type_annotation,
TypeAnnotation::Basic("number".into())
);
assert_eq!(**returns, TypeAnnotation::Basic("string".into()));
}
other => panic!("expected Function, got {:?}", other),
}
match &mono.return_type {
Some(TypeAnnotation::Generic { name, args }) => {
assert_eq!(name.as_str(), "Array");
assert_eq!(args, &vec![TypeAnnotation::Basic("string".into())]);
}
other => panic!("expected Generic Array, got {:?}", other),
}
assert_eq!(mono.name, "map::f64_string");
}
#[test]
fn substitute_let_with_array_of_t_in_body() {
let func = FunctionDef {
name: "pack".into(),
name_span: Span::default(),
declaring_module_path: None,
doc_comment: None,
type_params: Some(vec![type_param("T")]),
params: vec![ident_param("x", ref_t("T"))],
return_type: None,
where_clause: None,
body: vec![
Statement::VariableDecl(
shape_ast::ast::program::VariableDecl {
kind: shape_ast::ast::program::VarKind::Let,
is_mut: false,
pattern: DestructurePattern::Identifier("arr".into(), Span::default()),
type_annotation: Some(TypeAnnotation::Generic {
name: TypePath::simple("Array"),
args: vec![ref_t("T")],
}),
value: Some(Expr::Array(vec![], Span::default())),
ownership: Default::default(),
},
Span::default(),
),
Statement::Return(
Some(Expr::Identifier("arr".into(), Span::default())),
Span::default(),
),
],
annotations: vec![],
is_async: false,
is_comptime: false,
};
let mut subs = HashMap::new();
subs.insert("T".to_string(), ConcreteType::I64);
let mono = substitute_function_def(&func, &subs);
match &mono.body[0] {
Statement::VariableDecl(decl, _) => match &decl.type_annotation {
Some(TypeAnnotation::Generic { name, args }) => {
assert_eq!(name.as_str(), "Array");
assert_eq!(args, &vec![TypeAnnotation::Basic("int".into())]);
}
other => panic!("expected Array<int>, got {:?}", other),
},
other => panic!("expected VariableDecl, got {:?}", other),
}
}
#[test]
fn mono_key_is_deterministic() {
let mut a = HashMap::new();
a.insert("U".to_string(), ConcreteType::String);
a.insert("T".to_string(), ConcreteType::F64);
let mut b = HashMap::new();
b.insert("T".to_string(), ConcreteType::F64);
b.insert("U".to_string(), ConcreteType::String);
assert_eq!(mono_key_from_subs(&a), mono_key_from_subs(&b));
assert_eq!(mono_key_from_subs(&a), "f64_string");
}
#[test]
fn cloned_function_name_has_mono_suffix() {
let mut subs = HashMap::new();
subs.insert("T".to_string(), ConcreteType::Bool);
let func = identity_fn();
let mono = substitute_function_def(&func, &subs);
assert!(
mono.name.starts_with("id::"),
"expected name to start with 'id::', got {}",
mono.name
);
assert_eq!(mono.name, "id::bool");
}
fn repeat_fn() -> FunctionDef {
FunctionDef {
name: "repeat".into(),
name_span: Span::default(),
declaring_module_path: None,
doc_comment: None,
type_params: None,
params: vec![ident_param("x", TypeAnnotation::Basic("number".into()))],
return_type: Some(TypeAnnotation::Generic {
name: TypePath::simple("Array"),
args: vec![TypeAnnotation::Basic("number".into())],
}),
where_clause: None,
body: vec![Statement::Return(
Some(Expr::Identifier("__const_0".into(), Span::default())),
Span::default(),
)],
annotations: vec![],
is_async: false,
is_comptime: false,
}
}
#[test]
fn substitute_with_consts_renames_function() {
let func = repeat_fn();
let type_subs: HashMap<String, ConcreteType> = HashMap::new();
let mut const_subs: HashMap<String, ComptimeConstValue> = HashMap::new();
const_subs.insert("__const_0".into(), ComptimeConstValue::Int(3));
let mono = substitute_function_def_with_consts(
&func,
&type_subs,
&const_subs,
"repeat::int_3",
);
assert_eq!(mono.name, "repeat::int_3");
assert!(mono.type_params.is_none());
}
#[test]
fn substitute_with_consts_replaces_identifier_with_literal() {
let func = repeat_fn();
let type_subs: HashMap<String, ConcreteType> = HashMap::new();
let mut const_subs: HashMap<String, ComptimeConstValue> = HashMap::new();
const_subs.insert("__const_0".into(), ComptimeConstValue::Int(3));
let mono = substitute_function_def_with_consts(
&func,
&type_subs,
&const_subs,
"repeat::int_3",
);
match &mono.body[0] {
Statement::Return(Some(Expr::Literal(lit, _)), _) => match lit {
shape_ast::ast::Literal::Int(3) => {}
other => panic!("expected Literal::Int(3), got {:?}", other),
},
other => panic!("expected return with int literal, got {:?}", other),
}
}
#[test]
fn substitute_with_consts_two_distinct_values_produce_distinct_bodies() {
let func = repeat_fn();
let type_subs: HashMap<String, ConcreteType> = HashMap::new();
let mut subs_3: HashMap<String, ComptimeConstValue> = HashMap::new();
subs_3.insert("__const_0".into(), ComptimeConstValue::Int(3));
let mut subs_5: HashMap<String, ComptimeConstValue> = HashMap::new();
subs_5.insert("__const_0".into(), ComptimeConstValue::Int(5));
let mono_3 =
substitute_function_def_with_consts(&func, &type_subs, &subs_3, "repeat::int_3");
let mono_5 =
substitute_function_def_with_consts(&func, &type_subs, &subs_5, "repeat::int_5");
assert_ne!(mono_3.name, mono_5.name);
assert_eq!(mono_3.name, "repeat::int_3");
assert_eq!(mono_5.name, "repeat::int_5");
let extract_int = |def: &FunctionDef| -> i64 {
match &def.body[0] {
Statement::Return(Some(Expr::Literal(shape_ast::ast::Literal::Int(i), _)), _) => *i,
other => panic!("unexpected body shape: {:?}", other),
}
};
assert_eq!(extract_int(&mono_3), 3);
assert_eq!(extract_int(&mono_5), 5);
}
#[test]
fn substitute_with_consts_empty_const_subs_is_identity_on_body() {
let func = repeat_fn();
let type_subs: HashMap<String, ConcreteType> = HashMap::new();
let const_subs: HashMap<String, ComptimeConstValue> = HashMap::new();
let mono = substitute_function_def_with_consts(
&func,
&type_subs,
&const_subs,
"repeat",
);
match &mono.body[0] {
Statement::Return(Some(Expr::Identifier(name, _)), _) => {
assert_eq!(name, "__const_0");
}
other => panic!("expected identifier return, got {:?}", other),
}
}
#[test]
fn substitute_with_consts_combined_with_type_substitution() {
let func = FunctionDef {
name: "matrix".into(),
name_span: Span::default(),
declaring_module_path: None,
doc_comment: None,
type_params: Some(vec![type_param("T")]),
params: vec![ident_param("x", ref_t("T"))],
return_type: Some(ref_t("T")),
where_clause: None,
body: vec![Statement::Return(
Some(Expr::Identifier("__const_0".into(), Span::default())),
Span::default(),
)],
annotations: vec![],
is_async: false,
is_comptime: false,
};
let mut type_subs: HashMap<String, ConcreteType> = HashMap::new();
type_subs.insert("T".into(), ConcreteType::F64);
let mut const_subs: HashMap<String, ComptimeConstValue> = HashMap::new();
const_subs.insert("__const_0".into(), ComptimeConstValue::Int(4));
let mono = substitute_function_def_with_consts(
&func,
&type_subs,
&const_subs,
"matrix::f64_int_4",
);
assert_eq!(mono.name, "matrix::f64_int_4");
assert_eq!(
mono.params[0].type_annotation,
Some(TypeAnnotation::Basic("number".into()))
);
assert_eq!(
mono.return_type,
Some(TypeAnnotation::Basic("number".into()))
);
match &mono.body[0] {
Statement::Return(Some(Expr::Literal(shape_ast::ast::Literal::Int(4), _)), _) => {}
other => panic!("expected Int(4) literal in body, got {:?}", other),
}
}
#[test]
fn phase_c_inline_closure_body_replaces_call_in_return() {
let mut spec = FunctionDef {
name: "map::i64".into(),
name_span: Span::default(),
declaring_module_path: None,
doc_comment: None,
type_params: None,
params: vec![
ident_param("self", ref_t("Array")),
ident_param("f", ref_t("fn_type")),
],
return_type: None,
where_clause: None,
body: vec![Statement::Return(
Some(Expr::FunctionCall {
name: "f".into(),
args: vec![Expr::Identifier("item".into(), Span::default())],
named_args: vec![],
span: Span::default(),
}),
Span::default(),
)],
annotations: vec![],
is_async: false,
is_comptime: false,
};
let closure_body = vec![Statement::Expression(
Expr::BinaryOp {
left: Box::new(Expr::Identifier("x".into(), Span::default())),
op: shape_ast::ast::BinaryOp::Add,
right: Box::new(Expr::Literal(
shape_ast::ast::Literal::Int(1),
Span::default(),
)),
span: Span::default(),
},
Span::default(),
)];
super::inline_closure_body_into_specialization(
&mut spec,
"f",
&["x".into()],
&closure_body,
&[], &[], )
.expect("inlining should succeed");
match &spec.body[0] {
Statement::Return(Some(Expr::Block(block, _)), _) => {
assert_eq!(block.items.len(), 2);
}
other => panic!("expected Return(Block(..)), got {:?}", other),
}
}
#[test]
fn phase_c_inline_preserves_specialization_name() {
let mut spec = FunctionDef {
name: "original_name".into(),
name_span: Span::default(),
declaring_module_path: None,
doc_comment: None,
type_params: None,
params: vec![ident_param("f", ref_t("fn_type"))],
return_type: None,
where_clause: None,
body: vec![],
annotations: vec![],
is_async: false,
is_comptime: false,
};
super::inline_closure_body_into_specialization(
&mut spec,
"f",
&[],
&[],
&[],
&[],
)
.unwrap();
assert_eq!(spec.name, "original_name");
}
#[test]
fn phase_c_inline_preserves_formal_closure_param() {
let mut spec = FunctionDef {
name: "map::i64_closure_0_i64".into(),
name_span: Span::default(),
declaring_module_path: None,
doc_comment: None,
type_params: None,
params: vec![
ident_param("self", ref_t("Array")),
ident_param("f", ref_t("fn_type")),
],
return_type: None,
where_clause: None,
body: vec![],
annotations: vec![],
is_async: false,
is_comptime: false,
};
super::inline_closure_body_into_specialization(
&mut spec,
"f",
&["x".into()],
&[],
&["captured_n".into()], &[], )
.unwrap();
let names: Vec<String> = spec
.params
.iter()
.flat_map(|p| p.get_identifiers())
.collect();
assert!(
names.contains(&"self".to_string()),
"expected self in params: {:?}",
names
);
assert!(
names.contains(&"f".to_string()),
"expected formal closure param `f` preserved: {:?}",
names
);
}
#[test]
fn phase_c_inline_leaves_unrelated_calls_alone() {
let mut spec = FunctionDef {
name: "map".into(),
name_span: Span::default(),
declaring_module_path: None,
doc_comment: None,
type_params: None,
params: vec![ident_param("f", ref_t("fn_type"))],
return_type: None,
where_clause: None,
body: vec![Statement::Return(
Some(Expr::FunctionCall {
name: "println".into(), args: vec![Expr::Literal(
shape_ast::ast::Literal::String("hi".into()),
Span::default(),
)],
named_args: vec![],
span: Span::default(),
}),
Span::default(),
)],
annotations: vec![],
is_async: false,
is_comptime: false,
};
super::inline_closure_body_into_specialization(
&mut spec,
"f",
&[],
&[],
&[],
&[],
)
.unwrap();
match &spec.body[0] {
Statement::Return(Some(Expr::FunctionCall { name, .. }), _) => {
assert_eq!(name, "println");
}
other => panic!("expected FunctionCall(println) unchanged, got {:?}", other),
}
}
fn const_body_fn(body: Vec<Statement>) -> FunctionDef {
FunctionDef {
name: "harness".into(),
name_span: Span::default(),
declaring_module_path: None,
doc_comment: None,
type_params: None,
params: vec![],
return_type: None,
where_clause: None,
body,
annotations: vec![],
is_async: false,
is_comptime: false,
}
}
fn const_subs_int_0(v: i64) -> HashMap<String, ComptimeConstValue> {
let mut m: HashMap<String, ComptimeConstValue> = HashMap::new();
m.insert("__const_0".into(), ComptimeConstValue::Int(v));
m
}
fn assert_no_const_id_in_stmt(stmt: &Statement) {
match stmt {
Statement::Return(expr, _) => {
if let Some(e) = expr {
assert_no_const_id_in_expr(e);
}
}
Statement::Break(_) | Statement::Continue(_) | Statement::RemoveTarget(_) => {}
Statement::VariableDecl(decl, _) => {
if let Some(v) = &decl.value {
assert_no_const_id_in_expr(v);
}
}
Statement::Assignment(a, _) => assert_no_const_id_in_expr(&a.value),
Statement::Expression(e, _) => assert_no_const_id_in_expr(e),
Statement::For(fl, _) => {
match &fl.init {
ForInit::ForIn { iter, .. } => assert_no_const_id_in_expr(iter),
ForInit::ForC {
init,
condition,
update,
} => {
assert_no_const_id_in_stmt(init);
assert_no_const_id_in_expr(condition);
assert_no_const_id_in_expr(update);
}
}
for s in &fl.body {
assert_no_const_id_in_stmt(s);
}
}
Statement::While(wl, _) => {
assert_no_const_id_in_expr(&wl.condition);
for s in &wl.body {
assert_no_const_id_in_stmt(s);
}
}
Statement::If(is, _) => {
assert_no_const_id_in_expr(&is.condition);
for s in &is.then_body {
assert_no_const_id_in_stmt(s);
}
if let Some(eb) = &is.else_body {
for s in eb {
assert_no_const_id_in_stmt(s);
}
}
}
Statement::Extend(_, _) => {}
Statement::SetParamType { .. } | Statement::SetReturnType { .. } => {}
Statement::SetParamValue { expression, .. }
| Statement::SetReturnExpr { expression, .. }
| Statement::ReplaceBodyExpr { expression, .. }
| Statement::ReplaceModuleExpr { expression, .. } => {
assert_no_const_id_in_expr(expression)
}
Statement::ReplaceBody { body, .. } => {
for s in body {
assert_no_const_id_in_stmt(s);
}
}
}
}
fn assert_no_const_id_in_expr(expr: &Expr) {
if let Expr::Identifier(name, _) = expr {
assert_ne!(
name, "__const_0",
"residual const identifier in expression: {:?}",
expr
);
return;
}
match expr {
Expr::BinaryOp { left, right, .. }
| Expr::FuzzyComparison { left, right, .. } => {
assert_no_const_id_in_expr(left);
assert_no_const_id_in_expr(right);
}
Expr::UnaryOp { operand, .. } => assert_no_const_id_in_expr(operand),
Expr::Array(items, _) => items.iter().for_each(assert_no_const_id_in_expr),
Expr::FunctionCall { args, .. } | Expr::QualifiedFunctionCall { args, .. } => {
args.iter().for_each(assert_no_const_id_in_expr);
}
Expr::MethodCall { receiver, args, .. } => {
assert_no_const_id_in_expr(receiver);
args.iter().for_each(assert_no_const_id_in_expr);
}
Expr::IndexAccess { object, index, .. } => {
assert_no_const_id_in_expr(object);
assert_no_const_id_in_expr(index);
}
Expr::PropertyAccess { object, .. } => assert_no_const_id_in_expr(object),
Expr::If(ie, _) => {
assert_no_const_id_in_expr(&ie.condition);
assert_no_const_id_in_expr(&ie.then_branch);
if let Some(eb) = &ie.else_branch {
assert_no_const_id_in_expr(eb);
}
}
Expr::Match(me, _) => {
assert_no_const_id_in_expr(&me.scrutinee);
for arm in &me.arms {
assert_no_const_id_in_expr(&arm.body);
}
}
Expr::Block(block, _) => {
for it in &block.items {
match it {
BlockItem::Expression(e) => assert_no_const_id_in_expr(e),
BlockItem::VariableDecl(d) => {
if let Some(v) = &d.value {
assert_no_const_id_in_expr(v);
}
}
BlockItem::Assignment(a) => assert_no_const_id_in_expr(&a.value),
BlockItem::Statement(s) => assert_no_const_id_in_stmt(s),
}
}
}
Expr::FunctionExpr { body, .. } => {
for s in body {
assert_no_const_id_in_stmt(s);
}
}
Expr::Range { start, end, .. } => {
if let Some(s) = start {
assert_no_const_id_in_expr(s);
}
if let Some(e) = end {
assert_no_const_id_in_expr(e);
}
}
_ => {}
}
}
#[test]
fn b4_const_in_if_statement() {
let stmt = Statement::If(
IfStatement {
condition: Expr::BinaryOp {
left: Box::new(Expr::Identifier("__const_0".into(), Span::default())),
op: shape_ast::ast::BinaryOp::Equal,
right: Box::new(Expr::Literal(
shape_ast::ast::Literal::Int(1),
Span::default(),
)),
span: Span::default(),
},
then_body: vec![Statement::Return(
Some(Expr::Identifier("__const_0".into(), Span::default())),
Span::default(),
)],
else_body: Some(vec![Statement::Expression(
Expr::Identifier("__const_0".into(), Span::default()),
Span::default(),
)]),
},
Span::default(),
);
let func = const_body_fn(vec![stmt]);
let mono = substitute_function_def_with_consts(
&func,
&HashMap::new(),
&const_subs_int_0(7),
"harness::int_7",
);
assert_no_const_id_in_stmt(&mono.body[0]);
}
#[test]
fn b4_const_in_while_statement() {
let stmt = Statement::While(
WhileLoop {
condition: Expr::Identifier("__const_0".into(), Span::default()),
body: vec![Statement::Return(
Some(Expr::Identifier("__const_0".into(), Span::default())),
Span::default(),
)],
},
Span::default(),
);
let func = const_body_fn(vec![stmt]);
let mono = substitute_function_def_with_consts(
&func,
&HashMap::new(),
&const_subs_int_0(2),
"harness::int_2",
);
assert_no_const_id_in_stmt(&mono.body[0]);
}
#[test]
fn b4_const_in_for_statement() {
use shape_ast::ast::statements::ForLoop;
let stmt = Statement::For(
ForLoop {
init: ForInit::ForIn {
pattern: DestructurePattern::Identifier("i".into(), Span::default()),
iter: Expr::Identifier("__const_0".into(), Span::default()),
},
body: vec![Statement::Expression(
Expr::Identifier("__const_0".into(), Span::default()),
Span::default(),
)],
is_async: false,
},
Span::default(),
);
let func = const_body_fn(vec![stmt]);
let mono = substitute_function_def_with_consts(
&func,
&HashMap::new(),
&const_subs_int_0(4),
"harness::int_4",
);
assert_no_const_id_in_stmt(&mono.body[0]);
}
#[test]
fn b4_const_in_assignment_rhs() {
use shape_ast::ast::program::Assignment;
let stmt = Statement::Assignment(
Assignment {
pattern: DestructurePattern::Identifier("x".into(), Span::default()),
value: Expr::Identifier("__const_0".into(), Span::default()),
},
Span::default(),
);
let func = const_body_fn(vec![stmt]);
let mono = substitute_function_def_with_consts(
&func,
&HashMap::new(),
&const_subs_int_0(11),
"harness::int_11",
);
assert_no_const_id_in_stmt(&mono.body[0]);
}
#[test]
fn b4_const_in_method_call() {
let stmt = Statement::Expression(
Expr::MethodCall {
receiver: Box::new(Expr::Identifier("__const_0".into(), Span::default())),
method: "to_string".into(),
args: vec![Expr::Identifier("__const_0".into(), Span::default())],
named_args: vec![],
optional: false,
span: Span::default(),
},
Span::default(),
);
let func = const_body_fn(vec![stmt]);
let mono = substitute_function_def_with_consts(
&func,
&HashMap::new(),
&const_subs_int_0(9),
"harness::int_9",
);
assert_no_const_id_in_stmt(&mono.body[0]);
}
#[test]
fn b4_const_in_closure_body() {
let closure = Expr::FunctionExpr {
params: vec![ident_param("x", ref_t("int"))],
return_type: None,
body: vec![Statement::Return(
Some(Expr::BinaryOp {
left: Box::new(Expr::Identifier("x".into(), Span::default())),
op: shape_ast::ast::BinaryOp::Mul,
right: Box::new(Expr::Identifier("__const_0".into(), Span::default())),
span: Span::default(),
}),
Span::default(),
)],
span: Span::default(),
};
let stmt = Statement::Expression(closure, Span::default());
let func = const_body_fn(vec![stmt]);
let mono = substitute_function_def_with_consts(
&func,
&HashMap::new(),
&const_subs_int_0(5),
"harness::int_5",
);
assert_no_const_id_in_stmt(&mono.body[0]);
}
#[test]
fn b4_const_in_match_arm() {
use shape_ast::ast::expr_helpers::{MatchArm, MatchExpr};
use shape_ast::ast::patterns::Pattern;
let stmt = Statement::Expression(
Expr::Match(
Box::new(MatchExpr {
scrutinee: Box::new(Expr::Identifier("__const_0".into(), Span::default())),
arms: vec![MatchArm {
pattern: Pattern::Wildcard,
guard: Some(Box::new(Expr::Identifier(
"__const_0".into(),
Span::default(),
))),
body: Box::new(Expr::Identifier("__const_0".into(), Span::default())),
pattern_span: Some(Span::default()),
}],
}),
Span::default(),
),
Span::default(),
);
let func = const_body_fn(vec![stmt]);
let mono = substitute_function_def_with_consts(
&func,
&HashMap::new(),
&const_subs_int_0(3),
"harness::int_3",
);
assert_no_const_id_in_stmt(&mono.body[0]);
}
#[test]
fn b4_multi_const_params_substitute_distinctly() {
let stmt = Statement::Return(
Some(Expr::BinaryOp {
left: Box::new(Expr::Identifier("__const_0".into(), Span::default())),
op: shape_ast::ast::BinaryOp::Mul,
right: Box::new(Expr::Identifier("__const_1".into(), Span::default())),
span: Span::default(),
}),
Span::default(),
);
let func = const_body_fn(vec![stmt]);
let mut subs: HashMap<String, ComptimeConstValue> = HashMap::new();
subs.insert("__const_0".into(), ComptimeConstValue::Int(4));
subs.insert("__const_1".into(), ComptimeConstValue::Int(3));
let mono = substitute_function_def_with_consts(
&func,
&HashMap::new(),
&subs,
"harness::int_4_int_3",
);
match &mono.body[0] {
Statement::Return(Some(Expr::BinaryOp { left, right, .. }), _) => {
assert!(matches!(
left.as_ref(),
Expr::Literal(shape_ast::ast::Literal::Int(4), _)
));
assert!(matches!(
right.as_ref(),
Expr::Literal(shape_ast::ast::Literal::Int(3), _)
));
}
other => panic!("expected `return 4 * 3`, got {:?}", other),
}
}
}