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::Query(..)
108 | TypeRef::Stream(..)
109 | TypeRef::Connection(..)
110 | TypeRef::History(..)
111 | TypeRef::ValidationError(_)
112 | TypeRef::JsonError(_)
113 | TypeRef::Unit(_)
114 | TypeRef::Fn(..) => Err("this field type is not wire-representable".to_string()),
115 }
116}
117
118fn qualified_call(e: &Expr) -> Option<(&str, &str, &[Expr])> {
130 match &e.kind {
131 ExprKind::MethodCall {
132 receiver,
133 method,
134 args,
135 ..
136 } => {
137 let ExprKind::Ident(recv) = &receiver.kind else {
138 return None;
139 };
140 Some((recv.name.as_str(), method.name.as_str(), args.as_slice()))
141 }
142 ExprKind::ConstructorCall {
143 type_name,
144 method,
145 args,
146 } => Some((
147 type_name.name.as_str(),
148 method.name.as_str(),
149 args.as_slice(),
150 )),
151 _ => None,
152 }
153}
154
155fn lower_base_literal(base: BaseType, e: &Expr) -> Result<String, String> {
156 let (negate, inner) = match &e.kind {
160 ExprKind::UnaryOp(UnaryOp::Neg, inner) => (true, &inner.kind),
161 other => (false, other),
162 };
163 match (base, inner) {
164 (BaseType::Int | BaseType::Instant, ExprKind::IntLit { value, .. }) => {
165 let v = if negate {
166 value
167 .checked_neg()
168 .ok_or_else(|| "integer literal has no negation".to_string())?
169 } else {
170 *value
171 };
172 Ok(v.to_string())
173 }
174 (BaseType::Float, ExprKind::IntLit { value, .. }) => {
175 let v = if negate { -*value } else { *value };
176 Ok(v.to_string())
177 }
178 (BaseType::Float, ExprKind::FloatLit { lexeme, .. }) => Ok(if negate {
179 format!("-{lexeme}")
180 } else {
181 lexeme.clone()
182 }),
183 (BaseType::String, ExprKind::StrLit(s)) if !negate => {
184 Ok(format!("\"{}\"", escape_ts_literal(s)))
185 }
186 (BaseType::Bool, ExprKind::BoolLit(b)) if !negate => Ok(b.to_string()),
187 (BaseType::Duration, ExprKind::DurationLit { millis, .. }) if !negate => {
188 Ok(millis.to_string())
189 }
190 (BaseType::Bytes, _) => Err("a `Bytes` field has no literal default form".to_string()),
191 _ => Err(format!("expected a `{}` literal", base.name())),
192 }
193}
194
195fn lower_sum_default(
202 sum_name: &str,
203 body: &SumBody,
204 init: &Expr,
205 types: &std::collections::HashMap<String, Arc<TypeDecl>>,
206) -> Result<String, String> {
207 let (variant_name, args): (&str, &[Expr]) = match &init.kind {
208 ExprKind::Ident(id) => (id.name.as_str(), &[]),
209 ExprKind::FieldAccess { receiver, field } => {
210 let ExprKind::Ident(recv) = &receiver.kind else {
211 return Err(format!("expected a variant of `{sum_name}`"));
212 };
213 if recv.name != sum_name {
214 return Err(format!(
215 "expected a variant of `{sum_name}`, not `{}`",
216 recv.name
217 ));
218 }
219 (field.name.as_str(), &[])
220 }
221 ExprKind::Call { name, args, .. } => (name.name.as_str(), args.as_slice()),
222 _ => match qualified_call(init) {
223 Some((recv, method, args)) if recv == sum_name => (method, args),
224 Some((recv, ..)) => {
225 return Err(format!("expected a variant of `{sum_name}`, not `{recv}`"));
226 }
227 None => return Err(format!("expected a variant of `{sum_name}`")),
228 },
229 };
230 let Some(variant) = body.variants.iter().find(|v| v.name.name == variant_name) else {
231 return Err(format!("`{variant_name}` is not a variant of `{sum_name}`"));
232 };
233 if args.len() != variant.payload.len() {
234 return Err(format!(
235 "`{variant_name}` takes {} payload field(s), got {}",
236 variant.payload.len(),
237 args.len()
238 ));
239 }
240 let mut parts = vec![format!("kind: \"{variant_name}\"")];
241 for (field, arg) in variant.payload.iter().zip(args.iter()) {
242 let v = lower_field_default_wire(arg, &field.type_ref, types)?;
243 parts.push(format!("{}: {v}", field.name.name));
244 }
245 Ok(format!("{{ {} }}", parts.join(", ")))
246}
247
248fn lower_record_default(
253 body: &RecordBody,
254 init: &Expr,
255 types: &std::collections::HashMap<String, Arc<TypeDecl>>,
256) -> Result<String, String> {
257 let ExprKind::RecordConstruction { fields, .. } = &init.kind else {
258 return Err("expected a record literal".to_string());
259 };
260 let mut parts = Vec::new();
261 for f in &body.fields {
262 let Some(given) = fields.iter().find(|fi| fi.name.name == f.name.name) else {
263 return Err(format!("record default is missing field `{}`", f.name.name));
264 };
265 let Some(value) = &given.value else {
266 return Err(format!(
267 "record default's field `{}` cannot use shorthand (no bindings are in scope)",
268 f.name.name
269 ));
270 };
271 let v = lower_field_default_wire(value, &f.type_ref, types)?;
272 parts.push(format!("{}: {v}", f.name.name));
273 }
274 Ok(format!("{{ {} }}", parts.join(", ")))
275}
276
277pub fn escape_ts_literal(s: &str) -> String {
283 let mut out = String::with_capacity(s.len());
284 for c in s.chars() {
285 match c {
286 '\\' => out.push_str("\\\\"),
287 '"' => out.push_str("\\\""),
288 '\n' => out.push_str("\\n"),
289 '\t' => out.push_str("\\t"),
290 '\r' => out.push_str("\\r"),
291 c => out.push(c),
292 }
293 }
294 out
295}