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