1use std::sync::Arc;
2
3use bynk_syntax::ast::{
4 BaseType, Expr, ExprKind, RecordBody, SumBody, TypeBody, TypeDecl, TypeRef, UnaryOp,
5};
6
7pub fn lower_field_default_wire(
33 init: &Expr,
34 expected: &TypeRef,
35 types: &std::collections::HashMap<String, Arc<TypeDecl>>,
36) -> Result<String, String> {
37 if let ExprKind::Paren(inner) = &init.kind {
38 return lower_field_default_wire(inner, expected, types);
39 }
40 match expected {
41 TypeRef::Base(b, _) => lower_base_literal(*b, init),
42 TypeRef::Named(id) => {
43 let Some(decl) = types.get(&id.name) else {
44 return Err(format!("cannot resolve type `{}`", id.name));
45 };
46 match &decl.body {
47 TypeBody::Refined { base, .. } => lower_base_literal(*base, init),
48 TypeBody::Opaque { base, .. } => match qualified_call(init) {
56 Some((type_name, "unsafe", [lit])) if type_name == id.name => {
57 lower_base_literal(*base, lit)
58 }
59 Some((_, "unsafe", _)) => {
60 Err("`.unsafe` takes exactly one argument".to_string())
61 }
62 _ => Err(format!(
63 "an opaque type's default must be `{}.unsafe(<literal>)`",
64 id.name
65 )),
66 },
67 TypeBody::Sum(s) => lower_sum_default(&id.name, s, init, types),
68 TypeBody::Record(r) => lower_record_default(r, init, types),
69 }
70 }
71 TypeRef::Option(inner, _) => match &init.kind {
72 ExprKind::None => Ok("{ kind: \"None\" }".to_string()),
73 ExprKind::Some(e) => {
74 let v = lower_field_default_wire(e, inner, types)?;
75 Ok(format!("{{ kind: \"Some\", value: {v} }}"))
76 }
77 _ => Err("an `Option` field default must be `Some(...)` or `None`".to_string()),
78 },
79 TypeRef::Result(ok, err, _) => match &init.kind {
80 ExprKind::Ok(e) => {
81 let v = lower_field_default_wire(e, ok, types)?;
82 Ok(format!("{{ kind: \"Ok\", value: {v} }}"))
83 }
84 ExprKind::Err(e) => {
85 let v = lower_field_default_wire(e, err, types)?;
86 Ok(format!("{{ kind: \"Err\", error: {v} }}"))
87 }
88 _ => Err("a `Result` field default must be `Ok(...)` or `Err(...)`".to_string()),
89 },
90 TypeRef::List(elem, _) => match &init.kind {
91 ExprKind::ListLit(items) => {
92 let parts = items
93 .iter()
94 .map(|e| lower_field_default_wire(e, elem, types))
95 .collect::<Result<Vec<_>, _>>()?;
96 Ok(format!("[{}]", parts.join(", ")))
97 }
98 _ => Err("a `List` field default must be a list literal".to_string()),
99 },
100 TypeRef::Map(..) => Err("a `Map` field has no closed-form default literal".to_string()),
101 TypeRef::App { .. } => Err(
102 "a generic type's field cannot carry a default (events are never generic)".to_string(),
103 ),
104 TypeRef::Effect(..)
105 | TypeRef::HttpResult(..)
106 | TypeRef::QueueResult(_)
107 | TypeRef::Asset(_)
108 | TypeRef::RequestHeaders(_)
109 | TypeRef::TokenClaims(_)
110 | TypeRef::BoundaryRejection(_)
111 | TypeRef::Query(..)
112 | TypeRef::Stream(..)
113 | TypeRef::Connection(..)
114 | TypeRef::History(..)
115 | TypeRef::ValidationError(_)
116 | TypeRef::JsonError(_)
117 | TypeRef::Unit(_)
118 | TypeRef::Fn(..) => Err("this field type is not wire-representable".to_string()),
119 }
120}
121
122fn qualified_call(e: &Expr) -> Option<(&str, &str, &[Expr])> {
133 match &e.kind {
134 ExprKind::MethodCall {
135 receiver,
136 method,
137 args,
138 ..
139 } => {
140 let ExprKind::Ident(recv) = &receiver.kind else {
141 return None;
142 };
143 Some((recv.name.as_str(), method.name.as_str(), args.as_slice()))
144 }
145 _ => None,
146 }
147}
148
149fn lower_base_literal(base: BaseType, e: &Expr) -> Result<String, String> {
150 let (negate, inner) = match &e.kind {
154 ExprKind::UnaryOp(UnaryOp::Neg, inner) => (true, &inner.kind),
155 other => (false, other),
156 };
157 match (base, inner) {
158 (BaseType::Int | BaseType::Instant, ExprKind::IntLit { value, .. }) => {
159 let v = if negate {
160 value
161 .checked_neg()
162 .ok_or_else(|| "integer literal has no negation".to_string())?
163 } else {
164 *value
165 };
166 Ok(v.to_string())
167 }
168 (BaseType::Float, ExprKind::IntLit { value, .. }) => {
169 let v = if negate { -*value } else { *value };
170 Ok(v.to_string())
171 }
172 (BaseType::Float, ExprKind::FloatLit { lexeme, .. }) => Ok(if negate {
173 format!("-{lexeme}")
174 } else {
175 lexeme.clone()
176 }),
177 (BaseType::String, ExprKind::StrLit(s)) if !negate => {
178 Ok(format!("\"{}\"", escape_ts_literal(s)))
179 }
180 (BaseType::Bool, ExprKind::BoolLit(b)) if !negate => Ok(b.to_string()),
181 (BaseType::Duration, ExprKind::DurationLit { millis, .. }) if !negate => {
182 Ok(millis.to_string())
183 }
184 (BaseType::Bytes, _) => Err("a `Bytes` field has no literal default form".to_string()),
185 _ => Err(format!("expected a `{}` literal", base.name())),
186 }
187}
188
189fn lower_sum_default(
196 sum_name: &str,
197 body: &SumBody,
198 init: &Expr,
199 types: &std::collections::HashMap<String, Arc<TypeDecl>>,
200) -> Result<String, String> {
201 let (variant_name, args): (&str, &[Expr]) = match &init.kind {
202 ExprKind::Ident(id) => (id.name.as_str(), &[]),
203 ExprKind::FieldAccess { receiver, field } => {
204 let ExprKind::Ident(recv) = &receiver.kind else {
205 return Err(format!("expected a variant of `{sum_name}`"));
206 };
207 if recv.name != sum_name {
208 return Err(format!(
209 "expected a variant of `{sum_name}`, not `{}`",
210 recv.name
211 ));
212 }
213 (field.name.as_str(), &[])
214 }
215 ExprKind::Call { name, args, .. } => (name.name.as_str(), args.as_slice()),
216 _ => match qualified_call(init) {
217 Some((recv, method, args)) if recv == sum_name => (method, args),
218 Some((recv, ..)) => {
219 return Err(format!("expected a variant of `{sum_name}`, not `{recv}`"));
220 }
221 None => return Err(format!("expected a variant of `{sum_name}`")),
222 },
223 };
224 let Some(variant) = body.variants.iter().find(|v| v.name.name == variant_name) else {
225 return Err(format!("`{variant_name}` is not a variant of `{sum_name}`"));
226 };
227 if args.len() != variant.payload.len() {
228 return Err(format!(
229 "`{variant_name}` takes {} payload field(s), got {}",
230 variant.payload.len(),
231 args.len()
232 ));
233 }
234 let mut parts = vec![format!("kind: \"{variant_name}\"")];
235 for (field, arg) in variant.payload.iter().zip(args.iter()) {
236 let v = lower_field_default_wire(arg, &field.type_ref, types)?;
237 parts.push(format!("{}: {v}", field.name.name));
238 }
239 Ok(format!("{{ {} }}", parts.join(", ")))
240}
241
242fn lower_record_default(
247 body: &RecordBody,
248 init: &Expr,
249 types: &std::collections::HashMap<String, Arc<TypeDecl>>,
250) -> Result<String, String> {
251 let ExprKind::RecordConstruction { fields, .. } = &init.kind else {
252 return Err("expected a record literal".to_string());
253 };
254 let mut parts = Vec::new();
255 for f in &body.fields {
256 let Some(given) = fields.iter().find(|fi| fi.name.name == f.name.name) else {
257 return Err(format!("record default is missing field `{}`", f.name.name));
258 };
259 let Some(value) = &given.value else {
260 return Err(format!(
261 "record default's field `{}` cannot use shorthand (no bindings are in scope)",
262 f.name.name
263 ));
264 };
265 let v = lower_field_default_wire(value, &f.type_ref, types)?;
266 parts.push(format!("{}: {v}", f.name.name));
267 }
268 Ok(format!("{{ {} }}", parts.join(", ")))
269}
270
271pub fn escape_ts_literal(s: &str) -> String {
277 let mut out = String::with_capacity(s.len());
278 for c in s.chars() {
279 match c {
280 '\\' => out.push_str("\\\\"),
281 '"' => out.push_str("\\\""),
282 '\n' => out.push_str("\\n"),
283 '\t' => out.push_str("\\t"),
284 '\r' => out.push_str("\\r"),
285 c => out.push(c),
286 }
287 }
288 out
289}