jdc_core/emit.rs
1//! Java source emission from the statement/expression tree.
2
3use crate::types::JavaType;
4
5use crate::ctx::MethodBody;
6use crate::ir::expr::{BinOp, ConcatPart, ConstVal, Expr, LambdaKind, TypeRef, UnOp};
7use crate::ir::stmt::{CaseGroup, Catch, Stmt};
8use crate::var::VarTable;
9
10pub struct Printer<'a> {
11 /// Front-end services: naming, nesting, type queries, nested bodies.
12 pub ctx: &'a dyn crate::ctx::Ctx,
13 pub vt: &'a VarTable,
14 out: String,
15 indent: usize,
16 /// Depth guard for recursive lambda printing.
17 lambda_depth: usize,
18 /// Names visible in enclosing lambda/method scopes BEYOND self.vt,
19 /// accumulated down the lambda print chain: a nested lambda's locals
20 /// must not redeclare ANY of them (jdk26 PackageSnippets
21 /// .fooToBarUnrolled: the depth-2 codeBuilder lambda's foreach `i$`
22 /// collided with the METHOD-level `i$` — the depth-1 rename only
23 /// consulted depth-1's vt, 已在方法中定义了变量 i$).
24 outer_names: Vec<String>,
25 /// True when the enclosing method returns boolean.
26 /// Reused per-statement line buffer (millions of statements: the
27 /// fresh-String-per-line pattern re-allocated and re-grew each time).
28 line_buf: String,
29 pub ret_bool: bool,
30 /// True while rendering the ROOT of an expression statement: a
31 /// signature-polymorphic call there must stay bare (`NEXT.compareAndSet
32 /// (b, n, p);`) — `(boolean) call();` is not a legal statement.
33 suppress_poly_cast: bool,
34 /// True while rendering the direct child of a cast expression: a cast
35 /// gives the diamond NO target type, so ` (T) new X<>(args)` infers
36 /// Object bounds and then fails the cast — while the bare/raw form
37 /// `(T) new X(args)` compiles (unchecked).
38 suppress_diamond: bool,
39 /// True while rendering a conditional expression's branches. Pre-Java-8
40 /// (major < 52) conditionals are NOT poly expressions: a diamond branch
41 /// (`c ? new HashMap<>(..) : new LinkedHashMap<>(..)`) infers each side
42 /// standalone to <Object,Object> and fails the assignment target (jdk7
43 /// HashSet.readObject 不兼容的类型 — the golden source carried explicit
44 /// <E,Object> args). Diamond branches print RAW there instead:
45 /// unchecked but always compilable.
46 in_cond: bool,
47 /// Instantiated SAM return type while rendering the Lambda directly
48 /// inside a cast to a generic functional interface: the lambda body's
49 /// returns may need `(R) value` witnesses (jdk17 Collectors
50 /// `(Function<I,R>) i -> i`).
51 lambda_sam_ret: Option<TypeRef>,
52 /// The method's generic signature return (functional-interface or
53 /// array form): a lambda in RETURN position has no cast node to hang
54 /// the SAM witness on, so the Return arm derives it from this.
55 ret_sam: Option<TypeRef>,
56 /// True when the enclosing method returns char: int constants in the
57 /// returned expression render as char literals (bytecode chars are
58 /// ints; `return cond ? 63 : 105;` in a char method is a lossy
59 /// conversion — source was `? '?' : 'i'`, jdk XML Parser x23).
60 pub ret_char: bool,
61 /// Narrowing-return markers: int-valued expressions in the return
62 /// need `(byte)`/`(short)` casts (jdk11 MemberName:
63 /// `byte normalVirtual = cond ? 5 : 9;`).
64 pub ret_byte: bool,
65 pub ret_short: bool,
66 /// Memoized twin-exists probe for the same-package member-shadow
67 /// check, keyed by first segment (class_name is constant per ctx;
68 /// shared with sub-printers). The chain-walk does up to 4 registry
69 /// lookups per level — per-call that cost ~30% on weixin; per
70 /// distinct segment it is amortized to nothing.
71 twin_cache: std::rc::Rc<
72 std::cell::RefCell<std::collections::HashMap<String, bool>>,
73 >,
74}
75
76impl<'a> Printer<'a> {
77 pub fn new(ctx: &'a dyn crate::ctx::Ctx, vt: &'a VarTable) -> Self {
78 Printer {
79 ctx,
80 vt,
81 out: String::new(),
82 indent: 0,
83 lambda_depth: 0,
84 outer_names: Vec::new(),
85 line_buf: String::new(),
86 ret_bool: false,
87 suppress_poly_cast: false,
88 suppress_diamond: false,
89 in_cond: false,
90 lambda_sam_ret: None,
91 ret_sam: None,
92 ret_char: false,
93 ret_byte: false,
94 ret_short: false,
95 twin_cache: std::rc::Rc::new(std::cell::RefCell::new(
96 std::collections::HashMap::new(),
97 )),
98 }
99 }
100
101 pub fn with_ret_bool(mut self, b: bool) -> Self {
102 self.ret_bool = b;
103 self
104 }
105
106 pub fn with_ret_sam(mut self, t: Option<TypeRef>) -> Self {
107 self.ret_sam = t;
108 self
109 }
110 pub fn with_ret_char(mut self, b: bool) -> Self {
111 self.ret_char = b;
112 self
113 }
114
115 pub fn with_ret_narrow(mut self, byte: bool, short: bool) -> Self {
116 self.ret_byte = byte;
117 self.ret_short = short;
118 self
119 }
120
121 /// Printer rendering into a caller-provided buffer: the method text
122 /// (signature, braces) and the printed body land in ONE string —
123 /// ddc used to render the body separately and copy it line-by-line
124 /// into the class buffer (a full extra pass over every method).
125 pub fn with_output(mut self, out: String) -> Self {
126 self.out = out;
127 self
128 }
129
130 /// Printer starting at a fixed indentation level (method bodies at 1).
131 pub fn with_indent(mut self, indent: usize) -> Self {
132 self.indent = indent;
133 self
134 }
135
136 pub fn into_string(mut self, body: &Stmt) -> String {
137 // The old unconditional truncate_dead_ends rebuilt (deep-cloned)
138 // the whole statement tree per method — a top profile cost. The
139 // truncation case is rare: probe read-only first.
140 if tree_has_dead_end(body) {
141 let truncated = truncate_dead_ends(body);
142 self.stmt(&truncated);
143 } else {
144 self.stmt(body);
145 }
146 self.out
147 }
148
149 /// Print an expression that appears in a boolean context; int 0/1
150 /// constants become true/false.
151 fn bool_ish_expr(x: &Expr) -> bool {
152 match x {
153 Expr::Const(ConstVal::Int(n)) => *n == 0 || *n == 1,
154 Expr::Cond { t, f, .. } => {
155 matches!(&**t, Expr::Const(ConstVal::Int(0 | 1)))
156 && matches!(&**f, Expr::Const(ConstVal::Int(0 | 1)))
157 }
158 Expr::Un {
159 op: crate::ir::expr::UnOp::Not,
160 ..
161 }
162 | Expr::InstanceOf { .. } => true,
163 Expr::Bin { op, .. } => matches!(
164 op,
165 crate::ir::expr::BinOp::Eq
166 | crate::ir::expr::BinOp::Ne
167 | crate::ir::expr::BinOp::Lt
168 | crate::ir::expr::BinOp::Ge
169 | crate::ir::expr::BinOp::Gt
170 | crate::ir::expr::BinOp::Le
171 | crate::ir::expr::BinOp::RefEq
172 | crate::ir::expr::BinOp::RefNe
173 | crate::ir::expr::BinOp::LogAnd
174 | crate::ir::expr::BinOp::LogOr
175 ),
176 other => other.type_ref().erased() == crate::types::JavaType::Boolean,
177 }
178 }
179
180 /// bool_ish_expr with VarTable resolution: booleanize retypes the vt
181 /// while embedded Local tys stay frozen, and a bitwise bin over
182 /// bool-ish sides is itself boolean (jdk26 LinkedTransferQueue
183 /// `(spin & !upc) == 0`).
184 fn bool_ish_vt(&self, x: &Expr) -> bool {
185 match x {
186 Expr::Local { var, .. } => {
187 matches!(
188 self.vt.var(*var).ty.erased(),
189 crate::types::JavaType::Boolean
190 )
191 }
192 Expr::Bin {
193 op: crate::ir::expr::BinOp::And | crate::ir::expr::BinOp::Or | crate::ir::expr::BinOp::Xor,
194 l,
195 r,
196 ..
197 } => self.bool_ish_vt(l) && self.bool_ish_vt(r),
198 _ => Self::bool_ish_expr(x),
199 }
200 }
201
202 pub fn expr_bool(&mut self, e: &Expr, out: &mut String) {
203 match e {
204 Expr::Const(ConstVal::Int(n)) => {
205 out.push_str(if *n != 0 { "true" } else { "false" });
206 }
207 // Bitwise ops over boolean-ish operands print as their
208 // logical form: javac computes `a != b` over booleans as
209 // IXOR of 0/1 ints (jdk17 DecimalFormat isNegative:
210 // `(c1 ? 1 : 0) ^ (c2 ? 1 : 0)` against a boolean local —
211 // "int无法转换为boolean").
212 Expr::Bin { op: bop, l, r, .. }
213 if matches!(
214 bop,
215 crate::ir::expr::BinOp::Xor
216 | crate::ir::expr::BinOp::And
217 | crate::ir::expr::BinOp::Or
218 ) && self.bool_ish_vt(l)
219 && self.bool_ish_vt(r) =>
220 {
221 let sym = match bop {
222 crate::ir::expr::BinOp::Xor => "^",
223 crate::ir::expr::BinOp::And => "&",
224 _ => "|",
225 };
226 // Parenthesize composite sides: expr_bool renders `||`/
227 // `&&` conditions bare and ^ binds LOOSER than && but
228 // TIGHTER than || (a || b && c ^ d would regroup).
229 for (i, side) in [l.as_ref(), r.as_ref()].iter().enumerate() {
230 if i > 0 {
231 out.push(' ');
232 out.push_str(sym);
233 out.push(' ');
234 }
235 let needs = matches!(
236 side,
237 Expr::Bin { .. } | Expr::Cond { .. } | Expr::Assign { .. }
238 );
239 if needs {
240 out.push('(');
241 }
242 self.expr_bool(side, out);
243 if needs {
244 out.push(')');
245 }
246 }
247 }
248 Expr::Cond { c, t, f } => {
249 // `x ? true : false` → x ; `x ? false : true` → !x
250 let tb = const_bool(t);
251 let fb = const_bool(f);
252 match (tb, fb) {
253 (Some(true), Some(false)) => self.expr_bool(c, out),
254 (Some(false), Some(true)) => {
255 // !(!x) collapses to x
256 if let Expr::Un {
257 op: crate::ir::expr::UnOp::Not,
258 e: inner,
259 } = &**c
260 {
261 self.expr_bool(inner, out);
262 } else {
263 out.push('!');
264 // expr_bool renders composite conditions bare:
265 // `!` + `ssign != 45` would regroup as
266 // `(!ssign) != 45` (jdk26 FloatingDecimal
267 // "一元运算符 '!' 的操作数类型int错误").
268 let needs = matches!(
269 &**c,
270 Expr::Bin { .. } | Expr::Cond { .. } | Expr::Assign { .. }
271 );
272 if needs {
273 out.push('(');
274 }
275 self.expr_bool(c, out);
276 if needs {
277 out.push(')');
278 }
279 }
280 }
281 _ => {
282 self.expr(c, 3, out);
283 out.push_str(" ? ");
284 self.expr_bool(t, out);
285 out.push_str(" : ");
286 self.expr_bool(f, out);
287 }
288 }
289 }
290 _ => self.expr(e, 1, out),
291 }
292 }
293
294 // ---------------- statements ----------------
295
296 fn line(&mut self, s: &str) {
297 self.push_indent();
298 self.out.push_str(s);
299 self.out.push('\n');
300 }
301
302 /// Indentation as one contiguous push (the per-level push_str loop
303 /// ran per printed line).
304 fn push_indent(&mut self) {
305 const UNIT: &str = " "; // 32 spaces
306 let mut n = self.indent * 4;
307 while n >= 32 {
308 self.out.push_str(UNIT);
309 n -= 32;
310 }
311 if n > 0 {
312 self.out.push_str(&UNIT[..n]);
313 }
314 }
315
316 /// Render an int-typed expression in a byte/short target: constants
317 /// and ternary branches get the narrowing cast (javac's conditional
318 /// expression only narrows when the target type is known — a return
319 /// or local-decl of byte/short with int constants needs explicit
320 /// casts: `byte normalVirtual = !if() ? (byte) 5 : (byte) 9;`).
321 pub fn expr_narrow(&mut self, e: &Expr, ty: &crate::types::JavaType, out: &mut String) {
322 self.expr_narrow_f(e, ty, false, out)
323 }
324
325 /// `force_consts`: the enclosing conditional's condition is not a
326 /// compile-time constant, so the whole expression is not a constant
327 /// expression and javac's assignment-context narrowing does NOT apply
328 /// to in-range int constants either (byte getDirectionality: `return
329 /// (ch & 0xFFFE) == 0xFFFE ? -1 : 0` — 从int转换到byte可能会有损失;
330 /// the source branches are byte static finals the bytecode inlined).
331 fn expr_narrow_f(
332 &mut self,
333 e: &Expr,
334 ty: &crate::types::JavaType,
335 force_consts: bool,
336 out: &mut String,
337 ) {
338 match e {
339 Expr::Const(ConstVal::Int(n)) => {
340 let (lo, hi) = match ty {
341 crate::types::JavaType::Byte => (-128i64, 127i64),
342 crate::types::JavaType::Short => (-32768i64, 32767i64),
343 _ => (i32::MIN as i64, i32::MAX as i64),
344 };
345 if force_consts || (*n as i64) < lo || (*n as i64) > hi {
346 out.push('(');
347 out.push_str(&self.type_name(&TypeRef::J(ty.clone())));
348 out.push_str(") ");
349 }
350 out.push_str(&n.to_string());
351 }
352 Expr::Cond { c, t, f } => {
353 let force = force_consts || !matches!(&**c, Expr::Const(_));
354 self.expr(c, 3, out);
355 out.push_str(" ? ");
356 let prev = std::mem::replace(&mut self.in_cond, true);
357 self.expr_narrow_f(t, ty, force, out);
358 out.push_str(" : ");
359 self.expr_narrow_f(f, ty, force, out);
360 self.in_cond = prev;
361 }
362 // An int-typed local (typically a branch-merge stack temp)
363 // under a byte/short target needs the explicit narrowing cast
364 // (jdk17 InvokerBytecodeGenerator `return stack61;` —
365 // 从int转换到byte可能会有损失; the merged branches carried
366 // byte values the evidence widened to int).
367 Expr::Local { var, .. } => {
368 if matches!(
369 (self.vt.var(*var).ty.erased(), ty),
370 (crate::types::JavaType::Int, crate::types::JavaType::Byte)
371 | (crate::types::JavaType::Int, crate::types::JavaType::Short)
372 ) {
373 out.push('(');
374 out.push_str(&self.type_name(&TypeRef::J(ty.clone())));
375 out.push_str(") ");
376 }
377 self.expr(e, 1, out);
378 }
379 _ => self.expr(e, 1, out),
380 }
381 }
382
383 /// Call-argument variant of expr_narrow: invocation conversion never
384 /// narrows, so in-range int constants keep the explicit cast too, and
385 /// int conditionals get it per branch.
386 fn expr_narrow_arg(&mut self, e: &Expr, ty: &crate::types::JavaType, out: &mut String) {
387 match e {
388 Expr::Const(ConstVal::Int(n)) => {
389 out.push('(');
390 out.push_str(&self.type_name(&TypeRef::J(ty.clone())));
391 out.push_str(") ");
392 out.push_str(&n.to_string());
393 }
394 Expr::Cond { c, t, f } => {
395 self.expr(c, 3, out);
396 out.push_str(" ? ");
397 let prev = std::mem::replace(&mut self.in_cond, true);
398 self.expr_narrow_arg(t, ty, out);
399 out.push_str(" : ");
400 self.expr_narrow_arg(f, ty, out);
401 self.in_cond = prev;
402 }
403 _ => self.expr(e, 1, out),
404 }
405 }
406
407 /// Render an expression in a char-typed target: int constants become
408 /// char literals, ternary branches recurse (the lossy-conversion fix
409 /// for `char name2type(..) { return c ? 63 : 105; }`).
410 pub fn expr_char(&mut self, e: &Expr, out: &mut String) {
411 match e {
412 Expr::Const(ConstVal::Int(n)) => push_char_lit(out, *n),
413 Expr::Cond { c, t, f } => {
414 self.expr(c, 3, out);
415 out.push_str(" ? ");
416 let prev = std::mem::replace(&mut self.in_cond, true);
417 self.expr_char(t, out);
418 out.push_str(" : ");
419 self.expr_char(f, out);
420 self.in_cond = prev;
421 }
422 _ => self.expr(e, 1, out),
423 }
424 }
425
426 fn stmt(&mut self, s: &Stmt) {
427 match s {
428 Stmt::Block(v) => {
429 for x in v {
430 self.stmt(x);
431 }
432 }
433 Stmt::ExprStmt(e) => {
434 if !is_statement_expr(e) {
435 // Only assignments/calls/inc-dec/new are legal
436 // expression statements; a leftover pure concat
437 // (`"" + 48;`) renders as a comment instead.
438 let mut text = String::new();
439 self.expr(e, 0, &mut text);
440 self.line(&format!("/* {text}; */"));
441 return;
442 }
443 let mut line = std::mem::take(&mut self.line_buf);
444 line.clear();
445 if matches!(e, Expr::Method { .. }) {
446 self.suppress_poly_cast = true;
447 }
448 self.expr(e, 0, &mut line);
449 self.suppress_poly_cast = false;
450 line.push(';');
451 self.line(&line);
452 self.line_buf = line;
453 }
454 Stmt::LocalDef {
455 var,
456 init,
457 is_final,
458 force_type,
459 } => {
460 let _ = force_type;
461 let info = self.vt.var(*var);
462 let mut line = std::mem::take(&mut self.line_buf);
463 line.clear();
464 if *is_final {
465 line.push_str("final ");
466 }
467 line.push_str(&self.local_decl_ty(*var, &info.ty));
468 line.push(' ');
469 line.push_str(&java_ident(&info.name));
470 if let Some(e) = init {
471 line.push_str(" = ");
472 // A lambda initializing a generically-typed SAM local:
473 // the declared type is the only record of the
474 // instantiated SAM return (the indy's
475 // instantiatedMethodType is erased — IntFunction<T[]>
476 // records (I)[Object), so prime the lambda-body cast
477 // from it (jdk11 ForEachOps: `IntFunction<T[]>
478 // generator = size -> (T[]) new Object[size]` —
479 // without the cast "lambda 表达式中的返回类型错误").
480 if let (TypeRef::G(g @ crate::types::GenericType::Class(_)), Expr::Lambda(l)) =
481 (&info.ty, e)
482 {
483 if self.lambda_sam_ret.is_none() {
484 self.lambda_sam_ret = self.sam_ret_cast(g, &l.sam_name);
485 }
486 }
487 // Assigning a concrete type to a type-variable local
488 // needs the (erased-away) cast back in source form.
489 let need_cast =
490 matches!(&info.ty, TypeRef::G(crate::types::GenericType::TypeVar(_)))
491 && e.type_ref() != info.ty;
492 if need_cast {
493 line.push('(');
494 line.push_str(&self.type_name(&info.ty));
495 line.push_str(") ");
496 self.expr(e, 14, &mut line);
497 } else if info.ty.erased() == crate::types::JavaType::Boolean {
498 self.expr_bool(e, &mut line);
499 } else if info.ty.erased() == crate::types::JavaType::Char {
500 self.expr_char(e, &mut line);
501 } else if info.ty.erased() == crate::types::JavaType::Byte
502 || info.ty.erased() == crate::types::JavaType::Short
503 {
504 let t = info.ty.erased();
505 self.expr_narrow(e, &t, &mut line);
506 } else {
507 self.expr(e, 1, &mut line);
508 }
509 }
510 line.push(';');
511 self.line(&line);
512 self.line_buf = line;
513 }
514 Stmt::Return(Some(e)) => {
515 let mut line = String::from("return ");
516 if self.ret_bool {
517 self.expr_bool(e, &mut line);
518 } else if self.ret_char {
519 self.expr_char(e, &mut line);
520 } else if self.ret_byte {
521 self.expr_narrow(e, &crate::types::JavaType::Byte, &mut line);
522 } else if self.ret_short {
523 self.expr_narrow(e, &crate::types::JavaType::Short, &mut line);
524 } else {
525 // Lambda in return position of a generic method: the
526 // SAM return witnesses the erased body value
527 // (castingIdentity `return i -> (R) i`, toArray
528 // `() -> (T[]) new Object[]{..}` — jdk17 Collectors).
529 if let (Some(TypeRef::G(g)), Expr::Lambda(l)) = (&self.ret_sam, e) {
530 self.lambda_sam_ret = match g {
531 crate::types::GenericType::Class(_) => {
532 self.sam_ret_cast(g, &l.sam_name)
533 }
534 crate::types::GenericType::Array(_) => Some(TypeRef::G(g.clone())),
535 _ => None,
536 };
537 }
538 self.expr(e, 1, &mut line);
539 self.lambda_sam_ret = None;
540 }
541 line.push(';');
542 self.line(&line);
543 }
544 Stmt::Return(None) => self.line("return;"),
545 Stmt::Throw(e) => {
546 let mut line = String::from("throw ");
547 self.expr(e, 1, &mut line);
548 line.push(';');
549 self.line(&line);
550 }
551 Stmt::If {
552 cond,
553 then_stmt,
554 else_stmt,
555 } => {
556 self.print_if(cond, then_stmt, else_stmt.as_deref(), "");
557 }
558 Stmt::While { cond, body } => {
559 let mut head = String::from("while (");
560 if matches!(cond, Expr::Const(ConstVal::Int(1))) {
561 head.push_str("true");
562 } else {
563 self.expr_bool(cond, &mut head);
564 }
565 head.push(')');
566 self.block_stmt(&head, body);
567 }
568 Stmt::DoWhile { body, cond } => {
569 self.line("do {");
570 self.indent += 1;
571 self.stmt(body);
572 self.indent -= 1;
573 let mut tail = String::from("} while (");
574 self.expr_bool(cond, &mut tail);
575 tail.push_str(");");
576 self.line(&tail);
577 }
578 Stmt::For {
579 init,
580 cond,
581 update,
582 body,
583 } => {
584 let mut head = String::from("for (");
585 for (i, s) in init.iter().enumerate() {
586 if i > 0 {
587 head.push_str(", ");
588 }
589 self.stmt_inline(s, &mut head);
590 }
591 head.push_str("; ");
592 if let Some(c) = cond {
593 self.expr_bool(c, &mut head);
594 }
595 head.push_str("; ");
596 for (i, u) in update.iter().enumerate() {
597 if i > 0 {
598 head.push_str(", ");
599 }
600 self.expr(u, 1, &mut head);
601 }
602 head.push(')');
603 self.block_stmt(&head, body);
604 }
605 Stmt::ForEach {
606 var,
607 iterable,
608 is_array,
609 body,
610 } => {
611 let _ = is_array;
612 let info = self.vt.var(*var);
613 let mut head = String::from("for (");
614 head.push_str(&self.type_name(&info.ty));
615 head.push(' ');
616 head.push_str(&java_ident(&info.name));
617 head.push_str(" : ");
618 self.expr(iterable, 1, &mut head);
619 head.push(')');
620 self.block_stmt(&head, body);
621 }
622 Stmt::Switch {
623 selector,
624 cases,
625 default,
626 on_string,
627 } => {
628 let _ = on_string;
629 let mut head = String::from("switch (");
630 self.expr(selector, 1, &mut head);
631 head.push(')');
632 self.line(&format!("{} {{", head));
633 self.indent += 1;
634 for c in cases {
635 // MULTIPLE pattern labels in one arm cannot share a
636 // colon body: neither stacked (`case P1 x:` `case P2
637 // y:` — 贯穿到模式非法) nor comma-joined (`case P1 x,
638 // P2 y:` — javac still demands a break between pattern
639 // labels) compile. Duplicate the body per label — the
640 // source's arrow-form multi-pattern arm
641 // (`case A _, B _ -> r;`, jdk26 ParserVerifier
642 // valueSize) in colon form.
643 if c.raw_labels.len() > 1
644 && c.labels.is_empty()
645 && c.string_labels.is_empty()
646 && c.enum_labels.is_empty()
647 {
648 for l in &c.raw_labels {
649 match &c.guard {
650 Some(g) => {
651 let mut gtxt = String::new();
652 self.expr(g, 1, &mut gtxt);
653 self.line(&format!("case {} when {}:", l, gtxt));
654 }
655 None => self.line(&format!("case {}:", l)),
656 }
657 self.line("{");
658 self.indent += 1;
659 for st in &c.body {
660 self.stmt(st);
661 }
662 self.indent -= 1;
663 self.line("}");
664 }
665 continue;
666 }
667 for l in &c.enum_labels {
668 self.line(&format!("case {}:", l));
669 }
670 if let Some(g) = &c.guard {
671 // Folded pattern guard: the desugared restart shape
672 // (`if (guard) body else { state=N; continue; }`)
673 // restored to `case T v when guard:` (jdk26
674 // DecimalFormat — duplicate type-pattern labels are
675 // "此 case 标签由前一个 case 标签支配" without it).
676 let mut gtxt = String::new();
677 self.expr(g, 1, &mut gtxt);
678 let joined = c
679 .raw_labels
680 .iter()
681 .map(|l| format!("{} when {}", l, gtxt))
682 .collect::<Vec<_>>()
683 .join(", ");
684 self.line(&format!("case {}:", joined));
685 } else if !c.raw_labels.is_empty() {
686 // Pattern labels of one arm MUST be comma-joined:
687 // stacked `case P1 x:` / `case P2 y:` lines are a
688 // fall-through between patterns ("从模式贯穿非法",
689 // jdk26 ParserVerifier `case OfConstant _,
690 // OfClass _ -> 2`).
691 self.line(&format!("case {}:", c.raw_labels.join(", ")));
692 }
693 for l in &c.labels {
694 self.line(&format!("case {}:", l));
695 }
696 for l in &c.string_labels {
697 self.line(&format!("case \"{}\":", escape_string(l)));
698 }
699 // Braces give each case its own scope: pattern-switch
700 // restorations reuse binding names across cases.
701 self.line("{");
702 self.indent += 1;
703 for st in &c.body {
704 self.stmt(st);
705 }
706 self.indent -= 1;
707 self.line("}");
708 }
709 if let Some(d) = default {
710 self.line("default:");
711 self.indent += 1;
712 self.stmt(d);
713 self.indent -= 1;
714 }
715 self.indent -= 1;
716 self.line("}");
717 }
718 Stmt::Try {
719 body,
720 catches,
721 finally,
722 } => {
723 self.line("try {");
724 self.indent += 1;
725 self.stmt(body);
726 self.indent -= 1;
727 for c in catches {
728 let exc_name = if c.exc.is_empty() {
729 "Throwable".to_string()
730 } else {
731 c.exc
732 .iter()
733 .map(|e| self.shorten(e))
734 .collect::<Vec<_>>()
735 .join(" | ")
736 };
737 let var_name = if c.var == u32::MAX {
738 "ignored".to_string()
739 } else {
740 c.var_name
741 .clone()
742 .unwrap_or_else(|| self.vt.var(c.var).name.clone())
743 };
744 self.line(&format!("}} catch ({} {}) {{", exc_name, var_name));
745 self.indent += 1;
746 self.stmt(&c.body);
747 self.indent -= 1;
748 }
749 if let Some(f) = finally {
750 self.line("} finally {");
751 self.indent += 1;
752 self.stmt(f);
753 self.indent -= 1;
754 }
755 // A try with no catches and no finally is not valid Java
756 // (the structurer can emit a bare degenerate try); an
757 // empty finally keeps the statement compilable.
758 if catches.is_empty() && finally.is_none() {
759 self.line("} finally {");
760 }
761 self.line("}");
762 }
763 Stmt::TryWithResources {
764 resources,
765 body,
766 catches,
767 finally,
768 } => {
769 let mut res: Vec<String> = Vec::new();
770 for r in resources {
771 if let Stmt::LocalDef { var, init, .. } = r {
772 let info = self.vt.var(*var);
773 let mut t = String::new();
774 t.push_str(&self.type_name(&info.ty));
775 t.push(' ');
776 t.push_str(&java_ident(&info.name));
777 if let Some(e) = init {
778 t.push_str(" = ");
779 self.expr(e, 1, &mut t);
780 }
781 res.push(t);
782 } else {
783 // The resource is not a declaration: a reused
784 // variable was rewritten to a plain assignment by
785 // dedupe_declarations, and `try (x = expr)` is not
786 // Java. Print it as the statement it is, BEFORE the
787 // try (resource init runs before the body anyway) —
788 // emitting nothing turned the header into the
789 // illegal `try () {`.
790 self.stmt(r);
791 }
792 }
793 if res.is_empty() && catches.is_empty() && finally.is_none() {
794 // Vestigial try-with-resources: the resource was
795 // hoisted into a plain assignment above and its close
796 // is a sibling statement, so nothing is left to head
797 // the statement — a bare `try {` does not compile
798 // ("'try' without 'catch', 'finally' or resource
799 // declarations"). A handler-less try IS a block.
800 self.line("{");
801 } else if res.is_empty() {
802 self.line("try {");
803 } else {
804 self.line(&format!("try ({}) {{", res.join("; ")));
805 }
806 self.indent += 1;
807 self.stmt(body);
808 self.indent -= 1;
809 for c in catches {
810 let exc_name = if c.exc.is_empty() {
811 "Throwable".to_string()
812 } else {
813 c.exc
814 .iter()
815 .map(|e| self.shorten(e))
816 .collect::<Vec<_>>()
817 .join(" | ")
818 };
819 let var_name = if c.var == u32::MAX {
820 "ignored".to_string()
821 } else {
822 c.var_name
823 .clone()
824 .unwrap_or_else(|| self.vt.var(c.var).name.clone())
825 };
826 self.line(&format!("}} catch ({} {}) {{", exc_name, var_name));
827 self.indent += 1;
828 self.stmt(&c.body);
829 self.indent -= 1;
830 }
831 if let Some(f) = finally {
832 self.line("} finally {");
833 self.indent += 1;
834 self.stmt(f);
835 self.indent -= 1;
836 }
837 self.line("}");
838 }
839 Stmt::Assert { cond, msg } => {
840 let mut line = String::from("assert ");
841 // The assert-condition is boolean-typed: the synthesized
842 // `assert false` carries Const Int(0) — plain expr would
843 // print `assert 0` (int无法转换为boolean, jdk17 URI).
844 self.expr_bool(cond, &mut line);
845 if let Some(m) = msg {
846 line.push_str(" : ");
847 self.expr(m, 1, &mut line);
848 }
849 line.push(';');
850 self.line(&line);
851 }
852 Stmt::Synchronized { lock, body } => {
853 let mut head = String::from("synchronized (");
854 self.expr(lock, 1, &mut head);
855 head.push(')');
856 self.block_stmt(&head, body);
857 }
858 Stmt::TernaryValue { e } => {
859 let mut line = String::from("/* ternary */ ");
860 self.expr(e, 1, &mut line);
861 line.push(';');
862 self.line(&line);
863 }
864 Stmt::MonitorEnter(e) => {
865 // The lock expression goes INSIDE the comment: a bare
866 // local/field is not a valid expression statement
867 // (`/* monitorenter */ a;` failed javac).
868 let mut line = String::from("/* monitorenter ");
869 self.expr(e, 1, &mut line);
870 line.push_str(" */");
871 self.line(&line);
872 }
873 Stmt::MonitorExit(e) => {
874 let mut line = String::from("/* monitorexit ");
875 self.expr(e, 1, &mut line);
876 line.push_str(" */");
877 self.line(&line);
878 }
879 Stmt::ClassDecl { header, body, .. } => {
880 let kw = if header.starts_with("record ")
881 || header.starts_with("interface ")
882 || header.starts_with("enum ")
883 {
884 String::new()
885 } else {
886 "class ".to_string()
887 };
888 self.line(&format!("{}{} {{", kw, header));
889 for line in body.lines() {
890 if line.trim().is_empty() {
891 self.out.push('\n');
892 } else {
893 for _ in 0..self.indent + 1 {
894 self.out.push_str(" ");
895 }
896 self.out.push_str(line.trim_start());
897 self.out.push('\n');
898 }
899 }
900 self.line("}");
901 }
902 Stmt::Labeled { label, body } => {
903 // label prefix on the following statement's line
904 let save = self.out.len();
905 self.stmt(body);
906 // prepend "label: " before the first line just emitted
907 let indent_str = " ".repeat(self.indent);
908 let emitted = self.out[save..].to_string();
909 self.out.truncate(save);
910 if let Some(rest) = emitted.strip_prefix(&indent_str) {
911 self.out.push_str(&indent_str);
912 self.out.push_str(label);
913 self.out.push_str(": ");
914 self.out.push_str(rest);
915 } else {
916 self.out.push_str(&emitted);
917 }
918 }
919 Stmt::Break(lbl) => match lbl {
920 Some(l) => self.line(&format!("break {};", l)),
921 None => self.line("break;"),
922 },
923 Stmt::Continue(lbl) => match lbl {
924 Some(l) => self.line(&format!("continue {};", l)),
925 None => self.line("continue;"),
926 },
927 Stmt::Label(id) => {
928 // Emitted by breaking the following statement; standalone
929 // labels are invalid before non-statements, so use a marker.
930 self.line(&format!("L{}: ;", id));
931 }
932 Stmt::Goto(id) => self.line(&format!("break L{};", id)),
933 Stmt::Comment(c) => self.line(&format!("// {}", c)),
934 }
935 }
936
937 /// `head {` + body + `}`.
938 fn block_stmt(&mut self, head: &str, body: &Stmt) {
939 self.line(&format!("{} {{", head));
940 self.indent += 1;
941 self.stmt(body);
942 self.indent -= 1;
943 self.line("}");
944 }
945
946 /// Print an if statement; `opener` is "" for the first if and
947 /// "} else " when continuing an else-if chain on the closing line.
948 fn print_if(&mut self, cond: &Expr, then_stmt: &Stmt, else_stmt: Option<&Stmt>, opener: &str) {
949 let mut head = String::from(opener);
950 head.push_str("if (");
951 self.expr_bool(cond, &mut head);
952 head.push_str(") {");
953 self.line(&head);
954 self.indent += 1;
955 self.stmt(then_stmt);
956 self.indent -= 1;
957 match else_stmt {
958 Some(Stmt::If {
959 cond: c2,
960 then_stmt: t2,
961 else_stmt: e2,
962 }) => {
963 self.print_if(c2, t2, e2.as_deref(), "} else ");
964 }
965 Some(other) => {
966 self.line("} else {");
967 self.indent += 1;
968 self.stmt(other);
969 self.indent -= 1;
970 self.line("}");
971 }
972 None => self.line("}"),
973 }
974 }
975
976 fn stmt_inline(&self, s: &Stmt, out: &mut String) {
977 match s {
978 Stmt::ExprStmt(e) => {
979 let mut p = self.sub();
980 if matches!(e, Expr::Method { .. }) {
981 p.suppress_poly_cast = true;
982 }
983 p.expr(e, 1, out);
984 }
985 Stmt::LocalDef { var, init, .. } => {
986 let info = self.vt.var(*var);
987 out.push_str(&self.local_decl_ty(*var, &info.ty));
988 out.push(' ');
989 out.push_str(&java_ident(&info.name));
990 if let Some(e) = init {
991 out.push_str(" = ");
992 let mut p = self.sub();
993 p.expr(e, 1, out);
994 }
995 }
996 other => {
997 let mut p = self.sub();
998 p.stmt(other);
999 out.push_str(p.out.trim_end());
1000 }
1001 }
1002 }
1003
1004 fn sub(&self) -> Printer<'a> {
1005 Printer {
1006 ctx: self.ctx,
1007 vt: self.vt,
1008 out: String::new(),
1009 line_buf: String::new(),
1010 indent: 0,
1011 lambda_depth: self.lambda_depth,
1012 outer_names: self.outer_names.clone(),
1013 ret_bool: self.ret_bool,
1014 ret_char: self.ret_char,
1015 ret_byte: self.ret_byte,
1016 ret_short: self.ret_short,
1017 suppress_poly_cast: self.suppress_poly_cast,
1018 suppress_diamond: self.suppress_diamond,
1019 in_cond: self.in_cond,
1020 lambda_sam_ret: self.lambda_sam_ret.clone(),
1021 ret_sam: self.ret_sam.clone(),
1022 twin_cache: self.twin_cache.clone(),
1023 }
1024 }
1025
1026 // ---------------- expressions ----------------
1027
1028 pub fn expr(&mut self, e: &Expr, outer_prec: u8, out: &mut String) {
1029 let parens = e.needs_parens(outer_prec, false);
1030 if parens {
1031 out.push('(');
1032 }
1033 match e {
1034 Expr::Const(c) => self.const_val(c, out),
1035 Expr::Raw(t) if t == "\u{3}" => out.push_str("this"),
1036 Expr::Raw(t) => out.push_str(t),
1037 Expr::RawT(t, _) => out.push_str(t),
1038 Expr::Local { var, .. } => {
1039 let info = self.vt.var(*var);
1040 // The receiver var carries the dex debug name "this" —
1041 // reads render as the keyword (legal in expression
1042 // position, and it is never declared).
1043 if info.name == "this" {
1044 out.push_str("this");
1045 } else {
1046 out.push_str(&java_ident(&info.name));
1047 }
1048 }
1049 Expr::This => out.push_str("this"),
1050 Expr::New { cls, args, ty, .. } => {
1051 let no_diamond = self.suppress_diamond;
1052 self.suppress_diamond = false;
1053 // Explicit type arguments pinned by the AST passes
1054 // (diamond_explicit_args_from_call_args): print them
1055 // instead of the diamond.
1056 let explicit = match ty {
1057 TypeRef::G(crate::types::GenericType::Class(cs))
1058 if cs.parts.last().map(|p| !p.args.is_empty()).unwrap_or(false)
1059 && crate::typeutil::classsig_internal(cs).as_str() == cls.as_ref() =>
1060 {
1061 let rendered: Vec<String> = cs
1062 .parts
1063 .last()
1064 .unwrap()
1065 .args
1066 .iter()
1067 .map(|a| self.generic_name(a))
1068 .collect();
1069 Some(format!("<{}>", rendered.join(", ")))
1070 }
1071 _ => None,
1072 };
1073 // A wildcard-typed ctor argument dooms diamond inference
1074 // (the inference variable gets an equality constraint from
1075 // the capture and a different bound from the context —
1076 // "cannot infer type arguments for Entry<>", jdk11
1077 // Hashtable `tab[i] = new Entry<>(hash, key, value, e)`
1078 // with e: Entry<?,?>; the source declared e as Entry<K,V>
1079 // via an unchecked cast that leaves no bytecode trace).
1080 // The raw form is always applicable (unchecked).
1081 let args_wildcard = args.iter().any(|a| {
1082 matches!(a.type_ref(), TypeRef::G(ref g)
1083 if crate::typeutil::g_has_wildcard(g))
1084 });
1085 let diamond: std::borrow::Cow<str> = match &explicit {
1086 Some(x) => x.as_str().into(),
1087 // Pre-Java-8 conditional branches are not poly
1088 // expressions: the diamond cannot see the assignment
1089 // target through the `?:` and infers <Object,Object>
1090 // (jdk7 HashSet.readObject map-ternary 不兼容的类型).
1091 // Raw is unchecked but always compilable.
1092 None if no_diamond
1093 || args_wildcard
1094 || (self.in_cond && self.ctx.source_level() < 52) =>
1095 {
1096 "".into()
1097 }
1098 None => self.diamond_for(cls).into(),
1099 };
1100 let diamond = diamond.as_ref();
1101 if let Some(local) = cls.strip_prefix('\u{2}') {
1102 out.push_str("new ");
1103 out.push_str(local);
1104 out.push_str(diamond);
1105 out.push('(');
1106 // Local records/classes keep bytecode-arity ctors:
1107 // typed rendering turns an int constant into the
1108 // char/boolean literal the ctor expects (jdk17
1109 // MessageFormat `new Qchar(39, quoted)`).
1110 // Nested local-class method printing runs under a
1111 // TAKEN LOCAL_CLASS_INTERNALS registry — resolve a
1112 // self-construction (`new BitSetSpliterator(..)` inside
1113 // BitSetSpliterator.trySplit) through the printer's
1114 // own class when the registry misses. Local-class
1115 // ctors capture the enclosing instance as a leading
1116 // param that the printed new-site omits; the this$0
1117 // fallback in ctor_param_types lines the formals up
1118 // (jdk11 BitSet$1BitSetSpliterator(BitSet,int,int,int,
1119 // boolean) — the boolean formal rendered its arg `0`).
1120 let internal = self.ctx.local_class_internal(local).or_else(|| {
1121 let tail = self.ctx.class_name().rsplit('$').next().unwrap_or("");
1122 let simple = tail.trim_start_matches(|c: char| c.is_ascii_digit());
1123 if !simple.is_empty() && simple == local {
1124 return Some(self.ctx.class_name().to_string());
1125 }
1126 // Probe the nested-name space of the printing
1127 // context (javac numbers method-local classes
1128 // `$1Name`): the new-site needs the real class to
1129 // resolve its ctor formals.
1130 let base = format!("{}${}", self.ctx.class_name(), local);
1131 if self.ctx.has_class(&base) {
1132 return Some(base);
1133 }
1134 for d in 0..10 {
1135 let cand = format!("{}${}{}", self.ctx.class_name(), d, local);
1136 if self.ctx.has_class(&cand) {
1137 return Some(cand);
1138 }
1139 }
1140 None
1141 });
1142 match internal
1143 .as_deref()
1144 .and_then(|i| self.ctor_param_types(i, 0, args.len(), args))
1145 {
1146 Some(pt) => self.args_typed(args, &pt, out),
1147 None => self.args(args, out),
1148 }
1149 out.push(')');
1150 } else if self.is_member_inner(cls)
1151 && !args.is_empty()
1152 && !matches!(args[0], Expr::This)
1153 && !is_this_expr(&args[0])
1154 // A constant (null/0) as the first argument is a real
1155 // parameter value, not the synthetic outer instance —
1156 // `0.new a()` / `null.new a()` are not valid Java.
1157 && !matches!(args[0], Expr::Const(_))
1158 // Anonymous classes (digit tails) are emitted as
1159 // separate top-level-ish classes — a qualified
1160 // `this.new 1(...)` is not valid Java; the flat
1161 // `new Outer$1(...)` form is. Test the RAW tail:
1162 // inner_simple already sanitizes a leading digit to
1163 // `_`, which silently bypassed this guard and emitted
1164 // `this.new _1(..)` against a flat top-level
1165 // declaration (weibo HorseRaceDetector$4 family).
1166 && !cls
1167 .rsplit('$')
1168 .next()
1169 .map(|s| !s.is_empty() && s.chars().all(|c| c.is_ascii_digit()))
1170 .unwrap_or(false)
1171 {
1172 // `outerExpr.new Inner(rest...)` — the first ctor arg
1173 // is the synthetic outer instance when the class has a
1174 // this$0 field, or when the ctor forwards it straight
1175 // to super (no field of its own; is_member_inner
1176 // detected it). Either way the qualified-new form
1177 // supplies the outer implicitly, so skip args[0].
1178 self.expr(&args[0], 15, out);
1179 out.push_str(".new ");
1180 out.push_str(&inner_simple(cls));
1181 out.push_str(diamond);
1182 out.push('(');
1183 match self.ctor_param_types(cls, 1, args.len() - 1, &args[1..]) {
1184 Some(pt) => self.args_typed(&args[1..], &pt, out),
1185 None => self.args(&args[1..], out),
1186 }
1187 out.push(')');
1188 } else {
1189 out.push_str("new ");
1190 let digit_tail = cls
1191 .rsplit('$')
1192 .next()
1193 .map(|s| !s.is_empty() && s.chars().all(|c| c.is_ascii_digit()))
1194 .unwrap_or(false);
1195 // A digit-tail class (anonymous `Outer$1`, d8 lambda)
1196 // is rendered as a SEPARATE top-level file declaring
1197 // `class Outer$1` with the outer instance as an
1198 // ordinary ctor parameter — the ctor signature keeps
1199 // that parameter (the inner-ctor strip excludes
1200 // digit tails for exactly this shape). The member
1201 // form `this.new _1(..)` would look for a member of
1202 // the enclosing class that does not exist there —
1203 // the flat `new Outer$1(outer, ..)` with ALL args is
1204 // the shape that matches the declaration.
1205 let member_this = self.is_member_inner(cls)
1206 && !digit_tail
1207 && !args.is_empty()
1208 && matches!(args[0], Expr::This);
1209 let shown = if member_this {
1210 // `new Inner()` from inside the outer class
1211 inner_simple(cls)
1212 } else if digit_tail {
1213 // Digit-suffixed class at a `new` site: a d8/R8-
1214 // desugared lambda (`...$lambda$N$$inlined$log$1`,
1215 // `Outer$$ExternalSyntheticLambda0`) or an anonymous
1216 // class (`Outer$1`). Both are concrete, pool-rendered
1217 // classes, so print the real binary name — NOT the
1218 // abstract SAM interface `shorten` falls back to
1219 // (which yields an invalid `new Iface(..)`: "a是抽象
1220 // 的; 无法实例化", the biggest abstract-instantiation
1221 // family on Kotlin corpora, v6.a/l/p = Function0/1/2).
1222 // Type positions keep `shorten` (interface), so the
1223 // declaration stays phi-compatible across branches
1224 // that mint different lambdas: `Iface v = new C(..)`.
1225 self.shorten_concrete(cls)
1226 } else {
1227 self.shorten(cls)
1228 };
1229 out.push_str(&shown);
1230 out.push_str(diamond);
1231 out.push('(');
1232 if member_this {
1233 match self.ctor_param_types(cls, 1, args.len() - 1, &args[1..]) {
1234 Some(pt) => self.args_typed(&args[1..], &pt, out),
1235 None => self.args(&args[1..], out),
1236 }
1237 } else {
1238 // Pinned generic instantiation: prime lambda args
1239 // from the ctor's instantiated Signature formals.
1240 let sam_rets: Vec<Option<TypeRef>> = match ty {
1241 TypeRef::G(crate::types::GenericType::Class(cs))
1242 if explicit.is_some()
1243 && crate::typeutil::classsig_internal(cs).as_str() == cls.as_ref() =>
1244 {
1245 let pinned = cs.parts.last().map(|p| p.args.clone());
1246 let formals = self.ctx.ctor_formals_by_arity(cls, args.len());
1247 // The raw ctor formals carry the class's
1248 // own typevars — substitute the pinned
1249 // instantiation before reading the SAM
1250 // return (an unsubstituted R printed `(R)`
1251 // — 找不到符号).
1252 let class_params = self.ctx.class_type_params(cls);
1253 args.iter()
1254 .enumerate()
1255 .map(|(i, a)| {
1256 if !matches!(a, Expr::Lambda(_)) {
1257 return None;
1258 }
1259 let f = formals.as_ref()?.get(i)?;
1260 let pinned = pinned.as_ref()?;
1261 if pinned.len() != class_params.len() {
1262 return None;
1263 }
1264 let inst = crate::typeutil::subst_typevars(
1265 f,
1266 &class_params,
1267 pinned,
1268 );
1269 let Expr::Lambda(l) = a else { return None };
1270 self.sam_ret_cast(&inst, &l.sam_name)
1271 })
1272 .collect()
1273 }
1274 _ => Vec::new(),
1275 };
1276 match self.ctor_param_types(cls, 0, args.len(), args) {
1277 Some(pt) => self.args_typed_sam(args, &pt, &sam_rets, out),
1278 None => self.args(args, out),
1279 }
1280 }
1281 out.push(')');
1282 }
1283 }
1284 Expr::NewArray {
1285 elem,
1286 dims,
1287 trailing_dims,
1288 init,
1289 } => {
1290 out.push_str("new ");
1291 out.push_str(&self.type_name(elem));
1292 match init {
1293 Some(vals) => {
1294 for _ in 0..=*trailing_dims {
1295 out.push_str("[]");
1296 }
1297 out.push_str(" {");
1298 // boolean[]/char[] initializers hold int constants
1299 // in bytecode (iconst + bastore/castore): render
1300 // them as true/false and char literals (jdk11
1301 // PKCS9Attribute `new boolean[] {0, 0, 1, ..}` x18).
1302 let elem_bool = elem.erased() == crate::types::JavaType::Boolean;
1303 let elem_char = elem.erased() == crate::types::JavaType::Char;
1304 for (i, v) in vals.iter().enumerate() {
1305 if i > 0 {
1306 out.push_str(", ");
1307 }
1308 if elem_bool {
1309 self.expr_bool(v, out);
1310 } else if elem_char {
1311 self.expr_char(v, out);
1312 } else {
1313 self.expr(v, 1, out);
1314 }
1315 }
1316 out.push('}');
1317 }
1318 None => {
1319 for d in dims {
1320 out.push('[');
1321 self.expr(d, 1, out);
1322 out.push(']');
1323 }
1324 for _ in 0..*trailing_dims {
1325 out.push_str("[]");
1326 }
1327 }
1328 }
1329 }
1330 Expr::NewMultiArray { ty, dims } => {
1331 // Strip the dims.len() leading array levels from the type.
1332 let mut base = ty.erased();
1333 for _ in 0..dims.len() {
1334 match base {
1335 crate::types::JavaType::Array(inner) => base = *inner,
1336 _ => break,
1337 }
1338 }
1339 out.push_str("new ");
1340 out.push_str(&self.type_name(&TypeRef::J(base)));
1341 for d in dims {
1342 out.push('[');
1343 self.expr(d, 1, out);
1344 out.push(']');
1345 }
1346 }
1347 Expr::Field {
1348 owner,
1349 cls,
1350 name,
1351 is_static,
1352 ..
1353 } => {
1354 // javac reserves `$assertionsDisabled`; the declaration and
1355 // all references are emitted under a private alias.
1356 let name: &str = if name.as_ref() == "$assertionsDisabled" {
1357 crate::analysis::ASSERT_FIELD
1358 } else {
1359 name
1360 };
1361 if name == crate::analysis::ASSERT_FIELD {
1362 // Assert guards print bare: the field is declared on
1363 // the enclosing emitted class or inlined body; the
1364 // bytecode owner is often a synthetic holder class
1365 // (`ConstantGroup$1`) whose shorten() fallback is
1366 // `Object` — a qualified read can never resolve.
1367 out.push_str(&java_ident(name));
1368 } else if name == "length" && cls.is_empty() {
1369 if let Some(o) = owner {
1370 if let Expr::Const(_) = o.as_ref() {
1371 // Receiver tracking lost: `0.length` is not
1372 // parseable — cast-null array form.
1373 out.push_str("((java.lang.Object[]) null)");
1374 } else {
1375 self.expr(o, 15, out);
1376 }
1377 }
1378 out.push_str(".length");
1379 } else if let Some(o) = owner {
1380 if let Expr::Const(cv) = o.as_ref() {
1381 if matches!(
1382 cv,
1383 crate::ir::expr::ConstVal::Str(_)
1384 | crate::ir::expr::ConstVal::ClassLit(_)
1385 ) {
1386 // Literal receivers keep their value (see the
1387 // Method arm): `Foo.class.name`, not
1388 // `((Class) null).name`.
1389 self.expr(o, 15, out);
1390 out.push('.');
1391 } else {
1392 // Receiver tracking lost (a null placeholder leaked
1393 // through): `0.b` is not parseable — the same
1394 // cast-null form the Method arm uses.
1395 out.push_str("((");
1396 out.push_str(&self.shorten(cls));
1397 out.push_str(") null).");
1398 }
1399 out.push_str(&java_ident(name));
1400 } else if matches!(o.as_ref(), Expr::Raw(t) if t == "\u{3}") {
1401 // Outer anonymous class member: unqualified lexical
1402 // resolution (an anon outer has no nameable this).
1403 out.push_str(&java_ident(name));
1404 } else if self.private_super_field(cls, name, o.as_ref()) {
1405 // A private field of a SUPERCLASS is not inherited
1406 // into the subclass's member scope: `this.algorithm`
1407 // inside Delegate (extends MessageDigest, algorithm
1408 // private) is "private access" even though the
1409 // source comment says it all — jdk11
1410 // MessageDigest.Delegate.clone casts
1411 // `((MessageDigest)this).algorithm`.
1412 out.push_str("((");
1413 out.push_str(&self.shorten(cls));
1414 out.push_str(") ");
1415 if matches!(o.as_ref(), Expr::This) {
1416 out.push_str("this");
1417 } else {
1418 self.expr(o, 14, out);
1419 }
1420 out.push_str(").");
1421 out.push_str(&java_ident(name));
1422 } else if match o.type_ref().erased() {
1423 crate::types::JavaType::Object(ref n) => {
1424 n.as_ref() != cls.as_ref()
1425 && !matches!(o.as_ref(), Expr::Raw(_) | Expr::RawT(..) | Expr::This)
1426 && (n.as_ref() == "java/lang/Object"
1427 || self.ctx.is_subtype_of(
1428 &crate::types::JavaType::Object(
1429 std::sync::Arc::from(cls.as_ref()),
1430 ),
1431 n,
1432 ))
1433 }
1434 _ => false,
1435 } {
1436 // The receiver's static type is a strict SUPERTYPE
1437 // of the declaring class: either widened to Object
1438 // (when-materialization residue) or a register
1439 // reuse the vt kept at the supertype (`v9.label`
1440 // with v9: ContinuationImpl, label on the suspend
1441 // frame subclass — weibo coroutine cluster ×1.3k).
1442 // The downcast is what the bytecode's field
1443 // resolution did; same honest cast as the Method
1444 // arm.
1445 out.push_str("((");
1446 out.push_str(&self.shorten(cls));
1447 out.push_str(") ");
1448 self.expr(o, 14, out);
1449 out.push_str(").");
1450 out.push_str(&java_ident(name));
1451 } else {
1452 self.expr(o, 15, out);
1453 out.push('.');
1454 out.push_str(&java_ident(name));
1455 }
1456 } else if *is_static {
1457 // A same-class static read prints bare — unless a local
1458 // of this method shadows the field name: javac resolves
1459 // the bare name as the LOCAL (jdk17 Socket.setImpl's
1460 // `SocketImplFactory factory = factory;` self-reference
1461 // — 可能尚未初始化变量factory x2 sites). Qualify with the
1462 // class name to reach the field.
1463 let shadowed_by_local = cls.as_ref() == self.ctx.class_name()
1464 && self.vt.vars.iter().any(|v| v.name == name);
1465 // ENCLOSING-class static fields read from a nested
1466 // class print bare too (lexical scope) — and MUST when
1467 // the class name itself is shadowed by a member of the
1468 // same name: jdk26 HPKE$Impl reads the outer
1469 // `byte[] HPKE`/PSK_ID_HASH/SECRET/EXP/KEY constants;
1470 // `HPKE.PSK_ID_HASH` resolves the qualifier as the
1471 // byte[] FIELD (variables obscure type names in
1472 // expression names) — 找不到符号 变量 PSK_ID_HASH x6.
1473 let enclosing_static = cls.as_ref() != self.ctx.class_name()
1474 && self.renders_nested_in(cls)
1475 && !self.vt.vars.iter().any(|v| v.name == name)
1476 && {
1477 let mut cur = self.ctx.class_name().to_string();
1478 let mut shadow = false;
1479 while cur.as_str() != cls.as_ref() {
1480 // Inherited fields shadow too (superclass
1481 // chain — see the method-side twin).
1482 let mut sup = Some(cur.clone());
1483 while let Some(c) = sup.take() {
1484 {
1485 if self.ctx.declares_field(&c, name) {
1486 shadow = true;
1487 break;
1488 }
1489 sup = self.ctx.super_name(&c);
1490 }
1491 }
1492 if shadow {
1493 break;
1494 }
1495 match cur.rfind('$') {
1496 Some(i) => cur.truncate(i),
1497 None => break,
1498 }
1499 }
1500 !shadow
1501 };
1502 if !enclosing_static && (cls.as_ref() != self.ctx.class_name() || shadowed_by_local) {
1503 // The owner must name a DECLARABLE class: the
1504 // anonymous→interface/Object fallback (right for
1505 // phi-compatible type positions) leaves the field
1506 // unresolvable — `Object.$SwitchMap$java$math$
1507 // RoundingMode` (485 weibo / 308 lark / 217
1508 // weixin cannot-finds; the synthetic holder
1509 // `LongMath$1` IS rendered as a flat file with
1510 // the field, 0 own errors). Fall back to the
1511 // concrete `$N` name — `$` and digits are legal
1512 // Java identifier characters, and the holder
1513 // always sits in the referring class's package.
1514 // The qualifier of a STATIC member is always the
1515 // CONCRETE name: anon/lambda classes emit as flat
1516 // `$`-named top-level files, while shorten()'s
1517 // anon fallback renders the SAM interface — which
1518 // does not declare the member. `Function0.INSTANCE`
1519 // (1.7k weibo lines) is the sget of
1520 // ActivityResultRegistry$generateRandomNumber$1
1521 // .INSTANCE; the same trap took $SwitchMap holders
1522 // to `Object.` before (the narrow rendered=="Object"
1523 // check this replaces).
1524 out.push_str(&self.shorten_concrete(cls));
1525 out.push('.');
1526 }
1527 out.push_str(&java_ident(name));
1528 } else if self.private_super_field(cls, name, &Expr::This) {
1529 out.push_str("((");
1530 out.push_str(&self.shorten(cls));
1531 out.push_str(") this).");
1532 out.push_str(&java_ident(name));
1533 } else {
1534 out.push_str("this.");
1535 out.push_str(&java_ident(name));
1536 }
1537 }
1538 Expr::Method {
1539 owner,
1540 cls,
1541 name,
1542 desc,
1543 args,
1544 is_static,
1545 is_special,
1546 is_super,
1547 type_args,
1548 ..
1549 } => {
1550 // Signature-polymorphic calls need the descriptor return cast
1551 // in source form (see classdec::polymorphic_ret_cast) — except
1552 // at the root of an expression statement, where the bare call
1553 // is the only legal form. Consume the flag so a poly call
1554 // NESTED in the args still gets its cast.
1555 let bare_stmt_root = self.suppress_poly_cast;
1556 self.suppress_poly_cast = false;
1557 if !bare_stmt_root {
1558 if let Some(t) = self.ctx.polymorphic_ret_cast(cls, name, desc) {
1559 out.push('(');
1560 out.push_str(&self.type_name(&crate::ir::expr::TypeRef::J(t)));
1561 out.push_str(") ");
1562 }
1563 }
1564 if name.as_ref() == "<init>" && *is_special {
1565 // The ctor fold normally turns `new X; <init>` into a
1566 // New expr at the LIFT. When the new-instance view was
1567 // lost (crossed a merge / got materialized), the call
1568 // arrives here with a NON-this receiver: rendering a
1569 // this/super delegation produced `this.super(...)`,
1570 // which is not valid Java. The allocation did happen —
1571 // print the construction call.
1572 let lost_alloc = owner
1573 .as_deref()
1574 .is_some_and(|o| !is_this_expr(o) && !matches!(o, Expr::This));
1575 if lost_alloc {
1576 // A member-inner construction call carries the
1577 // synthetic outer as args[0] and the emitted
1578 // ctor signature drops it (the inner-ctor
1579 // normalization) — supply it implicitly with the
1580 // qualified-new form, mirroring the New-expr
1581 // branches. Digit-tail classes keep the flat
1582 // full-args form (their ctor declares the
1583 // outer parameter).
1584 let digit_tail = cls
1585 .rsplit('$')
1586 .next()
1587 .map(|s| !s.is_empty() && s.chars().all(|c| c.is_ascii_digit()))
1588 .unwrap_or(false);
1589 if self.is_member_inner(cls)
1590 && !digit_tail
1591 && !args.is_empty()
1592 && !matches!(args[0], Expr::Const(_))
1593 {
1594 if matches!(args[0], Expr::This) || is_this_expr(&args[0]) {
1595 out.push_str("this.new ");
1596 } else {
1597 self.expr(&args[0], 15, out);
1598 out.push_str(".new ");
1599 }
1600 out.push_str(&inner_simple(cls));
1601 out.push('(');
1602 match self.ctor_param_types(cls, 1, args.len() - 1, &args[1..]) {
1603 Some(pt) => self.args_typed(&args[1..], &pt, out),
1604 None => self.args(&args[1..], out),
1605 }
1606 out.push(')');
1607 return;
1608 }
1609 out.push_str("new ");
1610 out.push_str(&self.shorten(cls));
1611 out.push('(');
1612 self.args(args, out);
1613 out.push(')');
1614 return;
1615 }
1616 // super(...) / this(...)
1617 let is_super_form = *is_super || cls.as_ref() != self.ctx.class_name();
1618 // Qualified super for a STATIC class extending an
1619 // INNER superclass: javac synthesizes the enclosing
1620 // instance as the ctor's first param, and the source
1621 // form is `outer.super(rest)` (jdk11/17/26
1622 // BoundMethodHandle.SpeciesData extends
1623 // ClassSpecializer<..>.SpeciesData — plain
1624 // super(outer, key) is "需要包含..的封闭实例" x3 trees).
1625 let qualified_outer = if is_super_form
1626 && !args.is_empty()
1627 && cls.contains('$')
1628 && !self.ctx.class_has_this0(self.ctx.class_name())
1629 {
1630 let enclosing = cls.rsplit_once('$').map(|(o, _)| o.to_string());
1631 match enclosing {
1632 Some(enc)
1633 if self.ctx.class_has_this0(cls)
1634 && self
1635 .ctx
1636 .is_subtype_of(&args[0].type_ref().erased(), &enc) =>
1637 {
1638 Some(enc)
1639 }
1640 _ => None,
1641 }
1642 } else {
1643 None
1644 };
1645 // The enclosing instance passed explicitly is `this`
1646 // itself (qualified_outer found the enclosing, but the
1647 // argument IS this class's own outer): the source form
1648 // is plain `super(rest...)` — the implicit outer —
1649 // because `this.super(...)` is not valid Java (weibo
1650 // HorseRaceDetector, reqable androidx.fragment.app.d).
1651 let this_outer =
1652 is_super_form && !args.is_empty() && is_this_expr(&args[0]);
1653 let skip_outer_arg = qualified_outer.is_some() || this_outer;
1654 if qualified_outer.is_some() && !this_outer {
1655 self.expr(&args[0], 15, out);
1656 out.push_str(".super(");
1657 } else if is_super_form {
1658 out.push_str("super(");
1659 } else {
1660 out.push_str("this(");
1661 }
1662 // Typed rendering: boolean/char params must not receive
1663 // raw 0/1 int constants (`this(refKind, false)`). The
1664 // methodref's descriptor `desc.args` is the EXACT invoked
1665 // ctor signature, so prefer it over `ctor_param_types`
1666 // (which matches by arg count and picks the wrong
1667 // overload when a class has several ctors of equal arity,
1668 // e.g. jdk26 Thread(ThreadGroup,Runnable,String,long,
1669 // boolean) vs (...,long,Thread[])).
1670 let _ = is_super_form;
1671 // Upcast restoration only for SELF delegation: this(..)
1672 // overload resolution binds the arg's static type (a
1673 // JarEntry arg would recurse into JarEntry(JarEntry)
1674 // instead of the descriptor's JarEntry(ZipEntry)).
1675 // super(..) resolution is unambiguous through the
1676 // generic formals — casting there broke FindOps
1677 // `super(parent, spliterator)` (K := FindTask<..>
1678 // accepts the arg fine; the raw erasure cast does not).
1679 if skip_outer_arg {
1680 if desc.args.len() == args.len() && !desc.args.is_empty() {
1681 self.args_typed(&args[1..], &desc.args[1..], out);
1682 } else {
1683 self.args(&args[1..], out);
1684 }
1685 } else if desc.args.len() == args.len() {
1686 if is_super_form {
1687 self.args_typed(args, &desc.args, out);
1688 } else {
1689 self.args_delegation(args, &desc.args, out);
1690 }
1691 } else if !is_super_form {
1692 // desc is the EXACT invoked ctor signature and the
1693 // args are outer-stripped: args_delegation aligns
1694 // by tail (jdk11 ZipFileInflaterInputStream —
1695 // ctor_param_types' arity match would pick the
1696 // wrong same-arity overload and cast against
1697 // shifted formals, (ZipFile) zfin x3).
1698 self.args_delegation(args, &desc.args, out);
1699 } else {
1700 match self.ctor_param_types(cls, 0, args.len(), args) {
1701 Some(pt) if pt.len() == args.len() => self.args_typed(args, &pt, out),
1702 _ => self.args(args, out),
1703 }
1704 }
1705 out.push(')');
1706 } else {
1707 if *is_super {
1708 // invokespecial on `this` targeting another class:
1709 // a superclass method call. Interface targets are
1710 // qualified `Iface.super.m()` (JDK8 default-method
1711 // invocations from implementors).
1712 let itf = self.ctx.is_interface(cls);
1713 if itf {
1714 out.push_str(&self.shorten(cls));
1715 out.push('.');
1716 }
1717 out.push_str("super.");
1718 } else if let Some(o) = owner {
1719 if let Expr::Const(cv) = o.as_ref() {
1720 if matches!(
1721 cv,
1722 crate::ir::expr::ConstVal::Str(_)
1723 | crate::ir::expr::ConstVal::ClassLit(_)
1724 ) {
1725 // A literal receiver is valid, faithful
1726 // Java (`"http".equalsIgnoreCase(x)`,
1727 // `Foo.class.getName()`). Rendering it as
1728 // cast-null silently DROPPED the constant
1729 // — 73.5k sites across the 4 corpora
1730 // (`((String) null).m()`), invisible to
1731 // javac (it compiles) and an NPE at run
1732 // time. Only valueless placeholders take
1733 // the cast-null path below.
1734 self.expr(o, 15, out);
1735 out.push('.');
1736 } else {
1737 // Receiver tracking lost: a null placeholder
1738 // (const/4 0) leaked through as the receiver —
1739 // `0.close()` is not parseable. Cast-null keeps
1740 // the statement compilable and honestly marks
1741 // the value as unknown on this path.
1742 out.push_str("((");
1743 out.push_str(&self.shorten(cls));
1744 out.push_str(") null).");
1745 }
1746 } else if let Expr::Lambda(l) = o.as_ref() {
1747 // A lambda RECEIVER needs the source's SAM cast
1748 // (`((BooleanSupplier) () -> ..).getAsBoolean()`
1749 // — a bare lambda cannot own a call: 此处不应为
1750 // lambda 表达式, jdk26 Proxy assert).
1751 if !l.sam_cls.is_empty() {
1752 out.push_str("((");
1753 out.push_str(&self.shorten(&l.sam_cls));
1754 out.push_str(") ");
1755 self.expr(o, 14, out);
1756 out.push_str(").");
1757 } else {
1758 self.expr(o, 15, out);
1759 out.push('.');
1760 }
1761 } else if matches!(o.as_ref(), Expr::Raw(t) if t == "\u{3}") {
1762 // Outer anonymous member call: unqualified
1763 // lexical resolution.
1764 } else if self.needs_owner_cast(cls, name, desc, o) {
1765 // Private / cross-package members are NOT
1766 // inherited by the receiver's static type:
1767 // the source cast to the declaring class
1768 // (`((ZipFile) jar).getManifestNum()`) leaves
1769 // no bytecode trace when the receiver is
1770 // already a subtype.
1771 out.push_str("((");
1772 out.push_str(&self.shorten(cls));
1773 out.push_str(") ");
1774 self.expr(o, 14, out);
1775 out.push_str(").");
1776 } else {
1777 self.expr(o, 15, out);
1778 out.push('.');
1779 }
1780 } else if *is_static && (cls.as_ref() != self.ctx.class_name() || !type_args.is_empty())
1781 {
1782 // A static member of an ENCLOSING class resolves
1783 // through lexical scope unqualified — and the
1784 // qualified form is a TRAP when a field/local is
1785 // named after the class: expression-name
1786 // resolution prefers variables, so jdk26
1787 // HPKE$Impl's `private static final byte[] HPKE`
1788 // hijacked `HPKE.usePSK(psk)` into a member
1789 // lookup on the array (找不到符号 方法
1790 // usePSK(SecretKey) 位置: 类型为byte[]的变量
1791 // HPKE). Keep the qualifier when an own or
1792 // intermediate class declares a same-named
1793 // method (real member shadowing) or when type
1794 // witnesses need an explicit receiver.
1795 // renders_nested_in: flat-rendered (digit-tail)
1796 // nested classes have NO lexical scope.
1797 let enclosing_static = type_args.is_empty()
1798 && self.renders_nested_in(cls)
1799 && {
1800 let mut cur = self.ctx.class_name().to_string();
1801 let mut shadow = false;
1802 while cur.as_str() != cls.as_ref() {
1803 // Inherited members shadow too: the
1804 // lexical member scope of a class
1805 // includes its whole superclass chain
1806 // (jdk26 GaloisCounterMode$GCMDecrypt
1807 // extends GCMEngine, whose own
1808 // implGCMCrypt(ByteBuffer,..) hijacks
1809 // the bare 9-arg call to the OUTER
1810 // static — 无法将方法应用到给定类型).
1811 let mut sup = Some(cur.clone());
1812 while let Some(c) = sup.take() {
1813 {
1814 if self.ctx.declares_method_named(&c, name) {
1815 shadow = true;
1816 break;
1817 }
1818 sup = self.ctx.super_name(&c);
1819 }
1820 }
1821 if shadow {
1822 break;
1823 }
1824 match cur.rfind('$') {
1825 Some(i) => cur.truncate(i),
1826 None => break,
1827 }
1828 }
1829 !shadow
1830 };
1831 if !enclosing_static {
1832 // Concrete name: same anon-class trap as the
1833 // static FIELD qualifier (the SAM interface
1834 // does not declare the synthetic static).
1835 out.push_str(&self.shorten_concrete(cls));
1836 out.push('.');
1837 }
1838 } else if !type_args.is_empty() {
1839 // Type witnesses require an explicit receiver.
1840 out.push_str("this.");
1841 }
1842 if !type_args.is_empty() {
1843 out.push('<');
1844 out.push_str(&type_args.join(", "));
1845 out.push('>');
1846 }
1847 // A BARE `yield(...)` invocation trips Java 13's
1848 // restricted identifier (Kotlin suspend funs name
1849 // their continuation entry `yield`; news_article
1850 // ships CoroutineExtKt$yield). Qualified calls are
1851 // fine — when no receiver was printed, add one.
1852 if name.as_ref() == "yield" && !out.ends_with('.') && !out.ends_with('>') {
1853 out.push_str(&self.shorten(cls));
1854 out.push('.');
1855 }
1856 out.push_str(&java_ident(name));
1857 out.push('(');
1858 // Lambda args of a GENERIC call: prime each one's
1859 // body-return cast from the instantiated formal's SAM
1860 // return (jdk17 AbstractPipeline.opEvaluateParallelLazy:
1861 // `i -> (E_OUT[]) new Object[i]` — the erasure-equal
1862 // cast leaves no bytecode trace and without it the
1863 // IntFunction<E_OUT[]> formal starves: Object[]无法
1864 // 转换为E_OUT[]).
1865 let sam_rets: Vec<Option<TypeRef>> = {
1866 let mut v: Vec<Option<TypeRef>> =
1867 std::iter::repeat_with(|| None).take(args.len()).collect();
1868 if self.ctx.is_generic_call(e) {
1869 if let Some((formals, mtvars)) = self.ctx.generic_call_formals(e) {
1870 if formals.len() == args.len() {
1871 for (i, (a, f)) in args.iter().zip(formals.iter()).enumerate() {
1872 // A raw-SAM-cast wrapper still
1873 // exposes the lambda (the overload
1874 // disambiguation cast; the primed
1875 // body return types the lambda
1876 // precisely).
1877 let a_inner = match a {
1878 Expr::Cast { e: ce, .. } => ce.as_ref(),
1879 other => other,
1880 };
1881 if let Expr::Lambda(l) = a_inner {
1882 // A formal still carrying the
1883 // callee's own method typevars
1884 // is not denotable at this
1885 // call site.
1886 if crate::typeutil::g_mentions_any(f, &mtvars) {
1887 continue;
1888 }
1889 v[i] = self.sam_ret_cast(f, &l.sam_name);
1890 }
1891 }
1892 }
1893 }
1894 }
1895 v
1896 };
1897 if sam_rets.iter().any(|x| x.is_some()) {
1898 self.args_typed_sam(args, &desc.args, &sam_rets, out);
1899 } else {
1900 self.args_typed(args, &desc.args, out);
1901 }
1902 out.push(')');
1903 }
1904 }
1905 Expr::ArrayIndex { array, index } => {
1906 self.expr(array, 15, out);
1907 out.push('[');
1908 // Array indexes are int by definition: a long-typed index
1909 // local (slot-sharing/merge typed the foreach lowering's
1910 // counter long — jdk26 Files.copy `options[i]`,
1911 // 从long转换到int可能会有损失) needs the narrowing cast.
1912 let idx_ty = match &**index {
1913 Expr::Local { var, .. } => self.vt.var(*var).ty.erased(),
1914 other => other.type_ref().erased(),
1915 };
1916 if matches!(idx_ty, crate::types::JavaType::Long) {
1917 out.push_str("(int) ");
1918 }
1919 self.expr(index, 1, out);
1920 out.push(']');
1921 }
1922 Expr::Cast { ty, e } => {
1923 // `(T) (Serializable) lambda` is invalid Java: the source
1924 // form of a serializable-lambda cast is the intersection
1925 // `(T & Serializable) lambda`.
1926 if let Expr::Cast {
1927 ty: inner_ty,
1928 e: inner_e,
1929 } = &**e
1930 {
1931 let is_serializable = |t: &crate::ir::expr::TypeRef| {
1932 t.erased() == crate::types::JavaType::Object("java/io/Serializable".into())
1933 };
1934 if matches!(&**inner_e, Expr::Lambda(_)) && is_serializable(inner_ty) {
1935 out.push('(');
1936 out.push_str(&self.type_name(ty));
1937 out.push_str(" & ");
1938 out.push_str(&self.type_name(inner_ty));
1939 out.push_str(") ");
1940 self.expr(inner_e, 14, out);
1941 return;
1942 }
1943 }
1944 // CLASS-argument generic casts only: a TYPEVAR cast is
1945 // load-bearing (`(T) new SimpleEntry<>(..)` — jdk11
1946 // IdentityHashMap.toArray: the diamond infers from the
1947 // ctor args and the unchecked (T) makes the T[] store
1948 // legal; dropping it left the diamond to infer from the
1949 // array target T — 无法推断SimpleEntry<>的类型参数).
1950 if let (TypeRef::G(crate::types::GenericType::Class(_)), Expr::New { cls, .. }) =
1951 (ty, &**e)
1952 {
1953 if !self.diamond_for(cls).is_empty() {
1954 // Synthetic generics-only cast around a diamond new:
1955 // DROP the cast and keep the diamond. A generic-typed
1956 // cast is always jcdc's own witness (a real checkcast
1957 // carries only erasure), and every cast form loses
1958 // here: with the diamond the cast starves inference
1959 // (`(PendingFuture<Void,A>) new PendingFuture<>(..)`
1960 // infers Object bounds and fails the cast), and the
1961 // bare/raw form makes the whole creation raw so
1962 // method-ref/lambda arguments stop type-checking
1963 // (jdk17 Collectors `(Collector<T,?,C>) new
1964 // CollectorImpl(.., Collection::add, ..)`). Cast-free
1965 // `return new CollectorImpl<>(..)` / `x = new
1966 // PendingFuture<>(..)` target-types the diamond —
1967 // the source shape.
1968 self.expr(e, outer_prec, out);
1969 return;
1970 }
1971 }
1972 out.push('(');
1973 out.push_str(&self.type_name(ty));
1974 out.push_str(") ");
1975 // Inconvertible direct casts from mis-joined stack-merge
1976 // vars (jdk26 AnnotationReader: `(SupertypeTarget)
1977 // var3_341` with var3 typed Iterator — sealed targets
1978 // reject the cross-cast): relay through Object, which is
1979 // always legal and semantically identical (the runtime
1980 // value IS the target type).
1981 if self.needs_object_relay(ty, e) {
1982 out.push_str("(Object) ");
1983 }
1984 if matches!(&**e, Expr::New { .. }) {
1985 self.suppress_diamond = true;
1986 }
1987 if let (TypeRef::G(g), Expr::Lambda(l)) = (ty, &**e) {
1988 self.lambda_sam_ret = self.sam_ret_cast(g, &l.sam_name);
1989 }
1990 self.expr(e, 14, out);
1991 self.suppress_diamond = false;
1992 self.lambda_sam_ret = None;
1993 }
1994 Expr::InstanceOf { e, ty } => {
1995 self.expr(e, 10, out);
1996 out.push_str(" instanceof ");
1997 out.push_str(&self.type_name(ty));
1998 }
1999 Expr::Un { op, e } => {
2000 out.push_str(match op {
2001 UnOp::Neg => "-",
2002 UnOp::Not => "!",
2003 UnOp::BitNot => "~",
2004 });
2005 self.expr(e, 14, out);
2006 }
2007 Expr::Bin { op, l, r, .. } => {
2008 // Boolean context special cases: `b == 0` → `!b`. A
2009 // Local's embedded type can be stale (booleanize retypes
2010 // the VarTable), and a boolified bitwise bin over
2011 // bool-ish operands is boolean too (jdk26
2012 // LinkedTransferQueue `(spin & !upc) == 0` — the frozen-Int
2013 // And with an int-form ternary side printed
2014 // `spin & (!upc ? 1 : 0)`: boolean & int).
2015 let lt = match &**l {
2016 Expr::Local { var, .. } => self.vt.var(*var).ty.erased(),
2017 other => other.type_ref().erased(),
2018 };
2019 let l_bool = lt == JavaType::Boolean;
2020 let l_boolish = l_bool || self.bool_ish_vt(l);
2021 if matches!(op, BinOp::Eq | BinOp::Ne | BinOp::RefEq | BinOp::RefNe) && l_boolish {
2022 if let Expr::Const(ConstVal::Int(n)) = &**r {
2023 let polarity = matches!(op, BinOp::Eq | BinOp::RefEq);
2024 let want_true = (*n != 0) == polarity;
2025 if !want_true {
2026 out.push('!');
2027 }
2028 if l_bool {
2029 self.expr(l, 14, out);
2030 } else {
2031 // Composite boolean (a bitwise bin): parenthesize
2032 // so a leading `!` binds the whole value.
2033 out.push('(');
2034 self.expr_bool(l, out);
2035 out.push(')');
2036 }
2037 if parens {
2038 out.push(')');
2039 }
2040 return;
2041 }
2042 }
2043 // Mixed boolean/int equality on non-constants (stack-merge
2044 // vars of an assert desugaring: jdk26 VirtualThread
2045 // `stack38 == stack39` with stack38 boolean, stack39 int
2046 // 0/1 — "boolean和int不可比较"): normalize the int side
2047 // through `!= 0`.
2048 {
2049 let rt = r.type_ref().erased();
2050 if matches!(op, BinOp::Eq | BinOp::Ne)
2051 && ((lt == crate::types::JavaType::Boolean
2052 && rt == crate::types::JavaType::Int)
2053 || (lt == crate::types::JavaType::Int
2054 && rt == crate::types::JavaType::Boolean))
2055 {
2056 for (i, side) in [l, r].iter().enumerate() {
2057 if i > 0 {
2058 out.push(' ');
2059 out.push_str(op.symbol());
2060 out.push(' ');
2061 }
2062 if side.type_ref().erased() == crate::types::JavaType::Boolean {
2063 self.expr_bool(side, out);
2064 } else {
2065 out.push('(');
2066 self.expr(side, 1, out);
2067 out.push_str(" != 0)");
2068 }
2069 }
2070 if parens {
2071 out.push(')');
2072 }
2073 return;
2074 }
2075 }
2076 let p = op.precedence();
2077 self.expr(l, p, out);
2078 out.push(' ');
2079 out.push_str(op.symbol());
2080 out.push(' ');
2081 self.expr(r, p + 1, out);
2082 }
2083 Expr::Cond { c, t, f } => {
2084 self.expr(c, 3, out);
2085 out.push_str(" ? ");
2086 let prev = std::mem::replace(&mut self.in_cond, true);
2087 self.expr(t, 2, out);
2088 out.push_str(" : ");
2089 self.expr(f, 2, out);
2090 self.in_cond = prev;
2091 }
2092 Expr::Assign { target, op, value } => {
2093 // The local's VarTable type is authoritative: booleanize
2094 // flips vt types after the embedded Local tys were frozen
2095 // (Security `boolean var4_122 = 1` printed the int form —
2096 // "int无法转换为boolean").
2097 let tgt_er = match &**target {
2098 Expr::Local { var, .. } => self.vt.var(*var).ty.erased(),
2099 other => other.type_ref().erased(),
2100 };
2101 self.expr(target, 1, out);
2102 out.push(' ');
2103 out.push_str(op.symbol());
2104 out.push(' ');
2105 if matches!(op, crate::ir::expr::AssignOp::Plain) {
2106 // Narrow-target assignments render int constants in the
2107 // target's form (jdk11/17/26 xml Parser: `mESt = ch !=
2108 // 116 ? 512 : 60` against `private char mESt` —
2109 // "从int转换到char可能会有损失").
2110 match tgt_er {
2111 crate::types::JavaType::Boolean => self.expr_bool(value, out),
2112 crate::types::JavaType::Char => self.expr_char(value, out),
2113 crate::types::JavaType::Byte => {
2114 self.expr_narrow(value, &crate::types::JavaType::Byte, out)
2115 }
2116 crate::types::JavaType::Short => {
2117 self.expr_narrow(value, &crate::types::JavaType::Short, out)
2118 }
2119 _ => self.expr(value, 1, out),
2120 }
2121 } else {
2122 self.expr(value, 1, out);
2123 }
2124 }
2125 Expr::PreIncDec { e, delta, .. } => {
2126 out.push_str(if *delta > 0 { "++" } else { "--" });
2127 self.expr(e, 14, out);
2128 }
2129 Expr::PostIncDec { e, delta, .. } => {
2130 self.expr(e, 14, out);
2131 out.push_str(if *delta > 0 { "++" } else { "--" });
2132 }
2133 Expr::Lambda(l) => self.lambda(l, out),
2134 Expr::AnonNew {
2135 base, args, body, ..
2136 } => {
2137 out.push_str("new ");
2138 let base_s = self.type_name(base);
2139 out.push_str(&base_s);
2140 out.push('(');
2141 // Render the ctor args through the base class's matching
2142 // <init> descriptor: an int 0/1 at a boolean parameter
2143 // must print as false/true (jdk11 Console's anonymous
2144 // PrintWriter subclass — `new PrintWriter(out, 1)` finds
2145 // no ctor). Pick the arity-matching ctor whose param
2146 // erasures best fit the actual arg types.
2147 let base_internal = match base {
2148 TypeRef::J(crate::types::JavaType::Object(n)) => Some(n.clone()),
2149 TypeRef::G(crate::types::GenericType::Class(cs)) => {
2150 Some(crate::typeutil::classsig_internal(cs).into())
2151 }
2152 _ => None,
2153 };
2154 // Overload choice for the ctor args (int 0/1 at a boolean
2155 // formal must print false/true) reads the base class's own
2156 // `<init>` set: front-end metadata.
2157 let ctor_params = base_internal
2158 .and_then(|bi| self.ctx.ctor_param_types(&bi, 0, args.len(), args));
2159 match ctor_params {
2160 Some(pt) => self.args_typed(args, &pt, out),
2161 None => self.args(args, out),
2162 }
2163 out.push_str(") {\n");
2164 for line in body.lines() {
2165 for _ in 0..self.indent + 1 {
2166 out.push_str(" ");
2167 }
2168 out.push_str(line.trim_start_matches(' '));
2169 out.push('\n');
2170 }
2171 for _ in 0..self.indent {
2172 out.push_str(" ");
2173 }
2174 out.push('}');
2175 }
2176 Expr::StringConcat(parts) => {
2177 let mut first = true;
2178 // Java semantics: the concatenation must start with a
2179 // String. NOT for an empty parts list: the trailing
2180 // is_empty guard below already prints `""` — both firing
2181 // printed four quotes.
2182 let needs_prefix =
2183 !parts.is_empty() && !matches!(parts.first(), Some(ConcatPart::Const(_)));
2184 if needs_prefix {
2185 out.push_str("\"\"");
2186 first = false;
2187 }
2188 for p in parts {
2189 // Right-hand operands of `+` need parens at equal
2190 // precedence to keep arithmetic grouping (`"t" + (x+1)`).
2191 let is_first = first;
2192 if !first {
2193 out.push_str(" + ");
2194 }
2195 first = false;
2196 match p {
2197 ConcatPart::Const(s) => {
2198 out.push('"');
2199 out.push_str(&escape_string(s));
2200 out.push('"');
2201 }
2202 ConcatPart::Str(e) => self.expr(e, if is_first { 12 } else { 13 }, out),
2203 }
2204 }
2205 if parts.is_empty() {
2206 out.push_str("\"\"");
2207 }
2208 }
2209 Expr::Invokedynamic {
2210 name,
2211 args,
2212 bsm_text,
2213 ..
2214 } => {
2215 if name.starts_with('\u{0}') {
2216 out.push_str("/*bad-cmp*/0");
2217 } else {
2218 out.push_str("/* invokedynamic ");
2219 out.push_str(name);
2220 out.push(' ');
2221 out.push_str(bsm_text);
2222 out.push_str(" */ (");
2223 self.args(args, out);
2224 out.push(')');
2225 }
2226 }
2227 }
2228 if parens {
2229 out.push(')');
2230 }
2231 }
2232
2233 fn args(&mut self, args: &[Expr], out: &mut String) {
2234 for (i, a) in args.iter().enumerate() {
2235 if i > 0 {
2236 out.push_str(", ");
2237 }
2238 self.expr(a, 1, out);
2239 }
2240 }
2241
2242 /// Print call args applying boolean-parameter constant adjustment.
2243 /// Delegation (this(..)/super(..)) args: typed rendering PLUS the
2244 /// elided-upcast restoration when the arg's static type differs from
2245 /// the descriptor formal — overload resolution binds the arg's type,
2246 /// not the descriptor (jdk17 JarEntry copy ctor: `this(je)` resolves
2247 /// recursively to JarEntry(JarEntry); the source `this((ZipEntry) je)`
2248 /// upcast is elided from bytecode because je <: ZipEntry is provable).
2249 fn args_delegation(
2250 &mut self,
2251 args: &[Expr],
2252 param_types: &[crate::types::JavaType],
2253 out: &mut String,
2254 ) {
2255 // Inner-class ctor descriptors carry the synthetic outer param
2256 // while the expression args are outer-stripped: align the formals
2257 // with the TRAIL of the args, like apply_ctor_param_casts (jdk11
2258 // ZipFileInflaterInputStream this(zfin, res, res.getInflater(),
2259 // size) was cast against the shifted descriptor formals —
2260 // (ZipFile) zfin, (ZipFileInputStream) res... x3 inconvertible).
2261 let off = param_types.len().saturating_sub(args.len());
2262 for (i, a) in args.iter().enumerate() {
2263 if i > 0 {
2264 out.push_str(", ");
2265 }
2266 match param_types.get(off + i) {
2267 Some(pt @ crate::types::JavaType::Object(_)) => {
2268 let have = a.type_ref().erased();
2269 // Generically-typed args (typevars/parameterized) are
2270 // already source-typed: casting them to the descriptor
2271 // erasure breaks convertibility to typevar formals
2272 // (jdk17 StreamSpliterators SliceSpliterator.OfPrimitive
2273 // this((Spliterator.OfPrimitive) s, ..) against a
2274 // T_SPLITR formal — OfPrimitive无法转换为T_SPLITR).
2275 if &have != pt
2276 && !matches!(a.type_ref(), TypeRef::G(_))
2277 && !matches!(
2278 a,
2279 Expr::Cast { .. }
2280 | Expr::Const(_)
2281 | Expr::Lambda(_)
2282 | Expr::New { .. }
2283 | Expr::AnonNew { .. }
2284 )
2285 {
2286 out.push('(');
2287 out.push_str(&self.type_name(&TypeRef::J(pt.clone())));
2288 out.push_str(") ");
2289 self.expr(a, 14, out);
2290 continue;
2291 }
2292 self.expr(a, 1, out);
2293 }
2294 Some(crate::types::JavaType::Boolean) => self.expr_bool(a, out),
2295 Some(crate::types::JavaType::Char) => self.expr_char(a, out),
2296 Some(pt @ (crate::types::JavaType::Byte | crate::types::JavaType::Short)) => {
2297 self.expr_narrow_arg(a, pt, out)
2298 }
2299 _ => self.expr(a, 1, out),
2300 }
2301 }
2302 }
2303
2304 fn args_typed(
2305 &mut self,
2306 args: &[Expr],
2307 param_types: &[crate::types::JavaType],
2308 out: &mut String,
2309 ) {
2310 self.args_typed_sam(args, param_types, &[], out)
2311 }
2312
2313 /// args_typed with per-argument SAM-return priming: a lambda actual at
2314 /// a parameterized-SAM formal of a pinned generic ctor gets its body
2315 /// return cast from the formal's instantiated SAM return (jdk11/17/26
2316 /// Collectors.toUnmodifiable*'s finisher `list -> (List<T>)
2317 /// List.of(list.toArray())` — the erased instantiatedMethodType says
2318 /// only List, so the generic cast is recoverable solely from the
2319 /// diamond's resolved formal).
2320 fn args_typed_sam(
2321 &mut self,
2322 args: &[Expr],
2323 param_types: &[crate::types::JavaType],
2324 sam_rets: &[Option<TypeRef>],
2325 out: &mut String,
2326 ) {
2327 for (i, a) in args.iter().enumerate() {
2328 if i > 0 {
2329 out.push_str(", ");
2330 }
2331 match param_types.get(i) {
2332 Some(crate::types::JavaType::Boolean) => self.expr_bool(a, out),
2333 // Char/narrow params: expr_char/expr_narrow render both
2334 // constants AND conditional branches in the target form
2335 // (jdk17 BasicAuthentication `super(!isProxy ? 115 : 112,
2336 // ..)` — the char param needs `!isProxy ? 's' : 'p'`;
2337 // a plain int ternary is "条件表达式中的类型错误").
2338 Some(crate::types::JavaType::Char) => self.expr_char(a, out),
2339 // Byte/short parameters: an int literal argument needs an
2340 // explicit cast (invocation conversion never narrows, even
2341 // for in-range constants) — and so do the branches of an
2342 // int conditional (jdk CompressedResourceHeader
2343 // `buffer.put(isTerminal ? (byte) 1 : (byte) 0)` — the
2344 // byte casts fold away in bytecode; a bare int ternary is
2345 // "对于put(int), 找不到合适的方法" x3 trees).
2346 Some(crate::types::JavaType::Byte) | Some(crate::types::JavaType::Short) => {
2347 self.expr_narrow_arg(a, ¶m_types[i].clone(), out);
2348 }
2349 _ => {
2350 let lam_under_cast = matches!(a, Expr::Lambda(_))
2351 || matches!(a, Expr::Cast { e: ce, .. } if matches!(&**ce, Expr::Lambda(_)));
2352 match sam_rets.get(i).and_then(|x| x.as_ref()) {
2353 Some(t) if lam_under_cast => {
2354 let prev = self.lambda_sam_ret.replace(t.clone());
2355 self.expr(a, 1, out);
2356 self.lambda_sam_ret = prev;
2357 }
2358 _ => self.expr(a, 1, out),
2359 }
2360 }
2361 }
2362 }
2363 }
2364
2365 fn const_val(&self, c: &ConstVal, out: &mut String) {
2366 match c {
2367 ConstVal::Int(i) => out.push_str(&i.to_string()),
2368 ConstVal::Long(l) => {
2369 if *l == i64::MIN {
2370 out.push_str("-9223372036854775808L");
2371 } else {
2372 out.push_str(&format!("{}L", l));
2373 }
2374 }
2375 ConstVal::Float(f) => out.push_str(&format_float(*f as f64, true)),
2376 ConstVal::Double(d) => out.push_str(&format_float(*d, false)),
2377 ConstVal::Str(s) => {
2378 out.push('"');
2379 out.push_str(&escape_string(s));
2380 out.push('"');
2381 }
2382 ConstVal::Null => out.push_str("null"),
2383 ConstVal::ClassLit(t) => {
2384 out.push_str(&self.type_name(t));
2385 out.push_str(".class");
2386 }
2387 }
2388 }
2389
2390 fn lambda(&mut self, l: &crate::ir::expr::LambdaExpr, out: &mut String) {
2391 // A `lambda$...` implementation method in the current class is a
2392 // compiler-generated lambda body, never a real method reference —
2393 // emit the body inline even when classified as a reference (the
2394 // method itself is hidden from the output).
2395 let synthetic_self_lambda =
2396 l.impl_name.starts_with("lambda$") && l.impl_owner == self.ctx.class_name();
2397 match l.kind {
2398 LambdaKind::MethodRef if !synthetic_self_lambda => {
2399 // Determine receiver form.
2400 if l.impl_name == "<init>" {
2401 out.push_str(&self.shorten(&l.impl_owner));
2402 out.push_str("::new");
2403 return;
2404 }
2405 if let Some(recv) = &l.ref_receiver {
2406 out.push_str(&self.shorten(recv));
2407 out.push_str("::");
2408 out.push_str(&l.impl_name);
2409 } else if !l.captures.is_empty() {
2410 // bound receiver: first capture is the instance
2411 // A method-ref/lambda receiver needs its functional
2412 // type spelled out: `A::new::get` parses as a
2413 // QUALIFIED reference and is rejected — the source form
2414 // is `((Supplier<A>) A::new)::get`.
2415 let cap = &l.captures[0];
2416 let func_ty = match cap {
2417 Expr::Lambda(l2) => Some(l2.sam_cls.clone()),
2418 Expr::Cast { ty, e } if matches!(**e, Expr::Lambda(_)) => {
2419 Some(ty.erased().to_descriptor())
2420 }
2421 _ => None,
2422 };
2423 if let Some(f) = func_ty {
2424 out.push_str("((");
2425 out.push_str(&self.shorten(&f));
2426 out.push_str(") ");
2427 self.expr(cap, 1, out);
2428 out.push(')');
2429 } else {
2430 self.expr(cap, 15, out);
2431 }
2432 out.push_str("::");
2433 out.push_str(&l.impl_name);
2434 } else {
2435 out.push_str(&self.shorten(&l.impl_owner));
2436 out.push_str("::");
2437 out.push_str(&l.impl_name);
2438 }
2439 }
2440 _ => {
2441 if self.lambda_depth >= 8 {
2442 out.push_str("/*nested-lambda*/null");
2443 return;
2444 }
2445 // The nested-body preparation — decompiling the impl
2446 // method and running the front-end's idiom recovery
2447 // (anonymous inlining, capture snapshots, enum switches,
2448 // witness casts, outer-this substitution, label pruning,
2449 // ...) — belongs to the front-end: that is where
2450 // machine-specific desugaring lives. Ask it for a finished
2451 // statement tree and print that.
2452 // The nested-body preparation — decompiling the impl method
2453 // and running the front-end's idiom recovery (anonymous
2454 // inlining, capture snapshots, enum switches, witness casts,
2455 // outer-this substitution, label pruning, ...) — belongs to
2456 // the front-end: that is where machine-specific desugaring
2457 // lives. Ask it for a finished statement tree and print that.
2458 let nested = self.ctx.nested_method(l, self.vt);
2459 // params: prefer the impl method's own parameter names so the
2460 // printed parameter list matches the body's references
2461 // (instance lambda impls carry only the SAM params).
2462 let mut pnames = l.param_names.clone();
2463 if let Some(MethodBody { vt, .. }) = &nested {
2464 let mut vt_params: Vec<String> = vt
2465 .vars
2466 .iter()
2467 .filter(|v| v.is_param && v.name != "this")
2468 .map(|v| v.name.clone())
2469 .collect();
2470 if vt_params.len() == pnames.len() {
2471 pnames = vt_params;
2472 } else if vt_params.len() > pnames.len() {
2473 // The impl method's signature is (captures..., SAM
2474 // params...): take the TRAILING SAM slice so the
2475 // printed parameter list matches the body's variable
2476 // references (ConcurrentMap.replaceAll printed
2477 // `(x0, x1) -> { .. replace(k, v, ..) }` — undefined
2478 // symbols; the captured `function` occupied the first
2479 // impl slot, breaking the exact-length alignment).
2480 let n = pnames.len();
2481 pnames = vt_params.split_off(vt_params.len() - n);
2482 }
2483 }
2484 let single = pnames.len() == 1;
2485 if !single {
2486 out.push('(');
2487 }
2488 for (i, n) in pnames.iter().enumerate() {
2489 if i > 0 {
2490 out.push_str(", ");
2491 }
2492 out.push_str(n);
2493 }
2494 if !single {
2495 out.push(')');
2496 }
2497 out.push_str(" -> ");
2498 let sam_wrap = self.lambda_sam_ret.take();
2499 match nested {
2500 Some(MethodBody {
2501 mut body, mut vt, ..
2502 }) => {
2503 // Single-return body → expression lambda.
2504 let mut single_expr = match &body {
2505 Stmt::Return(Some(e)) => Some(e.clone()),
2506 Stmt::Block(v) if v.len() == 1 => match &v[0] {
2507 Stmt::Return(Some(e)) => Some(e.clone()),
2508 _ => None,
2509 },
2510 _ => None,
2511 };
2512 // Witness the instantiated SAM return on the body's
2513 // returns: the impl method is erased, so `i -> i`
2514 // under `(Function<I,R>)` fails inference without
2515 // `(R) i`.
2516 fn sam_ok(t: &TypeRef, e: &Expr) -> bool {
2517 !matches!(e, Expr::Cast { .. } | Expr::Const(_)) && e.type_ref() != *t
2518 }
2519 fn wrap_returns(s: &mut Stmt, t: &TypeRef) {
2520 match s {
2521 Stmt::Return(Some(e)) => {
2522 if sam_ok(t, e) {
2523 let v = std::mem::replace(e, Expr::This);
2524 *e = Expr::Cast {
2525 ty: t.clone(),
2526 e: Box::new(v),
2527 };
2528 }
2529 }
2530 Stmt::Block(v) => v.iter_mut().for_each(|x| wrap_returns(x, t)),
2531 Stmt::If {
2532 then_stmt,
2533 else_stmt,
2534 ..
2535 } => {
2536 wrap_returns(then_stmt, t);
2537 if let Some(x) = else_stmt {
2538 wrap_returns(x, t);
2539 }
2540 }
2541 Stmt::While { body, .. }
2542 | Stmt::DoWhile { body, .. }
2543 | Stmt::ForEach { body, .. }
2544 | Stmt::Labeled { body, .. }
2545 | Stmt::Synchronized { body, .. } => wrap_returns(body, t),
2546 Stmt::For { init, body, .. } => {
2547 init.iter_mut().for_each(|i| wrap_returns(i, t));
2548 wrap_returns(body, t);
2549 }
2550 Stmt::Switch { cases, default, .. } => {
2551 for c in cases.iter_mut() {
2552 for st in c.body.iter_mut() {
2553 wrap_returns(st, t);
2554 }
2555 }
2556 if let Some(d) = default {
2557 wrap_returns(d, t);
2558 }
2559 }
2560 Stmt::Try {
2561 body,
2562 catches,
2563 finally,
2564 } => {
2565 wrap_returns(body, t);
2566 for c in catches.iter_mut() {
2567 wrap_returns(&mut c.body, t);
2568 }
2569 if let Some(f) = finally {
2570 wrap_returns(f, t);
2571 }
2572 }
2573 _ => {}
2574 }
2575 }
2576 if let Some(t) = &sam_wrap {
2577 if let Some(e) = &mut single_expr {
2578 if sam_ok(t, e) {
2579 let v = std::mem::replace(e, Expr::This);
2580 *e = Expr::Cast {
2581 ty: t.clone(),
2582 e: Box::new(v),
2583 };
2584 } else if let TypeRef::G(_) = t {
2585 // The body already carries the erasure
2586 // checkcast: retype it to the generic
2587 // SAM return (jdk26
2588 // CopyOnWriteArrayList.toArray `i ->
2589 // (T[]) new Object[i]` printed
2590 // `(Object[])` — Object[]无法转换为T[]
2591 // against IntFunction<T[]>).
2592 if let Expr::Cast { ty: ct, e: ce } = e {
2593 if ct.erased() == t.erased()
2594 && !matches!(&**ce, Expr::Cast { .. })
2595 {
2596 *ct = t.clone();
2597 }
2598 }
2599 }
2600 } else {
2601 wrap_returns(&mut body, t);
2602 }
2603 } else if let Some(inst) = &l.inst_sam_desc {
2604 if crate::dbg_flag!("JCDC_DBG_LAMRET") {
2605 eprintln!(
2606 "LAMRET impl={}.{} inst_ret={:?} single={}",
2607 l.impl_owner,
2608 l.impl_name,
2609 inst.ret,
2610 single_expr.is_some()
2611 );
2612 }
2613 // The invokedynamic's instantiatedMethodType
2614 // records the lambda's inferred return at the
2615 // call site; a source upcast can be elided from
2616 // the bytecode when provable (jdk17
2617 // JrtFileSystem.iteratorOf: `(Path)` around
2618 // path.resolve(..) inside map(child -> ...) —
2619 // JrtPath <: Path needs no checkcast, but
2620 // without the cast map infers Stream<JrtPath>
2621 // and .iterator() fails the Iterator<Path>
2622 // return). Restore it when the expression body
2623 // is typed a strict subtype of the recorded
2624 // return.
2625 fn upcast_ok(
2626 e: &Expr,
2627 target: &crate::types::JavaType,
2628 ctx: &dyn crate::ctx::Ctx,
2629 ) -> bool {
2630 let crate::types::JavaType::Object(t) = target else {
2631 return false;
2632 };
2633 // A parameterized (generic) value whose
2634 // erasure IS the target reaches it unchecked
2635 // (jdk17 CompletionStage exceptionallyCompose:
2636 // the cond branch fn.apply(ex) types as the
2637 // capture ? extends CompletionStage<T>; the
2638 // erased impl return CompletionStage needs no
2639 // cast, and printing one breaks the outer
2640 // chain's inference).
2641 if let TypeRef::G(crate::types::GenericType::Class(cs)) =
2642 e.type_ref()
2643 {
2644 if crate::typeutil::classsig_internal(&cs).as_str() == t.as_ref() {
2645 return true;
2646 }
2647 }
2648 let crate::types::JavaType::Object(e0) = e.type_ref().erased()
2649 else {
2650 return false;
2651 };
2652 if &e0 == t {
2653 return false;
2654 }
2655 let mut queue = vec![e0];
2656 let mut seen = std::collections::HashSet::new();
2657 while let Some(cur) = queue.pop() {
2658 if !seen.insert(cur.clone()) {
2659 continue;
2660 }
2661 if !ctx.has_class(&cur) {
2662 continue;
2663 }
2664 for (sup, _) in ctx.class_supers_args(&cur, &[]) {
2665 if sup.as_str() == t.as_ref() {
2666 return true;
2667 }
2668 queue.push(sup.into());
2669 }
2670 }
2671 false
2672 }
2673 let want = TypeRef::J(inst.ret.clone());
2674 // The sam_wrap prime (declared local / pinned
2675 // ctor formal / generic-call formal) carries the
2676 // GENERIC SAM return the erased
2677 // instantiatedMethodType lost: upgrade an
2678 // existing erasure-equal cast on the body (jdk26
2679 // CopyOnWriteArrayList.toArray's `i -> (T[]) new
2680 // Object[i]` printed `(Object[])` — the real
2681 // checkcast erases T[] and Object[] alike;
2682 // Object[]无法转换为T[] against
2683 // IntFunction<T[]>).
2684 let g_want: Option<TypeRef> = sam_wrap.as_ref().and_then(|t| {
2685 if let TypeRef::G(g) = t {
2686 if TypeRef::G(g.clone()).erased() == inst.ret {
2687 return Some(t.clone());
2688 }
2689 }
2690 None
2691 });
2692 if let Some(e) = &mut single_expr {
2693 if crate::dbg_flag!("JCDC_DBG_LAMRET") {
2694 eprintln!(
2695 "LAMRET2 inst_ret={:?} expr_ty={:?} sam_ok={} upcast_ok={} g_want={:?}",
2696 inst.ret,
2697 e.type_ref(),
2698 sam_ok(&want, e),
2699 upcast_ok(e, &inst.ret, self.ctx),
2700 g_want
2701 );
2702 }
2703 let mut upgraded = false;
2704 if let Some(gw) = &g_want {
2705 if let Expr::Cast { ty, e: ce } = e {
2706 if ty.erased() == inst.ret
2707 && !matches!(&**ce, Expr::Cast { .. })
2708 {
2709 *ty = gw.clone();
2710 upgraded = true;
2711 }
2712 }
2713 }
2714 if !upgraded
2715 && sam_ok(&want, e)
2716 && upcast_ok(e, &inst.ret, self.ctx)
2717 {
2718 let v = std::mem::replace(e, Expr::This);
2719 *e = Expr::Cast {
2720 ty: want,
2721 e: Box::new(v),
2722 };
2723 }
2724 }
2725 }
2726 // A nested lambda body shares the ENCLOSING
2727 // lambda's scope rules: its own locals must not
2728 // redeclare names visible there (jdk26
2729 // MethodHandleProxies.createTemplate — the clb
2730 // lambda's foreach-desugared `i$`/`mi` collided
2731 // with the nested cob lambda's own `i$`/`mi`,
2732 // 已在方法中定义了变量 x2). Rename the inner
2733 // impl's non-param locals on collision; the
2734 // method-level twin is handled by
2735 // disambiguate_lambda_locals.
2736 {
2737 let mut outer_names: std::collections::HashSet<&str> =
2738 self.vt.vars.iter().map(|v| v.name.as_str()).collect();
2739 outer_names.extend(self.outer_names.iter().map(|n| n.as_str()));
2740 let mut used: std::collections::HashSet<String> =
2741 vt.vars.iter().map(|v| v.name.clone()).collect();
2742 for v in vt.vars.iter_mut() {
2743 if v.is_param || !outer_names.contains(v.name.as_str()) {
2744 continue;
2745 }
2746 let base = v.name.clone();
2747 let mut k = 1;
2748 loop {
2749 let cand = format!("{}${}", base, k);
2750 if !used.contains(&cand) && !outer_names.contains(cand.as_str())
2751 {
2752 used.insert(cand.clone());
2753 v.name = cand;
2754 break;
2755 }
2756 k += 1;
2757 }
2758 }
2759 }
2760 let mut sub = Printer {
2761 ctx: self.ctx,
2762 vt: &vt,
2763 out: String::new(),
2764 line_buf: String::new(),
2765 indent: self.indent,
2766 lambda_depth: self.lambda_depth + 1,
2767 outer_names: {
2768 let mut n = self.outer_names.clone();
2769 n.extend(self.vt.vars.iter().map(|v| v.name.clone()));
2770 n
2771 },
2772 // The SAM's return type governs the impl
2773 // body's renders: boolean SAMs must print
2774 // `return true;` not the JVM-int `return 1;`
2775 // (jdk26 Gatherers.fold's ofGreedy lambda).
2776 ret_bool: l.sam_desc.ret == crate::types::JavaType::Boolean,
2777 ret_char: l.sam_desc.ret == crate::types::JavaType::Char,
2778 ret_byte: l.sam_desc.ret == crate::types::JavaType::Byte,
2779 ret_short: l.sam_desc.ret == crate::types::JavaType::Short,
2780 suppress_poly_cast: false,
2781 suppress_diamond: false,
2782 // A lambda body is a fresh expression context:
2783 // the enclosing conditional's pre-8 non-poly
2784 // rule does not propagate into it (the lambda
2785 // is a standalone method body).
2786 in_cond: false,
2787 lambda_sam_ret: None,
2788 ret_sam: None,
2789 twin_cache: self.twin_cache.clone(),
2790 };
2791 if let Some(e) = single_expr {
2792 if l.sam_desc.ret == crate::types::JavaType::Boolean {
2793 sub.expr_bool(&e, out);
2794 } else {
2795 sub.expr(&e, 1, out);
2796 }
2797 } else {
2798 out.push_str("{\n");
2799 sub.indent += 1;
2800 sub.stmt(&body);
2801 let text = sub.out;
2802 out.push_str(&text);
2803 for _ in 0..self.indent {
2804 out.push_str(" ");
2805 }
2806 out.push('}');
2807 }
2808 }
2809 None => {
2810 out.push_str("/* lambda body in ");
2811 out.push_str(&l.impl_owner);
2812 out.push('.');
2813 out.push_str(&l.impl_name);
2814 out.push_str(" */ {}");
2815 }
2816 }
2817 }
2818 }
2819 }
2820
2821 // ---------------- names & types ----------------
2822
2823 /// True if `cls` is a member inner class (declares this$0) per the pool.
2824 /// Parameter types of the `<init>` matching `skip + n` descriptor args,
2825 /// returning the tail after `skip` leading synthetic params. Used to
2826 /// print `new` arguments with boolean/char constant adjustment.
2827 fn ctor_param_types(
2828 &self,
2829 cls: &str,
2830 skip: usize,
2831 n: usize,
2832 args: &[Expr],
2833 ) -> Option<Vec<crate::types::JavaType>> {
2834 // Overload/parameter recovery from the class's own `<init>` set is
2835 // container metadata: the front-end answers it.
2836 self.ctx.ctor_param_types(cls, skip, n, args)
2837 }
2838
2839 fn is_member_inner(&self, cls: &str) -> bool {
2840 // Front-end metadata question: synthetic outer field, or an enclosing
2841 // instance forwarded to super?
2842 if self.ctx.class_has_this0(cls) {
2843 return true;
2844 }
2845 if !cls.contains('$') || self.ctx.nested_is_static(cls) {
2846 return false;
2847 }
2848 self.ctx.outer_param_via_super(cls)
2849 }
2850
2851 /// Is `cls` lexically enclosing the CURRENT class in the RENDERED
2852 /// layout? Digit-tail classes (anonymous/d8-lambda shapes) are
2853 /// emitted as separate flat top-level files — the nested lexical
2854 /// scope they would provide does not exist there, so members of an
2855 /// outer class must be QUALIFIED from them (weibo
2856 /// CronetUrlRequest$3: bare `_$$Nest$misDoneLocked(..)` calls of
2857 /// the outer's statics were unresolvable).
2858 fn renders_nested_in(&self, cls: &str) -> bool {
2859 let cur = self.ctx.class_name();
2860 if !cur.starts_with(&format!("{}$", cls)) {
2861 return false;
2862 }
2863 let mut c = cur.to_string();
2864 while c.as_str() != cls {
2865 let tail = c.rsplit('$').next().unwrap_or(&c);
2866 if tail.is_empty()
2867 || tail.chars().next().is_some_and(|ch| ch.is_ascii_digit())
2868 {
2869 return false; // flat-rendered boundary: no lexical scope
2870 }
2871 match c.rfind('$') {
2872 Some(i) => c.truncate(i),
2873 None => return false,
2874 }
2875 }
2876 true
2877 }
2878
2879 /// Instantiated SAM return for a cast to a generic functional
2880 /// interface (`(Function<I,R>) lambda`): resolves the interface's SAM
2881 /// method Signature return against the cast's type arguments. Only
2882 /// meaningful when the result still contains a type variable — the
2883 /// erased impl body then needs `(R) value` witnesses on its returns
2884 /// (javac: "return type I cannot be converted to R" for `i -> i`).
2885 fn sam_ret_cast(&self, g: &crate::types::GenericType, sam_name: &str) -> Option<TypeRef> {
2886 // Resolving a functional interface's SAM return against a cast's type
2887 // arguments reads the interface's method signatures: front-end
2888 // metadata.
2889 self.ctx.sam_ret_cast(g, sam_name)
2890 }
2891
2892 /// `<>` when `new cls(...)` should carry a diamond: the class is
2893 /// generic (class-level Signature with type params) and THIS file is
2894 /// Java 7+. A bare generic new compiles under old-style inference
2895 /// (standalone, not target-typed), which collapses to Object when
2896 /// implicitly-typed lambda args are involved — jdk11 Collectors.
2897 /// summingInt's `new CollectorImpl<>(() -> new int[1], (a, t) -> ...)`
2898 /// fails as bare `new CollectorImpl(...)` ("array required, but found
2899 /// Object", 72 errors in the concurrent-family closure). Bytecode
2900 /// cannot distinguish diamond from bare, so always prefer the diamond
2901 /// for generic classes.
2902 fn diamond_for(&self, cls: &str) -> &'static str {
2903 if self.ctx.source_level() < 51 {
2904 return "";
2905 }
2906 if self.ctx.class_declares_generics(cls) {
2907 "<>"
2908 } else {
2909 ""
2910 }
2911 }
2912
2913 /// Shorten an internal class name for emission: java.lang.* and same
2914 /// package use simple names; others fully qualified dotted.
2915 /// True when `cls.name` is a PRIVATE instance field and the owner
2916 /// expression's compile-time type is a strict subclass of cls: the
2917 /// field is not in the subclass's member scope, so the source must
2918 /// cast the owner to the declaring class
2919 /// (`((MessageDigest)this).algorithm` — jdk11 Delegate.clone x3,
2920 /// Signature.clone).
2921 fn private_super_field(&self, cls: &str, name: &str, o: &Expr) -> bool {
2922 if matches!(o, Expr::Raw(_) | Expr::RawT(..)) {
2923 return false;
2924 }
2925 let owner_ct = if matches!(o, Expr::This) {
2926 self.ctx.class_name().into()
2927 } else {
2928 match o.type_ref().erased() {
2929 crate::types::JavaType::Object(n) => n,
2930 _ => return false,
2931 }
2932 };
2933 if owner_ct.as_ref() == cls {
2934 return false;
2935 }
2936 let Some(flags) = self.ctx.field_flags(cls, name) else {
2937 return false;
2938 };
2939 if flags.contains(crate::types::FieldAccessFlags::STATIC)
2940 || !flags.contains(crate::types::FieldAccessFlags::PRIVATE)
2941 {
2942 return false;
2943 }
2944 // cls must be a strict superclass of the owner's compile-time type.
2945 let mut cur = owner_ct;
2946 for _ in 0..64 {
2947 let Some(sup) = self.ctx.super_name(&cur) else {
2948 return false;
2949 };
2950 if sup == cls {
2951 return true;
2952 }
2953 cur = sup.into();
2954 }
2955 false
2956 }
2957
2958 fn needs_owner_cast(
2959 &self,
2960 cls: &str,
2961 name: &str,
2962 desc: &crate::types::MethodDescriptor,
2963 o: &Expr,
2964 ) -> bool {
2965 // Raw-rendered owners (qualified `HashMap.this`, substituted
2966 // capture text) carry no trustworthy static type — and casting
2967 // them to a RAW class erases the whole generic member chain
2968 // (`((HashMap) HashMap.this).<T>keysToArray(..)` made the call
2969 // raw: "Object[]无法转换为T[]").
2970 if matches!(o, Expr::Raw(_) | Expr::RawT(..) | Expr::This) {
2971 return false;
2972 }
2973 let crate::types::JavaType::Object(on) = o.type_ref().erased() else {
2974 return false;
2975 };
2976 if on.as_ref() == cls {
2977 return false;
2978 }
2979 // A receiver widened to Object (Kotlin `when` materialization
2980 // residue — every branch's value lowered into one slot) cannot
2981 // resolve ANY member: cast to the invoke's resolved class. The
2982 // bytecode guarantees the runtime type (invokevirtual dispatch
2983 // on exactly this object), so the cast is always honest.
2984 if on.as_ref() == "java/lang/Object" && cls != "java/lang/Object" {
2985 return true;
2986 }
2987 let want = format!(
2988 "({}){}",
2989 desc.args
2990 .iter()
2991 .map(|t| t.to_descriptor())
2992 .collect::<String>(),
2993 desc.ret.to_descriptor()
2994 );
2995 // The receiver's static type is a strict SUPERTYPE of the
2996 // resolved class (register reuse left the vt at the supertype):
2997 // the member lives on `cls`, and the downcast is exactly what
2998 // the bytecode's resolution performed. ONLY when the owner type
2999 // cannot resolve the member itself: an interface-typed owner
3000 // whose IMPL the dex named resolves fine through the
3001 // interface, and casting to the impl sprinkles obscured/flat
3002 // impl names into expression position (weixin +3.3k
3003 // cannot-find when this fired unguarded).
3004 if self.ctx.is_subtype_of(
3005 &crate::types::JavaType::Object(std::sync::Arc::from(cls)),
3006 on.as_ref(),
3007 ) && self.ctx.method_flags(on.as_ref(), name, &want).is_none()
3008 {
3009 return true;
3010 }
3011 let Some(acc) = self.ctx.method_flags(cls, name, &want) else {
3012 return false;
3013 };
3014 use crate::types::MethodAccessFlags as M;
3015 if acc.contains(M::PRIVATE) {
3016 return true;
3017 }
3018 if !acc.contains(M::PUBLIC) && !acc.contains(M::PROTECTED) {
3019 fn pkg(n: &str) -> &str {
3020 n.rsplit_once('/').map(|(p, _)| p).unwrap_or("")
3021 }
3022 return pkg(&on) != pkg(cls);
3023 }
3024 false
3025 }
3026
3027 fn needs_object_relay(&self, ty: &TypeRef, e: &Expr) -> bool {
3028 if matches!(e, Expr::Cast { .. } | Expr::Const(_)) {
3029 return false;
3030 }
3031 let (crate::types::JavaType::Object(from), crate::types::JavaType::Object(to)) =
3032 (e.type_ref().erased(), ty.erased())
3033 else {
3034 return false;
3035 };
3036 if from == to || from.as_ref() == "java/lang/Object" || to.as_ref() == "java/lang/Object" {
3037 return false;
3038 }
3039 if !self.ctx.has_class(&from) || !self.ctx.has_class(&to) {
3040 return false;
3041 }
3042 if self.ctx.is_subtype_of(&e.type_ref().erased(), &to)
3043 || self.ctx.is_subtype_of(&ty.erased(), &from)
3044 {
3045 return false;
3046 }
3047 let both_classes = !self.ctx.is_interface(&from) && !self.ctx.is_interface(&to);
3048 both_classes || self.ctx.is_sealed(&from) || self.ctx.is_sealed(&to)
3049 }
3050
3051 pub fn shorten(&self, internal: &str) -> String {
3052 // NOTE: deliberately NOT memoized across Printers — shorten_inner
3053 // consults per-Printer state (the emitting class's package via
3054 // ctx.class_name, its fields, and this method's vt.vars for the
3055 // shadowed-simple-name check), so the result is not a pure
3056 // function of (pool, internal).
3057 // Class-file names may contain characters Java cannot parse
3058 // (R8 desugared `Collection$-EL`) — EVERY return path runs the
3059 // deterministic sanitizer (early returns bypassed it).
3060 sanitize_source_name(&self.shorten_inner(internal, true))
3061 }
3062
3063 /// Like `shorten` but WITHOUT the anonymous-class → interface/super
3064 /// fallback: used at `new <cls>` sites so a d8/R8-desugared lambda
3065 /// class (which ends in `$<digits>` like an anonymous class) prints as
3066 /// its concrete, instantiable binary name instead of its abstract SAM
3067 /// interface. Type positions keep using `shorten` (interface), so the
3068 /// declaration and the constructor disagree in the correct direction:
3069 /// `Function0 v = new Outer$..$log$1(..)`.
3070 pub fn shorten_concrete(&self, internal: &str) -> String {
3071 sanitize_source_name(&self.shorten_inner(internal, false))
3072 }
3073
3074 /// Nesting-aware display chain: a `$` boundary dots ONLY when the
3075 /// prefix has a resolvable outer (a true member); when the prefix
3076 /// is itself an emission-unit top-level (R8 deleted the outer —
3077 /// weixin matrix `JiffiesMonitorFeature` gone, `$JiffiesSnapshot`
3078 /// survives as a flat file), its `$` stays and the member tail dots
3079 /// onto it: `...JiffiesMonitorFeature$JiffiesSnapshot.ThreadJiffiesEntry`.
3080 /// Blind `$`→`.` rendered a chain through the missing type
3081 /// ("是在不可访问的类或接口中定义的").
3082 fn nested_display(&self, internal: &str) -> String {
3083 if let Some(o) = self.ctx.find_outer(internal) {
3084 if internal.len() > o.len() + 1 && internal.starts_with(&o) {
3085 let tail = &internal[o.len() + 1..];
3086 if !tail.is_empty() && !tail.starts_with('$') && !tail.contains('/') {
3087 // The tail may span several member levels
3088 // (`a17$a18`): below a RESOLVED outer they are all
3089 // inline members of the family file — dot them.
3090 return format!(
3091 "{}.{}",
3092 self.nested_display(&o),
3093 tail.replace('$', ".")
3094 );
3095 }
3096 }
3097 }
3098 // Base: a POOL class with no resolvable outer is a flat `$`-named
3099 // emission unit — keep the `$` (its file name carries it). An
3100 // off-pool name is a framework member class (android.jar's
3101 // `LinearLayout$LayoutParams`) — dot it like the old blind
3102 // replace; the jar declares it as a member.
3103 if self.ctx.has_class(internal) {
3104 internal.replace('/', ".")
3105 } else {
3106 internal.replace(['/', '$'], ".")
3107 }
3108 }
3109
3110 fn shorten_inner(&self, internal: &str, anon_fallback: bool) -> String {
3111 if internal.is_empty() {
3112 return String::new();
3113 }
3114 // An import-resolved obscured reference renders its simple name
3115 // (the qualified form binds to an in-scope class — JLS 6.4.2).
3116 if let Some(simple) = self.ctx.obscured_simple(internal) {
3117 return simple;
3118 }
3119 // Case-collision renames (`X/Cua` → `X/Cua_2`): the registry is
3120 // empty for ordinary corpora — one relaxed atomic load.
3121 let cow = crate::rename::apply_class_rename(internal);
3122 let internal: &str = &cow;
3123 // Literal-$ top-level class (in pool, no InnerClasses nesting
3124 // evidence): the $ is part of the SOURCE name (jextract-generated
3125 // errno_h$shared) — never dot it into a nested qualifier. Same rule
3126 // whenever dotting could not parse: `DolTest2$$dollah$$` has empty
3127 // segments, and `$` is a legal identifier character while `A..b` is
3128 // not.
3129 let dot_safe = |simple: &str| -> bool {
3130 // A LEADING `$` (`$Gson$Types`) is part of the source name —
3131 // dotting it yields `.Gson.Types` with a leading dot.
3132 simple.split('$').all(|seg| {
3133 !seg.is_empty()
3134 && seg
3135 .chars()
3136 .next()
3137 .map(|c| c.is_ascii_alphabetic() || c == '_')
3138 .unwrap_or(false)
3139 })
3140 };
3141 let simple_here = internal.rsplit('/').next().unwrap_or(internal).to_string();
3142 let keep_dollar_pool = internal.contains('$')
3143 && self.ctx.has_class(internal)
3144 && self.ctx.find_outer(internal).is_none();
3145 // R8 desugared-library names carry `$` inside the PACKAGE path
3146 // (`j$/util/Collection$-EL`): a `$` followed by `/` is never a
3147 // nesting boundary — dotting it produced `j..util`.
3148 let dollar_in_pkg = internal.contains("$/");
3149 let keep_dollar = keep_dollar_pool
3150 || dollar_in_pkg
3151 || (internal.contains('$') && !dot_safe(&simple_here));
3152 // Anonymous class types (all-digit last segment) have no source
3153 // name: print the base interface/superclass instead. Skipped for
3154 // `new <cls>` sites (anon_fallback=false): a d8/R8-desugared
3155 // LAMBDA class also ends in `$<digits>` (`...$lambda$N$$inlined
3156 // $log$1`, `Outer$$ExternalSyntheticLambda0`) but IS a concrete,
3157 // pool-rendered class — printing its SAM interface there yields an
3158 // invalid `new Iface(..)` ("a是抽象的; 无法实例化", the biggest
3159 // abstract-instantiation family on Kotlin corpora: v6.a/l/p =
3160 // Function0/1/2). The var/phi TYPE positions keep the interface
3161 // (shorten with fallback), so `Iface v = new ConcreteLambda(..)`
3162 // stays phi-compatible across branches that mint different lambdas.
3163 if anon_fallback {
3164 if let Some(last) = internal.rsplit('$').next() {
3165 if !last.is_empty() && last.chars().all(|c| c.is_ascii_digit()) {
3166 if let Some((ifaces, sup)) = self.ctx.class_bases(internal) {
3167 if let Some(n) = ifaces.first() {
3168 return self.shorten(n);
3169 }
3170 if let Some(sup) = sup {
3171 if sup != "java/lang/Object" {
3172 return self.shorten(&sup);
3173 }
3174 }
3175 return "Object".to_string();
3176 }
3177 }
3178 }
3179 }
3180 // Local classes are emitted with their simple source name.
3181 if let Some(last) = internal.rsplit('$').next() {
3182 if !keep_dollar_pool
3183 && !last.is_empty()
3184 && !last.chars().all(|c| c.is_ascii_digit())
3185 && internal.contains('$')
3186 {
3187 // Nested-class names print as Outer.Inner, except local
3188 // classes whose outer chain includes digits (e.g. Foo$1Bar).
3189 let mut segs = internal.split('$');
3190 if let Some(first) = segs.next() {
3191 let rest: Vec<&str> = segs.collect();
3192 if rest
3193 .iter()
3194 .any(|r| r.starts_with(|c: char| c.is_ascii_digit()))
3195 {
3196 // Local/synthetic class (digit-led segment): javac
3197 // encodes a method-local class as `Outer$1Name`; its
3198 // source name is the digit prefix stripped, declared
3199 // `class Name` at its use site and referenced only
3200 // within that scope. Printing the binary name leaves
3201 // an unresolvable symbol (ClassSpecializer$Factory$1Var).
3202 if let Some(last) = internal.rsplit('$').next() {
3203 let stripped = last.trim_start_matches(|c: char| c.is_ascii_digit());
3204 if !stripped.is_empty()
3205 && !stripped.chars().next().unwrap().is_ascii_digit()
3206 {
3207 return stripped.to_string();
3208 }
3209 }
3210 return internal.replace('/', ".");
3211 }
3212 let _ = first;
3213 let _ = last;
3214 }
3215 }
3216 }
3217 let dotted = internal.replace('/', ".");
3218 // A simple type name shadowed by an in-scope VARIABLE (a field of
3219 // this class or a method local) cannot qualify a static access:
3220 // javac resolves the leading name as the variable first
3221 // (无法取消引用int — jdk26 VerificationType declares
3222 // `private static final int Integer` and the source spells
3223 // java.lang.Integer.toHexString in full). Keep the fully-qualified
3224 // form whenever the leading segment collides.
3225 let shadowed = |first: &str| {
3226 self.ctx.declares_field(self.ctx.class_name(), first)
3227 || self.vt.vars.iter().any(|v| v.name == first)
3228 };
3229 if internal.starts_with("java/lang/") && !internal[10..].contains('/') {
3230 let simple = if keep_dollar {
3231 internal[10..].to_string()
3232 } else {
3233 internal[10..].replace('$', ".")
3234 };
3235 let first = simple.split('.').next().unwrap_or(simple.as_str());
3236 if !shadowed(first) {
3237 return simple;
3238 }
3239 return dotted.replace('$', ".");
3240 }
3241 if pkg_of(internal) == pkg_of(self.ctx.class_name()) {
3242 let simple = if keep_dollar {
3243 internal.rsplit('/').next().unwrap_or(internal).to_string()
3244 } else {
3245 let d = self.nested_display(internal);
3246 let pkg_dotted = pkg_of(internal).replace('/', ".");
3247 d.strip_prefix(&pkg_dotted)
3248 .and_then(|r| r.strip_prefix('.'))
3249 .map(str::to_string)
3250 .unwrap_or(d)
3251 };
3252 let first = simple.split('.').next().unwrap_or(simple.as_str());
3253 // Member-type shadow: the emitting class's OWN nested type
3254 // with the same simple name captures the bare render
3255 // (member types outrank same-package types in class scope) —
3256 // weibo PhoneInfoUtils: `b2.b(ctx,..)` for sibling class
3257 // com/weibo/ssosdk/b2 bound to nested PhoneInfoUtils$b2
3258 // (找不到符号 方法 b). The reference to the twin itself (or
3259 // its own nestees) keeps the simple form. FQN fallback is
3260 // unambiguous — no field/variable can shadow a dotted
3261 // package chain past its first segment.
3262 // Member-type twin shadow: a nested type of ANY enclosing
3263 // level whose display simple name equals `first` captures
3264 // the bare render (member types outrank same-package types
3265 // in class scope — weixin d2's `((w1) h0)` bound to nested
3266 // d2$$w1, not sibling viewitems/w1: 1,045 h0→w1 lines).
3267 // Walk the `$`-chain of the emitting class (the ctx may be a
3268 // nested `d2$$c`, whose lexical scope still sees d2's
3269 // members); R8 orphans use `$$`, normalization displays use
3270 // `$`, and rename displays live only in the registry's
3271 // reverse index — check all four shapes per level.
3272 let twin_exists = {
3273 // The borrow temp must die before the else arm's
3274 // borrow_mut (if-let scrutinee temps live across the
3275 // whole statement — RefCell double-borrow panic).
3276 let cached = self.twin_cache.borrow().get(first).copied();
3277 if let Some(v) = cached {
3278 v
3279 } else {
3280 let mut v = false;
3281 let mut cur: &str = self.ctx.class_name();
3282 loop {
3283 let t1 = format!("{cur}${first}");
3284 let t2 = format!("{cur}$${first}");
3285 if self.ctx.has_class(&t1)
3286 || self.ctx.has_class(&t2)
3287 || crate::rename::is_renamed_display(&t1)
3288 || crate::rename::is_renamed_display(&t2)
3289 {
3290 v = true;
3291 break;
3292 }
3293 match cur.rfind('$') {
3294 Some(i) => cur = &cur[..i],
3295 None => break,
3296 }
3297 }
3298 self.twin_cache
3299 .borrow_mut()
3300 .insert(first.to_string(), v);
3301 v
3302 }
3303 };
3304 // is_twin_ref only matters when a twin exists (otherwise
3305 // `!twin_exists` already grants the simple form) — keep it
3306 // off the hot path.
3307 let is_twin_ref = twin_exists && {
3308 let mut r = false;
3309 let mut cur: &str = self.ctx.class_name();
3310 loop {
3311 let t1 = format!("{cur}${first}");
3312 let t2 = format!("{cur}$${first}");
3313 if internal == t1
3314 || internal == t2
3315 || internal.starts_with(&format!("{t1}$"))
3316 || internal.starts_with(&format!("{t2}$"))
3317 {
3318 r = true;
3319 break;
3320 }
3321 match cur.rfind('$') {
3322 Some(i) => cur = &cur[..i],
3323 None => break,
3324 }
3325 }
3326 r
3327 };
3328 if !shadowed(first) && (is_twin_ref || !twin_exists) {
3329 return simple;
3330 }
3331 }
3332 if keep_dollar {
3333 dotted
3334 } else {
3335 self.nested_display(internal)
3336 }
3337 }
3338
3339 pub fn type_name(&self, ty: &TypeRef) -> String {
3340 match ty {
3341 TypeRef::J(t) => self.java_type_name(t),
3342 TypeRef::G(g) => {
3343 // Render generic signature with shortened class names.
3344 self.generic_name(g)
3345 }
3346 }
3347 }
3348
3349 /// Declared-type name for a local: normally `type_name` (which lets a
3350 /// `$<digits>` class fall back to its SAM interface for phi-friendly
3351 /// readability), but a var in `force_concrete_vars` keeps its concrete
3352 /// binary name so concrete-member accesses (`v.L$0`, the Kotlin
3353 /// coroutine capture fields) resolve — interfaces have no instance
3354 /// fields.
3355 fn local_decl_ty(&self, var: u32, ty: &TypeRef) -> String {
3356 if self.vt.force_concrete_vars.contains(&var) {
3357 if let TypeRef::J(JavaType::Object(cls)) = ty {
3358 return sanitize_source_name(&self.shorten_concrete(cls));
3359 }
3360 }
3361 self.type_name(ty)
3362 }
3363
3364 fn java_type_name(&self, t: &JavaType) -> String {
3365 // An import-resolved obscured class renders its simple name in
3366 // every type position (signatures, casts, type refs).
3367 if let JavaType::Object(c) = t {
3368 if let Some(simple) = self.ctx.obscured_simple(c) {
3369 return simple;
3370 }
3371 }
3372 match t {
3373 JavaType::Object(n) => self.shorten(n),
3374 JavaType::Array(inner) => format!("{}[]", self.java_type_name(inner)),
3375 other => other.to_java(false),
3376 }
3377 }
3378
3379 fn generic_name(&self, g: &crate::types::GenericType) -> String {
3380 use crate::types::{GenericType, WildcardBound};
3381 match g {
3382 GenericType::Primitive(c) => prim_name(*c).to_string(),
3383 GenericType::Class(cs) => {
3384 // The signature parser splits a literal-$ top-level name
3385 // (jextract-generated errno_h$shared) into nested-looking
3386 // parts [errno_h, shared]; rejoin and, when the pool
3387 // proves the class is NOT nested, render the binary name
3388 // through shorten (same-package simple form keeps the $).
3389 if cs.parts.len() > 1 && cs.parts.iter().skip(1).all(|p| p.args.is_empty()) {
3390 let joined = cs
3391 .parts
3392 .iter()
3393 .map(|p| p.name.as_str())
3394 .collect::<Vec<_>>()
3395 .join("$");
3396 let full = if cs.package.is_empty() {
3397 joined
3398 } else {
3399 format!("{}/{}", cs.package, joined)
3400 };
3401 let literal = self.ctx.has_class(&full) && self.ctx.find_outer(&full).is_none();
3402 if literal {
3403 return self.shorten(&full);
3404 }
3405 }
3406 let mut s = String::new();
3407 if !cs.package.is_empty() {
3408 let full = format!(
3409 "{}/{}",
3410 cs.package,
3411 cs.parts.first().map(|p| p.name.as_str()).unwrap_or("")
3412 );
3413 s.push_str(&self.shorten(&full));
3414 } else {
3415 s.push_str(&cs.parts.first().map(|p| p.name.clone()).unwrap_or_default());
3416 }
3417 if let Some(first) = cs.parts.first() {
3418 if !first.args.is_empty() {
3419 s.push('<');
3420 for (i, a) in first.args.iter().enumerate() {
3421 if i > 0 {
3422 s.push_str(", ");
3423 }
3424 s.push_str(&self.generic_name(a));
3425 }
3426 s.push('>');
3427 }
3428 }
3429 for p in cs.parts.iter().skip(1) {
3430 let digit_led = p
3431 .name
3432 .chars()
3433 .next()
3434 .map(|c| c.is_ascii_digit())
3435 .unwrap_or(false);
3436 let stripped = p.name.trim_start_matches(|c: char| c.is_ascii_digit());
3437 if digit_led && !stripped.is_empty() && p.args.is_empty() {
3438 // javac's local-class encoding `Outer$1Name`: the
3439 // source name is the prefix-stripped simple name,
3440 // referenced WITHOUT qualification (there is no
3441 // legal `DocLint.1Pair` — `> or ',' expected`).
3442 s = stripped.to_string();
3443 continue;
3444 }
3445 s.push('.');
3446 s.push_str(&p.name);
3447 if !p.args.is_empty() {
3448 s.push('<');
3449 for (i, a) in p.args.iter().enumerate() {
3450 if i > 0 {
3451 s.push_str(", ");
3452 }
3453 s.push_str(&self.generic_name(a));
3454 }
3455 s.push('>');
3456 }
3457 }
3458 s
3459 }
3460 GenericType::Array(inner) => format!("{}[]", self.generic_name(inner)),
3461 GenericType::TypeVar(n) => n.clone(),
3462 GenericType::Wildcard(w) => match w {
3463 WildcardBound::Any => "?".to_string(),
3464 WildcardBound::Extends(t) => format!("? extends {}", self.generic_name(t)),
3465 WildcardBound::Super(t) => format!("? super {}", self.generic_name(t)),
3466 },
3467 }
3468 }
3469}
3470
3471fn inner_simple(cls: &str) -> String {
3472 // Apply the class-rename registry FIRST: a renamed nested class must
3473 // print its display tail at `outer.new Inner(..)` sites too — this
3474 // free function was the one emit-side path bypassing the registry
3475 // (round-55: declarations carried the rename while `this.new b()`
3476 // printed the raw tail → cannot-find).
3477 let cow = crate::rename::apply_class_rename(cls);
3478 let cls: &str = &cow;
3479 let last = cls.rsplit('/').next().unwrap_or(cls);
3480 // R8 leaves class names ending in `$` (rimet's `ThreadMsg$$$`):
3481 // the last `$`-segment is EMPTY — fall back to the whole simple
3482 // name instead of printing `receiver.new (args)`.
3483 let seg = last.rsplit('$').next().unwrap_or(last);
3484 let last = if seg.is_empty() { last } else { seg };
3485 // Obfuscators name classes after keywords (`v10.new do("")`): the
3486 // qualified-new form prints this simple name raw.
3487 let name = last.to_string();
3488 if is_java_keyword(name.as_str())
3489 || is_restricted_type_name(name.as_str())
3490 || name.chars().next().is_some_and(|c| c.is_ascii_digit())
3491 {
3492 format!("_{name}")
3493 } else if name
3494 .chars()
3495 .all(|c| c.is_ascii_alphanumeric() || c == '_' || c == '$')
3496 {
3497 name
3498 } else {
3499 // Non-ASCII single chars (Alipay names a field `支`) map to a
3500 // lone `_` — itself reserved since Java 9. Escape it.
3501 let mapped: String = name
3502 .chars()
3503 .map(|c| {
3504 if c.is_ascii_alphanumeric() || c == '_' || c == '$' {
3505 c
3506 } else {
3507 '_'
3508 }
3509 })
3510 .collect();
3511 if mapped == "_" {
3512 "__".to_string()
3513 } else {
3514 mapped
3515 }
3516 }
3517}
3518
3519fn push_char_lit(out: &mut String, n: i32) {
3520 let c = (n as u16) as u32;
3521 match char::from_u32(c) {
3522 Some('\'') => out.push_str("'\\''"),
3523 Some('\\') => out.push_str("'\\\\'"),
3524 Some('\n') => out.push_str("'\\n'"),
3525 Some('\r') => out.push_str("'\\r'"),
3526 Some('\t') => out.push_str("'\\t'"),
3527 Some(ch) if ch.is_control() => out.push_str(&format!("'\\u{:04x}'", c)),
3528 Some(ch) => {
3529 out.push('\'');
3530 out.push(ch);
3531 out.push('\'');
3532 }
3533 None => out.push_str(&format!("'\\u{:04x}'", c)),
3534 }
3535}
3536
3537fn const_bool(e: &Expr) -> Option<bool> {
3538 match e {
3539 Expr::Const(ConstVal::Int(n)) => Some(*n != 0),
3540 _ => None,
3541 }
3542}
3543
3544fn pkg_of(n: &str) -> &str {
3545 n.rfind('/').map(|i| &n[..i]).unwrap_or("")
3546}
3547
3548fn prim_name(c: char) -> &'static str {
3549 match c {
3550 'V' => "void",
3551 'Z' => "boolean",
3552 'B' => "byte",
3553 'C' => "char",
3554 'S' => "short",
3555 'I' => "int",
3556 'F' => "float",
3557 'J' => "long",
3558 'D' => "double",
3559 _ => "?",
3560 }
3561}
3562
3563/// Kotlin emits method names like `invokeSuspend$lambda-0` — `-` and any
3564/// other non-identifier character is not legal Java. Deterministic
3565/// mapping; ddc's classdec applies the identical rule at declarations.
3566pub fn java_ident(name: &str) -> std::borrow::Cow<'_, str> {
3567 let clean = name
3568 .chars()
3569 .all(|c| c.is_ascii_alphanumeric() || c == '_' || c == '$');
3570 let keyword = is_java_keyword(name);
3571 // A simple name may not START with a digit either (nova names fields
3572 // `1x`/`2x`/`3x`): ddc's declaration side has the same branch.
3573 let digit_start = name.chars().next().is_some_and(|c| c.is_ascii_digit());
3574 if clean && !keyword && !digit_start {
3575 // The overwhelmingly common case: borrow, zero allocation (this
3576 // runs per rendered IDENTIFIER).
3577 std::borrow::Cow::Borrowed(name)
3578 } else if keyword || digit_start {
3579 std::borrow::Cow::Owned(format!("_{name}"))
3580 } else {
3581 // Non-ASCII single chars (Alipay names a field `支`) map to a
3582 // lone `_` — itself reserved since Java 9. Escape it.
3583 let mapped: String = name
3584 .chars()
3585 .map(|c| {
3586 if c.is_ascii_alphanumeric() || c == '_' || c == '$' {
3587 c
3588 } else {
3589 '_'
3590 }
3591 })
3592 .collect();
3593 if mapped == "_" {
3594 std::borrow::Cow::Owned("__".to_string())
3595 } else {
3596 std::borrow::Cow::Owned(mapped)
3597 }
3598 }
3599}
3600
3601/// Java reserved words (plus the null/true/false literals, and `_` — a
3602/// reserved IDENTIFIER since Java 9: Alipay's instant-run patch system
3603/// names fields `_`, and `javac` rejects it with "underscores are not
3604/// allowed here"): Kotlin emits methods and fields named `default`
3605/// (Companion.default()) — no Java program can name them, so both ddc's
3606/// declarations and these call sites map them to `_<name>` identically
3607/// (which turns `_` into `__`). NOTE: these are MEMBER-level escapes —
3608/// locals/fields/params MAY legally carry the restricted contextual
3609/// type names below (rt.jar has `var` locals), so those live in their
3610/// own predicate.
3611pub fn is_java_keyword(name: &str) -> bool {
3612 matches!(
3613 name,
3614 "abstract" | "assert" | "boolean" | "break" | "byte" | "case" | "catch" | "char"
3615 | "class" | "const" | "continue" | "default" | "do" | "double" | "else" | "enum"
3616 | "extends" | "final" | "finally" | "float" | "for" | "goto" | "if" | "implements"
3617 | "import" | "instanceof" | "int" | "interface" | "long" | "native" | "new"
3618 | "package" | "private" | "protected" | "public" | "return" | "short" | "static"
3619 | "strictfp" | "super" | "switch" | "synchronized" | "this" | "throw" | "throws"
3620 | "transient" | "try" | "void" | "volatile" | "while" | "true" | "false" | "null"
3621 | "_"
3622 )
3623}
3624
3625/// Restricted CONTEXTUAL type names — javac rejects them in TYPE
3626/// declarations and type references (`'var' 是受限类型名称`), but they
3627/// stay legal as member/local names (rt.jar compiles `var` locals).
3628/// Consulted ONLY where a CLASS name renders: type declarations,
3629/// segment sanitizers, the qualified-new simple name.
3630pub fn is_restricted_type_name(name: &str) -> bool {
3631 matches!(
3632 name,
3633 "var" | "yield" | "record" | "sealed" | "permits"
3634 )
3635}
3636
3637/// `this` in its Raw encoding (prints as `this`; see the Raw arm).
3638fn is_this_expr(e: &Expr) -> bool {
3639 matches!(e, Expr::This) || matches!(e, Expr::Raw(t) if t == "\u{3}" || t == "this")
3640}
3641
3642/// Class-file names may contain characters Java source identifiers
3643/// cannot (R8 desugared `Collection$-EL`); deterministic -→_ mapping,
3644/// matching ddc's declaration-site java_ident.
3645/// Legal Java expression-statement forms (JLS 14.8): assignment, method
3646/// invocation, inc/dec, class instance creation.
3647fn is_statement_expr(e: &Expr) -> bool {
3648 matches!(
3649 e,
3650 Expr::Assign { .. }
3651 | Expr::Method { .. }
3652 | Expr::New { .. }
3653 | Expr::PreIncDec { .. }
3654 | Expr::PostIncDec { .. }
3655 )
3656}
3657
3658pub fn sanitize_source_name(name: &str) -> String {
3659 // Obfuscators emit classes named `if`/`do`, PACKAGES named `do`
3660 // (weixin `package do;`) and `..badge.new..` paths (qq), and
3661 // WhatsApp nests digit-start simple names under one-letter packages
3662 // (`X/0Xx`). EVERY `.`-segment that cannot START a Java identifier
3663 // maps to `_<seg>`; `$`-attached tails (`RequestId$1`, `X$0Xx`) are
3664 // legal as-is once their leading segment is clean.
3665 let bad_char = !name
3666 .chars()
3667 .all(|c| c.is_ascii_alphanumeric() || c == '_' || c == '$' || c == '.');
3668 let segs: Vec<&str> = name.split('.').collect();
3669 let needs = bad_char
3670 || segs.iter().any(|seg| {
3671 let first = seg.chars().next().unwrap_or('a');
3672 is_java_keyword(seg) || is_restricted_type_name(seg) || first.is_ascii_digit()
3673 });
3674 if !needs {
3675 return name.to_string();
3676 }
3677 segs.iter()
3678 .map(|seg| {
3679 let first = seg.chars().next().unwrap_or('a');
3680 let fixed = if is_java_keyword(seg) || is_restricted_type_name(seg) || first.is_ascii_digit() {
3681 format!("_{seg}")
3682 } else {
3683 seg.to_string()
3684 };
3685 if fixed
3686 .chars()
3687 .all(|c| c.is_ascii_alphanumeric() || c == '_' || c == '$')
3688 {
3689 fixed
3690 } else {
3691 // Non-ASCII single chars map to a lone `_` — reserved
3692 // since Java 9. Escape it (declaration/ref consistency).
3693 let mapped: String = fixed
3694 .chars()
3695 .map(|c| {
3696 if c.is_ascii_alphanumeric() || c == '_' || c == '$' {
3697 c
3698 } else {
3699 '_'
3700 }
3701 })
3702 .collect();
3703 if mapped == "_" {
3704 "__".to_string()
3705 } else {
3706 mapped
3707 }
3708 }
3709 })
3710 .collect::<Vec<_>>()
3711 .join(".")
3712}
3713
3714pub fn escape_string(s: &str) -> String {
3715 let mut out = String::with_capacity(s.len());
3716 for c in s.chars() {
3717 match c {
3718 '"' => out.push_str("\\\""),
3719 '\\' => out.push_str("\\\\"),
3720 '\n' => out.push_str("\\n"),
3721 '\r' => out.push_str("\\r"),
3722 '\t' => out.push_str("\\t"),
3723 '\u{0}' => out.push_str("\\0"),
3724 '\u{8}' => out.push_str("\\b"),
3725 '\u{C}' => out.push_str("\\f"),
3726 c if (c as u32) < 0x20 || (c as u32) == 0x7F => {
3727 out.push_str(&format!("\\u{:04x}", c as u32));
3728 }
3729 c => out.push(c),
3730 }
3731 }
3732 out
3733}
3734
3735pub fn escape_char(c: char) -> String {
3736 match c {
3737 '\'' => "\\'".to_string(),
3738 '\\' => "\\\\".to_string(),
3739 '\n' => "\\n".to_string(),
3740 '\r' => "\\r".to_string(),
3741 '\t' => "\\t".to_string(),
3742 '\0' => "\\0".to_string(),
3743 c if (c as u32) < 0x20 || (c as u32) == 0x7F => format!("\\u{:04x}", c as u32),
3744 c => c.to_string(),
3745 }
3746}
3747
3748pub fn format_float(v: f64, is_float: bool) -> String {
3749 let suffix = if is_float { "f" } else { "" };
3750 // Arithmetic literals, not `Double.NaN`/`POSITIVE_INFINITY` field refs:
3751 // inside java.lang.Double/Float themselves a field reference would be
3752 // a self-reference initializing the very field being printed.
3753 if v.is_nan() {
3754 return if is_float {
3755 "(0.0f / 0.0f)".into()
3756 } else {
3757 "(0.0 / 0.0)".into()
3758 };
3759 }
3760 if v.is_infinite() {
3761 let one = if is_float { "1.0f" } else { "1.0" };
3762 let zero = if is_float { "0.0f" } else { "0.0" };
3763 return if v < 0.0 {
3764 format!("(-{} / {})", one, zero)
3765 } else {
3766 format!("({} / {})", one, zero)
3767 };
3768 }
3769 let mut s = format!("{}", v);
3770 if !s.contains('.') && !s.contains('e') && !s.contains('E') {
3771 s.push_str(".0");
3772 }
3773 // Java doesn't accept exponent forms like "1e-7"? It does: 1e-7 is a valid
3774 // double literal. But "inf"/"nan" handled above.
3775 format!("{}{}", s, suffix)
3776}
3777
3778// ---------------------------------------------------------------------------
3779// Unreachable-code truncation (final statement tree)
3780// ---------------------------------------------------------------------------
3781
3782/// Drop statements that follow a `while (true)` which cannot complete
3783/// normally (no `break` in its body binds to it): javac rejects them as
3784/// 无法访问的语句, and the structurers only emitted them as shared-tail
3785/// copies whose real arrivals all render INSIDE the loop arms (jdk17/26
3786/// Pattern.clazz's for(;;){switch}: case-93's `return prev/negate` tail
3787/// landed after the never-completing loop). Runs on the FINAL tree (after
3788/// switch restoration and goto resolution) so the break inventory is
3789/// exactly what gets emitted; every unknown shape stays conservative
3790/// (assumes the loop can complete).
3791fn tree_has_dead_end(s: &Stmt) -> bool {
3792 match s {
3793 Stmt::Block(v) => {
3794 for (i, x) in v.iter().enumerate() {
3795 if i > 0 && dead_end_infinite_while(&v[i - 1]) {
3796 return true;
3797 }
3798 if tree_has_dead_end(x) {
3799 return true;
3800 }
3801 }
3802 false
3803 }
3804 Stmt::If {
3805 then_stmt,
3806 else_stmt,
3807 ..
3808 } => tree_has_dead_end(then_stmt) || else_stmt.as_deref().is_some_and(tree_has_dead_end),
3809 Stmt::While { body, .. } | Stmt::DoWhile { body, .. } => tree_has_dead_end(body),
3810 Stmt::For { init, body, .. } => {
3811 init.iter().any(tree_has_dead_end) || tree_has_dead_end(body)
3812 }
3813 Stmt::ForEach { body, .. } => tree_has_dead_end(body),
3814 Stmt::Switch { cases, default, .. } => {
3815 cases.iter().any(|c| c.body.iter().any(tree_has_dead_end))
3816 || default.as_deref().is_some_and(tree_has_dead_end)
3817 }
3818 Stmt::Try {
3819 body,
3820 catches,
3821 finally,
3822 } => {
3823 tree_has_dead_end(body)
3824 || catches.iter().any(|c| tree_has_dead_end(&c.body))
3825 || finally.as_deref().is_some_and(tree_has_dead_end)
3826 }
3827 Stmt::TryWithResources {
3828 resources,
3829 body,
3830 catches,
3831 finally,
3832 ..
3833 } => {
3834 resources.iter().any(tree_has_dead_end)
3835 || tree_has_dead_end(body)
3836 || catches.iter().any(|c| tree_has_dead_end(&c.body))
3837 || finally.as_deref().is_some_and(tree_has_dead_end)
3838 }
3839 Stmt::Synchronized { body, .. } | Stmt::Labeled { body, .. } => tree_has_dead_end(body),
3840 _ => false,
3841 }
3842}
3843
3844fn truncate_dead_ends(s: &Stmt) -> Stmt {
3845 match s {
3846 Stmt::Block(v) => {
3847 let mut out: Vec<Stmt> = Vec::with_capacity(v.len());
3848 for x in v {
3849 if !out.is_empty() && dead_end_infinite_while(out.last().unwrap()) {
3850 if crate::dbg_flag!("JCDC_DBG_GOTO") {
3851 eprintln!(
3852 "DEADEND truncate {} unreachable stmt(s)",
3853 v.len() - out.len()
3854 );
3855 }
3856 break;
3857 }
3858 out.push(truncate_dead_ends(x));
3859 }
3860 Stmt::Block(out)
3861 }
3862 Stmt::If {
3863 cond,
3864 then_stmt,
3865 else_stmt,
3866 } => Stmt::If {
3867 cond: cond.clone(),
3868 then_stmt: Box::new(truncate_dead_ends(then_stmt)),
3869 else_stmt: else_stmt.as_deref().map(truncate_dead_ends).map(Box::new),
3870 },
3871 Stmt::While { cond, body } => Stmt::While {
3872 cond: cond.clone(),
3873 body: Box::new(truncate_dead_ends(body)),
3874 },
3875 Stmt::DoWhile { body, cond } => Stmt::DoWhile {
3876 body: Box::new(truncate_dead_ends(body)),
3877 cond: cond.clone(),
3878 },
3879 Stmt::For {
3880 init,
3881 cond,
3882 update,
3883 body,
3884 } => Stmt::For {
3885 init: init.iter().map(truncate_dead_ends).collect(),
3886 cond: cond.clone(),
3887 update: update.clone(),
3888 body: Box::new(truncate_dead_ends(body)),
3889 },
3890 Stmt::ForEach {
3891 var,
3892 iterable,
3893 is_array,
3894 body,
3895 } => Stmt::ForEach {
3896 var: *var,
3897 iterable: iterable.clone(),
3898 is_array: *is_array,
3899 body: Box::new(truncate_dead_ends(body)),
3900 },
3901 Stmt::Switch {
3902 selector,
3903 cases,
3904 default,
3905 on_string,
3906 } => Stmt::Switch {
3907 selector: selector.clone(),
3908 cases: cases
3909 .iter()
3910 .map(|c| CaseGroup {
3911 labels: c.labels.clone(),
3912 string_labels: c.string_labels.clone(),
3913 enum_labels: c.enum_labels.clone(),
3914 raw_labels: c.raw_labels.clone(),
3915 guard: c.guard.clone(),
3916 body: c.body.iter().map(truncate_dead_ends).collect(),
3917 })
3918 .collect(),
3919 default: default.as_deref().map(truncate_dead_ends).map(Box::new),
3920 on_string: *on_string,
3921 },
3922 Stmt::Try {
3923 body,
3924 catches,
3925 finally,
3926 } => Stmt::Try {
3927 body: Box::new(truncate_dead_ends(body)),
3928 catches: catches
3929 .iter()
3930 .map(|c| Catch {
3931 exc: c.exc.clone(),
3932 var: c.var,
3933 var_name: c.var_name.clone(),
3934 body: Box::new(truncate_dead_ends(&c.body)),
3935 })
3936 .collect(),
3937 finally: finally.as_deref().map(truncate_dead_ends).map(Box::new),
3938 },
3939 Stmt::TryWithResources {
3940 resources,
3941 body,
3942 catches,
3943 finally,
3944 } => Stmt::TryWithResources {
3945 resources: resources.iter().map(truncate_dead_ends).collect(),
3946 body: Box::new(truncate_dead_ends(body)),
3947 catches: catches
3948 .iter()
3949 .map(|c| Catch {
3950 exc: c.exc.clone(),
3951 var: c.var,
3952 var_name: c.var_name.clone(),
3953 body: Box::new(truncate_dead_ends(&c.body)),
3954 })
3955 .collect(),
3956 finally: finally.as_deref().map(truncate_dead_ends).map(Box::new),
3957 },
3958 Stmt::Synchronized { lock, body } => Stmt::Synchronized {
3959 lock: lock.clone(),
3960 body: Box::new(truncate_dead_ends(body)),
3961 },
3962 Stmt::Labeled { label, body } => Stmt::Labeled {
3963 label: label.clone(),
3964 body: Box::new(truncate_dead_ends(body)),
3965 },
3966 other => other.clone(),
3967 }
3968}
3969
3970/// True when `s` is a `while (true)` (labeled or not) whose body holds
3971/// no `break` binding to it — it can never complete normally.
3972pub(crate) fn dead_end_infinite_while(s: &Stmt) -> bool {
3973 let (label, body, cond) = match s {
3974 Stmt::While { cond, body } => (None, body.as_ref(), cond),
3975 Stmt::Labeled { label, body } => match &**body {
3976 Stmt::While { cond, body } => (Some(label.as_str()), body.as_ref(), cond),
3977 _ => return false,
3978 },
3979 _ => return false,
3980 };
3981 if !matches!(cond, Expr::Const(ConstVal::Int(1))) {
3982 return false;
3983 }
3984 // JLS 14.14: `while (true)` completes normally iff its body holds a
3985 // reachable break that exits IT: an unlabeled break not captured by
3986 // an inner breakable construct, or `break L` where L is THIS loop's
3987 // own label. A labeled break to any OTHER label — inner (Pattern
3988 // .clazz's `break L4`) or OUTER (jdk11/26 ThreadPoolExecutor
3989 // .addWorker's `break retry` inside the inner for(;;)) — completes
3990 // the while ABRUPTLY: javac's flow model marks the statements after
3991 // such a loop unreachable (the w26 `continue;` after the inner
3992 // while(true) was a latent 无法访问的语句 masked by alphabetically
3993 // earlier census errors).
3994 !has_break_exiting(body, label, 0)
3995}
3996
3997fn has_break_exiting(s: &Stmt, lbl: Option<&str>, depth: usize) -> bool {
3998 match s {
3999 Stmt::Break(None) => depth == 0,
4000 // Only this loop's OWN label completes it; any other labeled
4001 // break exits abruptly (JLS 14.14/14.17).
4002 Stmt::Break(Some(l)) => Some(l.as_str()) == lbl,
4003 Stmt::Continue(_) | Stmt::Return(_) | Stmt::Throw(_) => false,
4004 Stmt::Goto(_) => true,
4005 Stmt::Block(v) => v.iter().any(|x| has_break_exiting(x, lbl, depth)),
4006 Stmt::If {
4007 then_stmt,
4008 else_stmt,
4009 ..
4010 } => {
4011 has_break_exiting(then_stmt, lbl, depth)
4012 || else_stmt
4013 .as_deref()
4014 .map(|e| has_break_exiting(e, lbl, depth))
4015 .unwrap_or(false)
4016 }
4017 Stmt::While { body, .. }
4018 | Stmt::DoWhile { body, .. }
4019 | Stmt::For { body, .. }
4020 | Stmt::ForEach { body, .. } => has_break_exiting(body, lbl, depth + 1),
4021 Stmt::Switch { cases, default, .. } => {
4022 cases
4023 .iter()
4024 .any(|c| c.body.iter().any(|x| has_break_exiting(x, lbl, depth + 1)))
4025 || default
4026 .as_deref()
4027 .map(|d| has_break_exiting(d, lbl, depth + 1))
4028 .unwrap_or(false)
4029 }
4030 Stmt::Try {
4031 body,
4032 catches,
4033 finally,
4034 }
4035 | Stmt::TryWithResources {
4036 body,
4037 catches,
4038 finally,
4039 ..
4040 } => {
4041 has_break_exiting(body, lbl, depth)
4042 || catches
4043 .iter()
4044 .any(|c| has_break_exiting(&c.body, lbl, depth))
4045 || finally
4046 .as_deref()
4047 .map(|f| has_break_exiting(f, lbl, depth))
4048 .unwrap_or(false)
4049 }
4050 Stmt::Synchronized { body, .. } => has_break_exiting(body, lbl, depth),
4051 Stmt::Labeled { body, .. } => has_break_exiting(body, lbl, depth),
4052 // Plain leaf statements never contain a break.
4053 Stmt::ExprStmt(_)
4054 | Stmt::LocalDef { .. }
4055 | Stmt::Assert { .. }
4056 | Stmt::TernaryValue { .. }
4057 | Stmt::MonitorEnter(_)
4058 | Stmt::MonitorExit(_)
4059 | Stmt::Comment(_) => false,
4060 // Opaque shapes (raw labels, inline class decls, anything new):
4061 // keep truncation off when an escape cannot be ruled out.
4062 _ => true,
4063 }
4064}