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