Skip to main content

jdc_core/
emit.rs

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