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