Skip to main content

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, &param_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}