1use crate::codegen::CodegenCtx;
4use crate::codegen::function_emitter::FunctionEmitter;
5use crate::codegen::function_emitter::expr::{emit_expr, emit_vtable_dispatch_on_object_stack};
6use crate::codegen::intrinsics::IntrinsicTypeIndices;
7use crate::codegen::symbol_table::may_hold_null;
8use crate::{ExprId, Type};
9use wasm_encoder::{BlockType, Function, HeapType, Instruction, RefType, ValType};
10
11pub(crate) const NO_JSON_TEXT_MESSAGE: &str = "`JSON.stringify` has no JSON text for this value (it is `undefined` or a function); use `JSON.stringify(value) ?? …` or check the value first";
13
14pub(super) fn emit_stringify(
15 emitter: &mut FunctionEmitter,
16 ctx: &CodegenCtx,
17 args: &[ExprId],
18 slot_ty: &Type,
19 source_ty: &Type,
20) -> Result<(), crate::compiler_error::CompilerFailure> {
21 let [arg, rest @ ..] = args else {
22 return Err(crate::codegen::internal_failure(
23 "JSON.stringify requires a value",
24 ));
25 };
26 if rest.len() > 2 {
27 return Err(crate::codegen::internal_failure(
28 "JSON.stringify accepts at most three arguments",
29 ));
30 }
31 let arg = *arg;
32 let arg_ty = ctx
33 .ta
34 .try_expr(arg)
35 .map_err(crate::codegen::arena_failure)?
36 .ty
37 .clone();
38 if args.len() == 1 {
39 emit_expr(emitter, ctx, arg)?;
40 emit_stringify_value(emitter, ctx, &arg_ty);
41 } else {
42 emit_expr(emitter, ctx, arg)?;
43 let arg_local = emitter.add_anonymous_local(ctx.symbols.value_type(&arg_ty)?)?;
44 emitter.instruction(Instruction::LocalSet(arg_local));
45 emit_stringify_optional_args(emitter, ctx, args, arg_local, &arg_ty)?;
46 }
47 let may_be_undefined = may_stringify_undefined(ctx, &arg_ty);
52 if may_be_undefined && !source_ty.holds_undefined() {
53 emit_reject_missing_json_text(emitter, ctx)?;
54 return super::cast::emit_coerce_to_slot(emitter, ctx, &Type::String, slot_ty);
55 }
56 let emitted_ty = if may_be_undefined {
57 Type::union(vec![Type::String, Type::Undefined])
58 } else {
59 Type::String
60 };
61 super::cast::emit_coerce_to_slot(emitter, ctx, &emitted_ty, slot_ty)
62}
63
64fn emit_reject_missing_json_text(
67 emitter: &mut FunctionEmitter,
68 ctx: &CodegenCtx,
69) -> Result<(), crate::compiler_error::CompilerFailure> {
70 let intrinsics = ctx
71 .symbols
72 .intrinsic_type_indices()
73 .ok_or_else(|| crate::codegen::internal_failure("intrinsics registered"))?;
74 let value = emitter.add_anonymous_local(ValType::Ref(RefType {
75 nullable: false,
76 heap_type: HeapType::Concrete(intrinsics.object),
77 }))?;
78 emitter.instruction(Instruction::LocalSet(value));
79 emitter.instruction(Instruction::LocalGet(value));
80 emitter.instruction(Instruction::RefTestNonNull(HeapType::Concrete(
81 intrinsics.undefined,
82 )));
83 emitter.emit_if(BlockType::Empty);
84 crate::codegen::throw::emit_missing_value_error_throw(emitter, ctx, NO_JSON_TEXT_MESSAGE);
85 emitter.emit_end();
86 emitter.instruction(Instruction::LocalGet(value));
87 emitter.instruction(Instruction::RefCastNonNull(HeapType::Concrete(
88 intrinsics.string,
89 )));
90 Ok(())
91}
92
93fn emit_stringify_value(emitter: &mut FunctionEmitter, ctx: &CodegenCtx, arg_ty: &Type) {
96 if !may_stringify_undefined(ctx, arg_ty) {
97 emit_defined_stringify_value(emitter, ctx, arg_ty);
98 return;
99 }
100 let Some(intrinsics) = ctx.require(
101 ctx.symbols.intrinsic_type_indices(),
102 "intrinsics registered",
103 ) else {
104 return;
105 };
106 let Some(value) = ctx.latch(emitter.add_anonymous_local(ValType::Ref(RefType {
107 nullable: true,
108 heap_type: HeapType::Concrete(intrinsics.object),
109 }))) else {
110 return;
111 };
112 emitter.instruction(Instruction::LocalSet(value));
113 emitter.instructions(crate::codegen::nullish::is_undefined_or_closure(
114 &[Instruction::LocalGet(value)],
115 intrinsics,
116 ));
117 emitter.emit_if(BlockType::Result(ValType::Ref(RefType {
118 nullable: false,
119 heap_type: HeapType::Concrete(intrinsics.object),
120 })));
121 if ctx
122 .latch(super::expr::emit_undefined(emitter, ctx))
123 .is_none()
124 {
125 return;
126 }
127 emitter.emit_else();
128 emitter.instruction(Instruction::LocalGet(value));
129 emit_stringify_nullable(emitter, ctx);
130 emitter.emit_end();
131}
132
133fn may_stringify_undefined(ctx: &CodegenCtx, ty: &Type) -> bool {
134 ty.any_member(&|member| {
135 Type::stringifies_to_undefined(member) || is_constructor_value(ctx, member)
136 })
137}
138
139fn is_constructor_value(ctx: &CodegenCtx, ty: &Type) -> bool {
143 let Type::InterfaceRef { mangled, .. } = ty else {
144 return false;
145 };
146 ctx.symbols
147 .static_value_tag(mangled)
148 .is_some_and(|tag| tag.starts_with("function "))
149}
150
151fn emit_defined_stringify_value(emitter: &mut FunctionEmitter, ctx: &CodegenCtx, arg_ty: &Type) {
152 let peeled = match arg_ty.lowered() {
156 Type::Union(members) if is_unboxed_number_union(members) => &Type::Number,
157 peeled => peeled,
158 };
159 match peeled {
160 Type::Null => {
161 emitter.instruction(Instruction::Drop);
163 ctx.latch(super::emit_inline_string_literal(emitter, ctx, "null"));
164 }
165 Type::Boolean | Type::BooleanLiteral(_) => emit_to_json_direct(emitter, ctx, "Boolean"),
166 Type::Number | Type::NumberLiteral(_) => {
167 emit_to_json_direct(emitter, ctx, "Number");
168 }
169 Type::String | Type::StringLiteral(_) => {
170 emit_stringify_string_host(emitter, ctx);
171 }
172 _ if may_hold_null(peeled) => {
173 emit_stringify_nullable(emitter, ctx);
177 }
178 _ => {
179 emit_vtable_dispatch_on_object_stack(emitter, ctx, 1);
180 }
181 }
182}
183
184fn is_unboxed_number_union(members: &[Type]) -> bool {
187 !members.is_empty()
188 && members
189 .iter()
190 .all(|member| matches!(member.lowered(), Type::Number | Type::NumberLiteral(_)))
191}
192
193fn emit_stringify_nullable(emitter: &mut FunctionEmitter, ctx: &CodegenCtx) {
194 let Some(intrinsics) = ctx.require(
195 ctx.symbols.intrinsic_type_indices(),
196 "intrinsics declared by codegen entry",
197 ) else {
198 return;
199 };
200 let Some(obj_tmp) = ctx.latch(emitter.add_anonymous_local(ValType::Ref(RefType {
201 nullable: true,
202 heap_type: HeapType::Concrete(intrinsics.object),
203 }))) else {
204 return;
205 };
206 emitter.instruction(Instruction::LocalTee(obj_tmp));
207 emitter.instruction(Instruction::RefIsNull);
208 emitter.emit_if(BlockType::Result(ValType::Ref(RefType {
209 nullable: false,
210 heap_type: HeapType::Concrete(intrinsics.string),
211 })));
212 ctx.latch(super::emit_inline_string_literal(emitter, ctx, "null"));
213 emitter.emit_else();
214 emitter.instruction(Instruction::LocalGet(obj_tmp));
215 emitter.instruction(Instruction::RefAsNonNull);
216 emit_vtable_dispatch_on_object_stack(emitter, ctx, 1);
217 emitter.emit_end();
218}
219
220fn emit_stringify_string_host(emitter: &mut FunctionEmitter, ctx: &CodegenCtx) {
225 let Some(intrinsics) = ctx.require(
226 ctx.symbols.intrinsic_type_indices(),
227 "intrinsic type indices registered",
228 ) else {
229 return;
230 };
231 let Some(string_type_idx) = ctx.require(ctx.symbols.string_type_idx(), "$string registered")
232 else {
233 return;
234 };
235 emitter.instruction(Instruction::StructGet {
236 struct_type_index: string_type_idx,
237 field_index: 1,
238 });
239
240 let Some(stringify_idx) = ctx.require(
241 ctx.symbols.func_idx(&crate::mangle::host(
242 crate::runtime::JSON_MODULE_NAME,
243 "stringify",
244 )),
245 "submilli:json.stringify imported during codegen",
246 ) else {
247 return;
248 };
249 emitter.instruction(Instruction::Call(stringify_idx));
250
251 let Some(raw_local) = ctx.latch(emitter.add_anonymous_local(ValType::Ref(RefType {
252 nullable: false,
253 heap_type: HeapType::Concrete(intrinsics.raw_string),
254 }))) else {
255 return;
256 };
257 emitter.instruction(Instruction::LocalSet(raw_local));
258 let Some(string_vtable_idx) = ctx.require(
259 ctx.symbols.prelude_global_idx("string_vtable"),
260 "string_vtable imported",
261 ) else {
262 return;
263 };
264 emitter.instruction(Instruction::GlobalGet(string_vtable_idx));
265 emitter.instruction(Instruction::LocalGet(raw_local));
266 emitter.instruction(Instruction::I64Const(0));
267 emitter.instruction(Instruction::StructNew(intrinsics.string));
268}
269
270fn emit_stringify_optional_args(
271 emitter: &mut FunctionEmitter,
272 ctx: &CodegenCtx,
273 args: &[ExprId],
274 arg_local: u32,
275 arg_ty: &Type,
276) -> Result<(), crate::compiler_error::CompilerFailure> {
277 let [_, replacer, rest @ ..] = args else {
278 return Err(crate::codegen::internal_failure(
279 "JSON.stringify optional arguments are missing",
280 ));
281 };
282 emit_expr(emitter, ctx, *replacer)?;
283 emitter.instruction(Instruction::Drop);
284
285 let space = match rest {
286 [] => StringifySpace::None,
287 [space] => emit_stringify_space_arg(emitter, ctx, *space)?,
288 _ => {
289 return Err(crate::codegen::internal_failure(
290 "JSON.stringify has extra optional arguments",
291 ));
292 }
293 };
294
295 emitter.instruction(Instruction::LocalGet(arg_local));
296 emit_stringify_value(emitter, ctx, arg_ty);
297 let may_be_undefined = may_stringify_undefined(ctx, arg_ty);
298 if may_be_undefined {
299 let intr = ctx
300 .symbols
301 .intrinsic_type_indices()
302 .ok_or_else(|| crate::codegen::internal_failure("intrinsics registered"))?;
303 let result_type = ValType::Ref(RefType {
304 nullable: false,
305 heap_type: HeapType::Concrete(intr.object),
306 });
307 let result = emitter.add_anonymous_local(result_type)?;
308 emitter.instruction(Instruction::LocalTee(result));
309 emitter.instruction(Instruction::RefTestNonNull(HeapType::Concrete(
310 intr.undefined,
311 )));
312 emitter.emit_if(BlockType::Result(result_type));
313 emitter.instruction(Instruction::LocalGet(result));
314 emitter.emit_else();
315 emitter.instruction(Instruction::LocalGet(result));
316 emitter.instruction(Instruction::RefCastNonNull(HeapType::Concrete(intr.string)));
317 }
318 match space {
319 StringifySpace::None => {}
320 StringifySpace::Dynamic(local) => emit_dynamic_space(emitter, ctx, local)?,
321 StringifySpace::Number(local) => {
322 emit_string_on_stack_raw(emitter, ctx);
323 emitter.instruction(Instruction::LocalGet(local));
324 emit_pretty_number_host(emitter, ctx);
325 emit_wrap_raw_string(emitter, ctx);
326 }
327 StringifySpace::String(local) => {
328 emit_string_on_stack_raw(emitter, ctx);
329 emitter.instruction(Instruction::LocalGet(local));
330 emit_string_on_stack_raw(emitter, ctx);
331 emit_pretty_string_host(emitter, ctx);
332 emit_wrap_raw_string(emitter, ctx);
333 }
334 };
335 if may_be_undefined {
336 emitter.emit_end();
337 }
338 Ok(())
339}
340
341enum StringifySpace {
342 None,
343 Number(u32),
344 String(u32),
345 Dynamic(u32),
346}
347
348fn emit_stringify_space_arg(
349 emitter: &mut FunctionEmitter,
350 ctx: &CodegenCtx,
351 space: ExprId,
352) -> Result<StringifySpace, crate::compiler_error::CompilerFailure> {
353 let space_ty = ctx
354 .ta
355 .try_expr(space)
356 .map_err(crate::codegen::arena_failure)?
357 .ty
358 .clone();
359 Ok(match space_ty.lowered() {
360 Type::Number | Type::NumberLiteral(_) => {
361 emit_expr(emitter, ctx, space)?;
362 let local = emitter.add_anonymous_local(ValType::F64)?;
363 emitter.instruction(Instruction::LocalSet(local));
364 StringifySpace::Number(local)
365 }
366 Type::String | Type::StringLiteral(_) => {
367 emit_expr(emitter, ctx, space)?;
368 let local = emitter.add_anonymous_local(ctx.symbols.value_type(&space_ty)?)?;
369 emitter.instruction(Instruction::LocalSet(local));
370 StringifySpace::String(local)
371 }
372 Type::Null | Type::Undefined | Type::Void => {
373 emit_expr(emitter, ctx, space)?;
374 emitter.instruction(Instruction::Drop);
375 StringifySpace::None
376 }
377 Type::Unknown | Type::Union(_) => {
378 emit_expr(emitter, ctx, space)?;
379 let local = emitter.add_anonymous_local(ctx.symbols.value_type(&Type::Unknown)?)?;
380 emitter.instruction(Instruction::LocalSet(local));
381 StringifySpace::Dynamic(local)
382 }
383 other => {
384 return Err(crate::codegen::internal_failure(format!(
385 "JSON.stringify space type was not validated: {other}"
386 )));
387 }
388 })
389}
390
391fn emit_dynamic_space(
392 emitter: &mut FunctionEmitter,
393 ctx: &CodegenCtx,
394 space: u32,
395) -> Result<(), crate::compiler_error::CompilerFailure> {
396 let string_type = ctx.symbols.value_type(&Type::String)?;
397 let json = emitter.add_anonymous_local(string_type)?;
398 emitter.instruction(Instruction::LocalSet(json));
399 let number = ctx
400 .symbols
401 .boxed_number_type_idx()
402 .ok_or_else(|| crate::codegen::internal_failure("boxed number registered"))?;
403 let string = ctx
404 .symbols
405 .string_type_idx()
406 .ok_or_else(|| crate::codegen::internal_failure("string registered"))?;
407 emitter.instruction(Instruction::LocalGet(space));
408 emitter.instruction(Instruction::RefTestNonNull(HeapType::Concrete(number)));
409 emitter.emit_if(BlockType::Result(string_type));
410 emitter.instruction(Instruction::LocalGet(json));
411 emit_string_on_stack_raw(emitter, ctx);
412 emitter.instruction(Instruction::LocalGet(space));
413 super::cast::emit_cast_to(emitter, ctx, &Type::Number)?;
414 emit_pretty_number_host(emitter, ctx);
415 emit_wrap_raw_string(emitter, ctx);
416 emitter.emit_else();
417 emitter.instruction(Instruction::LocalGet(space));
418 emitter.instruction(Instruction::RefTestNonNull(HeapType::Concrete(string)));
419 emitter.emit_if(BlockType::Result(string_type));
420 emitter.instruction(Instruction::LocalGet(json));
421 emit_string_on_stack_raw(emitter, ctx);
422 emitter.instruction(Instruction::LocalGet(space));
423 super::cast::emit_cast_to(emitter, ctx, &Type::String)?;
424 emit_string_on_stack_raw(emitter, ctx);
425 emit_pretty_string_host(emitter, ctx);
426 emit_wrap_raw_string(emitter, ctx);
427 emitter.emit_else();
428 emitter.instruction(Instruction::LocalGet(json));
429 emitter.emit_end();
430 emitter.emit_end();
431
432 Ok(())
433}
434
435fn emit_string_on_stack_raw(emitter: &mut FunctionEmitter, ctx: &CodegenCtx) {
436 let Some(string_type_idx) = ctx.require(ctx.symbols.string_type_idx(), "$string registered")
437 else {
438 return;
439 };
440 emitter.instruction(Instruction::StructGet {
441 struct_type_index: string_type_idx,
442 field_index: 1,
443 });
444}
445
446fn emit_pretty_number_host(emitter: &mut FunctionEmitter, ctx: &CodegenCtx) {
447 let Some(pretty_idx) = ctx.require(
448 ctx.symbols.func_idx(&crate::mangle::host(
449 crate::runtime::JSON_MODULE_NAME,
450 "stringifyPrettyNumber",
451 )),
452 "submilli:json.stringifyPrettyNumber imported during codegen",
453 ) else {
454 return;
455 };
456 emitter.instruction(Instruction::Call(pretty_idx));
457}
458
459fn emit_pretty_string_host(emitter: &mut FunctionEmitter, ctx: &CodegenCtx) {
460 let Some(pretty_idx) = ctx.require(
461 ctx.symbols.func_idx(&crate::mangle::host(
462 crate::runtime::JSON_MODULE_NAME,
463 "stringifyPrettyString",
464 )),
465 "submilli:json.stringifyPrettyString imported during codegen",
466 ) else {
467 return;
468 };
469 emitter.instruction(Instruction::Call(pretty_idx));
470}
471
472fn emit_wrap_raw_string(emitter: &mut FunctionEmitter, ctx: &CodegenCtx) {
473 let Some(intrinsics) = ctx.require(
474 ctx.symbols.intrinsic_type_indices(),
475 "intrinsic type indices registered",
476 ) else {
477 return;
478 };
479 let Some(raw_local) = ctx.latch(emitter.add_anonymous_local(ValType::Ref(RefType {
480 nullable: false,
481 heap_type: HeapType::Concrete(intrinsics.raw_string),
482 }))) else {
483 return;
484 };
485 emitter.instruction(Instruction::LocalSet(raw_local));
486 let Some(string_vtable_idx) = ctx.require(
487 ctx.symbols.prelude_global_idx("string_vtable"),
488 "string_vtable imported",
489 ) else {
490 return;
491 };
492 emitter.instruction(Instruction::GlobalGet(string_vtable_idx));
493 emitter.instruction(Instruction::LocalGet(raw_local));
494 emitter.instruction(Instruction::I64Const(0));
495 emitter.instruction(Instruction::StructNew(intrinsics.string));
496}
497
498fn emit_to_json_direct(emitter: &mut FunctionEmitter, ctx: &CodegenCtx, iface: &str) {
499 let mangled = crate::mangle::extend(&crate::mangle::prelude(iface), "toJson");
500 let Some(func_idx) = ctx.latch(ctx.symbols.func_idx(&mangled).ok_or_else(|| {
501 crate::codegen::internal_failure(format!("{iface}#toJson imported from prelude"))
502 })) else {
503 return;
504 };
505 emitter.instruction(Instruction::Call(func_idx));
506}
507
508pub(crate) fn emit_main_output_shim(
513 ctx: &CodegenCtx,
514 main_func_idx: u32,
515 return_ty: &Type,
516 source_return_ty: &Type,
517) -> Result<Function, crate::compiler_error::CompilerFailure> {
518 let mut emitter = FunctionEmitter::new(ctx, &[])?;
519 emitter.instruction(Instruction::Call(main_func_idx));
520 let optional_output = may_stringify_undefined(ctx, return_ty);
521 if optional_output {
522 let intr = ctx
523 .symbols
524 .intrinsic_type_indices()
525 .ok_or_else(|| crate::codegen::internal_failure("intrinsics registered"))?;
526 let value = emitter.add_anonymous_local(ValType::Ref(RefType {
527 nullable: true,
528 heap_type: HeapType::Concrete(intr.object),
529 }))?;
530 emitter.instruction(Instruction::LocalSet(value));
531 emitter.instructions(crate::codegen::nullish::is_undefined_or_closure(
532 &[Instruction::LocalGet(value)],
533 intr,
534 ));
535 emitter.emit_if(BlockType::Result(ValType::Ref(RefType {
536 nullable: true,
537 heap_type: HeapType::Concrete(intr.string),
538 })));
539 emitter.instruction(Instruction::RefNull(HeapType::Concrete(intr.string)));
540 emitter.emit_else();
541 emitter.instruction(Instruction::LocalGet(value));
542 }
543 let scalar_output = match source_return_ty.lowered() {
544 Type::String
545 | Type::StringLiteral(_)
546 | Type::Number
547 | Type::NumberLiteral(_)
548 | Type::Boolean
549 | Type::BooleanLiteral(_) => true,
550 Type::Union(members) => is_primitive_union(members),
551 _ => false,
552 };
553 if return_ty == &Type::Unknown && scalar_output {
554 emit_nullable_primitive_to_string(&mut emitter, ctx);
555 } else {
556 emit_main_output_value(&mut emitter, ctx, return_ty.lowered());
557 }
558 if optional_output {
559 emitter.emit_end();
560 }
561 emitter.build()
562}
563
564fn emit_main_output_value(emitter: &mut FunctionEmitter, ctx: &CodegenCtx, arg_ty: &Type) {
570 match arg_ty {
571 Type::String | Type::StringLiteral(_) => {}
572 Type::Number | Type::NumberLiteral(_) => emit_number_to_string_radix10(emitter, ctx),
573 Type::Boolean | Type::BooleanLiteral(_) => emit_to_string_direct(emitter, ctx, "Boolean"),
574 Type::Union(members) if is_primitive_union(members) => {
578 emit_nullable_primitive_to_string(emitter, ctx);
579 }
580 Type::Undefined
581 | Type::Void
582 | Type::Unknown
583 | Type::TypeVar(_)
584 | Type::GenericParam { .. } => emit_stringify_nullable(emitter, ctx),
585 _ => emit_defined_stringify_value(emitter, ctx, arg_ty),
586 }
587}
588
589fn is_primitive_union(members: &[Type]) -> bool {
593 let mut has_primitive = false;
594 for member in members {
595 match member.lowered() {
596 Type::Null | Type::Undefined => {}
597 Type::String
598 | Type::StringLiteral(_)
599 | Type::Number
600 | Type::NumberLiteral(_)
601 | Type::Boolean
602 | Type::BooleanLiteral(_) => has_primitive = true,
603 _ => return false,
604 }
605 }
606 has_primitive
607}
608
609fn emit_nullable_primitive_to_string(emitter: &mut FunctionEmitter, ctx: &CodegenCtx) {
613 let Some(intrinsics) = ctx.require(
614 ctx.symbols.intrinsic_type_indices(),
615 "intrinsics declared by codegen entry",
616 ) else {
617 return;
618 };
619 let Some(obj_tmp) = ctx.latch(emitter.add_anonymous_local(ValType::Ref(RefType {
620 nullable: true,
621 heap_type: HeapType::Concrete(intrinsics.object),
622 }))) else {
623 return;
624 };
625 emitter.instruction(Instruction::LocalTee(obj_tmp));
626 emitter.instruction(Instruction::RefIsNull);
627 emitter.emit_if(BlockType::Result(ValType::Ref(RefType {
628 nullable: false,
629 heap_type: HeapType::Concrete(intrinsics.string),
630 })));
631 ctx.latch(super::emit_inline_string_literal(emitter, ctx, "null"));
632 emitter.emit_else();
633 emitter.instruction(Instruction::LocalGet(obj_tmp));
634 emitter.instruction(Instruction::RefAsNonNull);
635 emit_vtable_dispatch_on_object_stack(emitter, ctx, 0);
636 emitter.emit_end();
637}
638
639fn emit_number_to_string_radix10(emitter: &mut FunctionEmitter, ctx: &CodegenCtx) {
643 emitter.instruction(Instruction::F64Const(10.0_f64.into()));
644 emit_to_string_direct(emitter, ctx, "Number");
645}
646
647fn emit_to_string_direct(emitter: &mut FunctionEmitter, ctx: &CodegenCtx, iface: &str) {
648 let mangled = crate::mangle::extend(&crate::mangle::prelude(iface), "toString");
649 let Some(func_idx) = ctx.latch(ctx.symbols.func_idx(&mangled).ok_or_else(|| {
650 crate::codegen::internal_failure(format!("{iface}#toString imported from prelude"))
651 })) else {
652 return;
653 };
654 emitter.instruction(Instruction::Call(func_idx));
655}
656
657pub(super) fn emit_parse(
658 emitter: &mut FunctionEmitter,
659 ctx: &CodegenCtx,
660 args: &[ExprId],
661) -> Result<(), crate::compiler_error::CompilerFailure> {
662 let [arg] = args else {
663 return Err(crate::codegen::internal_failure(
664 "JSON.parse requires exactly one argument",
665 ));
666 };
667
668 emit_expr(emitter, ctx, *arg)?;
671 super::cast::emit_coerce_to_slot(
672 emitter,
673 ctx,
674 &ctx.ta
675 .try_expr(*arg)
676 .map_err(crate::codegen::arena_failure)?
677 .ty,
678 &Type::String,
679 )?;
680 let string_type_idx = ctx
681 .symbols
682 .string_type_idx()
683 .ok_or_else(|| crate::codegen::internal_failure("$string registered"))?;
684 emitter.instruction(Instruction::StructGet {
685 struct_type_index: string_type_idx,
686 field_index: 1,
687 });
688
689 let parse_idx = ctx
690 .symbols
691 .func_idx(&crate::mangle::host(
692 crate::runtime::JSON_MODULE_NAME,
693 "parse",
694 ))
695 .ok_or_else(|| {
696 crate::codegen::internal_failure("submilli:json.parse imported during codegen")
697 })?;
698 emitter.instruction(Instruction::Call(parse_idx));
701 Ok(())
702}
703
704pub(crate) fn emit_inline_const_raw_string(
707 emitter: &mut FunctionEmitter,
708 ctx: &CodegenCtx,
709 text: &str,
710) -> Result<(), crate::compiler_error::CompilerFailure> {
711 let intrinsics = ctx.symbols.intrinsic_type_indices().ok_or_else(|| {
712 crate::codegen::internal_failure("inline string intrinsic types are missing")
713 })?;
714 let units = crate::literal_units::literal_units(text);
715 let array_size = u32::try_from(units.len()).map_err(|_| {
716 crate::codegen::internal_failure("inline string exceeds the array length representation")
717 })?;
718 for unit in units {
719 emitter.instruction(Instruction::I32Const(i32::from(unit)));
720 }
721 emitter.instruction(Instruction::ArrayNewFixed {
722 array_type_index: intrinsics.raw_string,
723 array_size,
724 });
725 Ok(())
726}
727
728pub(crate) fn emit_raw_string_matches_literal(
729 emitter: &mut FunctionEmitter,
730 intrinsics: IntrinsicTypeIndices,
731 key_raw_local: u32,
732 expected: &str,
733) {
734 let expected = crate::literal_units::literal_units(expected);
735 let Some(expected_length) = emitter.ctx.latch(crate::codegen::wasm_u32(expected.len())) else {
736 return;
737 };
738 let Some(result_local) = emitter.ctx.latch(emitter.add_anonymous_local(ValType::I32)) else {
739 return;
740 };
741 emitter.instruction(Instruction::I32Const(0));
742 emitter.instruction(Instruction::LocalSet(result_local));
743
744 emitter.emit_block(BlockType::Empty);
745
746 emitter.instruction(Instruction::LocalGet(key_raw_local));
747 emitter.instruction(Instruction::ArrayLen);
748 emitter.instruction(Instruction::I32Const(expected_length as i32));
749 emitter.instruction(Instruction::I32Ne);
750 emitter.instruction(Instruction::BrIf(0));
751
752 for (&unit, i) in expected.iter().zip(0..expected_length) {
753 emitter.instruction(Instruction::LocalGet(key_raw_local));
754 emitter.instruction(Instruction::I32Const(i as i32));
755 emitter.instruction(Instruction::ArrayGetU(intrinsics.raw_string));
756 emitter.instruction(Instruction::I32Const(i32::from(unit)));
757 emitter.instruction(Instruction::I32Ne);
758 emitter.instruction(Instruction::BrIf(0));
759 }
760
761 emitter.instruction(Instruction::I32Const(1));
762 emitter.instruction(Instruction::LocalSet(result_local));
763
764 emitter.emit_end();
765
766 emitter.instruction(Instruction::LocalGet(result_local));
767}
768
769#[cfg(test)]
770mod tests {
771 use super::*;
772 use crate::codegen::invariant_tests::{assert_internal, with_context};
773 use crate::codegen::{SymbolTable, tests::mock_symbols_with_intrinsics};
774 use crate::{Span, TypedAst, TypedExpr, TypedExprKind};
775
776 #[test]
777 fn invalid_json_arity_is_an_internal_failure_before_reading_arguments() {
778 with_context(&TypedAst::new(), &SymbolTable::default(), |ctx| {
779 let mut emitter = FunctionEmitter::new(ctx, &[]).unwrap();
780 assert_internal(
781 emit_stringify(&mut emitter, ctx, &[], &Type::String, &Type::String).unwrap_err(),
782 );
783 assert_internal(emit_parse(&mut emitter, ctx, &[]).unwrap_err());
784 let invalid = crate::ExprId(u32::MAX);
785 assert_internal(
786 emit_stringify(
787 &mut emitter,
788 ctx,
789 &[invalid; 4],
790 &Type::String,
791 &Type::String,
792 )
793 .unwrap_err(),
794 );
795 assert_internal(emit_parse(&mut emitter, ctx, &[invalid; 2]).unwrap_err());
796 ctx.check_failure().unwrap();
797 });
798 }
799
800 #[test]
801 fn json_emitters_latch_missing_registrations() {
802 type Emit = fn(&mut FunctionEmitter<'_>, &CodegenCtx<'_>);
803 let emitters: &[Emit] = &[
804 emit_stringify_nullable,
805 emit_stringify_string_host,
806 emit_string_on_stack_raw,
807 emit_pretty_number_host,
808 emit_pretty_string_host,
809 emit_wrap_raw_string,
810 emit_nullable_primitive_to_string,
811 |emitter, ctx| emit_to_json_direct(emitter, ctx, "Number"),
812 |emitter, ctx| emit_to_string_direct(emitter, ctx, "Boolean"),
813 |emitter, ctx| {
814 ctx.latch(emit_inline_const_raw_string(emitter, ctx, "\u{1f642}"));
815 },
816 ];
817 for emit in emitters {
818 with_context(&TypedAst::new(), &SymbolTable::default(), |ctx| {
819 emit(&mut FunctionEmitter::new(ctx, &[]).unwrap(), ctx);
820 assert_internal(ctx.check_failure().unwrap_err());
821 });
822 }
823 for emit in [emit_stringify_string_host as Emit, emit_wrap_raw_string] {
824 with_context(&TypedAst::new(), &mock_symbols_with_intrinsics(), |ctx| {
825 emit(&mut FunctionEmitter::new(ctx, &[]).unwrap(), ctx);
826 assert_internal(ctx.check_failure().unwrap_err());
827 });
828 }
829 }
830
831 #[test]
832 fn invalid_space_metadata_returns_internal_failure() {
833 for ty in [Type::Boolean, Type::Error] {
834 let mut ta = TypedAst::new();
835 let space = ta
836 .try_push_expr(TypedExpr {
837 kind: TypedExprKind::Boolean(true),
838 ty,
839 span: Span::at(crate::FileId(0)),
840 })
841 .unwrap();
842 with_context(&ta, &mock_symbols_with_intrinsics(), |ctx| {
843 let mut emitter = FunctionEmitter::new(ctx, &[]).unwrap();
844 match emit_stringify_space_arg(&mut emitter, ctx, space) {
845 Err(error) => assert_internal(error),
846 Ok(_) => panic!("invalid space metadata was accepted"),
847 }
848 });
849 }
850 }
851
852 #[test]
853 fn inline_raw_literals_keep_utf16_units() {
854 with_context(&TypedAst::new(), &mock_symbols_with_intrinsics(), |ctx| {
855 let mut emitter = FunctionEmitter::new(ctx, &[]).unwrap();
856 emit_inline_const_raw_string(&mut emitter, ctx, "a\u{1f642}").unwrap();
857 ctx.check_failure().unwrap();
858 let units: Vec<_> = emitter
859 .instructions
860 .iter()
861 .filter_map(|inst| match inst {
862 Instruction::I32Const(unit) => Some(*unit),
863 _ => None,
864 })
865 .collect();
866 assert_eq!(units, vec![97, 0xd83d, 0xde42]);
867 assert!(matches!(
868 emitter.instructions.last(),
869 Some(Instruction::ArrayNewFixed { array_size: 3, .. })
870 ));
871 });
872 }
873}