Repository: FHIR/sushi (ts)

Issue:
SUSHI does not recursively set fixed values on instances
See this https://chat.fhir.org/#narrow/stream/215610-shorthand/topic/shorthand.20.26.20TestScript.20instances for more details. SUSHI will set a fixed value on an instance in one of two cases:
1) The element is named in a path on the instance
2) The parent of the element is named in a path on an instance and the element is min 1.

SUSHI should be able to follow paths of `1..1` elements recursively and look for any fixed children at the end of the path. For example in this profile:
![Testscript-profile](https://user-images.githubusercontent.com/52747882/77645465-2272f680-6f39-11ea-9f09-a7f0ee0af168.png)

If `.test.name` is set in the Instance, SUSHI should be smart enough to recognize that the slices of `action` are `1..1`, and `action.assert` is `1..1` and `action.assert.description` is `1..1`, so the slices can be constructed with `description` set.

Pull request:
Recursively set Fixed Values on Instances
This fixes #300. 

This refactors the `setFixedValues` function on the `InstanceExporter`. The `setFixedValuesFromStructureDefinition` function is removed, and its functionality is replaced directly in the `setFixedValues` function. In addition to this refactor, this PR adds the ability to set values automatically that are deeply nested on the StructureDefinition. For example, if `a`, `a.b` , and `a.b.c` are `1..1` and `a.b.c = "somefixedvalue"`, then the instance will now automatically populate:
```
{
"a": {
       "b": {
              "c": "somefixedvalue"
               }
      }
}
```
You will also note that I removed a debug message that we used to be emitting when a `0..n` element was fixed. I didn't think this debug message is necessary, since automatically fixing elements that are optional would be unexpected behavior in my opinion. If we feel this message needs to be present, I can add it back.

Patch:
diff --git a/src/export/InstanceExporter.ts b/src/export/InstanceExporter.ts
index ff2d9e8..653da5a 100644
--- a/src/export/InstanceExporter.ts
+++ b/src/export/InstanceExporter.ts
@@ -1,5 +1,5 @@
 import { FSHTank } from '../import/FSHTank';
-import { StructureDefinition, InstanceDefinition, ElementDefinition } from '../fhirtypes';
+import { StructureDefinition, InstanceDefinition, ElementDefinition, PathPart } from '../fhirtypes';
 import { Instance } from '../fshtypes';
 import { logger, Fishable, Type, Metadata } from '../utils';
 import {
@@ -7,7 +7,8 @@ import {
   replaceReferences,
   replaceField,
   splitOnPathPeriods,
-  applyMixinRules
+  applyMixinRules,
+  getAllImpliedPaths
 } from '../fhirtypes/common';
 import { InstanceOfNotDefinedError } from '../errors/InstanceOfNotDefinedError';
 import { Package } from '.';
@@ -25,14 +26,6 @@ export class InstanceExporter implements Fishable {
     instanceDef: InstanceDefinition,
     instanceOfStructureDefinition: StructureDefinition
   ): InstanceDefinition {
-    // Fix values from the SD for all elements at the top level of the SD
-    this.setFixedValuesFromStructureDefinition(
-      instanceOfStructureDefinition.findElement(instanceDef.resourceType),
-      '',
-      instanceDef,
-      instanceOfStructureDefinition
-    );
-
     let rules = fshInstanceDef.rules.map(r => cloneDeep(r));
     rules = rules.map(r => replaceReferences(r, this.tank, this.fisher));
     // Convert strings in fixedValueRules to instances
@@ -53,41 +46,15 @@ export class InstanceExporter implements Fishable {
       return true;
     });
 
-    // Fix all values from the SD that are exposed when processing the instance rules
-    rules.forEach(rule => {
-      try {
-        const validated = instanceOfStructureDefinition.validateValueAtPath(
-          rule.path,
-          rule.fixedValue,
-          this.fisher
-        );
-
-        // For each part of that path, we add fixed values from the SD
-        let path = '';
-        for (const [i, pathPart] of validated.pathParts.entries()) {
-          path += `${path ? '.' : ''}${pathPart.base}`;
-          // Add back non-numeric (slice) brackets
-          pathPart.brackets?.forEach(b => (path += /^[-+]?\d+$/.test(b) ? '' : `[${b}]`));
-          const element = instanceOfStructureDefinition.findElementByPath(path, this.fisher);
-          // Reconstruct the part of the rule's path that we just got the element for
-          let rulePathPart = splitOnPathPeriods(rule.path)
-            .slice(0, i + 1)
-            .join('.');
-          rulePathPart += '.';
-
-          this.setFixedValuesFromStructureDefinition(
-            element,
-            rulePathPart,
-            instanceDef,
-            instanceOfStructureDefinition
-          );
-        }
-      } catch (e) {
-        logger.error(e.message, rule.sourceInfo);
-      }
-    });
-
-    // Fix all values explicitly set in the instance rules (all rules will be FixValueRule)
+    // When fixing values, things happen in the order:
+    // 1 - Validate values for rules that are on the instance
+    // 2 - Determine all rules implied by the Structure Definition
+    // 3 - Set values from rules implied by the Structure Definition
+    // 4 - Set values from rules directly on the instance
+    // This order is required due to the fact that validateValueAtPath changes instanceOfStructureDefinition
+    // in certain cases that must happen before setting rules from the Structure Definition. In the future
+    // we may want to refactor validateValueAtPath, but for now things should happen in this order
+    const ruleMap: Map<string, { pathParts: PathPart[]; fixedValue: any }> = new Map();
     rules.forEach(rule => {
       try {
         const { fixedValue, pathParts } = instanceOfStructureDefinition.validateValueAtPath(
@@ -96,76 +63,68 @@ export class InstanceExporter implements Fishable {
           this.fisher,
           rule.units
         );
-        // Fix value fom the rule
-        setPropertyOnInstance(instanceDef, pathParts, fixedValue);
+        // Record each valid rule in a map
+        ruleMap.set(rule.path, { pathParts, fixedValue });
       } catch (e) {
         logger.error(e.message, rule.sourceInfo);
       }
     });
 
-    return instanceDef;
-  }
-
-  /**
-   * Given an ElementDefinition, set fixed values for the direct children of that element
-   * according to the ElementDefinitions of the children
-   * This function assumes that when it is called on an element, it has alreday been called on parents of that element
-   * @param {ElementDefinition} element - The element whose children we will fix
-   * @param {string} existingPath - The path to the element whose children we will fix
-   * @param {InstanceDefinition} instanceDef - The InstanceDefinition to fix values on
-   * @param {StructureDefinition} instanceOfStructureDefinition - The structure definition the instance instantiates
-   */
-  private setFixedValuesFromStructureDefinition(
-    element: ElementDefinition,
-    existingPath: string,
-    instanceDef: InstanceDefinition,
-    instanceOfStructureDefinition: StructureDefinition
-  ) {
-    // We will fix values on the element, or direct children
-    const fixableElements = [element, ...element.children(true)];
-    for (const fixableElement of fixableElements) {
-      // Fixed values may be specified by the fixed[x] or pattern[x] fields
-      const fixedValueKey = Object.keys(fixableElement).find(
-        k => k.startsWith('fixed') || k.startsWith('pattern')
-      );
-      // Fixed value can come from fixed[x] or pattern[x] directly on element, or via pattern[x] on parent
-      const foundFixedValue = cloneDeep(fixableElement[fixedValueKey as keyof ElementDefinition]);
-      // We only fix the value if the element is the original element, or it is a direct child with card 1..n
-      if (foundFixedValue && (fixableElement.id === element.id || fixableElement.min > 0)) {
-        // Get the end of the path, this is the part that differs from existingPath
-        let fixablePath = fixableElement.diffId().replace(`${element.diffId()}`, '');
-        // If the fixableElement is a child of the original element, we must remove prefixed '.' from path
-        if (fixablePath.startsWith('.')) fixablePath = fixablePath.slice(1);
+    // Record the values implied by the structure definition in sdRuleMap
+    const sdRuleMap: Map<string, any> = new Map();
+    const paths = ['', ...rules.map(rule => rule.path)];
+    paths.forEach(path => {
+      const nonNumericPath = path.replace(/\[[-+]?\d+\]/g, '');
+      const element = instanceOfStructureDefinition.findElementByPath(nonNumericPath, this.fisher);
+      if (element) {
+        // If an element is pointed to by a path, that means we must fix values on its parents, its own
+        // fixable descendents, and fixable descenendents of its parents. A fixable descendent is a 1..n direct child
+        // or a fixable descendent of such a child
+        const parents = element.getAllParents();
+        const associatedElements = [element, ...element.getFixableDescendents(), ...parents];
+        parents.map(p => p.getFixableDescendents()).forEach(pd => associatedElements.push(...pd));
 
-        // Turn FHIR slicing (element:slicName/resliceName) into FSH slicing (element[sliceName][resliceName])
-        const colonSplitPath = fixablePath.split(':');
-        let fshElementPath = colonSplitPath[0];
-        const slicePathSection = colonSplitPath[1];
-        const sliceNames = slicePathSection?.split('/');
-        sliceNames?.forEach(s => {
-          fshElementPath += `[${s}]`;
-        });
-        // Fix the value if we validly can
-        try {
-          const { pathParts } = instanceOfStructureDefinition.validateValueAtPath(
-            // fshElementPath is '' when the fixableElement is the original element, trailing '.' on this path must be removed
-            // Otherwise add the child path to existing path
-            fshElementPath === '' ? existingPath.slice(0, -1) : existingPath + fshElementPath,
-            // We don't actually want to validate the value, we only want to validate the path, so pass null
-            // we already know the value is valid, since we got it from the Structure Definition
-            null,
-            this.fisher
+        for (const associatedEl of associatedElements) {
+          const fixedValueKey = Object.keys(associatedEl).find(
+            k => k.startsWith('fixed') || k.startsWith('pattern')
           );
-          setPropertyOnInstance(instanceDef, pathParts, foundFixedValue);
-        } catch (e) {
-          logger.error(e.message);
+          const foundFixedValue = cloneDeep(associatedEl[fixedValueKey as keyof ElementDefinition]);
+          if (foundFixedValue) {
+            // Find how much the two paths overlap, for example, a.b.c, and a.b.d overlap for a.b
+            let overlapIndex = 0;
+            element.id.split('.').forEach((p, i) => {
+              if (p === associatedEl.id.split('.')[i]) overlapIndex++;
+            });
+            // We must keep the relevant portion of the beginning of path to preserve sliceNames
+            // and combine this with portion of the associatedEl's path that is not overlapped
+            const pathStart = splitOnPathPeriods(path).slice(0, overlapIndex - 1);
+            const pathEnd = associatedEl
+              .diffId()
+              .split('.')
+              .slice(overlapIndex)
+              // Replace FHIR slicing with FSH slicing, a:b.c:d -> a[b].c[d]
+              .map(r => r.replace(/(\S):(\S+)/, (match, c1, c2) => `${c1}[${c2}]`));
+            const finalPath = [...pathStart, ...pathEnd].join('.');
+            const impliedPaths = getAllImpliedPaths(
+              associatedEl,
+              // Implied paths are found via the index free path
+              finalPath.replace(/\[[-+]?\d+\]/g, '')
+            );
+            [finalPath, ...impliedPaths].forEach(ip => sdRuleMap.set(ip, foundFixedValue));
+          }
         }
-      } else if (foundFixedValue) {
-        logger.debug(
-          `Element ${fixableElement.id} is optional with min cardinality 0, so fixed value for optional element is not set on instance ${instanceDef._instanceMeta.name}`
-        );
       }
-    }
+    });
+    sdRuleMap.forEach((value, path) => {
+      const { pathParts } = instanceOfStructureDefinition.validateValueAtPath(
+        path,
+        null,
+        this.fisher
+      );
+      setPropertyOnInstance(instanceDef, pathParts, value);
+    });
+    ruleMap.forEach(rule => setPropertyOnInstance(instanceDef, rule.pathParts, rule.fixedValue));
+    return instanceDef;
   }
 
   /**
diff --git a/src/fhirtypes/ElementDefinition.ts b/src/fhirtypes/ElementDefinition.ts
index cf577af..10a9fa9 100644
--- a/src/fhirtypes/ElementDefinition.ts
+++ b/src/fhirtypes/ElementDefinition.ts
@@ -1449,6 +1449,20 @@ export class ElementDefinition {
     }
   }
 
+  /**
+   * Finds and returns all parent elements.  For example, the parent elements of `Foo.bar.one` are [`Foo.bar`, `Foo`].
+   * @returns {ElementDefinition[]} the array of parents, empty if no parents
+   */
+  getAllParents(): ElementDefinition[] {
+    const parents = [];
+    let parent = this.parent();
+    while (parent) {
+      parents.push(parent);
+      parent = parent.parent();
+    }
+    return parents;
+  }
+
   /**
    * Finds and returns all child elements of this element.  For example, the children of `Foo.bar` might be the
    * elements `Foo.bar.one`, `Foo.bar.two`, and `Foo.bar.two.a`.  This will not "expand" or "unroll" elements; it
@@ -1465,6 +1479,19 @@ export class ElementDefinition {
       );
     });
   }
+  /**
+   * Finds and returns all fixable descendents of the element. A fixable descendent is a direct child of the
+   * element that has minimum cardinality greater than 0, and all fixable descendents of such children
+   * @returns {ElementDefinition[]} the fixable descendents of this element
+   */
+  getFixableDescendents(): ElementDefinition[] {
+    const fixableChildren = this.children(true).filter(e => e.min > 0);
+    let fixableDescendents: ElementDefinition[] = [];
+    fixableChildren.forEach(fc => {
+      fixableDescendents = fixableDescendents.concat(fc.getFixableDescendents());
+    });
+    return [...fixableChildren, ...fixableDescendents];
+  }
 
   /**
    * Finds and returns the elemnent being sliced
diff --git a/src/fhirtypes/common.ts b/src/fhirtypes/common.ts
index 887e4cc..cce52cb 100644
--- a/src/fhirtypes/common.ts
+++ b/src/fhirtypes/common.ts
@@ -256,6 +256,54 @@ export function applyMixinRules(
   fshDefinition.rules = [...mixedInRules, ...fshDefinition.rules];
 }
 
+/**
+ * Finds all FSH paths implied by the FSH path pointing at element. Paths are implied by array elements.
+ * For example, if foo is 2..* and bar is 2..*, and bar has a fixed value of "hello", then the rule
+ * "foo[0].baz = "hey" " implies the following:
+ * foo[0].baz = "hey"
+ * foo[0].bar[0] = "hello"
+ * foo[1].bar[0] = "hello"
+ * foo[0].bar[1] = "hello"
+ * foo[1].bar[1] = "hello"
+ * @param {ElementDefinition} element - The element that the path corresponds to
+ * @param {path} string - The FSH path to the element
+ * @returns {string[]} - All implied FSH paths by the path pointing to element
+ */
+export function getAllImpliedPaths(element: ElementDefinition, path: string): string[] {
+  const parentPaths = [];
+  const parent = element.parent();
+  if (parent) {
+    if (parent.min === 0) {
+      // If the parent has min = 0, then the path above this point has no additional implied paths
+      // so add the path to this point to the parentPaths
+      parentPaths.push(splitOnPathPeriods(path).slice(0, -1).join('.'));
+    } else {
+      // If min >= 1, the parent or its parents my have implied paths, recursively find those
+      parentPaths.push(
+        ...this.getAllImpliedPaths(parent, splitOnPathPeriods(path).slice(0, -1).join('.'))
+      );
+    }
+  } else {
+    parentPaths.push('');
+  }
+  const pathEnd = splitOnPathPeriods(path).slice(-1)[0];
+  const pathEnds = [pathEnd];
+  if (element.max === '*' || parseInt(element.max) > 1) {
+    // Index 0 element doesn't need index, since it is implied
+    for (let i = 1; i < element.min; i++) {
+      pathEnds.push(`${pathEnd}[${i}]`);
+    }
+  }
+  const finalPaths = [];
+  for (const parentPath of parentPaths) {
+    for (const pathEnd of pathEnds) {
+      // Combine the parentPaths with the pathEnds
+      finalPaths.push(`${parentPath == '' ? '' : parentPath + '.'}${pathEnd}`);
+    }
+  }
+  return finalPaths;
+}
+
 const nameRegex = /^[A-Z]([A-Za-z0-9_]){0,254}$/;
 
 export class HasName {


Test patch:
diff --git a/test/export/InstanceExporter.test.ts b/test/export/InstanceExporter.test.ts
index e2d8333..df98bc4 100644
--- a/test/export/InstanceExporter.test.ts
+++ b/test/export/InstanceExporter.test.ts
@@ -127,11 +127,13 @@ describe('InstanceExporter', () => {
 
   describe('#exportInstance', () => {
     let patient: Profile;
+    let respRate: Profile;
     let patientProf: Profile;
     let patientInstance: Instance;
     let patientProfInstance: Instance;
     let lipidInstance: Instance;
     let valueSetInstance: Instance;
+    let respRateInstance: Instance;
     beforeEach(() => {
       patient = new Profile('TestPatient');
       patient.parent = 'Patient';
@@ -139,6 +141,9 @@ describe('InstanceExporter', () => {
       patientProf = new Profile('TestPatientProf');
       patientProf.parent = 'patient-proficiency';
       doc.profiles.set(patientProf.name, patientProf);
+      respRate = new Profile('TestRespRate');
+      respRate.parent = 'resprate';
+      doc.profiles.set(respRate.name, respRate);
       patientInstance = new Instance('Bar')
         .withFile('PatientInstance.fsh')
         .withLocation([10, 1, 20, 30]);
@@ -155,6 +160,9 @@ describe('InstanceExporter', () => {
       valueSetInstance = new Instance('Boom');
       valueSetInstance.instanceOf = 'ValueSet';
       doc.instances.set(valueSetInstance.name, valueSetInstance);
+      respRateInstance = new Instance('Bang');
+      respRateInstance.instanceOf = 'TestRespRate';
+      doc.instances.set(respRateInstance.name, respRateInstance);
     });
 
     // Setting Metadata
@@ -245,9 +253,6 @@ describe('InstanceExporter', () => {
       patient.rules.push(fixedValRule);
       const exported = exportInstance(patientInstance);
       expect(exported.active).toBeUndefined();
-      expect(loggerSpy.getLastMessage()).toMatch(
-        'Element Patient.active is optional with min cardinality 0, so fixed value for optional element is not set on instance Bar'
-      );
     });
 
     it('should fix top level elements to an array even if constrained on the Structure Definition', () => {
@@ -322,34 +327,26 @@ describe('InstanceExporter', () => {
     });
 
     it('should fix a value onto slice elements that are fixed by a pattern on the Structure Definition', () => {
-      const resprate = new Profile('TestResprate');
-      resprate.parent = 'resprate';
-      doc.profiles.set(resprate.name, resprate);
       const containsRule = new ContainsRule('category');
       containsRule.items = [{ name: 'niceSlice' }];
-      resprate.rules.push(containsRule); // * identifier contains niceSlice
+      respRate.rules.push(containsRule); // * category contains niceSlice
       const cardRule = new CardRule('category[niceSlice]');
       cardRule.min = 1;
       cardRule.max = '*';
-      resprate.rules.push(cardRule); // * category[niceSlice] 1..*
+      respRate.rules.push(cardRule); // * category[niceSlice] 1..*
       const fixedValRule = new FixedValueRule('category[niceSlice]');
       const fixedFshCode = new FshCode('rice', 'http://spice.com');
       fixedValRule.fixedValue = fixedFshCode;
-      resprate.rules.push(fixedValRule); // * category[niceSlice] = http://spice.com#rice
-      const resprateInstance = new Instance('myResprate');
-      resprateInstance.instanceOf = 'TestResprate';
-      doc.instances.set(resprateInstance.name, resprateInstance);
-      const exported = exportInstance(resprateInstance);
-      expect(exported.category).toEqual([
-        {
-          coding: [
-            {
-              code: 'rice',
-              system: 'http://spice.com'
-            }
-          ]
-        }
-      ]);
+      respRate.rules.push(fixedValRule); // * category[niceSlice] = http://spice.com#rice
+      const exported = exportInstance(respRateInstance);
+      expect(exported.category).toContainEqual({
+        coding: [
+          {
+            code: 'rice',
+            system: 'http://spice.com'
+          }
+        ]
+      });
     });
 
     it('should fix top level choice elements that are fixed on the Structure Definition', () => {
@@ -511,6 +508,135 @@ describe('InstanceExporter', () => {
       ]);
     });
 
+    // Deeply Nested Elements
+    it('should fix a deeply nested element that is fixed on the Structure Definition and has 1..1 parents', () => {
+      // * telecom.period 1..1
+      // * telecom.period.start 1..1
+      // * telecom.period.start = "2000-07-04"
+      const periodCard = new CardRule('telecom.period');
+      periodCard.min = 1;
+      periodCard.max = '1';
+      const startCard = new CardRule('telecom.period.start');
+      startCard.min = 1;
+      startCard.max = '1';
+      const fixedValRule = new FixedValueRule('telecom.period.start');
+      fixedValRule.fixedValue = '2000-07-04';
+
+      patient.rules.push(periodCard, startCard, fixedValRule);
+      const instanceFixedValRule = new FixedValueRule('telecom[0].system');
+      instanceFixedValRule.fixedValue = new FshCode('email');
+      patientInstance.rules.push(instanceFixedValRule); // * telecom[0].system = #email
+      const exported = exportInstance(patientInstance);
+      expect(exported.telecom[0]).toEqual({
+        system: 'email',
+        period: {
+          start: '2000-07-04'
+        }
+      });
+    });
+
+    it('should fix a deeply nested element that is fixed on the Structure Definition and has array parents with min > 1', () => {
+      // * identifier 2..*
+      // * identifier.type.coding 2..*
+      // * identifier.type.coding.display 1..1
+      // * identifier.type.coding.display = "This is my coding"
+      const idCard = new CardRule('identifier');
+      idCard.min = 2;
+      idCard.max = '*';
+      const typeCard = new CardRule('identifier.type.coding');
+      typeCard.min = 2;
+      typeCard.max = '*';
+      const displayCard = new CardRule('identifier.type.coding.display');
+      displayCard.min = 1;
+      displayCard.max = '1';
+      const fixedValRule = new FixedValueRule('identifier.type.coding.display');
+      fixedValRule.fixedValue = 'This is my coding';
+
+      patient.rules.push(idCard, typeCard, displayCard, fixedValRule);
+      const instanceFixedValRule = new FixedValueRule('identifier.type.coding[2].version');
+      instanceFixedValRule.fixedValue = '1.2.3';
+      patientInstance.rules.push(instanceFixedValRule); // * identifier.type.coding[2].version = "1.2.3"
+      const exported = exportInstance(patientInstance);
+      expect(exported.identifier).toEqual([
+        {
+          type: {
+            coding: [
+              {
+                display: 'This is my coding'
+              },
+              {
+                display: 'This is my coding'
+              },
+              {
+                display: 'This is my coding',
+                version: '1.2.3'
+              }
+            ]
+          }
+        }
+      ]);
+    });
+
+    it('should fix a deeply nested element that is fixed on the Structure Definition and has slice array parents with min > 1', () => {
+      // * category contains niceSlice
+      // * category[niceSlice] 1..1
+      // * category[niceSlice] = http://spice.com#rice
+      const containsRule = new ContainsRule('category');
+      containsRule.items = [{ name: 'niceSlice' }];
+      respRate.rules.push(containsRule);
+      const cardRule = new CardRule('category[niceSlice]');
+      cardRule.min = 1;
+      cardRule.max = '1';
+      respRate.rules.push(cardRule);
+      const fixedValRule = new FixedValueRule('category[niceSlice]');
+      const fixedFshCode = new FshCode('rice', 'http://spice.com');
+      fixedValRule.fixedValue = fixedFshCode;
+      respRate.rules.push(containsRule, cardRule, fixedValRule);
+      const exported = exportInstance(respRateInstance);
+      expect(exported.category).toEqual([
+        {
+          coding: [
+            {
+              code: 'rice',
+              system: 'http://spice.com'
+            }
+          ]
+        },
+        {
+          coding: [
+            {
+              system: 'http://terminology.hl7.org/CodeSystem/observation-category',
+              code: 'vital-signs'
+            }
+          ]
+        }
+      ]);
+    });
+
+    it('should not fix a deeply nested element that is fixed on the Structure Definition but does not have 1..1 parents', () => {
+      // * telecom.period 0..1 // Element is optional
+      // * telecom.period.start 1..1
+      // * telecom.period.start = "2000-07-04"
+      const periodCard = new CardRule('telecom.period');
+      periodCard.min = 0;
+      periodCard.max = '1';
+      const startCard = new CardRule('telecom.period.start');
+      startCard.min = 1;
+      startCard.max = '1';
+      const fixedValRule = new FixedValueRule('telecom.period.start');
+      fixedValRule.fixedValue = '2000-07-04';
+
+      patient.rules.push(periodCard, startCard, fixedValRule);
+      const instanceFixedValRule = new FixedValueRule('telecom[0].system');
+      instanceFixedValRule.fixedValue = new FshCode('email');
+      patientInstance.rules.push(instanceFixedValRule); // * telecom[0].system = #email
+      const exported = exportInstance(patientInstance);
+      expect(exported.telecom[0]).toEqual({
+        system: 'email'
+        // period not included since it is 0..1
+      });
+    });
+
     // Fixing with pattern[x]
     it('should fix a nested element that is fixed by pattern[x] from a parent on the SD', () => {
       const fixedValRule = new FixedValueRule('maritalStatus.coding');
@@ -868,7 +994,7 @@ describe('InstanceExporter', () => {
       patientProfInstance.rules.push(barRule);
       const exported = exportInstance(patientProfInstance);
       expect(exported.extension).toEqual([
-        null,
+        { url: 'type' },
         { url: 'type', valueCoding: { system: 'foo', version: '1.2.3' } }
       ]);
     });
@@ -887,12 +1013,15 @@ describe('InstanceExporter', () => {
       ]);
     });
 
-    it('should fix sliced elements in an array and null fill empty values', () => {
+    it('should fix sliced elements in an array and fill empty values', () => {
       const fooRule = new FixedValueRule('extension[type][1].valueCoding.system');
       fooRule.fixedValue = 'foo';
       patientProfInstance.rules.push(fooRule);
       const exported = exportInstance(patientProfInstance);
-      expect(exported.extension).toEqual([null, { url: 'type', valueCoding: { system: 'foo' } }]);
+      expect(exported.extension).toEqual([
+        { url: 'type' },
+        { url: 'type', valueCoding: { system: 'foo' } }
+      ]);
     });
 
     it('should fix mixed sliced elements in an array', () => {
@@ -937,7 +1066,7 @@ describe('InstanceExporter', () => {
       const containsRule = new ContainsRule('extension');
       containsRule.items = [{ name: 'foo', type: 'FooExtension' }];
       patientProf.rules.push(containsRule);
-      const barRule = new FixedValueRule('extension[FooExtension].valueString');
+      const barRule = new FixedValueRule('extension[foo].valueString');
       barRule.fixedValue = 'bar';
       patientProfInstance.rules.push(barRule);
       const exported = exportInstance(patientProfInstance);