Skip to content

Reuse shared content types instead of duplicating them per owner - #290

Merged
Bergmann89 merged 3 commits into
masterfrom
fix/289-shared-group-content
Sep 4, 2026
Merged

Bergmann89 merged 3 commits into
masterfrom
fix/289-shared-group-content

Conversation

@Bergmann89

Copy link
Copy Markdown
Owner

Two complex types referencing the same xs:group produced two structurally
identical content enums instead of one shared type.

REMOVE_DUPLICATES was never at fault: it merges the content meta types
correctly and resolve_typedefs points both parents at the same ident. The
generator then discarded that ident and synthesized a fresh <Parent>Content
copy per owner, undoing the optimizer's work.

Commits

  1. Reuse shared content types instead of duplicating them per owner - the
    fix. A content type with more than one owner is now referenced through the
    existing StructMode::Content seam. Sharing makes a reference cycle real,
    so need_box learned the complex type to content type edge.
  2. Clear the group mixed flag when simplifying mixed content - a stale
    GroupMeta::is_mixed kept non-mixed groups out of the sharing. Unblocks an
    onix group with 30 owners.
  3. Follow group elements when looking ahead for recursive types - a loop
    through two distinct shared content types needs two group hops to be
    visible to need_box.

Impact

Generated output changes for REMOVE_DUPLICATES users, so this needs at
least a minor bump. +161/-32 lines of source across five files, all under
pipeline/. Expectations shrink by ~23k lines net (ofd, xml_schema,
onix, xccdf_1_2); in onix 33 duplicate content enums collapse into 4.
Public API unchanged.

Tests

New shared_group_content case with six variants: the reported schema,
1..1, mixed content, a recursive schema and a two-group cycle (both
Generate::All, the repo's first such coverage), plus a quick_xml round
trip. Each expectation is paired with an include! block, so a missing Box
fails to compile rather than to diff. Every case verified to go red without
its fix.

Related to #289

When two complex types reference the same `xs:group`, the optimizer merges
the identical content meta types and the second `resolve_typedefs` run
rewrites both parents to the same content ident. The generator then threw
that ident away: `ComplexData::new_complex` read the content type only for
its shape, so `new_enum` and `new_struct` synthesized a fresh content type
named after each parent, re-creating the duplication after the optimizer
had already removed it. `REMOVE_DUPLICATES` was never at fault.

The generator now references the shared content type through the existing
`StructMode::Content` seam whenever the content meta type has more than one
owner. Mixed content stays excluded, because the optimizer already rewrites
it to a `Text` element and sharing it would apply `Mixed<..>` a second time.

Sharing the type creates a reference cycle that the boxing analysis did not
model before: `need_box` never traversed the complex type to content type
edge, which was harmless while every content type was nested below its only
owner. It now recurses into the shared content type when the type is reached
directly, otherwise a recursive schema with a `1..1` group reference emits
types of infinite size. A shared content ident set precomputed once on
`State` keeps the added lookups off the hot path.

Tests cover the reported schema, the `1..1` variant that exercises the boxing
path, a quick_xml round trip, and a recursive schema generated with
`Generate::All` whose expectation fails to compile if the boxing regresses.
`simplify_mixed_type` rewrites mixed content into explicit `Text` elements and
clears `ComplexMeta::is_mixed` afterwards, but left `GroupMeta::is_mixed` set.
The flag was stale from that point on: the text content is explicit, so nothing
downstream should still treat the group as mixed. `remove_duplicates` runs
before this pass and never observes the change.

The generator reads the flag when it decides whether a content type may be
shared between complex types, so a stale flag kept groups out of the sharing
that are not mixed any more. In `onix` this affected a group with 30 owners,
which now collapses into one shared type; `xccdf_1_2` shows the same pattern.
`need_box` terminated on `All`, `Choice` and `Sequence`, so the lookahead
never walked a group's element list. That was harmless while every content
type was nested below its only owner, because such a type can not close a
loop on its own. Shared content types can, and a loop that runs through two
different shared content types needs two group hops to be seen.

Without the traversal the generator emits unboxed variants for such a loop
and the generated code does not compile at all. The walk only follows
elements that are referenced directly, so a `Vec` or `Option` in the loop
still ends it, and it only extends paths that already crossed a shared
content edge. No existing expectation changes.
@Bergmann89 Bergmann89 linked an issue Sep 4, 2026 that may be closed by this pull request
@Bergmann89
Bergmann89 merged commit 4787567 into master Sep 4, 2026
2 checks passed
@Bergmann89
Bergmann89 deleted the fix/289-shared-group-content branch September 4, 2026 20:03
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Optimization request: merging identical Content enums

1 participant