Skip to content

feat: Support FHIRPath union() and combine() combining functions#2587

Open
piotrszul wants to merge 4 commits intorelease/9.6.0from
issue/2384
Open

feat: Support FHIRPath union() and combine() combining functions#2587
piotrszul wants to merge 4 commits intorelease/9.6.0from
issue/2384

Conversation

@piotrszul
Copy link
Copy Markdown
Collaborator

Summary

  • Closes Support combining functions #2384 by implementing the FHIRPath union(other) and combine(other) functions from §6.5 Combining.
  • Both function forms are parser-desugared into EvalOperator ASTs so that x.union(y) is strictly equivalent to x | y (including inside iteration contexts such as name.select(use.union(given))), and x.combine(y) reaches a new peer CombineOperator that merges without deduplicating.
  • Shared CombiningLogic helper holds the array-level merge primitives (Decimal normalization, comparator-aware dedup/concat) used by both UnionOperator and CombineOperator.

Why parser desugaring

The spec explicitly declares x.union(y) to be synonymous with x | y, and the worked example name.select(use.union(given)) ≡ name.select(use | given) requires the given argument to resolve against the current iteration focus. Routing the argument through Pathling's standard function invocation path would evaluate given against %context (the outer root focus), losing the per-name iteration. Desugaring x.union(y) and x.combine(y) at parse time into operator-form ASTs gives this equivalence by construction, without refactoring Composite.apply / EvalFunction / FunctionParameterResolver.

The full design rationale lives in openspec/changes/archive/2026-04-11-add-combining-functions/design.md.

Test plan

  • mvn test -pl fhirpath -Dtest=CombiningFunctionsDslTest — 78 new assertions covering the type matrix, duplicate-preservation, union() ≡ \| equivalence across 9 types (Boolean, Integer, Decimal, String, Date, DateTime, Time, Quantity, Coding), iteration-context pin-downs inside select, incompatible-type errors, and arity errors
  • mvn test -pl fhirpath -Dtest=CombiningOperatorsDslTest — 188/188 pass (refactor behaviour-preserving for \|)
  • mvn test -pl fhirpath -Dtest=YamlReferenceImplTest — 1821 run, 0 failures, 979 skipped; many previously-failing reference tests for .combine(...) now pass
  • mvn test -pl fhirpath — no new failures introduced
  • fhirpath-js/config.yaml exclusions reviewed: extended #2398 (polymorphic) and #437 (primitive-with-extensions) to cover the new function forms; added an entry for a latent Quantity + repeat() canonicalization bug that combine() now surfaces (tracked as a separate follow-up)

Documentation

site/docs/fhirpath/index.md gains a new "Combining functions" section listing union() and combine(), and the | operator row cross-references the union() function.

🤖 Generated with Claude Code

piotrszul and others added 2 commits April 11, 2026 17:42
Closes #2384. Implements the FHIRPath combining functions from the
specification's §6.5 Combining section as parser-level desugarings into
the existing `|` operator AST (for `union`) and a new `CombineOperator`
(for `combine`). Desugaring is the only approach that preserves the
spec's `name.select(use.union(given)) ≡ name.select(use | given)`
equivalence, because routing the argument through the standard function
invocation path would lose the surrounding iteration focus.

A shared `CombiningLogic` helper holds the array-level merge primitives
used by both `UnionOperator` and `CombineOperator`.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Reuse stateless UnionOperator and CombineOperator instances via a static
map instead of allocating them on every parse of `union()` / `combine()`.
Remove javadoc paragraphs that re-explained the parser desugaring in each
operator, since that rationale lives with the parser where the rewrite
happens.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@github-project-automation github-project-automation bot moved this to Backlog in Pathling Apr 11, 2026
@piotrszul piotrszul added new feature New feature or request fhirpath Related to fhirpath reference implementation labels Apr 11, 2026
@piotrszul piotrszul moved this from Backlog to In progress in Pathling Apr 11, 2026
Reference #2588 from the repeat()-after-combine() exclusion so the latent
Quantity struct shape mismatch is tracked alongside the existing feature
exclusions that follow the same id format.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@sonarqubecloud
Copy link
Copy Markdown

Quantity literals built by QuantityEncoding.encodeLiteral used lit(null) for
absent fields, producing Spark NullType (VOID) in the struct schema.
to_variant_object() rejects VOID fields, breaking repeat($this) over any
Quantity literal collection via variantTransformTree.

Cast the struct to dataType() after construction and fix
FlexiDecimalSupport.toLiteral to return a typed null. The (3 'min').combine(180
seconds).repeat($this) exclusion is removed (now passes); the (1 year).combine
(12 months) case is re-classified as wontfix (equality semantics, not VOID).

Closes #2588

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

fhirpath Related to fhirpath reference implementation new feature New feature or request

Projects

Status: In progress

Development

Successfully merging this pull request may close these issues.

1 participant