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