ID |
|
|---|---|
Status |
Backlog |
Bucket |
architecture |
Priority |
4 |
Theme |
codegen-correctness |
Created |
2026-08-13 |
Updated |
2026-08-19 |
Enforce @condition table-parameter assignability against the anchor table
ServiceCatalog.reflectTableMethod claims the reserved Table<?> slot with
org.jooq.Table.class.isAssignableFrom(p.getType()), which admits any jOOQ table and is never
compared against the table the emit site will actually hand it. The emitted condition call passes a
concretely typed local (InputFieldConditionFixtures.addressDistrictAlberta(table_fkt0_0, addressId)
in the generated QueryConditions), so a @condition naming a helper typed on the wrong generated
table classifies clean and produces a javac error inside generated sources with no line back to the
SDL. Real fixtures already use both widened (Condition c(Table<?> table, ...)) and concrete
(Condition c(Address address, ...), Condition c(Customer table, ...)) parameter types, so both
forms have to keep working.
This is the @condition analogue of the @externalField gap covered by
roadmap/externalfield-parent-table-assignability.md, and it is deliberately not folded into that
item: the invariant is harder to state here. A @condition attaches at several coordinates
(field-level, argument-level, through a @reference path), the reference-path form takes two
table parameters (ReferencePathConditionFixtures.customerToAddress(Table<?> customerTable, Table<?> addressTable)),
and the table a given slot receives depends on which arm the emit site takes. So "the anchor table"
needs defining per arm, and per slot within the two-table arm, before a check can exist. The
@externalField item introduces the comparison shape (erased assignability against the class the
emitted signature is rendered from, plus a parameterised Table<R> layer against the record type);
this item’s work is deciding what each slot’s expected table is, not inventing the comparison.
The anchor definition is fixed, and it is stated by provenance per slot rather than as a per-coordinate
list, because not every anchor comes from the method signature. An anchor is the table the emitter will
pass in that slot, wherever that table was resolved. The provenances: the coordinate’s table for the
single-table arms; each participant’s table per branch for the multitable arm; for a path-step
{condition:} hop, the hop’s origin and the declared or slot-2-resolved target; and for a condition on
an FK-derived hop, reached through validateWhereFilterParamTables (the second caller of
validateConditionParamTables), the synthesizeFkJoin-resolved originTable and targetTable, which
the method signature never sees.
Overload admission has since shipped, so a @condition name may denote a set of declarations that agree
on the binding shape and differ only in their table slots. The check here is therefore per-anchor
applicability across that set: at least one declaration whose table slot accepts the anchor, with
most-specific selection left to the consumer’s javac. The slot already carries its decided catalog
answer (ParamSource.Table.TableSlot), so the comparison is a denotesSameTableAs scan over the
admitted tables rather than a type-name decode.