Skip to content

Fix inherited extension slices incorrectly appearing in child differential#1609

Merged
cmoesel merged 3 commits intoFHIR:masterfrom
glichtner:fix/inherited-extension-slice-in-differential
Feb 19, 2026
Merged

Fix inherited extension slices incorrectly appearing in child differential#1609
cmoesel merged 3 commits intoFHIR:masterfrom
glichtner:fix/inherited-extension-slice-in-differential

Conversation

@glichtner
Copy link
Contributor

@glichtner glichtner commented Jan 29, 2026

Problem

When a child profile inherits from a parent that defines an extension slice (e.g., on content.component.extension in the example below), and the child adds its own slicing on a parent element (e.g., content with an outcome slice), the inherited extension slice incorrectly appears in the child's differential under the new slice path (which later leads to validation errors in the IG Publisher).

How to reproduce

Minimal reproduction case: https://github.com/glichtner/fsh-extension-slice-repro

Extension: MyExtension
Id: my-extension
Context: Element
* value[x] only string

Profile: Parent
Parent: ArtifactAssessment
Id: parent
* content
  * component
    * extension contains MyExtension named MyExtension 0..*

Profile: Child
Parent: Parent
Id: child
* content ^slicing.discriminator.type = #value
* content ^slicing.discriminator.path = "type"
* content ^slicing.rules = #open
* content contains outcome 0..1
* content[outcome]
  * type 1..1

Expected

The Child profile's differential should only contain elements that were actually constrained:

"differential": {
    "element": [
      {
        "id": "ArtifactAssessment.content",
        "path": "ArtifactAssessment.content",
        "slicing": {
          "discriminator": [
            {
              "type": "value",
              "path": "type"
            }
          ],
          "rules": "open"
        }
      },
      {
        "id": "ArtifactAssessment.content:outcome",
        "path": "ArtifactAssessment.content",
        "sliceName": "outcome",
        "min": 0,
        "max": "1"
      },
      {
        "id": "ArtifactAssessment.content:outcome.type",
        "path": "ArtifactAssessment.content.type",
        "min": 1
      }
    ]
  }

Actual

The differential incorrectly contains the inherited extension slice:

  "differential": {
    "element": [
      {
        "id": "ArtifactAssessment.content",
        "path": "ArtifactAssessment.content",
        "slicing": {
          "discriminator": [
            {
              "type": "value",
              "path": "type"
            }
          ],
          "rules": "open"
        }
      },
      {
        "id": "ArtifactAssessment.content:outcome",
        "path": "ArtifactAssessment.content",
        "sliceName": "outcome",
        "min": 0,
        "max": "1"
      },
      {
        "id": "ArtifactAssessment.content:outcome.type",
        "path": "ArtifactAssessment.content.type",
        "min": 1
      },
      {
        "id": "ArtifactAssessment.content:outcome.component.extension:MyExtension",
        "path": "ArtifactAssessment.content.component.extension",
        "sliceName": "MyExtension"
      }
    ]
  }

Cause

In cloneChildren(), when recaptureSliceExtensions=false, extension slices retain their original _original via e.clone(false), but the cloned element's id is updated to include the new slice prefix. Since id is compared in hasDiff(), this mismatch causes the inherited extension slice to appear in the differential even though nothing actually changed.

Suggested fix

Update _original.id to match the cloned element's new id when not recapturing, so the id comparison doesn't produce false positives in hasDiff().

…ntial

When a child profile inherits from a parent that defines an extension slice
and the child adds its own slicing on a parent element, the inherited
extension slice was incorrectly appearing in the child's differential.

Root cause: In cloneChildren(), when recaptureSliceExtensions=false,
extension slices retain their _original via e.clone(false), but the
cloned element's id is updated to include the new slice prefix. Since
id is compared in hasDiff(), this mismatch caused inherited extension
slices to appear in the differential even though nothing changed.

Fix: Update _original.id to match the cloned element's new id when not
recapturing, so the id comparison doesn't produce false positives.
@cmoesel
Copy link
Member

cmoesel commented Feb 16, 2026

Thank you for this contribution, @glichtner. I apologize for the time it has taken me to review it. I've run this against the 100 most recently updated FSH projects and confirmed it works as expected. There are changes in output compared to the latest release, but those changes make sense in light of this PR and do not negatively impact those projects. So that's good!

We prefer to have changes accompanied by unit tests demonstrating the change. I appreciate the simple test case you put in the description. Might you be able to add a similar unit test? I'd suggest add a test similar to this test (or updating that test if that makes more sense). If you need any help, let me know.

Thanks again!

Copy link
Member

@cmoesel cmoesel left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for adding the test! I confirmed that the test fails without your fix and succeeds with your fix. Perfect!

Thanks again for your contribution!

@cmoesel cmoesel merged commit 7f40fa5 into FHIR:master Feb 19, 2026
7 checks passed
@glichtner
Copy link
Contributor Author

Happy to help. Thank you for your work on sushi!

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.

2 participants