From 8390d7af3e5d95849cd0f8ae77dbf1d935548f04 Mon Sep 17 00:00:00 2001 From: Bryn Rhodes Date: Tue, 5 Nov 2024 13:49:57 -0600 Subject: [PATCH] =?UTF-8?q?Improved=20generic=20instantiation=20behavior?= =?UTF-8?q?=20and=20added=20type=20precedence=20to=20=E2=80=A6=20(#1428)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Improved generic instantiation behavior and added type precedence to ambiguous resolution logic * Fixes from regression testing * Spotless and quality gate fixes * Fixed precedence of infix set operators to match FHIRPath * Call isInstantiable recursively to make sure that type parameters (if present) are bound (#1431) * Removed unused typeMap field in InstantiationResult class * Spotless --------- Co-authored-by: Anton Vasetenkov --- Src/grammar/cql.g4 | 2 +- .../cql/cql2elm/Cql2ElmVisitor.java | 18 +- .../cql/cql2elm/LibraryBuilder.java | 37 ++++ .../cql/cql2elm/model/ConversionMap.java | 39 ++++ .../cql/cql2elm/model/OperatorEntry.java | 190 ++++++++++++------ .../cql/cql2elm/model/OperatorMap.java | 22 ++ .../cql/cql2elm/model/OperatorResolution.java | 5 +- .../cql/cql2elm/SemanticTests.java | 11 +- .../cql/cql2elm/TranslationTests.java | 79 +++++++- .../cql/cql2elm/fhir/r4/BaseTest.java | 20 +- .../cql/cql2elm/fhir/r401/BaseTest.java | 20 +- .../cql/cql2elm/fhir/stu3/BaseTest.java | 18 +- .../cql/cql2elm/fhir/stu301/BaseTest.java | 12 +- .../operators/CqlListOperatorsTest.java | 2 +- .../cql/cql2elm/quick/v330/BaseTest.java | 18 +- ...6v2_Expected_SignatureLevel_Overloads.json | 13 ++ ...46v2_Expected_SignatureLevel_Overloads.xml | 10 +- .../org/cqframework/cql/cql2elm/Issue435.cql | 10 + .../ResolutionTests/ProperlyIncludesTests.cql | 9 + .../cql/cql2elm/fhir/r4/TestFHIRTiming.cql | 1 + .../fhir/OpioidCDSSTU3/cql/OMTKData2019.cql | 8 +- .../engine/execution/ListOperatorsTest.java | 5 +- .../java/org/hl7/cql/model/ChoiceType.java | 10 + .../java/org/hl7/cql/model/IntervalType.java | 4 + .../main/java/org/hl7/cql/model/ListType.java | 4 + .../java/org/hl7/cql/model/TupleType.java | 10 + 26 files changed, 455 insertions(+), 122 deletions(-) create mode 100644 Src/java/cql-to-elm/src/test/resources/org/cqframework/cql/cql2elm/Issue435.cql create mode 100644 Src/java/cql-to-elm/src/test/resources/org/cqframework/cql/cql2elm/ResolutionTests/ProperlyIncludesTests.cql diff --git a/Src/grammar/cql.g4 b/Src/grammar/cql.g4 index 894c626e8..0ed90486c 100644 --- a/Src/grammar/cql.g4 +++ b/Src/grammar/cql.g4 @@ -320,6 +320,7 @@ expression | expression 'properly'? 'between' expressionTerm 'and' expressionTerm #betweenExpression | ('duration' 'in')? pluralDateTimePrecision 'between' expressionTerm 'and' expressionTerm #durationBetweenExpression | 'difference' 'in' pluralDateTimePrecision 'between' expressionTerm 'and' expressionTerm #differenceBetweenExpression + | expression ('|' | 'union' | 'intersect' | 'except') expression #inFixSetExpression | expression ('<=' | '<' | '>' | '>=') expression #inequalityExpression | expression intervalOperatorPhrase expression #timingExpression | expression ('=' | '!=' | '~' | '!~') expression #equalityExpression @@ -327,7 +328,6 @@ expression | expression 'and' expression #andExpression | expression ('or' | 'xor') expression #orExpression | expression 'implies' expression #impliesExpression - | expression ('|' | 'union' | 'intersect' | 'except') expression #inFixSetExpression ; dateTimePrecision diff --git a/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/Cql2ElmVisitor.java b/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/Cql2ElmVisitor.java index 5954ca107..d5041a294 100755 --- a/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/Cql2ElmVisitor.java +++ b/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/Cql2ElmVisitor.java @@ -728,20 +728,32 @@ public Object visitListSelector(cqlParser.ListSelectorContext ctx) { DataType elementType = elementTypeSpecifier != null ? elementTypeSpecifier.getResultType() : null; DataType inferredElementType = null; + DataType initialInferredElementType = null; List elements = new ArrayList<>(); for (cqlParser.ExpressionContext elementContext : ctx.expression()) { Expression element = parseExpression(elementContext); + if (element == null) { + throw new RuntimeException("Element failed to parse"); + } + if (elementType != null) { libraryBuilder.verifyType(element.getResultType(), elementType); } else { - if (inferredElementType == null) { - inferredElementType = element.getResultType(); + if (initialInferredElementType == null) { + initialInferredElementType = element.getResultType(); + inferredElementType = initialInferredElementType; } else { + // Once a list type is inferred as Any, keep it that way + // The only potential exception to this is if the element responsible for the inferred type of Any + // is a null DataType compatibleType = libraryBuilder.findCompatibleType(inferredElementType, element.getResultType()); - if (compatibleType != null) { + if (compatibleType != null + && (!inferredElementType.equals(libraryBuilder.resolveTypeName("System", "Any")) + || initialInferredElementType.equals( + libraryBuilder.resolveTypeName("System", "Any")))) { inferredElementType = compatibleType; } else { inferredElementType = libraryBuilder.resolveTypeName("System", "Any"); diff --git a/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/LibraryBuilder.java b/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/LibraryBuilder.java index 4ece6bfc9..a4fc5efcf 100644 --- a/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/LibraryBuilder.java +++ b/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/LibraryBuilder.java @@ -1098,12 +1098,49 @@ public Invocation resolveProperContainsInvocation( return resolveBinaryInvocation("System", "ProperContains", properContains); } + private int getTypeScore(OperatorResolution resolution) { + int typeScore = ConversionMap.ConversionScore.ExactMatch.score(); + for (DataType operand : resolution.getOperator().getSignature().getOperandTypes()) { + typeScore += ConversionMap.getTypePrecedenceScore(operand); + } + + return typeScore; + } + private Expression lowestScoringInvocation(Invocation primary, Invocation secondary) { if (primary != null) { if (secondary != null) { if (secondary.getResolution().getScore() < primary.getResolution().getScore()) { return secondary.getExpression(); + } else if (primary.getResolution().getScore() + < secondary.getResolution().getScore()) { + return primary.getExpression(); + } + if (primary.getResolution().getScore() + == secondary.getResolution().getScore()) { + int primaryTypeScore = getTypeScore(primary.getResolution()); + int secondaryTypeScore = getTypeScore(secondary.getResolution()); + + if (secondaryTypeScore < primaryTypeScore) { + return secondary.getExpression(); + } else if (primaryTypeScore < secondaryTypeScore) { + return primary.getExpression(); + } else { + // ERROR: + StringBuilder message = new StringBuilder("Call to operator ") + .append(primary.getResolution().getOperator().getName()) + .append("/") + .append(secondary.getResolution().getOperator().getName()) + .append(" is ambiguous with: ") + .append("\n - ") + .append(primary.getResolution().getOperator().getName()) + .append(primary.getResolution().getOperator().getSignature()) + .append("\n - ") + .append(secondary.getResolution().getOperator().getName()) + .append(secondary.getResolution().getOperator().getSignature()); + throw new IllegalArgumentException(message.toString()); + } } } diff --git a/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/model/ConversionMap.java b/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/model/ConversionMap.java index fafbbca16..8ffbdaa79 100644 --- a/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/model/ConversionMap.java +++ b/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/model/ConversionMap.java @@ -7,6 +7,26 @@ import org.hl7.cql.model.*; public class ConversionMap { + public enum TypePrecedenceScore { + Simple(1), + Tuple(2), + Class(3), + Interval(4), + List(5), + Choice(6), + Other(7); + + private final int score; + + public int score() { + return score; + } + + TypePrecedenceScore(int score) { + this.score = score; + } + } + public enum ConversionScore { ExactMatch(0), SubType(1), @@ -30,6 +50,25 @@ public int score() { } } + public static int getTypePrecedenceScore(DataType operand) { + switch (operand.getClass().getSimpleName()) { + case "SimpleType": + return ConversionMap.TypePrecedenceScore.Simple.score(); + case "TupleType": + return ConversionMap.TypePrecedenceScore.Tuple.score(); + case "ClassType": + return ConversionMap.TypePrecedenceScore.Class.score(); + case "IntervalType": + return ConversionMap.TypePrecedenceScore.Interval.score(); + case "ListType": + return ConversionMap.TypePrecedenceScore.List.score(); + case "ChoiceType": + return ConversionMap.TypePrecedenceScore.Choice.score(); + default: + return ConversionMap.TypePrecedenceScore.Other.score(); + } + } + public static int getConversionScore(DataType callOperand, DataType operand, Conversion conversion) { if (operand.equals(callOperand)) { return ConversionMap.ConversionScore.ExactMatch.score(); diff --git a/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/model/OperatorEntry.java b/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/model/OperatorEntry.java index 6aa93db5f..ed1a17a34 100644 --- a/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/model/OperatorEntry.java +++ b/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/model/OperatorEntry.java @@ -41,44 +41,136 @@ public Signature getSignature() { return operator.getSignature(); } + /* + The invocation signature is the call signature with arguments of type Any set to the operand types + */ + private Signature getInvocationSignature(Signature callSignature, Signature operatorSignature) { + if (callSignature.getSize() == operatorSignature.getSize()) { + DataType[] invocationTypes = new DataType[callSignature.getSize()]; + Iterator callTypes = callSignature.getOperandTypes().iterator(); + Iterator operatorTypes = + operatorSignature.getOperandTypes().iterator(); + boolean isResolved = false; + for (int i = 0; i < invocationTypes.length; i++) { + DataType callType = callTypes.next(); + DataType operatorType = operatorTypes.next(); + if (callType.equals(DataType.ANY) && !operatorType.equals(DataType.ANY)) { + isResolved = true; + invocationTypes[i] = operatorType; + } else { + invocationTypes[i] = callType; + } + } + if (isResolved) { + return new Signature(invocationTypes); + } + } + return callSignature; + } + + private OperatorResolution getOperatorResolution( + Operator operator, + Signature callSignature, + Signature invocationSignature, + ConversionMap conversionMap, + OperatorMap operatorMap, + boolean allowPromotionAndDemotion, + boolean requireConversions) { + Conversion[] conversions = getConversions( + callSignature, operator.getSignature(), conversionMap, operatorMap, allowPromotionAndDemotion); + OperatorResolution result = new OperatorResolution(operator, conversions); + if (requireConversions && conversions == null) { + return null; + } + return result; + } + public List resolve( CallContext callContext, ConversionMap conversionMap, OperatorMap operatorMap) { List results = null; - if (operator.getSignature().equals(callContext.getSignature())) { - results = new ArrayList<>(); - results.add(new OperatorResolution(operator)); - return results; + Signature invocationSignature = getInvocationSignature(callContext.getSignature(), operator.getSignature()); + + // Attempt exact match against this signature + if (operator.getSignature().equals(invocationSignature)) { + OperatorResolution result = getOperatorResolution( + operator, + callContext.getSignature(), + invocationSignature, + conversionMap, + operatorMap, + callContext.getAllowPromotionAndDemotion(), + false); + if (result != null) { + results = new ArrayList<>(); + results.add(result); + return results; + } } + // Attempt to resolve against sub signatures results = subSignatures.resolve(callContext, conversionMap, operatorMap); - if (results == null && operator.getSignature().isSuperTypeOf(callContext.getSignature())) { - results = new ArrayList<>(); - results.add(new OperatorResolution(operator)); + + // If no subsignatures match, attempt subType match against this signature + if (results == null && operator.getSignature().isSuperTypeOf(invocationSignature)) { + OperatorResolution result = getOperatorResolution( + operator, + callContext.getSignature(), + invocationSignature, + conversionMap, + operatorMap, + callContext.getAllowPromotionAndDemotion(), + false); + if (result != null) { + results = new ArrayList<>(); + results.add(result); + return results; + } } if (results == null && conversionMap != null) { // Attempt to find a conversion path from the call signature to the target signature - Conversion[] conversions = - new Conversion[operator.getSignature().getSize()]; - boolean isConvertible = callContext - .getSignature() - .isConvertibleTo( - operator.getSignature(), - conversionMap, - operatorMap, - callContext.getAllowPromotionAndDemotion(), - conversions); - if (isConvertible) { - OperatorResolution resolution = new OperatorResolution(operator); - resolution.setConversions(conversions); - results = new ArrayList<>(); - results.add(resolution); + OperatorResolution result = getOperatorResolution( + operator, + callContext.getSignature(), + invocationSignature, + conversionMap, + operatorMap, + callContext.getAllowPromotionAndDemotion(), + true); + if (result != null) { + if (results == null) { + results = new ArrayList<>(); + } + results.add(result); } } return results; } + private Conversion[] getConversions( + Signature callSignature, + Signature operatorSignature, + ConversionMap conversionMap, + OperatorMap operatorMap, + boolean allowPromotionAndDemotion) { + if (callSignature == null + || operatorSignature == null + || callSignature.getSize() != operatorSignature.getSize()) { + return null; + } + + Conversion[] conversions = new Conversion[callSignature.getSize()]; + boolean isConvertible = callSignature.isConvertibleTo( + operatorSignature, conversionMap, operatorMap, allowPromotionAndDemotion, conversions); + + if (isConvertible) { + return conversions; + } + + return null; + } + private SignatureNodes subSignatures = new SignatureNodes(); public boolean hasSubSignatures() { @@ -277,60 +369,42 @@ public List resolve( throw new IllegalArgumentException("callContext is null"); } - List results = signatures.resolve(callContext, conversionMap, operatorMap); - - // If there is no resolution, or all resolutions require conversion, attempt to instantiate a generic signature - if (results == null || allResultsUseConversion(results)) { - // If the callContext signature contains choices, attempt instantiation with all possible combinations of - // the call signature (ouch, this could really hurt...) - boolean signaturesInstantiated = false; - List callSignatures = expandChoices(callContext.getSignature()); - for (Signature callSignature : callSignatures) { - Operator result = instantiate( - callSignature, operatorMap, conversionMap, callContext.getAllowPromotionAndDemotion()); - if (result != null && !signatures.contains(result)) { - // If the generic signature was instantiated, store it as an actual signature. - signatures.add(new SignatureNode(result)); - signaturesInstantiated = true; + // Attempt to instantiate any generic signatures + // If the callContext signature contains choices, attempt instantiation with all possible combinations of + // the call signature (ouch, this could really hurt...) + boolean signaturesInstantiated = false; + List callSignatures = expandChoices(callContext.getSignature()); + for (Signature callSignature : callSignatures) { + List instantiations = + instantiate(callSignature, operatorMap, conversionMap, callContext.getAllowPromotionAndDemotion()); + for (Operator instantiation : instantiations) { + // If the generic signature was instantiated, store it as an actual signature. + if (!signatures.contains(instantiation)) { + signatures.add(new SignatureNode(instantiation)); } } - - // re-attempt the resolution with the instantiated signature registered - if (signaturesInstantiated) { - results = signatures.resolve(callContext, conversionMap, operatorMap); - } } + List results = signatures.resolve(callContext, conversionMap, operatorMap); + return results; } - private Operator instantiate( + private List instantiate( Signature signature, OperatorMap operatorMap, ConversionMap conversionMap, boolean allowPromotionAndDemotion) { List instantiations = new ArrayList(); - int lowestConversionScore = Integer.MAX_VALUE; - Operator instantiation = null; + for (GenericOperator genericOperator : genericOperators.values()) { InstantiationResult instantiationResult = genericOperator.instantiate(signature, operatorMap, conversionMap, allowPromotionAndDemotion); if (instantiationResult.getOperator() != null) { - if (instantiationResult.getConversionScore() <= lowestConversionScore) { - if (instantiation == null || instantiationResult.getConversionScore() < lowestConversionScore) { - instantiation = instantiationResult.getOperator(); - lowestConversionScore = instantiationResult.getConversionScore(); - } else { - throw new IllegalArgumentException(String.format( - "Ambiguous generic instantiation of operator %s between signature %s and %s.", - this.name, - instantiation.getSignature().toString(), - instantiationResult.getOperator().getSignature().toString())); - } - } + instantiations.add(instantiationResult.getOperator()); } } - return instantiation; + return instantiations; } } diff --git a/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/model/OperatorMap.java b/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/model/OperatorMap.java index 769c3598a..246c250be 100644 --- a/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/model/OperatorMap.java +++ b/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/model/OperatorMap.java @@ -90,6 +90,28 @@ public OperatorResolution resolveOperator(CallContext callContext, ConversionMap } } + if (lowestScoringResults.size() > 1) { + int lowestTypeScore = Integer.MAX_VALUE; + List lowestTypeScoringResults = new ArrayList<>(); + for (OperatorResolution resolution : lowestScoringResults) { + int typeScore = ConversionMap.ConversionScore.ExactMatch.score(); + for (DataType operand : + resolution.getOperator().getSignature().getOperandTypes()) { + typeScore += ConversionMap.getTypePrecedenceScore(operand); + } + + if (typeScore < lowestTypeScore) { + lowestTypeScore = typeScore; + lowestTypeScoringResults.clear(); + lowestTypeScoringResults.add(resolution); + } else if (typeScore == lowestTypeScore) { + lowestTypeScoringResults.add(resolution); + } + } + + lowestScoringResults = lowestTypeScoringResults; + } + if (lowestScoringResults.size() > 1) { if (callContext.getMustResolve()) { // ERROR: diff --git a/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/model/OperatorResolution.java b/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/model/OperatorResolution.java index 7ca38d7d6..507429091 100644 --- a/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/model/OperatorResolution.java +++ b/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/model/OperatorResolution.java @@ -7,8 +7,11 @@ public class OperatorResolution { public OperatorResolution() {} - public OperatorResolution(Operator operator) { + public OperatorResolution(Operator operator, Conversion[] conversions) { this.operator = operator; + if (conversions != null) { + setConversions(conversions); + } } private Operator operator; diff --git a/Src/java/cql-to-elm/src/test/java/org/cqframework/cql/cql2elm/SemanticTests.java b/Src/java/cql-to-elm/src/test/java/org/cqframework/cql/cql2elm/SemanticTests.java index 5d9ddf396..6a264b97e 100644 --- a/Src/java/cql-to-elm/src/test/java/org/cqframework/cql/cql2elm/SemanticTests.java +++ b/Src/java/cql-to-elm/src/test/java/org/cqframework/cql/cql2elm/SemanticTests.java @@ -373,13 +373,20 @@ void issue395() throws IOException { assertThat(expressionDef.getExpression().getLocalId(), notNullValue()); } + @Test + void issue435() throws IOException { + CqlTranslator translator = TestUtils.runSemanticTest("Issue435.cql", 2); + // [#435](https://github.com/cqframework/clinical_quality_language/issues/435) + assertThat(translator.getErrors().size(), equalTo(2)); + } + @Test void issue587() throws IOException { - CqlTranslator translator = TestUtils.runSemanticTest("Issue587.cql", 2); + CqlTranslator translator = TestUtils.runSemanticTest("Issue587.cql", 1); // This doesn't resolve correctly, collapse null should work, but it's related to this issue: // [#435](https://github.com/cqframework/clinical_quality_language/issues/435) // So keeping as a verification of current behavior here, will address as part of vNext - assertThat(translator.getErrors().size(), equalTo(2)); + assertThat(translator.getErrors().size(), equalTo(1)); } @Test diff --git a/Src/java/cql-to-elm/src/test/java/org/cqframework/cql/cql2elm/TranslationTests.java b/Src/java/cql-to-elm/src/test/java/org/cqframework/cql/cql2elm/TranslationTests.java index afcfa35f5..876205432 100644 --- a/Src/java/cql-to-elm/src/test/java/org/cqframework/cql/cql2elm/TranslationTests.java +++ b/Src/java/cql-to-elm/src/test/java/org/cqframework/cql/cql2elm/TranslationTests.java @@ -17,6 +17,8 @@ import java.util.stream.Collectors; import org.cqframework.cql.elm.tracking.TrackBack; import org.hamcrest.Matchers; +import org.hl7.cql.model.IntervalType; +import org.hl7.cql.model.SimpleType; import org.hl7.cql_annotations.r1.CqlToElmInfo; import org.hl7.elm.r1.*; import org.junit.jupiter.api.Disabled; @@ -329,6 +331,81 @@ void multiThreadedTranslation() throws IOException { CompletableFuture.allOf(cfs).join(); } + @Test + void resolutionProperlyIncludesTests() throws IOException { + final CqlTranslator translator = TestUtils.runSemanticTest("ResolutionTests/ProperlyIncludesTests.cql", 0); + Library compiledLibrary = translator.getTranslatedLibrary().getLibrary(); + List statements = compiledLibrary.getStatements().getDef(); + + assertThat(statements.size(), equalTo(5)); + + ExpressionDef test = statements.get(0); + assertThat(test.getExpression(), instanceOf(ProperContains.class)); + ProperContains properContains = (ProperContains) test.getExpression(); + assertThat(properContains.getOperand().get(0), instanceOf(Interval.class)); + Interval interval = (Interval) properContains.getOperand().get(0); + assertThat(interval.getResultType(), instanceOf(IntervalType.class)); + IntervalType intervalType = (IntervalType) interval.getResultType(); + assertThat(intervalType.getPointType(), instanceOf(SimpleType.class)); + SimpleType pointType = (SimpleType) intervalType.getPointType(); + assertThat(pointType.getName(), equalTo("System.Integer")); + assertThat(properContains.getOperand().get(1), instanceOf(As.class)); + As _as = (As) properContains.getOperand().get(1); + assertThat(_as.getAsType().toString(), equalTo("{urn:hl7-org:elm-types:r1}Integer")); + assertThat(_as.getOperand(), instanceOf(Null.class)); + + test = statements.get(1); + assertThat(test.getExpression(), instanceOf(ProperContains.class)); + properContains = (ProperContains) test.getExpression(); + assertThat(properContains.getOperand().get(0), instanceOf(Interval.class)); + interval = (Interval) properContains.getOperand().get(0); + assertThat(interval.getResultType(), instanceOf(IntervalType.class)); + intervalType = (IntervalType) interval.getResultType(); + assertThat(intervalType.getPointType(), instanceOf(SimpleType.class)); + pointType = (SimpleType) intervalType.getPointType(); + assertThat(pointType.getName(), equalTo("System.Integer")); + assertThat(properContains.getOperand().get(1), instanceOf(As.class)); + _as = (As) properContains.getOperand().get(1); + assertThat(_as.getAsType().toString(), equalTo("{urn:hl7-org:elm-types:r1}Integer")); + assertThat(_as.getOperand(), instanceOf(Null.class)); + + test = statements.get(2); + assertThat(test.getExpression(), instanceOf(ProperContains.class)); + properContains = (ProperContains) test.getExpression(); + assertThat(properContains.getOperand().get(0), instanceOf(Interval.class)); + interval = (Interval) properContains.getOperand().get(0); + assertThat(interval.getResultType(), instanceOf(IntervalType.class)); + intervalType = (IntervalType) interval.getResultType(); + assertThat(intervalType.getPointType(), instanceOf(SimpleType.class)); + pointType = (SimpleType) intervalType.getPointType(); + assertThat(pointType.getName(), equalTo("System.Any")); + assertThat(properContains.getOperand().get(1), instanceOf(Null.class)); + + test = statements.get(3); + assertThat(test.getExpression(), instanceOf(ProperContains.class)); + properContains = (ProperContains) test.getExpression(); + assertThat(properContains.getOperand().get(0), instanceOf(Interval.class)); + interval = (Interval) properContains.getOperand().get(0); + assertThat(interval.getResultType(), instanceOf(IntervalType.class)); + intervalType = (IntervalType) interval.getResultType(); + assertThat(intervalType.getPointType(), instanceOf(SimpleType.class)); + pointType = (SimpleType) intervalType.getPointType(); + assertThat(pointType.getName(), equalTo("System.Any")); + assertThat(properContains.getOperand().get(1), instanceOf(Null.class)); + + test = statements.get(4); + assertThat(test.getExpression(), instanceOf(ProperContains.class)); + properContains = (ProperContains) test.getExpression(); + assertThat(properContains.getOperand().get(0), instanceOf(Interval.class)); + interval = (Interval) properContains.getOperand().get(0); + assertThat(interval.getResultType(), instanceOf(IntervalType.class)); + intervalType = (IntervalType) interval.getResultType(); + assertThat(intervalType.getPointType(), instanceOf(SimpleType.class)); + pointType = (SimpleType) intervalType.getPointType(); + assertThat(pointType.getName(), equalTo("System.Integer")); + assertThat(properContains.getOperand().get(1), instanceOf(As.class)); + } + @Test void hidingVariousUseCases() throws IOException { final CqlTranslator translator = TestUtils.runSemanticTest("HidingTests/TestHidingVariousUseCases.cql", 0); @@ -387,7 +464,7 @@ void abstractClassNotRetrievable() throws IOException { final CqlTranslator translator = TestUtils.runSemanticTest("abstractClassNotRetrievable.cql", 1); final List errors = translator.getErrors(); final List errorMessages = - errors.stream().map(Throwable::getMessage).toList(); + errors.stream().map(Throwable::getMessage).collect(Collectors.toList()); assertThat(errorMessages, contains("Specified data type DomainResource does not support retrieval.")); } } diff --git a/Src/java/cql-to-elm/src/test/java/org/cqframework/cql/cql2elm/fhir/r4/BaseTest.java b/Src/java/cql-to-elm/src/test/java/org/cqframework/cql/cql2elm/fhir/r4/BaseTest.java index 27abd12d5..c6227e015 100644 --- a/Src/java/cql-to-elm/src/test/java/org/cqframework/cql/cql2elm/fhir/r4/BaseTest.java +++ b/Src/java/cql-to-elm/src/test/java/org/cqframework/cql/cql2elm/fhir/r4/BaseTest.java @@ -55,10 +55,10 @@ void fhirTiming() throws IOException { ExpressionDef def = (ExpressionDef) visitFile("fhir/r4/TestFHIRTiming.cql"); // Query-> // where-> - // IncludedIn-> + // In-> // left-> - // ToInterval() - // As(fhir:Period) -> + // ToDateTime() + // As(fhir:dateTime) -> // Property(P.performed) // right-> MeasurementPeriod Query query = (Query) def.getExpression(); @@ -69,16 +69,16 @@ void fhirTiming() throws IOException { Retrieve request = (Retrieve) source.getExpression(); assertThat(request.getDataType(), quickDataType("Procedure")); - // Then check that the where an IncludedIn with a ToInterval as the left operand + // Then check that the where is an In with a ToDateTime as the left operand Expression where = query.getWhere(); - assertThat(where, instanceOf(IncludedIn.class)); - IncludedIn includedIn = (IncludedIn) where; - assertThat(includedIn.getOperand().get(0), instanceOf(FunctionRef.class)); - FunctionRef functionRef = (FunctionRef) includedIn.getOperand().get(0); - assertThat(functionRef.getName(), is("ToInterval")); + assertThat(where, instanceOf(In.class)); + In in = (In) where; + assertThat(in.getOperand().get(0), instanceOf(FunctionRef.class)); + FunctionRef functionRef = (FunctionRef) in.getOperand().get(0); + assertThat(functionRef.getName(), is("ToDateTime")); assertThat(functionRef.getOperand().get(0), instanceOf(As.class)); As asExpression = (As) functionRef.getOperand().get(0); - assertThat(asExpression.getAsType().getLocalPart(), is("Period")); + assertThat(asExpression.getAsType().getLocalPart(), is("dateTime")); assertThat(asExpression.getOperand(), instanceOf(Property.class)); Property property = (Property) asExpression.getOperand(); assertThat(property.getScope(), is("P")); diff --git a/Src/java/cql-to-elm/src/test/java/org/cqframework/cql/cql2elm/fhir/r401/BaseTest.java b/Src/java/cql-to-elm/src/test/java/org/cqframework/cql/cql2elm/fhir/r401/BaseTest.java index 37aea3a22..84b66898d 100644 --- a/Src/java/cql-to-elm/src/test/java/org/cqframework/cql/cql2elm/fhir/r401/BaseTest.java +++ b/Src/java/cql-to-elm/src/test/java/org/cqframework/cql/cql2elm/fhir/r401/BaseTest.java @@ -59,10 +59,10 @@ void fhirTiming() throws IOException { ExpressionDef def = (ExpressionDef) visitFile("fhir/r401/TestFHIRTiming.cql"); // Query-> // where-> - // IncludedIn-> + // In-> // left-> - // ToInterval() - // As(fhir:Period) -> + // ToDateTime() + // As(fhir:dateTime) -> // Property(P.performed) // right-> MeasurementPeriod Query query = (Query) def.getExpression(); @@ -73,16 +73,16 @@ void fhirTiming() throws IOException { Retrieve request = (Retrieve) source.getExpression(); assertThat(request.getDataType(), quickDataType("Procedure")); - // Then check that the where an IncludedIn with a ToInterval as the left operand + // Then check that the where is an In with a ToDateTime as the left operand Expression where = query.getWhere(); - assertThat(where, instanceOf(IncludedIn.class)); - IncludedIn includedIn = (IncludedIn) where; - assertThat(includedIn.getOperand().get(0), instanceOf(FunctionRef.class)); - FunctionRef functionRef = (FunctionRef) includedIn.getOperand().get(0); - assertThat(functionRef.getName(), is("ToInterval")); + assertThat(where, instanceOf(In.class)); + In in = (In) where; + assertThat(in.getOperand().get(0), instanceOf(FunctionRef.class)); + FunctionRef functionRef = (FunctionRef) in.getOperand().get(0); + assertThat(functionRef.getName(), is("ToDateTime")); assertThat(functionRef.getOperand().get(0), instanceOf(As.class)); As asExpression = (As) functionRef.getOperand().get(0); - assertThat(asExpression.getAsType().getLocalPart(), is("Period")); + assertThat(asExpression.getAsType().getLocalPart(), is("dateTime")); assertThat(asExpression.getOperand(), instanceOf(Property.class)); Property property = (Property) asExpression.getOperand(); assertThat(property.getScope(), is("P")); diff --git a/Src/java/cql-to-elm/src/test/java/org/cqframework/cql/cql2elm/fhir/stu3/BaseTest.java b/Src/java/cql-to-elm/src/test/java/org/cqframework/cql/cql2elm/fhir/stu3/BaseTest.java index bfdaf1916..8ea70b1b0 100644 --- a/Src/java/cql-to-elm/src/test/java/org/cqframework/cql/cql2elm/fhir/stu3/BaseTest.java +++ b/Src/java/cql-to-elm/src/test/java/org/cqframework/cql/cql2elm/fhir/stu3/BaseTest.java @@ -51,10 +51,10 @@ void fhirTiming() throws IOException { ExpressionDef def = (ExpressionDef) visitFile("fhir/stu3/TestFHIRTiming.cql"); // Query-> // where-> - // IncludedIn-> + // In-> // left-> - // ToInterval() - // As(fhir:Period) -> + // ToDateTime() + // As(fhir:dateTime) -> // Property(P.performed) // right-> MeasurementPeriod Query query = (Query) def.getExpression(); @@ -67,14 +67,14 @@ void fhirTiming() throws IOException { // Then check that the where an IncludedIn with a Case as the left operand Expression where = query.getWhere(); - assertThat(where, instanceOf(IncludedIn.class)); - IncludedIn includedIn = (IncludedIn) where; - assertThat(includedIn.getOperand().get(0), instanceOf(FunctionRef.class)); - FunctionRef functionRef = (FunctionRef) includedIn.getOperand().get(0); - assertThat(functionRef.getName(), is("ToInterval")); + assertThat(where, instanceOf(In.class)); + In in = (In) where; + assertThat(in.getOperand().get(0), instanceOf(FunctionRef.class)); + FunctionRef functionRef = (FunctionRef) in.getOperand().get(0); + assertThat(functionRef.getName(), is("ToDateTime")); assertThat(functionRef.getOperand().get(0), instanceOf(As.class)); As asExpression = (As) functionRef.getOperand().get(0); - assertThat(asExpression.getAsType().getLocalPart(), is("Period")); + assertThat(asExpression.getAsType().getLocalPart(), is("dateTime")); assertThat(asExpression.getOperand(), instanceOf(Property.class)); Property property = (Property) asExpression.getOperand(); assertThat(property.getScope(), is("P")); diff --git a/Src/java/cql-to-elm/src/test/java/org/cqframework/cql/cql2elm/fhir/stu301/BaseTest.java b/Src/java/cql-to-elm/src/test/java/org/cqframework/cql/cql2elm/fhir/stu301/BaseTest.java index ee06cb82f..a9cf737d1 100644 --- a/Src/java/cql-to-elm/src/test/java/org/cqframework/cql/cql2elm/fhir/stu301/BaseTest.java +++ b/Src/java/cql-to-elm/src/test/java/org/cqframework/cql/cql2elm/fhir/stu301/BaseTest.java @@ -69,14 +69,14 @@ void fhirTiming() throws IOException { // Then check that the where an IncludedIn with a ToInterval as the left operand Expression where = query.getWhere(); - assertThat(where, instanceOf(IncludedIn.class)); - IncludedIn includedIn = (IncludedIn) where; - assertThat(includedIn.getOperand().get(0), instanceOf(FunctionRef.class)); - FunctionRef functionRef = (FunctionRef) includedIn.getOperand().get(0); - assertThat(functionRef.getName(), is("ToInterval")); + assertThat(where, instanceOf(In.class)); + In in = (In) where; + assertThat(in.getOperand().get(0), instanceOf(FunctionRef.class)); + FunctionRef functionRef = (FunctionRef) in.getOperand().get(0); + assertThat(functionRef.getName(), is("ToDateTime")); assertThat(functionRef.getOperand().get(0), instanceOf(As.class)); As asExpression = (As) functionRef.getOperand().get(0); - assertThat(asExpression.getAsType().getLocalPart(), is("Period")); + assertThat(asExpression.getAsType().getLocalPart(), is("dateTime")); assertThat(asExpression.getOperand(), instanceOf(Property.class)); Property property = (Property) asExpression.getOperand(); assertThat(property.getScope(), is("P")); diff --git a/Src/java/cql-to-elm/src/test/java/org/cqframework/cql/cql2elm/operators/CqlListOperatorsTest.java b/Src/java/cql-to-elm/src/test/java/org/cqframework/cql/cql2elm/operators/CqlListOperatorsTest.java index 5d5ee1507..c8e413dcd 100644 --- a/Src/java/cql-to-elm/src/test/java/org/cqframework/cql/cql2elm/operators/CqlListOperatorsTest.java +++ b/Src/java/cql-to-elm/src/test/java/org/cqframework/cql/cql2elm/operators/CqlListOperatorsTest.java @@ -42,6 +42,6 @@ static void setup() throws IOException { @Test void union() { ExpressionDef def = defs.get("Union123AndEmpty"); - assertThat(def, hasTypeAndResult(Union.class, "list")); + assertThat(def, hasTypeAndResult(Union.class, "list")); } } diff --git a/Src/java/cql-to-elm/src/test/java/org/cqframework/cql/cql2elm/quick/v330/BaseTest.java b/Src/java/cql-to-elm/src/test/java/org/cqframework/cql/cql2elm/quick/v330/BaseTest.java index 67133edf9..0fddd1fd8 100644 --- a/Src/java/cql-to-elm/src/test/java/org/cqframework/cql/cql2elm/quick/v330/BaseTest.java +++ b/Src/java/cql-to-elm/src/test/java/org/cqframework/cql/cql2elm/quick/v330/BaseTest.java @@ -55,9 +55,9 @@ void fhirTiming() throws IOException { ExpressionDef def = (ExpressionDef) visitFile("quick/v330/TestFHIRTiming.cql"); // Query-> // where-> - // IncludedIn-> + // In-> // left-> - // As(Interval) -> + // As(DateTime) -> // Property(P.performed) // right-> MeasurementPeriod Query query = (Query) def.getExpression(); @@ -70,15 +70,11 @@ void fhirTiming() throws IOException { // Then check that the where an IncludedIn with a Case as the left operand Expression where = query.getWhere(); - assertThat(where, instanceOf(IncludedIn.class)); - IncludedIn includedIn = (IncludedIn) where; - assertThat(includedIn.getOperand().get(0), instanceOf(As.class)); - As asExpression = (As) includedIn.getOperand().get(0); - assertThat(asExpression.getAsTypeSpecifier(), instanceOf(IntervalTypeSpecifier.class)); - IntervalTypeSpecifier intervalTypeSpecifier = (IntervalTypeSpecifier) asExpression.getAsTypeSpecifier(); - assertThat(intervalTypeSpecifier.getPointType(), instanceOf(NamedTypeSpecifier.class)); - NamedTypeSpecifier namedTypeSpecifier = (NamedTypeSpecifier) intervalTypeSpecifier.getPointType(); - assertThat(namedTypeSpecifier.getName().getLocalPart(), is("DateTime")); + assertThat(where, instanceOf(In.class)); + In in = (In) where; + assertThat(in.getOperand().get(0), instanceOf(As.class)); + As asExpression = (As) in.getOperand().get(0); + assertThat(asExpression.getAsType().getLocalPart(), equalTo("DateTime")); assertThat(asExpression.getOperand(), instanceOf(Property.class)); Property property = (Property) asExpression.getOperand(); assertThat(property.getScope(), is("P")); diff --git a/Src/java/cql-to-elm/src/test/resources/org/cqframework/cql/cql2elm/CMS146v2_Expected_SignatureLevel_Overloads.json b/Src/java/cql-to-elm/src/test/resources/org/cqframework/cql/cql2elm/CMS146v2_Expected_SignatureLevel_Overloads.json index 0d7a10925..d92cc0903 100644 --- a/Src/java/cql-to-elm/src/test/resources/org/cqframework/cql/cql2elm/CMS146v2_Expected_SignatureLevel_Overloads.json +++ b/Src/java/cql-to-elm/src/test/resources/org/cqframework/cql/cql2elm/CMS146v2_Expected_SignatureLevel_Overloads.json @@ -412,6 +412,19 @@ } ], "where" : { "type" : "IncludedIn", + "signature" : [ { + "type" : "IntervalTypeSpecifier", + "pointType" : { + "type" : "NamedTypeSpecifier", + "name" : "{urn:hl7-org:elm-types:r1}DateTime" + } + }, { + "type" : "IntervalTypeSpecifier", + "pointType" : { + "type" : "NamedTypeSpecifier", + "name" : "{urn:hl7-org:elm-types:r1}DateTime" + } + } ], "operand" : [ { "type" : "Property", "path" : "period", diff --git a/Src/java/cql-to-elm/src/test/resources/org/cqframework/cql/cql2elm/CMS146v2_Expected_SignatureLevel_Overloads.xml b/Src/java/cql-to-elm/src/test/resources/org/cqframework/cql/cql2elm/CMS146v2_Expected_SignatureLevel_Overloads.xml index 4d7d0c31e..2721f5a87 100644 --- a/Src/java/cql-to-elm/src/test/resources/org/cqframework/cql/cql2elm/CMS146v2_Expected_SignatureLevel_Overloads.xml +++ b/Src/java/cql-to-elm/src/test/resources/org/cqframework/cql/cql2elm/CMS146v2_Expected_SignatureLevel_Overloads.xml @@ -184,6 +184,14 @@ + + + + + + + + @@ -333,4 +341,4 @@ - + \ No newline at end of file diff --git a/Src/java/cql-to-elm/src/test/resources/org/cqframework/cql/cql2elm/Issue435.cql b/Src/java/cql-to-elm/src/test/resources/org/cqframework/cql/cql2elm/Issue435.cql new file mode 100644 index 000000000..73f282bfb --- /dev/null +++ b/Src/java/cql-to-elm/src/test/resources/org/cqframework/cql/cql2elm/Issue435.cql @@ -0,0 +1,10 @@ +/* +@issue: [#435](https://github.com/cqframework/clinical_quality_language/issues/435) + */ +library Issue435 + +/* +These both now correctly result in ambiguous resolution among the various overloads of Equal for the system types +*/ +define Test1: null ~ null +define Test2: null = null \ No newline at end of file diff --git a/Src/java/cql-to-elm/src/test/resources/org/cqframework/cql/cql2elm/ResolutionTests/ProperlyIncludesTests.cql b/Src/java/cql-to-elm/src/test/resources/org/cqframework/cql/cql2elm/ResolutionTests/ProperlyIncludesTests.cql new file mode 100644 index 000000000..57b834ec7 --- /dev/null +++ b/Src/java/cql-to-elm/src/test/resources/org/cqframework/cql/cql2elm/ResolutionTests/ProperlyIncludesTests.cql @@ -0,0 +1,9 @@ +library ProperlyIncludesTests + +// These should all resolve to ProperContains(Interval, Integer) +// ProperContains(Interval, Integer) +define TestA: Interval[1, null] properly includes null +define TestB: Interval[1, null) properly includes null +define TestC: Interval[null, null] properly includes null +define TestD: Interval(null, null) properly includes null +define TestE: Interval[1, null] properly includes null as Choice> diff --git a/Src/java/cql-to-elm/src/test/resources/org/cqframework/cql/cql2elm/fhir/r4/TestFHIRTiming.cql b/Src/java/cql-to-elm/src/test/resources/org/cqframework/cql/cql2elm/fhir/r4/TestFHIRTiming.cql index 2428f8486..c2e262560 100644 --- a/Src/java/cql-to-elm/src/test/resources/org/cqframework/cql/cql2elm/fhir/r4/TestFHIRTiming.cql +++ b/Src/java/cql-to-elm/src/test/resources/org/cqframework/cql/cql2elm/fhir/r4/TestFHIRTiming.cql @@ -15,4 +15,5 @@ define Procedures: // Because performed is a "choice", it could be interval-valued, or it could be datetime-valued // In that case, it should continue to treat it as an interval timing phrase, not a left-point-valued timing phrase // Fixed much more generally with a combination of interval promotion/demotion and better instantiation in the presence of choice types + // NOTE: As of 1.5.3 with the clarification of type precedence (Simple, Class, Tuple, Interval, List) this resolves as an In, not an IncludedIn where P.performed included in "Measurement Period" diff --git a/Src/java/elm-fhir/src/test/resources/org/cqframework/cql/elm/requirements/fhir/OpioidCDSSTU3/cql/OMTKData2019.cql b/Src/java/elm-fhir/src/test/resources/org/cqframework/cql/elm/requirements/fhir/OpioidCDSSTU3/cql/OMTKData2019.cql index 21084ac05..e95d91690 100644 --- a/Src/java/elm-fhir/src/test/resources/org/cqframework/cql/elm/requirements/fhir/OpioidCDSSTU3/cql/OMTKData2019.cql +++ b/Src/java/elm-fhir/src/test/resources/org/cqframework/cql/elm/requirements/fhir/OpioidCDSSTU3/cql/OMTKData2019.cql @@ -113,7 +113,7 @@ define DrugIngredients: { drugCode: 247626, drugName: 'Oxycodone 10 MG Rectal Suppository', doseFormCode: 316978, doseFormName: 'Rectal Suppository', ingredientCode: 7804, ingredientName: 'Oxycodone', strength: ' 10 MG', strengthValue: 10.0 , strengthUnit: 'mg' }, { drugCode: 247627, drugName: 'Oxycodone 20 MG Rectal Suppository', doseFormCode: 316978, doseFormName: 'Rectal Suppository', ingredientCode: 7804, ingredientName: 'Oxycodone', strength: '20 mg', strengthValue: 20.0, strengthUnit: 'MG' }, { drugCode: 248307, drugName: 'Oxycodone 30 MG Rectal Suppository', doseFormCode: 316978, doseFormName: 'Rectal Suppository', ingredientCode: 7804, ingredientName: 'Oxycodone', strength: '30 mg', strengthValue: 30.0, strengthUnit: 'MG' }, - { drugCode: 248477, drugName: 'Belladonna Alkaloids 15 MG / Opium 65 MG Rectal Suppository', doseFormCode: 316978, doseFormName: 'Suppository', ingredientCode: 7676 , ingredientName: 'Opium', strength: '65 MG', strengthValue: 65, strengthUnit: 'MG' }, + { drugCode: 248477, drugName: 'Belladonna Alkaloids 15 MG / Opium 65 MG Rectal Suppository', doseFormCode: 316978, doseFormName: 'Suppository', ingredientCode: 7676 , ingredientName: 'Opium', strength: '65 MG', strengthValue: 65.0, strengthUnit: 'MG' }, { drugCode: 250426, drugName: 'Buprenorphine 0.4 MG Sublingual Tablet', doseFormCode: 317007, doseFormName: 'Sublingual Tablet', ingredientCode: 1819, ingredientName: 'Buprenorphine', strength: '0.4 MG', strengthValue: 0.4, strengthUnit: 'MG' }, { drugCode: 250485, drugName: 'Pentazocine 25 MG Oral Tablet', doseFormCode: 317541, doseFormName: 'Oral Tablet', ingredientCode: 8001, ingredientName: 'Pentazocine', strength: '25 MG', strengthValue: 25.0, strengthUnit: 'MG' }, { drugCode: 250486, drugName: 'Pentazocine 50 MG Oral Capsule', doseFormCode: 316965, doseFormName: 'Oral Capsule', ingredientCode: 8001, ingredientName: 'Pentazocine', strength: '50 MG', strengthValue: 50.0, strengthUnit: 'MG' }, @@ -136,8 +136,8 @@ define DrugIngredients: { drugCode: 310297, drugName: 'Fentanyl 0.4 MG Oral Lozenge', doseFormCode: 316992, doseFormName: 'Oral Lozenge', ingredientCode: 4337, ingredientName: 'Fentanyl', strength: '0.4 MG', strengthValue: 0.4, strengthUnit: 'MG' }, { drugCode: 311297, drugName: 'Levomethadyl 10 MG/ML Oral Solution', doseFormCode: 316968, doseFormName: 'Oral Solution', ingredientCode: 237005, ingredientName: 'Levomethadyl', strength: '10 MG/ML', strengthValue: 10.0, strengthUnit: 'MG/ML' }, { drugCode: 311300, drugName: 'Levorphanol 2 MG/ML Injectable Solution', doseFormCode: 316949, doseFormName: 'Injectable Solution', ingredientCode: 6378, ingredientName: 'Levorphanol', strength: '2 MG/ML', strengthValue: 2.0, strengthUnit: 'MG/ML' }, - { drugCode: 312104, drugName: 'Belladonna Alkaloids 16.2 MG / Opium 30 MG Rectal Suppository', doseFormCode: 316978, doseFormName: 'Suppository', ingredientCode: 7676, ingredientName: 'Opium', strength: '30 MG ', strengthValue: 30, strengthUnit: 'MG' }, - { drugCode: 312107, drugName: 'Belladonna Alkaloids 16.2 MG / Opium 60 MG Rectal Suppository', doseFormCode: 316978, doseFormName: 'Suppository', ingredientCode: 7676, ingredientName: 'Opium', strength: '60 MG', strengthValue: 60, strengthUnit: 'MG' }, + { drugCode: 312104, drugName: 'Belladonna Alkaloids 16.2 MG / Opium 30 MG Rectal Suppository', doseFormCode: 316978, doseFormName: 'Suppository', ingredientCode: 7676, ingredientName: 'Opium', strength: '30 MG ', strengthValue: 30.0, strengthUnit: 'MG' }, + { drugCode: 312107, drugName: 'Belladonna Alkaloids 16.2 MG / Opium 60 MG Rectal Suppository', doseFormCode: 316978, doseFormName: 'Suppository', ingredientCode: 7676, ingredientName: 'Opium', strength: '60 MG', strengthValue: 60.0, strengthUnit: 'MG' }, { drugCode: 312288, drugName: 'Acetaminophen 650 MG / Pentazocine 25 MG Oral Tablet', doseFormCode: 317541, doseFormName: 'Oral Tablet', ingredientCode: 8001, ingredientName: 'Pentazocine', strength: '25 MG', strengthValue: 25.0, strengthUnit: 'MG' }, { drugCode: 312289, drugName: 'Naloxone 0.5 MG / Pentazocine 50 MG Oral Tablet', doseFormCode: 317541, doseFormName: 'Oral Tablet', ingredientCode: 8001, ingredientName: 'Pentazocine', strength: '50 MG', strengthValue: 50.0, strengthUnit: 'MG' }, { drugCode: 313992, drugName: 'Fentanyl 0.6 MG Oral Lozenge', doseFormCode: 316992, doseFormName: 'Oral Lozenge', ingredientCode: 4337, ingredientName: 'Fentanyl', strength: '0.6 MG', strengthValue: 0.6, strengthUnit: 'MG' }, @@ -178,7 +178,7 @@ define DrugIngredients: { drugCode: 828581, drugName: 'Acetaminophen 650 MG / Propoxyphene Hydrochloride 65 MG Oral Tablet', doseFormCode: 317541, doseFormName: 'Oral Tablet', ingredientCode: 8785, ingredientName: 'Propoxyphene', strength: '65 MG', strengthValue: 65.0, strengthUnit: 'MG' }, { drugCode: 828585, drugName: 'Aspirin 389 MG / Caffeine 32.4 MG / Propoxyphene Hydrochloride 32 MG Oral Capsule', doseFormCode: 316965, doseFormName: 'Oral Capsule', ingredientCode: 8785, ingredientName: 'Propoxyphene', strength: '32 MG', strengthValue: 32.0, strengthUnit: 'MG' }, { drugCode: 828594, drugName: 'Aspirin 389 MG / Caffeine 32.4 MG / Propoxyphene Hydrochloride 65 MG Oral Capsule', doseFormCode: 316965, doseFormName: 'Oral Capsule', ingredientCode: 8785, ingredientName: 'Propoxyphene', strength: '65 MG', strengthValue: 65.0, strengthUnit: 'MG' }, - { drugCode: 830196, drugName: 'Opium tincture 100 MG/ML Oral Solution', doseFormCode: 316968, doseFormName: 'Oral', ingredientCode: 7676, ingredientName: 'opium tincture ', strength: ' 100 MG/ML', strengthValue: 100, strengthUnit: 'MG/ML' }, + { drugCode: 830196, drugName: 'Opium tincture 100 MG/ML Oral Solution', doseFormCode: 316968, doseFormName: 'Oral', ingredientCode: 7676, ingredientName: 'opium tincture ', strength: ' 100 MG/ML', strengthValue: 100.0, strengthUnit: 'MG/ML' }, { drugCode: 833036, drugName: 'Acetaminophen 750 MG / Hydrocodone Bitartrate 7.5 MG Oral Tablet', doseFormCode: 317541, doseFormName: 'Oral Tablet', ingredientCode: 5489, ingredientName: 'Hydrocodone', strength: '7.5 MG', strengthValue: 7.5, strengthUnit: 'MG' }, { drugCode: 833709, drugName: '24 HR tramadol hydrochloride 100 MG Extended Release Oral Tablet', doseFormCode: 316945, doseFormName: 'Extended Release Oral Tablet', ingredientCode: 10689, ingredientName: 'Tramadol', strength: '100 MG', strengthValue: 100.0, strengthUnit: 'MG' }, { drugCode: 833710, drugName: 'Matrix Delivery 24 HR tramadol hydrochloride 100 MG Extended Release Oral Tablet [Ryzolt]', doseFormCode: 316945, doseFormName: 'Extended Release Oral Tablet', ingredientCode: 10689, ingredientName: 'Tramadol', strength: '100 MG', strengthValue: 100.0, strengthUnit: 'MG' }, diff --git a/Src/java/engine/src/test/java/org/opencds/cqf/cql/engine/execution/ListOperatorsTest.java b/Src/java/engine/src/test/java/org/opencds/cqf/cql/engine/execution/ListOperatorsTest.java index 46ebd22be..cc5319bed 100644 --- a/Src/java/engine/src/test/java/org/opencds/cqf/cql/engine/execution/ListOperatorsTest.java +++ b/Src/java/engine/src/test/java/org/opencds/cqf/cql/engine/execution/ListOperatorsTest.java @@ -7,10 +7,7 @@ import static org.opencds.cqf.cql.engine.elm.executing.EquivalentEvaluator.equivalent; import java.math.BigDecimal; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.List; +import java.util.*; import org.cqframework.cql.cql2elm.CqlCompilerException; import org.cqframework.cql.cql2elm.CqlCompilerOptions; import org.junit.jupiter.api.Test; diff --git a/Src/java/model/src/main/java/org/hl7/cql/model/ChoiceType.java b/Src/java/model/src/main/java/org/hl7/cql/model/ChoiceType.java index 017a69b9e..5b43a3f0b 100644 --- a/Src/java/model/src/main/java/org/hl7/cql/model/ChoiceType.java +++ b/Src/java/model/src/main/java/org/hl7/cql/model/ChoiceType.java @@ -148,6 +148,16 @@ public boolean isGeneric() { @Override public boolean isInstantiable(DataType callType, InstantiationContext context) { + // Call isInstantiable recursively to make sure that type parameters (if present) are bound + if (callType.equals(DataType.ANY)) { + for (var type : types) { + if (!type.isInstantiable(callType, context)) { + return false; + } + } + return true; + } + return isSuperTypeOf(callType); } diff --git a/Src/java/model/src/main/java/org/hl7/cql/model/IntervalType.java b/Src/java/model/src/main/java/org/hl7/cql/model/IntervalType.java index 47a6976da..273859720 100644 --- a/Src/java/model/src/main/java/org/hl7/cql/model/IntervalType.java +++ b/Src/java/model/src/main/java/org/hl7/cql/model/IntervalType.java @@ -69,6 +69,10 @@ public boolean isGeneric() { @Override public boolean isInstantiable(DataType callType, InstantiationContext context) { + if (callType.equals(DataType.ANY)) { + return pointType.isInstantiable(callType, context); + } + if (callType instanceof IntervalType) { IntervalType intervalType = (IntervalType) callType; return pointType.isInstantiable(intervalType.pointType, context); diff --git a/Src/java/model/src/main/java/org/hl7/cql/model/ListType.java b/Src/java/model/src/main/java/org/hl7/cql/model/ListType.java index e9c34cde5..13dd6b7e3 100644 --- a/Src/java/model/src/main/java/org/hl7/cql/model/ListType.java +++ b/Src/java/model/src/main/java/org/hl7/cql/model/ListType.java @@ -69,6 +69,10 @@ public boolean isGeneric() { @Override public boolean isInstantiable(DataType callType, InstantiationContext context) { + if (callType.equals(DataType.ANY)) { + return elementType.isInstantiable(callType, context); + } + if (callType instanceof ListType) { ListType listType = (ListType) callType; return elementType.isInstantiable(listType.elementType, context); diff --git a/Src/java/model/src/main/java/org/hl7/cql/model/TupleType.java b/Src/java/model/src/main/java/org/hl7/cql/model/TupleType.java index 587619a05..473d6c9d2 100644 --- a/Src/java/model/src/main/java/org/hl7/cql/model/TupleType.java +++ b/Src/java/model/src/main/java/org/hl7/cql/model/TupleType.java @@ -164,6 +164,16 @@ public boolean isGeneric() { @Override public boolean isInstantiable(DataType callType, InstantiationContext context) { + // Call isInstantiable recursively to make sure that type parameters (if present) are bound + if (callType.equals(DataType.ANY)) { + for (var element : elements) { + if (!element.getType().isInstantiable(callType, context)) { + return false; + } + } + return true; + } + if (callType instanceof TupleType) { TupleType tupleType = (TupleType) callType; if (elements.size() == tupleType.elements.size()) {