Skip to main content

zerodds_lang_rules/
default_java.rs

1// SPDX-License-Identifier: Apache-2.0
2// Copyright 2026 ZeroDDS Contributors
3//! The value a Java member takes when the stream does not carry it.
4//!
5//! Table 9 of DDS-XTypes 1.3, as Java. [`crate::default_c`] is the same
6//! table as C and `crates/wire-probe/src/default.rs` is the reflective
7//! column's; that module carries the clause quotations and the reasoning
8//! for the three kinds Table 9 does not list. Deliberately a third
9//! derivation and not a call into either: the columns are compared
10//! against each other, and a shared helper would make them agree about a
11//! mistake.
12//!
13//! ## Why it is a generated method and not a literal
14//!
15//! A literal cannot carry an aggregate. "A structure without any of the
16//! optional members and with other members set to their default values"
17//! is a `new S()` and one assignment per member, and writing that at
18//! every position a member can go missing would put the same answer in
19//! the generated file as many times as the type is reached — where two
20//! copies can differ. One method per type is one answer per type, which
21//! is what the C column does and for the same reason.
22//!
23//! ## Why it answers with the value instead of filling one
24//!
25//! C's defaulter is `void zd_def_T(T *v)`: it writes through a pointer.
26//! Java has no pointer and no out-parameter, and half of Table 9's rows
27//! are values Java cannot mutate at all — a primitive, a `String`, an
28//! array whose length has to change. So the method **returns** Table 9's
29//! value and the caller assigns it, which is the same decision one step
30//! later. It is also what makes the reader's own shape work: every
31//! generated decoder opens with `T v = zd_def_T();`, so a position the
32//! stream does not carry is already what Table 9 says before a single
33//! octet is read.
34//!
35//! ## What Java forces that C does not
36//!
37//! `octet` is an `int` and `unsigned long` is a `long`, so the zero rows
38//! of Table 9 are spelled by the *holder* and not by the IDL width —
39//! `adapter/java.rs` has the table. A `sequence<T>` is a `T[]` and its
40//! "zero-length sequence" is `new T[0]` and not a null: null is what an
41//! `@optional` member's absence means here, and one value cannot mean
42//! both. §7.2.2.4.4.4.7 asks for a zero-length sequence, and
43//! §7.2.2.4.4.4.8's "Implementations shall not provide a default value"
44//! for an `@optional` member is the null.
45
46use crate::adapter::cst::MaterialiseError;
47use crate::adapter::java::{declared_path, java_member_type, type_tag, unsigned_holder};
48use crate::default_common::{discriminator_default_value, resolve_alias, selected_case};
49use crate::encode::EncodeError;
50use crate::fixed_literal::{fixed_text, zero_decimal};
51use zerodds_ast::{
52    Ast, ConstExpr, Literal as AstLiteral, MemberNode, PrimitiveKind, TypeKind, TypeNode,
53};
54use zerodds_ast_core::Id;
55use zerodds_lang_cst::common::{Doc, Ident, Literal, Path};
56use zerodds_lang_cst::java;
57
58/// The defaulter method name for a type.
59///
60/// Keyed the same way [`crate::emit::decode::decoder_name`] is, so a
61/// primitive's defaulter is named by its IDL kind: `double` and
62/// `long double` share no Java type at all, and `octet` and
63/// `unsigned short` share one.
64#[must_use]
65pub fn defaulter_name(ast: &Ast, id: Id<TypeNode>) -> String {
66    match ast.type_(id).map(|node| node.kind.clone()) {
67        Ok(TypeKind::Primitive(kind)) => {
68            format!("zd_def_p_{}", kind.as_str().replace(' ', "_"))
69        }
70        _ => format!(
71            "zd_def_{}",
72            type_tag(ast, id, 0).unwrap_or_else(|_| format!("unresolved_type_{}", id.slot()))
73        ),
74    }
75}
76
77/// The defaulter that goes with a decoder, by name.
78///
79/// The two schemes differ in one word — `zd_dec_p_long` against
80/// `zd_def_p_long` — because both are built from the same key. Doing it
81/// by text lets `crate::emit::java::Java::routine` name the defaulter
82/// without carrying the model into a method that has only a signature;
83/// a name that does not resolve is a `javac` error naming the method.
84#[must_use]
85pub fn defaulter_from_decoder(decoder: &str) -> String {
86    match decoder.strip_prefix("zd_dec_") {
87        Some(rest) => format!("zd_def_{rest}"),
88        None => format!("zd_def_{decoder}"),
89    }
90}
91
92/// `place = zd_def_<T>();`
93///
94/// # Errors
95/// Never; the signature matches the emitters around it.
96pub fn default_into(ast: &Ast, id: Id<TypeNode>, place: java::Expr) -> java::Statement {
97    assign(place, call(&defaulter_name(ast, id), Vec::new()))
98}
99
100/// One member's default: its `@default`, or its type's.
101///
102/// §7.2.2.4.4.4.9 puts the explicit one first — "An explicit definition
103/// associates a default value with the member at the time where the
104/// aggregated type is defined" — and falls back to "the default value of
105/// the member type. See Table 9."
106///
107/// # Errors
108/// [`EncodeError::NoWriter`] where a `@default` names a value this
109/// column cannot spell as a Java expression. Refused rather than
110/// silently replaced by Table 9's zero: a member the designer gave a
111/// default and a member that got a zero instead are different samples.
112pub fn member_default(
113    ast: &Ast,
114    node: &MemberNode,
115    type_: Id<TypeNode>,
116    place: java::Expr,
117) -> Result<Vec<java::Statement>, EncodeError> {
118    let Some(expr) = node.facets.xtypes.as_ref().and_then(|x| x.default_value) else {
119        return Ok(vec![default_into(ast, type_, place)]);
120    };
121    let value = explicit(ast, expr, type_, &node.name)?;
122    Ok(vec![assign(place, value)])
123}
124
125/// The defaulters `members` call, and exactly those.
126///
127/// The set is read **off the emitted calls** rather than derived a
128/// second time from the model, for the reason
129/// [`crate::default_c::c_defaulters_called_by`] gives at length: a
130/// derivation beside the decoder's is a second reading of §7.2.2.7, and
131/// the two readings drift. Here the seed set is larger than C's —
132/// every generated reader opens with its own type's defaulter — and the
133/// closure is taken the same way, by emitting and re-reading until the
134/// batch calls nothing it does not also define.
135///
136/// # Errors
137/// [`EncodeError`] for a construct with no Java default.
138pub fn java_defaulters_called_by(
139    ast: &Ast,
140    members: &[java::Member],
141) -> Result<Vec<java::Member>, EncodeError> {
142    let index: Vec<(String, Id<TypeNode>)> = ast
143        .types()
144        .map(|(id, _)| (defaulter_name(ast, id), id))
145        .collect();
146    let mut called: Vec<String> = Vec::new();
147    for member in members {
148        calls_in_member(member, &mut called);
149    }
150    let mut wanted: Vec<Id<TypeNode>> = Vec::new();
151    let mut have: Vec<String> = Vec::new();
152    // Bounded by the number of named defaulters, because every round
153    // but the last adds at least one. Written down rather than left to
154    // the loop, so a name that never resolves cannot spin.
155    for _ in 0..=index.len() {
156        let mut grew = false;
157        for name in &called {
158            if have.contains(name) {
159                continue;
160            }
161            have.push(name.clone());
162            // A name with no type behind it cannot happen for a call
163            // this generator emitted — every one came from
164            // `defaulter_name` over a type in this model — and if it
165            // ever does, the omission is a `javac` error naming the
166            // method, which is louder than anything this could return.
167            if let Some(id) = index.iter().find(|(n, _)| n == name).map(|(_, id)| *id) {
168                wanted.push(id);
169                grew = true;
170            }
171        }
172        let produced = java_defaulters_over(ast, &wanted)?;
173        if !grew {
174            return Ok(produced);
175        }
176        for member in &produced {
177            calls_in_member(member, &mut called);
178        }
179    }
180    java_defaulters_over(ast, &wanted)
181}
182
183/// Build the defaulter for each of `types`, once per name.
184///
185/// # Errors
186/// [`EncodeError`] for a construct with no Java default.
187pub fn java_defaulters_over(
188    ast: &Ast,
189    types: &[Id<TypeNode>],
190) -> Result<Vec<java::Member>, EncodeError> {
191    let mut out = Vec::new();
192    let mut emitted: Vec<String> = Vec::new();
193    for id in types.iter().copied() {
194        let name = defaulter_name(ast, id);
195        if emitted.contains(&name) {
196            continue;
197        }
198        let Some(body) = body_of(ast, id, &name)? else {
199            continue;
200        };
201        emitted.push(name.clone());
202        out.push(java::Member::Method(java::Method {
203            doc: Doc::line(
204                "Table 9 of DDS-XTypes 1.3: what this type is where the stream did not \
205                 carry it.",
206            ),
207            annotations: Vec::new(),
208            // Package-private: the function-call classes of an interface
209            // (`ZdServices`) take the default of a `@Mutable` member a peer
210            // did not send from here (XTypes 1.3 §7.2.4.4.8).
211            modifiers: vec![java::Modifier::Static],
212            generics: Vec::new(),
213            returns: value_type(ast, id),
214            name: Ident::new(&name),
215            params: Vec::new(),
216            throws: Vec::new(),
217            body: Some(java::Block { statements: body }),
218        }));
219    }
220    Ok(out)
221}
222
223/// The Java type a defaulter answers with.
224fn value_type(ast: &Ast, id: Id<TypeNode>) -> java::Type {
225    java_member_type(ast, id, "default").unwrap_or_else(|_| {
226        java::Type::named(Path::new(
227            &[format!("UnresolvedType{}", id.slot()).as_str()],
228        ))
229    })
230}
231
232/// The statements of one defaulter, or `None` where the type has no
233/// Java data form at all.
234#[allow(clippy::too_many_lines)]
235fn body_of(
236    ast: &Ast,
237    id: Id<TypeNode>,
238    own_name: &str,
239) -> Result<Option<Vec<java::Statement>>, EncodeError> {
240    let refuse = |kind: &'static str| EncodeError::NoWriter {
241        member: own_name.to_string(),
242        kind,
243    };
244    // A type the model does not hold. It used to answer `Ok(None)`,
245    // which the caller reads as "nothing to emit for this one" and
246    // skips — so a type whose handle no longer resolves quietly loses
247    // its defaulter, and the first sign of it is a member left at
248    // whatever the language zero-initialises to. Every other arm of
249    // this function either answers a value or refuses by name; this
250    // one now does too.
251    let Ok(node) = ast.type_(id) else {
252        return Err(refuse("a type the model does not hold"));
253    };
254    let statements = match &node.kind {
255        TypeKind::Primitive(kind) => vec![give_back(primitive_default(*kind))],
256
257        // `""`, for both widths: a Java `String` is UTF-16 already and
258        // the empty string is the same string in either.
259        TypeKind::String { .. } => vec![give_back(text(""))],
260
261        // A `fixed<P,S>` is the decimal it was written as, and
262        // `canonical::decimal` accepts exactly one spelling of zero for
263        // a given scale.
264        TypeKind::Fixed { scale, .. } => {
265            vec![give_back(big_decimal(&zero_decimal(*scale)))]
266        }
267
268        TypeKind::Alias { aliased } => {
269            let Some(target) = aliased.resolved() else {
270                return Err(refuse("typedef with an unresolved target"));
271            };
272            vec![give_back(call(&defaulter_name(ast, target), Vec::new()))]
273        }
274
275        // "The first literal in the enumeration", or the one
276        // `@default_literal` names — §7.3.1.2.1.10.
277        TypeKind::Enumeration { .. } => {
278            let Some(enumerator) = crate::emit::default_enumerator_name(ast, id) else {
279                return Err(refuse(
280                    "enumeration with no enumerator to take as its default",
281                ));
282            };
283            vec![give_back(enumerator_constant(ast, id, &enumerator))]
284        }
285
286        // No bit set: the holder's Table 9 default is zero and zero
287        // sets none. The holder decides the suffix — a `@bit_bound(40)`
288        // mask is a `long`.
289        TypeKind::Bitmask { .. } => {
290            let holder = unsigned_holder(ast.effective_bit_bound(id));
291            vec![give_back(zero_of(&holder))]
292        }
293
294        TypeKind::Bitset { .. } => {
295            let mut statements = vec![new_local(value_type(ast, id))];
296            for field in &ast.all_bitfields_of(id) {
297                let Ok(field_node) = ast.bitfield(*field) else {
298                    continue;
299                };
300                // An anonymous bitfield is padding and holds no value.
301                if let Some(field_name) = field_node.name.as_ref() {
302                    statements.push(assign(
303                        member("v", &java::escape_keyword(field_name)),
304                        zero_of(&unsigned_holder(field_node.bits)),
305                    ));
306                }
307            }
308            statements.push(give_back(name("v")));
309            statements
310        }
311
312        // "A zero-length sequence of the same element type", and the
313        // same for a map's entries. An empty array and not a null: a
314        // null is what an `@optional` member's absence means here, and
315        // one value cannot mean both.
316        TypeKind::Sequence { .. } | TypeKind::Map { .. } => match value_type(ast, id) {
317            // A map with a class of its own (XTypes 1.3 §7.5.1.3.5): an empty
318            // one, which is not a null for the reason above.
319            java::Type::Named { path, args }
320                if args.is_empty()
321                    && path.segments().last().is_some_and(|last| {
322                        last.as_str().starts_with(crate::adapter::java::MAP_PREFIX)
323                    }) =>
324            {
325                vec![give_back(java::Expr::New {
326                    type_: Box::new(java::Type::named(path)),
327                    args: Vec::new(),
328                })]
329            }
330            java::Type::Array(element) => vec![give_back(new_array(
331                element,
332                vec![java::Expr::Literal(Literal::int(0))],
333            ))],
334            // DDS-Java-PSM 1.0 §9.1.2: an unbounded sequence is a
335            // `Collection<E>`; the empty one is an empty list.
336            java::Type::Named { args, .. }
337                if crate::adapter::java::mapping() == crate::adapter::java::JavaMapping::Psm
338                    && !args.is_empty() =>
339            {
340                vec![give_back(java::Expr::New {
341                    type_: Box::new(java::Type::generic(
342                        Path::new(crate::adapter::java::ARRAY_LIST),
343                        args,
344                    )),
345                    args: Vec::new(),
346                })]
347            }
348            _ => {
349                return Err(refuse(
350                    "collection whose Java form is neither an array nor a Collection",
351                ));
352            }
353        },
354
355        // "An array of the same dimensions and same element type whose
356        // elements take the default value for their corresponding
357        // type." Java's array is allocated here and not declared with a
358        // length, so every dimension has to evaluate.
359        TypeKind::Array { element, dims } => {
360            let Some(element) = element.resolved() else {
361                return Err(refuse("array with an unresolved element"));
362            };
363            let mut lengths = Vec::new();
364            for dim in dims {
365                lengths.push(
366                    ast.eval_integral(*dim)
367                        .map_err(|_| refuse("array whose length does not evaluate"))?,
368                );
369            }
370            let mut statements = vec![java::Statement::Declare {
371                final_: false,
372                type_: value_type(ast, id),
373                name: Ident::new("v"),
374                value: Some(new_array(
375                    Box::new(value_type(ast, element)),
376                    lengths
377                        .iter()
378                        .map(|length| java::Expr::Literal(Literal::int(*length)))
379                        .collect(),
380                )),
381            }];
382            let mut access = name("v");
383            for level in 0..lengths.len() {
384                access = java::Expr::Index {
385                    value: Box::new(access),
386                    index: Box::new(name(&format!("zd_i{level}"))),
387                };
388            }
389            let mut inner = java::Block {
390                statements: vec![default_into(ast, element, access)],
391            };
392            for (level, length) in lengths.iter().enumerate().rev() {
393                inner = java::Block {
394                    statements: vec![counted_loop(
395                        &format!("zd_i{level}"),
396                        java::Expr::Literal(Literal::int(*length)),
397                        inner,
398                    )],
399                };
400            }
401            statements.extend(inner.statements);
402            statements.push(give_back(name("v")));
403            statements
404        }
405
406        // "A structure without any of the optional members and with
407        // other members set to their default values."
408        TypeKind::Structure { .. } | TypeKind::Valuetype { .. } => {
409            let mut statements = vec![new_local(value_type(ast, id))];
410            for member_id in &ast.all_members_of(id) {
411                let Ok(member_node) = ast.member(*member_id) else {
412                    continue;
413                };
414                let field = java::escape_keyword(&member_node.name);
415                if member_node.facets.is_optional() {
416                    // "Implementations shall not provide a default
417                    // value in such a case" — which here is the null
418                    // the boxed field already holds. Written out
419                    // anyway, so a defaulter run over a sample that
420                    // has been read once before clears it.
421                    statements.push(assign(
422                        member("v", &field),
423                        java::Expr::Literal(Literal::Null),
424                    ));
425                    continue;
426                }
427                let Some(member_type) = member_node.type_.resolved() else {
428                    return Err(EncodeError::NoWriter {
429                        member: member_node.name.clone(),
430                        kind: "member with an unresolved type",
431                    });
432                };
433                statements.extend(member_default(
434                    ast,
435                    member_node,
436                    member_type,
437                    member("v", &field),
438                )?);
439            }
440            statements.push(give_back(name("v")));
441            statements
442        }
443
444        // "A union with the discriminator set to the default value for
445        // the discriminator type. If this selects a branch, then the
446        // selected member is also set to the default value for the
447        // member type."
448        TypeKind::Union { discriminator, .. } => {
449            let Some(discriminator) = discriminator.resolved() else {
450                return Err(refuse("union with an unresolved discriminator"));
451            };
452            let mut statements = vec![
453                new_local(value_type(ast, id)),
454                default_into(ast, discriminator, member("v", "_d")),
455            ];
456            if let Some((member_type, field)) = selected_by_default(ast, id, discriminator)? {
457                statements.push(default_into(ast, member_type, member("v", &field)));
458            }
459            statements.push(give_back(name("v")));
460            statements
461        }
462
463        TypeKind::Native | TypeKind::ObjectRef { .. } | TypeKind::AnnotationType { .. } => {
464            return Ok(None);
465        }
466        TypeKind::ValueBox { .. } => {
467            return Err(refuse(
468                "boxed value, whose presence flag has no settled wire form",
469            ));
470        }
471        // Refused by name rather than passed over. `any` is in the
472        // model because IDL 4.2 has it; DDS-XTypes has no `any` and no
473        // encoding for one, and only CORBA CDR writes it — as a
474        // TypeCode ahead of the value. Returning `Ok(None)` here would
475        // generate a reader that silently omitted the member.
476        TypeKind::Any => {
477            return Err(refuse(
478                "`any`, which DDS-XTypes has no encoding for; only CORBA CDR writes one, \
479                 as a TypeCode ahead of the value",
480            ));
481        }
482        TypeKind::ValueBase => {
483            return Err(refuse(
484                "`ValueBase`, which DDS-XTypes has no encoding for; only CORBA CDR writes \
485                 one, as a chunked value with its repository id",
486            ));
487        }
488    };
489    Ok(Some(statements))
490}
491
492/// Table 9's primitive rows, as the Java holder each takes.
493///
494/// The holder and not the IDL width: `octet` is an `int` here and
495/// `unsigned long` is a `long`, so the zero that goes in one is not the
496/// zero that goes in the other. `adapter/java.rs` has the table.
497fn primitive_default(kind: PrimitiveKind) -> java::Expr {
498    match kind {
499        PrimitiveKind::Boolean => java::Expr::Literal(Literal::Bool(false)),
500        // `'\0'`, written as the character it is: the field is a Java
501        // `char` and a bare `0` would be an `int` needing a cast.
502        PrimitiveKind::Char | PrimitiveKind::WChar => {
503            java::Expr::Literal(Literal::Int("'\\0'".to_string()))
504        }
505        PrimitiveKind::Float32 => java::Expr::Literal(Literal::Float("0.0f".to_string())),
506        PrimitiveKind::Float64 => java::Expr::Literal(Literal::Float("0.0".to_string())),
507        // A binary128 zero is its zero bit pattern, and `Float128`
508        // names it so the spelling does not depend on the class layout.
509        PrimitiveKind::Float128 => java::Expr::Path(Path::new(&["Float128", "ZERO"])),
510        PrimitiveKind::Int8 | PrimitiveKind::Octet => java::Expr::Cast {
511            type_: Box::new(java::Type::Primitive(java::Primitive::Byte)),
512            value: Box::new(java::Expr::Literal(Literal::int(0))),
513        },
514        PrimitiveKind::Int16 => java::Expr::Cast {
515            type_: Box::new(java::Type::Primitive(java::Primitive::Short)),
516            value: Box::new(java::Expr::Literal(Literal::int(0))),
517        },
518        PrimitiveKind::Int64 | PrimitiveKind::UInt32 | PrimitiveKind::UInt64 => {
519            java::Expr::Literal(Literal::Int("0L".to_string()))
520        }
521        PrimitiveKind::UInt8 | PrimitiveKind::UInt16 | PrimitiveKind::Int32 => {
522            java::Expr::Literal(Literal::int(0))
523        }
524    }
525}
526
527/// The zero of a holder the materialiser chose.
528fn zero_of(holder: &java::Type) -> java::Expr {
529    if *holder == java::Type::Primitive(java::Primitive::Long) {
530        return java::Expr::Literal(Literal::Int("0L".to_string()));
531    }
532    java::Expr::Literal(Literal::int(0))
533}
534
535/// `ZdTypes.<modules>.<Enum>.<LITERAL>` — the instance of the enumeration
536/// class the Java materialiser declares (CORBA 3.3 part 3 §1.5.9), written
537/// from the outer class down.
538pub(crate) fn enumerator_constant(ast: &Ast, id: Id<TypeNode>, enumerator: &str) -> java::Expr {
539    let owner = declared_path(ast, id).unwrap_or_else(|_| {
540        // A path the module chain will not answer for is a name
541        // `javac` reports; a bare constant would compile against
542        // whatever else happened to be in scope.
543        format!("UnresolvedEnum{}", id.slot())
544    });
545    java::Expr::Path(Path::new(
546        &format!("{owner}.{}", java::escape_keyword(enumerator))
547            .split('.')
548            .collect::<Vec<_>>(),
549    ))
550}
551
552/// The case a defaulted discriminator selects, if any.
553fn selected_by_default(
554    ast: &Ast,
555    id: Id<TypeNode>,
556    discriminator: Id<TypeNode>,
557) -> Result<Option<(Id<TypeNode>, String)>, EncodeError> {
558    let Some(selector) = discriminator_default_value(ast, discriminator) else {
559        return Ok(None);
560    };
561    Ok(selected_case(ast, id, selector)?
562        .map(|(member, member_type)| (member_type, java::escape_keyword(&member.name))))
563}
564
565/// An explicit `@default` as a Java expression for `type_`.
566#[allow(clippy::too_many_lines)]
567fn explicit(
568    ast: &Ast,
569    expr: Id<zerodds_ast::ConstExprNode>,
570    type_: Id<TypeNode>,
571    member_name: &str,
572) -> Result<java::Expr, EncodeError> {
573    let refuse = |kind: &'static str| EncodeError::NoWriter {
574        member: member_name.to_string(),
575        kind,
576    };
577    let node = ast
578        .expr(expr)
579        .map_err(|_| refuse("a `@default` this frontend cannot follow"))?;
580    let resolved = resolve_alias(ast, type_);
581    let kind = ast.type_(resolved).map(|node| node.kind.clone());
582    match &node.expr {
583        ConstExpr::Literal(AstLiteral::Boolean(flag)) => {
584            Ok(java::Expr::Literal(Literal::Bool(*flag)))
585        }
586        // A code point and not a Java character escape: the member is a
587        // `char`, and `(char) 228` is the same code unit with no
588        // question about how the source file was encoded.
589        ConstExpr::Literal(AstLiteral::Char(ch)) => Ok(char_literal(u32::from(*ch))),
590        ConstExpr::Literal(AstLiteral::WChar(code)) => Ok(char_literal(*code)),
591        ConstExpr::Literal(AstLiteral::String(value)) => Ok(text(value)),
592        ConstExpr::Literal(AstLiteral::WString(codes)) => {
593            let mut out = String::new();
594            for code in codes {
595                let Some(ch) = char::from_u32(*code) else {
596                    return Err(refuse("a `@default` wide string that is not code points"));
597                };
598                out.push(ch);
599            }
600            Ok(text(&out))
601        }
602        ConstExpr::Literal(AstLiteral::Float { mantissa, exponent }) => {
603            let suffix = match kind {
604                Ok(TypeKind::Primitive(PrimitiveKind::Float32)) => "f",
605                _ => "",
606            };
607            let exponent = exponent
608                .as_ref()
609                .map_or(String::new(), |text| format!("e{text}"));
610            Ok(java::Expr::Literal(Literal::Float(format!(
611                "{mantissa}{exponent}{suffix}"
612            ))))
613        }
614        // A `fixed<P,S>` is the decimal text here, as the member's type
615        // is a `String`.
616        ConstExpr::Literal(AstLiteral::Fixed { digits, scale }) => {
617            Ok(big_decimal(&fixed_text(digits, *scale)))
618        }
619        ConstExpr::Literal(AstLiteral::Enumerator(reference)) => {
620            let target = reference
621                .resolved()
622                .ok_or_else(|| refuse("a `@default` enumerator that is unresolved"))?;
623            let enumerator = ast
624                .enumerator(target)
625                .map_err(|_| refuse("a `@default` enumerator this frontend cannot follow"))?;
626            Ok(enumerator_constant(ast, resolved, &enumerator.name))
627        }
628        _ => {
629            // An integer literal, a named constant or arithmetic over
630            // them, folded the same way a bound or a case label is.
631            let value = ast
632                .eval_integral(expr)
633                .map_err(|_| refuse("a `@default` this frontend cannot evaluate"))?;
634            Ok(match kind {
635                Ok(TypeKind::Primitive(PrimitiveKind::Char | PrimitiveKind::WChar)) => {
636                    char_literal(u32::try_from(value).unwrap_or(0))
637                }
638                Ok(TypeKind::Primitive(PrimitiveKind::Int8)) => java::Expr::Cast {
639                    type_: Box::new(java::Type::Primitive(java::Primitive::Byte)),
640                    value: Box::new(java::Expr::Literal(Literal::int(value))),
641                },
642                Ok(TypeKind::Primitive(PrimitiveKind::Int16)) => java::Expr::Cast {
643                    type_: Box::new(java::Type::Primitive(java::Primitive::Short)),
644                    value: Box::new(java::Expr::Literal(Literal::int(value))),
645                },
646                Ok(TypeKind::Primitive(
647                    PrimitiveKind::Int64 | PrimitiveKind::UInt32 | PrimitiveKind::UInt64,
648                )) => wide(value),
649                _ => java::Expr::Literal(Literal::int(value)),
650            })
651        }
652    }
653}
654
655/// A 64-bit constant as Java writes one: the pattern, with an `L`.
656fn wide(value: i128) -> java::Expr {
657    #[allow(clippy::cast_possible_truncation, clippy::cast_possible_wrap)]
658    let pattern = if value > i128::from(i64::MAX) {
659        value as u64 as i64
660    } else {
661        value as i64
662    };
663    java::Expr::Literal(Literal::Int(format!("{pattern}L")))
664}
665
666/// `(char) <code point>`.
667fn char_literal(code: u32) -> java::Expr {
668    java::Expr::Cast {
669        type_: Box::new(java::Type::Primitive(java::Primitive::Char)),
670        value: Box::new(java::Expr::Literal(Literal::int(i128::from(code)))),
671    }
672}
673
674/// `new java.math.BigDecimal("<text>")`: a `fixed<P,S>` is a `BigDecimal` (CORBA 3.3 part 3
675/// §1.5.6).
676fn big_decimal(decimal: &str) -> java::Expr {
677    java::Expr::New {
678        type_: Box::new(java::Type::named(Path::new(&[
679            "java",
680            "math",
681            "BigDecimal",
682        ]))),
683        args: vec![text(decimal)],
684    }
685}
686
687// -- reading the calls back out ----------------------------------------
688
689fn calls_in_member(member: &java::Member, out: &mut Vec<String>) {
690    match member {
691        java::Member::Method(method) => {
692            if let Some(body) = &method.body {
693                calls_in_block(body, out);
694            }
695        }
696        java::Member::Field(field) => {
697            if let Some(value) = &field.value {
698                calls_in_expr(value, out);
699            }
700        }
701        java::Member::Constructor(constructor) => calls_in_block(&constructor.body, out),
702        java::Member::Initialiser { body, .. } => calls_in_block(body, out),
703        java::Member::Nested(_) | java::Member::Comment(_) | java::Member::Verbatim(_) => {}
704    }
705}
706
707/// zerodds-lint: recursion-depth 64 (statement nesting the walk builds).
708/// The tree walked here is the one this generator emitted and owns, so
709/// it holds no cycle; the depth is that body's height, and a defaulter
710/// body is a handful of statements deep.
711fn calls_in_block(block: &java::Block, out: &mut Vec<String>) {
712    for statement in &block.statements {
713        calls_in_statement(statement, out);
714    }
715}
716
717/// zerodds-lint: recursion-depth 64 (statement nesting the walk builds).
718/// The tree walked here is the one this generator emitted and owns, so
719/// it holds no cycle; the depth is that body's height, and a defaulter
720/// body is a handful of statements deep.
721#[allow(clippy::too_many_lines)]
722fn calls_in_statement(statement: &java::Statement, out: &mut Vec<String>) {
723    match statement {
724        java::Statement::Declare { value, .. } => {
725            if let Some(value) = value {
726                calls_in_expr(value, out);
727            }
728        }
729        java::Statement::Expr(expr) | java::Statement::Throw(expr) => calls_in_expr(expr, out),
730        java::Statement::Return(value) => {
731            if let Some(value) = value {
732                calls_in_expr(value, out);
733            }
734        }
735        java::Statement::If {
736            condition,
737            then,
738            otherwise,
739        } => {
740            calls_in_expr(condition, out);
741            calls_in_block(then, out);
742            if let Some(otherwise) = otherwise {
743                calls_in_block(otherwise, out);
744            }
745        }
746        java::Statement::ForEach { range, body, .. } => {
747            calls_in_expr(range, out);
748            calls_in_block(body, out);
749        }
750        java::Statement::For {
751            init,
752            condition,
753            step,
754            body,
755        } => {
756            calls_in_statement(init, out);
757            calls_in_expr(condition, out);
758            calls_in_expr(step, out);
759            calls_in_block(body, out);
760        }
761        java::Statement::While { condition, body } => {
762            calls_in_expr(condition, out);
763            calls_in_block(body, out);
764        }
765        java::Statement::Switch { value, cases } => {
766            calls_in_expr(value, out);
767            for case in cases {
768                for label in &case.labels {
769                    calls_in_expr(label, out);
770                }
771                calls_in_block(&case.body, out);
772            }
773        }
774        java::Statement::Break | java::Statement::Comment(_) => {}
775    }
776}
777
778/// zerodds-lint: recursion-depth 64 (expression nesting the walk builds).
779/// The tree walked here is the one this generator emitted and owns, so
780/// it holds no cycle; the depth is that expression's height, which
781/// follows the member nesting the type walk already bounded.
782#[allow(clippy::too_many_lines)]
783fn calls_in_expr(expr: &java::Expr, out: &mut Vec<String>) {
784    match expr {
785        java::Expr::MethodCall {
786            receiver,
787            name,
788            args,
789        } => {
790            // Unqualified only: a defaulter is a static method of the
791            // generated class and this generator built every call to
792            // one with `defaulter_name`.
793            if receiver.is_none() {
794                let name = name.as_str();
795                if name.starts_with("zd_def_") && !out.iter().any(|seen| seen == name) {
796                    out.push(name.to_string());
797                }
798            }
799            if let Some(receiver) = receiver {
800                calls_in_expr(receiver, out);
801            }
802            for arg in args {
803                calls_in_expr(arg, out);
804            }
805        }
806        java::Expr::Field { value, .. }
807        | java::Expr::Cast { value, .. }
808        | java::Expr::InstanceOf { value, .. }
809        | java::Expr::Paren(value) => calls_in_expr(value, out),
810        java::Expr::Index { value, index } => {
811            calls_in_expr(value, out);
812            calls_in_expr(index, out);
813        }
814        java::Expr::New { args, .. } => {
815            for arg in args {
816                calls_in_expr(arg, out);
817            }
818        }
819        java::Expr::NewArray {
820            lengths, elements, ..
821        } => {
822            for length in lengths {
823                calls_in_expr(length, out);
824            }
825            for element in elements {
826                calls_in_expr(element, out);
827            }
828        }
829        java::Expr::Binary { lhs, rhs, .. } => {
830            calls_in_expr(lhs, out);
831            calls_in_expr(rhs, out);
832        }
833        java::Expr::Ternary {
834            condition,
835            then,
836            otherwise,
837        } => {
838            calls_in_expr(condition, out);
839            calls_in_expr(then, out);
840            calls_in_expr(otherwise, out);
841        }
842        java::Expr::Lambda { body, .. } => calls_in_expr(body, out),
843        java::Expr::ArrayInit(elements) => {
844            for element in elements {
845                calls_in_expr(element, out);
846            }
847        }
848        java::Expr::Literal(_)
849        | java::Expr::Path(_)
850        | java::Expr::This
851        | java::Expr::Annotation(_) => {}
852    }
853}
854
855// -- the small helpers --------------------------------------------------
856
857fn name(text: &str) -> java::Expr {
858    java::Expr::Path(Path::new(&text.split('.').collect::<Vec<_>>()))
859}
860
861fn text(value: &str) -> java::Expr {
862    java::Expr::Literal(Literal::Str(value.to_string()))
863}
864
865fn call(callee: &str, args: Vec<java::Expr>) -> java::Expr {
866    java::Expr::MethodCall {
867        receiver: None,
868        name: Ident::new(callee),
869        args,
870    }
871}
872
873/// The member `field` of `base`: the field itself, or under
874/// [`crate::adapter::java::JavaMapping::Psm`] the accessor that reads it
875/// (and, in [`assign`], writes it).
876fn member(base: &str, field: &str) -> java::Expr {
877    if crate::adapter::java::mapping() == crate::adapter::java::JavaMapping::Psm {
878        return crate::adapter::java::accessor(name(base), field);
879    }
880    java::Expr::Field {
881        value: Box::new(name(base)),
882        name: Ident::new(field),
883    }
884}
885
886fn assign(place: java::Expr, value: java::Expr) -> java::Statement {
887    crate::adapter::java::assign(place, value)
888}
889
890/// `new T[n]…`, or for a `T` that is a parameterized type
891/// `Sequences.newArray(T.class, n…)`.
892///
893/// Java refuses `new java.util.Collection<java.lang.Integer>[3]` (JLS
894/// 15.10.1, generic array creation), and under
895/// [`crate::adapter::java::JavaMapping::Psm`] an array of unbounded
896/// sequences has exactly that element type. The runtime creates the array
897/// from the erasure and the assignment gives it the parameterized type.
898fn new_array(element: Box<java::Type>, lengths: Vec<java::Expr>) -> java::Expr {
899    let mut innermost = &*element;
900    while let java::Type::Array(inner) = innermost {
901        innermost = inner;
902    }
903    if let java::Type::Named { path, args } = innermost {
904        if !args.is_empty() {
905            let mut segments: Vec<&str> = path
906                .segments()
907                .iter()
908                .map(|segment| segment.as_str())
909                .collect();
910            segments.push("class");
911            let mut arguments = vec![java::Expr::Path(Path::new(&segments))];
912            arguments.extend(lengths);
913            return java::Expr::MethodCall {
914                receiver: Some(Box::new(java::Expr::Path(Path::new(&[
915                    crate::adapter::java::SEQUENCES,
916                ])))),
917                name: Ident::new("newArray"),
918                args: arguments,
919            };
920        }
921    }
922    java::Expr::NewArray {
923        element,
924        lengths,
925        elements: Vec::new(),
926    }
927}
928
929fn give_back(value: java::Expr) -> java::Statement {
930    java::Statement::Return(Some(value))
931}
932
933/// `<T> v = new <T>();`
934fn new_local(type_: java::Type) -> java::Statement {
935    java::Statement::Declare {
936        final_: false,
937        type_: type_.clone(),
938        name: Ident::new("v"),
939        value: Some(java::Expr::New {
940            type_: Box::new(type_),
941            args: Vec::new(),
942        }),
943    }
944}
945
946fn counted_loop(index: &str, count: java::Expr, body: java::Block) -> java::Statement {
947    java::Statement::For {
948        init: Box::new(java::Statement::Declare {
949            final_: false,
950            type_: java::Type::Primitive(java::Primitive::Int),
951            name: Ident::new(index),
952            value: Some(java::Expr::Literal(Literal::int(0))),
953        }),
954        condition: java::Expr::Binary {
955            op: "<",
956            lhs: Box::new(name(index)),
957            rhs: Box::new(count),
958        },
959        step: java::Expr::Binary {
960            op: "=",
961            lhs: Box::new(name(index)),
962            rhs: Box::new(java::Expr::Binary {
963                op: "+",
964                lhs: Box::new(name(index)),
965                rhs: Box::new(java::Expr::Literal(Literal::int(1))),
966            }),
967        },
968        body,
969    }
970}
971
972/// Kept so the module's one dependency on the materialiser's error type
973/// is stated rather than inferred.
974const _: Option<MaterialiseError> = None;