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