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