Skip to content

Commit

Permalink
Merge pull request #733 from cqframework/fix-732-extension-functions
Browse files Browse the repository at this point in the history
#732: Fixed result type of FHIRPath extension functions and added mod…
  • Loading branch information
JPercival authored Apr 5, 2022
2 parents 86e55c2 + db4b912 commit 8ce9f93
Show file tree
Hide file tree
Showing 9 changed files with 128 additions and 20 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -4317,6 +4317,15 @@ public Expression resolveFunctionOrQualifiedFunction(String identifier, cqlParse
}
}

// If we are in an implicit context (i.e. a context named the same as a parameter), the function may be resolved as a method invocation
ParameterRef parameterRef = libraryBuilder.resolveImplicitContext();
if (parameterRef != null) {
Expression result = systemMethodResolver.resolveMethod(parameterRef, identifier, paramListCtx, false);
if (result != null) {
return result;
}
}

// If there is no target, resolve as a system function
return resolveFunction(null, identifier, paramListCtx);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1267,12 +1267,14 @@ public Expression resolveFunction(String libraryName, String functionName, Itera
// 2. It is an error condition that needs to be reported
if (fun == null) {
fun = buildFunctionRef(libraryName, functionName, paramList);
Expression systemFunction = systemFunctionResolver.resolveSystemFunction(fun);
if (systemFunction != null) {
return systemFunction;
}

if (mustResolve) {
if (!allowFluent) {
// Only attempt to resolve as a system function if this is not a fluent call or it is a required resolution
Expression systemFunction = systemFunctionResolver.resolveSystemFunction(fun);
if (systemFunction != null) {
return systemFunction;
}

checkLiteralContext();
}

Expand Down Expand Up @@ -2169,6 +2171,32 @@ public Expression resolveIdentifier(String identifier, boolean mustResolve) {

// If no other resolution occurs, and we are in a specific context, and there is a parameter with the same name as the context,
// the identifier may be resolved as an implicit property reference on that context.
ParameterRef parameterRef = resolveImplicitContext();
if (parameterRef != null) {
PropertyResolution resolution = resolveProperty(parameterRef.getResultType(), identifier, false);
if (resolution != null) {
Expression contextAccessor = buildProperty(parameterRef, resolution.getName(), resolution.isSearch(), resolution.getType());
contextAccessor = applyTargetMap(contextAccessor, resolution.getTargetMap());
return contextAccessor;
}
}

if (mustResolve) {
// ERROR:
throw new IllegalArgumentException(String.format("Could not resolve identifier %s in the current library.", identifier));
}

return null;
}

/**
* An implicit context is one where the context has the same name as a parameter. Implicit contexts are used to
* allow FHIRPath expressions to resolve on the implicit context of the expression
*
* For example, in a Patient context, with a parameter of type Patient, the expression `birthDate` resolves to a property reference.
* @return A reference to the parameter providing the implicit context value
*/
public ParameterRef resolveImplicitContext() {
if (!inLiteralContext() && inSpecificContext()) {
Element contextElement = resolve(currentExpressionContext());
if (contextElement instanceof ParameterDef) {
Expand All @@ -2182,21 +2210,10 @@ public Expression resolveIdentifier(String identifier, boolean mustResolve) {
throw new IllegalArgumentException(String.format("Could not validate reference to parameter %s because its definition contains errors.",
parameterRef.getName()));
}

PropertyResolution resolution = resolveProperty(parameterRef.getResultType(), identifier, false);
if (resolution != null) {
Expression contextAccessor = buildProperty(parameterRef, resolution.getName(), resolution.isSearch(), resolution.getType());
contextAccessor = applyTargetMap(contextAccessor, resolution.getTargetMap());
return contextAccessor;
}
return parameterRef;
}
}

if (mustResolve) {
// ERROR:
throw new IllegalArgumentException(String.format("Could not resolve identifier %s in the current library.", identifier));
}

return null;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
import org.cqframework.cql.cql2elm.NamespaceInfo;
import org.cqframework.cql.cql2elm.TestUtils;
import org.cqframework.cql.cql2elm.model.TranslatedLibrary;
import org.hl7.cql.model.ClassType;
import org.hl7.cql.model.DataType;
import org.hl7.elm.r1.*;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.Test;
Expand Down Expand Up @@ -203,9 +205,23 @@ public void testIntervalImplicitConversion() throws IOException {
TestUtils.runSemanticTest("fhir/r401/TestIntervalImplicitConversion.cql", 0);
}

private void assertResultType(TranslatedLibrary translatedLibrary, String expressionName, String namespace, String name) {
ExpressionDef ed = translatedLibrary.resolveExpressionRef(expressionName);
DataType resultType = ed.getExpression().getResultType();
assertThat(resultType, instanceOf(ClassType.class));
ClassType resultClassType = (ClassType)resultType;
assertThat(resultClassType.getNamespace(), equalTo(namespace));
assertThat(resultClassType.getSimpleName(), equalTo(name));
}

@Test
public void testFHIRHelpers() throws IOException {
TestUtils.runSemanticTest("fhir/r401/TestFHIRHelpers.cql", 0);
CqlTranslator translator = TestUtils.runSemanticTest("fhir/r401/TestFHIRHelpers.cql", 0);
TranslatedLibrary translatedLibrary = translator.getTranslatedLibrary();
assertResultType(translatedLibrary, "TestExtensions", "FHIR", "Extension");
assertResultType(translatedLibrary, "TestElementExtensions", "FHIR", "Extension");
assertResultType(translatedLibrary, "TestModifierExtensions", "FHIR", "Extension");
assertResultType(translatedLibrary, "TestElementModifierExtensions", "FHIR", "Extension");
}

@Test
Expand All @@ -228,6 +244,21 @@ public void testParameterContext() throws IOException {
TestUtils.runSemanticTest("fhir/r401/TestParameterContext.cql", 0);
}

@Test
public void testEncounterParameterContext() throws IOException {
TestUtils.runSemanticTest("fhir/r401/TestEncounterParameterContext.cql", 0);
}

@Test
public void testMeasureParameterContext() throws IOException {
TestUtils.runSemanticTest("fhir/r401/TestMeasureParameterContext.cql", 0);
}

@Test
public void testTrace() throws IOException {
TestUtils.runSemanticTest("fhir/r401/TestTrace.cql", 0);
}

@Test
public void testFHIR() throws IOException {
TestUtils.runSemanticTest("fhir/r401/TestFHIR.cql", 0);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
library TestEncounterParameterContext

using FHIR version '4.0.1'

parameter Encounter Encounter

context Encounter

define TestExpression: status.value = 'finished'

Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ using FHIR version '4.0.1'

include FHIRHelpers version '4.0.1'

context Patient

// Primitives
// instant
define TestInstant: instant { value: @2020-10-03T10:00:00.0 }
Expand Down Expand Up @@ -185,3 +187,9 @@ define TestQuantityWithComparator3: Quantity { value: decimal { value: 10.0 }, u
define TestQuantityWithComparator3Converts: FHIRHelpers.ToInterval(TestQuantityWithComparator3) = Interval[10 'mg', null]
define TestQuantityWithComparator4: Quantity { value: decimal { value: 10.0 }, unit: string { value: 'mg' }, comparator: FHIR.QuantityComparator { value: '>' } }
define TestQuantityWithComparator4Converts: FHIRHelpers.ToInterval(TestQuantityWithComparator4) = Interval(10 'mg', null]

// Extensions
define TestExtensions: Patient.extension('http://hl7.org/fhir/us/core/StructureDefinition/us-core-birthsex').single()
define TestElementExtensions: Patient.birthDate.extension('http://hl7.org/fhir/StructureDefinition/patient-birthTime').single()
define TestModifierExtensions: Patient.modifierExtension('http://hl7.org/fhir/StructureDefinition/request-doNotPerform').single()
define TestElementModifierExtensions: Patient.contact.modifierExtension('http://hl7.org/fhir/StructureDefinition/request-doNotPerform').single()
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
library TestMeasureParameterContext

using FHIR version '4.0.1'

include FHIRHelpers version '4.0.1'

parameter Measure Measure

context Measure

define TestExpression: extension('http://hl7.org/fhir/us/cqfmeasures/StructureDefinition/cqfm-populationBasis').exists()

Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,13 @@ library TestParameterContext

using FHIR version '4.0.1'

include FHIRHelpers version '4.0.1'

parameter Patient Patient

context Patient

define TestExpression: birthDate.value < Today()

define TestExtensionExpression: extension('http://hl7.org/fhir/StructureDefinition/birth-time').exists()

Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
library TestTrace

using FHIR version '4.0.1'

include FHIRHelpers version '4.0.1'

parameter Patient Patient

context Patient

define Test1: name.given.trace('test').count() = 5

// TODO: Not supported, the second argument here is a selector: https://hl7.org/fhir/fhirpath.html#functions
// define Test2: name.trace('test', given).count() = 3

Original file line number Diff line number Diff line change
Expand Up @@ -124,8 +124,10 @@ define function reference(reference String):
define function resolve(reference String) returns Resource: external
define function resolve(reference Reference) returns Resource: external
define function reference(resource Resource) returns Reference: external
define function extension(element Element, url String) returns List<Element>: external
define function extension(resource Resource, url String) returns List<Element>: external
define function extension(element Element, url String) returns List<Extension>: external
define function extension(resource DomainResource, url String) returns List<Extension>: external
define function modifierExtension(element BackboneElement, url String) returns List<Extension>: external
define function modifierExtension(resource DomainResource, url String) returns List<Extension>: external
define function hasValue(element Element) returns Boolean: external
define function getValue(element Element) returns Any: external
define function ofType(identifier String) returns List<Any>: external
Expand Down

0 comments on commit 8ce9f93

Please sign in to comment.