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