fix bugs in type checking discovered during R5 preparation

This commit is contained in:
Grahame Grieve 2023-02-18 15:14:24 +11:00
parent 1b86b29d1f
commit 98f4a02789
3 changed files with 142 additions and 55 deletions

View File

@ -42,6 +42,7 @@ public class ContextUtilities implements ProfileKnowledgeProvider {
private boolean ignoreProfileErrors; private boolean ignoreProfileErrors;
private XVerExtensionManager xverManager; private XVerExtensionManager xverManager;
private Map<String, String> oidCache = new HashMap<>(); private Map<String, String> oidCache = new HashMap<>();
private List<StructureDefinition> allStructuresList = new ArrayList<StructureDefinition>();
public ContextUtilities(IWorkerContext context) { public ContextUtilities(IWorkerContext context) {
super(); super();
@ -209,7 +210,7 @@ public class ContextUtilities implements ProfileKnowledgeProvider {
* @return a list of all structure definitions, with snapshots generated (if possible) * @return a list of all structure definitions, with snapshots generated (if possible)
*/ */
public List<StructureDefinition> allStructures(){ public List<StructureDefinition> allStructures(){
List<StructureDefinition> result = new ArrayList<StructureDefinition>(); if (allStructuresList.isEmpty()) {
Set<StructureDefinition> set = new HashSet<StructureDefinition>(); Set<StructureDefinition> set = new HashSet<StructureDefinition>();
for (StructureDefinition sd : getStructures()) { for (StructureDefinition sd : getStructures()) {
if (!set.contains(sd)) { if (!set.contains(sd)) {
@ -224,11 +225,12 @@ public class ContextUtilities implements ProfileKnowledgeProvider {
} }
} }
} }
result.add(sd); allStructuresList.add(sd);
set.add(sd); set.add(sd);
} }
} }
return result; }
return allStructuresList;
} }
/** /**

View File

@ -151,6 +151,7 @@ public class TypeDetails {
addType(pt); addType(pt);
return res; return res;
} }
public void addType(ProfiledType pt) { public void addType(ProfiledType pt) {
for (ProfiledType et : types) { for (ProfiledType et : types) {
if (et.uri.equals(pt.uri)) { if (et.uri.equals(pt.uri)) {
@ -176,6 +177,28 @@ public class TypeDetails {
types.add(pt); types.add(pt);
} }
public void addType(CollectionStatus status, ProfiledType pt) {
addType(pt);
if (collectionStatus == null) {
collectionStatus = status;
} else {
switch (status) {
case ORDERED:
if (collectionStatus == CollectionStatus.SINGLETON) {
collectionStatus = status;
}
break;
case SINGLETON:
break;
case UNORDERED:
collectionStatus = status;
break;
default:
break;
}
}
}
public void addTypes(Collection<String> names) { public void addTypes(Collection<String> names) {
for (String n : names) for (String n : names)
addType(new ProfiledType(n)); addType(new ProfiledType(n));

View File

@ -386,6 +386,7 @@ public class FHIRPathEngine {
} }
} }
initFlags(); initFlags();
cu = new ContextUtilities(worker);
} }
private void initFlags() { private void initFlags() {
@ -578,6 +579,22 @@ public class FHIRPathEngine {
* @if the path is not valid * @if the path is not valid
*/ */
public TypeDetails check(Object appContext, String resourceType, String context, ExpressionNode expr) throws FHIRLexerException, PathEngineException, DefinitionException { public TypeDetails check(Object appContext, String resourceType, String context, ExpressionNode expr) throws FHIRLexerException, PathEngineException, DefinitionException {
return check(appContext, resourceType, context, expr, null);
}
/**
* check that paths referred to in the ExpressionNode are valid
*
* xPathStartsWithValueRef is a hack work around for the fact that FHIR Path sometimes needs a different starting point than the xpath
*
* returns a list of the possible types that might be returned by executing the ExpressionNode against a particular context
*
* @param context - the logical type against which this path is applied
* @throws DefinitionException
* @throws PathEngineException
* @if the path is not valid
*/
public TypeDetails check(Object appContext, String resourceType, String context, ExpressionNode expr, Set<ElementDefinition> elementDependencies) throws FHIRLexerException, PathEngineException, DefinitionException {
// if context is a path that refers to a type, do that conversion now // if context is a path that refers to a type, do that conversion now
TypeDetails types; TypeDetails types;
if (context == null) { if (context == null) {
@ -610,7 +627,7 @@ public class FHIRPathEngine {
} }
} }
return executeType(new ExecutionTypeContext(appContext, resourceType, types, types), types, expr, true); return executeType(new ExecutionTypeContext(appContext, resourceType, types, types), types, expr, elementDependencies, true);
} }
private FHIRException makeExceptionPlural(Integer num, ExpressionNode holder, String constName, Object... args) { private FHIRException makeExceptionPlural(Integer num, ExpressionNode holder, String constName, Object... args) {
@ -659,13 +676,13 @@ public class FHIRPathEngine {
} }
} }
return executeType(new ExecutionTypeContext(appContext, sd.getUrl(), types, types), types, expr, true); return executeType(new ExecutionTypeContext(appContext, sd.getUrl(), types, types), types, expr, null, true);
} }
public TypeDetails check(Object appContext, StructureDefinition sd, ExpressionNode expr) throws FHIRLexerException, PathEngineException, DefinitionException { public TypeDetails check(Object appContext, StructureDefinition sd, ExpressionNode expr) throws FHIRLexerException, PathEngineException, DefinitionException {
// if context is a path that refers to a type, do that conversion now // if context is a path that refers to a type, do that conversion now
TypeDetails types = null; // this is a special case; the first path reference will have to resolve to something in the context TypeDetails types = null; // this is a special case; the first path reference will have to resolve to something in the context
return executeType(new ExecutionTypeContext(appContext, sd == null ? null : sd.getUrl(), null, types), types, expr, true); return executeType(new ExecutionTypeContext(appContext, sd == null ? null : sd.getUrl(), null, types), types, expr, null, true);
} }
public TypeDetails check(Object appContext, String resourceType, String context, String expr) throws FHIRLexerException, PathEngineException, DefinitionException { public TypeDetails check(Object appContext, String resourceType, String context, String expr) throws FHIRLexerException, PathEngineException, DefinitionException {
@ -1557,7 +1574,7 @@ public class FHIRPathEngine {
return new TypeDetails(CollectionStatus.SINGLETON, exp.getName()); return new TypeDetails(CollectionStatus.SINGLETON, exp.getName());
} }
private TypeDetails executeType(ExecutionTypeContext context, TypeDetails focus, ExpressionNode exp, boolean atEntry) throws PathEngineException, DefinitionException { private TypeDetails executeType(ExecutionTypeContext context, TypeDetails focus, ExpressionNode exp, Set<ElementDefinition> elementDependencies, boolean atEntry) throws PathEngineException, DefinitionException {
TypeDetails result = new TypeDetails(null); TypeDetails result = new TypeDetails(null);
switch (exp.getKind()) { switch (exp.getKind()) {
case Name: case Name:
@ -1571,7 +1588,7 @@ public class FHIRPathEngine {
result.update(executeContextType(context, exp.getName(), exp)); result.update(executeContextType(context, exp.getName(), exp));
} else { } else {
for (String s : focus.getTypes()) { for (String s : focus.getTypes()) {
result.update(executeType(s, exp, atEntry)); result.update(executeType(s, exp, atEntry, elementDependencies));
} }
if (result.hasNoTypes()) { if (result.hasNoTypes()) {
throw makeException(exp, I18nConstants.FHIRPATH_UNKNOWN_NAME, exp.getName(), focus.describe()); throw makeException(exp, I18nConstants.FHIRPATH_UNKNOWN_NAME, exp.getName(), focus.describe());
@ -1579,7 +1596,7 @@ public class FHIRPathEngine {
} }
break; break;
case Function: case Function:
result.update(evaluateFunctionType(context, focus, exp)); result.update(evaluateFunctionType(context, focus, exp, elementDependencies));
break; break;
case Unary: case Unary:
result.addType(TypeDetails.FP_Integer); result.addType(TypeDetails.FP_Integer);
@ -1590,12 +1607,12 @@ public class FHIRPathEngine {
result.update(resolveConstantType(context, exp.getConstant(), exp)); result.update(resolveConstantType(context, exp.getConstant(), exp));
break; break;
case Group: case Group:
result.update(executeType(context, focus, exp.getGroup(), atEntry)); result.update(executeType(context, focus, exp.getGroup(), elementDependencies, atEntry));
} }
exp.setTypes(result); exp.setTypes(result);
if (exp.getInner() != null) { if (exp.getInner() != null) {
result = executeType(context, result, exp.getInner(), false); result = executeType(context, result, exp.getInner(), elementDependencies, false);
} }
if (exp.isProximal() && exp.getOperation() != null) { if (exp.isProximal() && exp.getOperation() != null) {
@ -1606,7 +1623,7 @@ public class FHIRPathEngine {
if (last.getOperation() == Operation.Is || last.getOperation() == Operation.As) { if (last.getOperation() == Operation.Is || last.getOperation() == Operation.As) {
work = executeTypeName(context, focus, next, atEntry); work = executeTypeName(context, focus, next, atEntry);
} else { } else {
work = executeType(context, focus, next, atEntry); work = executeType(context, focus, next, elementDependencies, atEntry);
} }
result = operateTypes(result, last.getOperation(), work, last); result = operateTypes(result, last.getOperation(), work, last);
last = next; last = next;
@ -3102,12 +3119,12 @@ public class FHIRPathEngine {
return hostServices.resolveConstantType(context.appInfo, name); return hostServices.resolveConstantType(context.appInfo, name);
} }
private TypeDetails executeType(String type, ExpressionNode exp, boolean atEntry) throws PathEngineException, DefinitionException { private TypeDetails executeType(String type, ExpressionNode exp, boolean atEntry, Set<ElementDefinition> elementDependencies) throws PathEngineException, DefinitionException {
if (atEntry && Character.isUpperCase(exp.getName().charAt(0)) && hashTail(type).equals(exp.getName())) { // special case for start up if (atEntry && Character.isUpperCase(exp.getName().charAt(0)) && hashTail(type).equals(exp.getName())) { // special case for start up
return new TypeDetails(CollectionStatus.SINGLETON, type); return new TypeDetails(CollectionStatus.SINGLETON, type);
} }
TypeDetails result = new TypeDetails(null); TypeDetails result = new TypeDetails(null);
getChildTypesByName(type, exp.getName(), result, exp); getChildTypesByName(type, exp.getName(), result, exp, elementDependencies);
return result; return result;
} }
@ -3118,7 +3135,7 @@ public class FHIRPathEngine {
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
private TypeDetails evaluateFunctionType(ExecutionTypeContext context, TypeDetails focus, ExpressionNode exp) throws PathEngineException, DefinitionException { private TypeDetails evaluateFunctionType(ExecutionTypeContext context, TypeDetails focus, ExpressionNode exp, Set<ElementDefinition> elementDependencies) throws PathEngineException, DefinitionException {
List<TypeDetails> paramTypes = new ArrayList<TypeDetails>(); List<TypeDetails> paramTypes = new ArrayList<TypeDetails>();
if (exp.getFunction() == Function.Is || exp.getFunction() == Function.As || exp.getFunction() == Function.OfType) { if (exp.getFunction() == Function.Is || exp.getFunction() == Function.As || exp.getFunction() == Function.OfType) {
paramTypes.add(new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String)); paramTypes.add(new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String));
@ -3126,9 +3143,9 @@ public class FHIRPathEngine {
int i = 0; int i = 0;
for (ExpressionNode expr : exp.getParameters()) { for (ExpressionNode expr : exp.getParameters()) {
if (isExpressionParameter(exp, i)) { if (isExpressionParameter(exp, i)) {
paramTypes.add(executeType(changeThis(context, focus), focus, expr, true)); paramTypes.add(executeType(changeThis(context, focus), focus, expr, elementDependencies, true));
} else { } else {
paramTypes.add(executeType(context, context.thisItem, expr, true)); paramTypes.add(executeType(context, context.thisItem, expr, elementDependencies, true));
} }
i++; i++;
} }
@ -3159,11 +3176,11 @@ public class FHIRPathEngine {
case Where : case Where :
return focus; return focus;
case Select : case Select :
return anything(focus.getCollectionStatus()); return paramTypes.get(0);
case All : case All :
return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean); return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean);
case Repeat : case Repeat :
return anything(focus.getCollectionStatus()); return paramTypes.get(0); // this might be a little more complicated...
case Aggregate : case Aggregate :
return anything(focus.getCollectionStatus()); return anything(focus.getCollectionStatus());
case Item : { case Item : {
@ -3572,7 +3589,7 @@ public class FHIRPathEngine {
private TypeDetails childTypes(TypeDetails focus, String mask, ExpressionNode expr) throws PathEngineException, DefinitionException { private TypeDetails childTypes(TypeDetails focus, String mask, ExpressionNode expr) throws PathEngineException, DefinitionException {
TypeDetails result = new TypeDetails(CollectionStatus.UNORDERED); TypeDetails result = new TypeDetails(CollectionStatus.UNORDERED);
for (String f : focus.getTypes()) { for (String f : focus.getTypes()) {
getChildTypesByName(f, mask, result, expr); getChildTypesByName(f, mask, result, expr, null);
} }
return result; return result;
} }
@ -4011,6 +4028,7 @@ public class FHIRPathEngine {
} }
private static final char[] HEX_ARRAY = "0123456789ABCDEF".toCharArray(); private static final char[] HEX_ARRAY = "0123456789ABCDEF".toCharArray();
private ContextUtilities cu;
public static String bytesToHex(byte[] bytes) { public static String bytesToHex(byte[] bytes) {
char[] hexChars = new char[bytes.length * 2]; char[] hexChars = new char[bytes.length * 2];
for (int j = 0; j < bytes.length; j++) { for (int j = 0; j < bytes.length; j++) {
@ -5693,7 +5711,7 @@ public class FHIRPathEngine {
} }
private void getChildTypesByName(String type, String name, TypeDetails result, ExpressionNode expr) throws PathEngineException, DefinitionException { private void getChildTypesByName(String type, String name, TypeDetails result, ExpressionNode expr, Set<ElementDefinition> elementDependencies) throws PathEngineException, DefinitionException {
if (Utilities.noString(type)) { if (Utilities.noString(type)) {
throw makeException(expr, I18nConstants.FHIRPATH_NO_TYPE, "", "getChildTypesByName"); throw makeException(expr, I18nConstants.FHIRPATH_NO_TYPE, "", "getChildTypesByName");
} }
@ -5704,6 +5722,8 @@ public class FHIRPathEngine {
return; return;
} }
if (type.equals(TypeDetails.FP_SimpleTypeInfo)) { if (type.equals(TypeDetails.FP_SimpleTypeInfo)) {
getSimpleTypeChildTypesByName(name, result); getSimpleTypeChildTypesByName(name, result);
} else if (type.equals(TypeDetails.FP_ClassInfo)) { } else if (type.equals(TypeDetails.FP_ClassInfo)) {
@ -5737,11 +5757,11 @@ public class FHIRPathEngine {
if (dt == null) { if (dt == null) {
throw makeException(expr, I18nConstants.FHIRPATH_NO_TYPE, ProfileUtilities.sdNs(t.getCode(), null), "getChildTypesByName"); throw makeException(expr, I18nConstants.FHIRPATH_NO_TYPE, ProfileUtilities.sdNs(t.getCode(), null), "getChildTypesByName");
} }
addTypeAndDescendents(sdl, dt, new ContextUtilities(worker).allStructures()); addTypeAndDescendents(sdl, dt, cu.allStructures());
// also add any descendant types // also add any descendant types
} }
} else { } else {
addTypeAndDescendents(sdl, sd, new ContextUtilities(worker).allStructures()); addTypeAndDescendents(sdl, sd, cu.allStructures());
if (type.contains("#")) { if (type.contains("#")) {
tail = type.substring(type.indexOf("#")+1); tail = type.substring(type.indexOf("#")+1);
tail = tail.substring(tail.indexOf(".")); tail = tail.substring(tail.indexOf("."));
@ -5753,7 +5773,17 @@ public class FHIRPathEngine {
if (name.equals("**")) { if (name.equals("**")) {
assert(result.getCollectionStatus() == CollectionStatus.UNORDERED); assert(result.getCollectionStatus() == CollectionStatus.UNORDERED);
for (ElementDefinition ed : sdi.getSnapshot().getElement()) { for (ElementDefinition ed : sdi.getSnapshot().getElement()) {
if (ed.getPath().startsWith(path)) if (ed.getPath().startsWith(path)) {
if (ed.hasContentReference()) {
String cpath = ed.getContentReference();
String tn = sdi.getType()+cpath;
if (!result.hasType(worker, tn)) {
if (elementDependencies != null) {
elementDependencies.add(ed);
}
getChildTypesByName(result.addType(tn), "**", result, expr, elementDependencies);
}
} else {
for (TypeRefComponent t : ed.getType()) { for (TypeRefComponent t : ed.getType()) {
if (t.hasCode() && t.getCodeElement().hasValue()) { if (t.hasCode() && t.getCodeElement().hasValue()) {
String tn = null; String tn = null;
@ -5765,11 +5795,19 @@ public class FHIRPathEngine {
if (t.getCode().equals("Resource")) { if (t.getCode().equals("Resource")) {
for (String rn : worker.getResourceNames()) { for (String rn : worker.getResourceNames()) {
if (!result.hasType(worker, rn)) { if (!result.hasType(worker, rn)) {
getChildTypesByName(result.addType(rn), "**", result, expr); if (elementDependencies != null) {
elementDependencies.add(ed);
}
getChildTypesByName(result.addType(rn), "**", result, expr, elementDependencies);
} }
} }
} else if (!result.hasType(worker, tn)) { } else if (!result.hasType(worker, tn)) {
getChildTypesByName(result.addType(tn), "**", result, expr); if (elementDependencies != null) {
elementDependencies.add(ed);
}
getChildTypesByName(result.addType(tn), "**", result, expr, elementDependencies);
}
}
} }
} }
} }
@ -5780,12 +5818,24 @@ public class FHIRPathEngine {
if (ed.getPath().startsWith(path) && !ed.getPath().substring(path.length()).contains(".")) if (ed.getPath().startsWith(path) && !ed.getPath().substring(path.length()).contains("."))
for (TypeRefComponent t : ed.getType()) { for (TypeRefComponent t : ed.getType()) {
if (Utilities.noString(t.getCode())) { // Element.id or Extension.url if (Utilities.noString(t.getCode())) { // Element.id or Extension.url
if (elementDependencies != null) {
elementDependencies.add(ed);
}
result.addType("System.string"); result.addType("System.string");
} else if (t.getCode().equals("Element") || t.getCode().equals("BackboneElement")) { } else if (t.getCode().equals("Element") || t.getCode().equals("BackboneElement")) {
if (elementDependencies != null) {
elementDependencies.add(ed);
}
result.addType(sdi.getType()+"#"+ed.getPath()); result.addType(sdi.getType()+"#"+ed.getPath());
} else if (t.getCode().equals("Resource")) { } else if (t.getCode().equals("Resource")) {
if (elementDependencies != null) {
elementDependencies.add(ed);
}
result.addTypes(worker.getResourceNames()); result.addTypes(worker.getResourceNames());
} else { } else {
if (elementDependencies != null) {
elementDependencies.add(ed);
}
result.addType(t.getCode()); result.addType(t.getCode());
} }
} }
@ -5795,12 +5845,18 @@ public class FHIRPathEngine {
ElementDefinitionMatch ed = getElementDefinition(sdi, path, isAllowPolymorphicNames(), expr); ElementDefinitionMatch ed = getElementDefinition(sdi, path, isAllowPolymorphicNames(), expr);
if (ed != null) { if (ed != null) {
if (!Utilities.noString(ed.getFixedType())) if (!Utilities.noString(ed.getFixedType())) {
if (elementDependencies != null) {
elementDependencies.add(ed.definition);
}
result.addType(ed.getFixedType()); result.addType(ed.getFixedType());
else { } else {
for (TypeRefComponent t : ed.getDefinition().getType()) { for (TypeRefComponent t : ed.getDefinition().getType()) {
if (Utilities.noString(t.getCode())) { if (Utilities.noString(t.getCode())) {
if (Utilities.existsInList(ed.getDefinition().getId(), "Element.id", "Extension.url") || Utilities.existsInList(ed.getDefinition().getBase().getPath(), "Resource.id", "Element.id", "Extension.url")) { if (Utilities.existsInList(ed.getDefinition().getId(), "Element.id", "Extension.url") || Utilities.existsInList(ed.getDefinition().getBase().getPath(), "Resource.id", "Element.id", "Extension.url")) {
if (elementDependencies != null) {
elementDependencies.add(ed.definition);
}
result.addType(TypeDetails.FP_NS, "string"); result.addType(TypeDetails.FP_NS, "string");
} }
break; // throw new PathEngineException("Illegal reference to primitive value attribute @ "+path); break; // throw new PathEngineException("Illegal reference to primitive value attribute @ "+path);
@ -5810,6 +5866,9 @@ public class FHIRPathEngine {
if (t.getCode().equals("Element") || t.getCode().equals("BackboneElement")) { if (t.getCode().equals("Element") || t.getCode().equals("BackboneElement")) {
pt = new ProfiledType(sdi.getUrl()+"#"+path); pt = new ProfiledType(sdi.getUrl()+"#"+path);
} else if (t.getCode().equals("Resource")) { } else if (t.getCode().equals("Resource")) {
if (elementDependencies != null) {
elementDependencies.add(ed.definition);
}
result.addTypes(worker.getResourceNames()); result.addTypes(worker.getResourceNames());
} else { } else {
pt = new ProfiledType(t.getCode()); pt = new ProfiledType(t.getCode());
@ -5821,7 +5880,10 @@ public class FHIRPathEngine {
if (ed.getDefinition().hasBinding()) { if (ed.getDefinition().hasBinding()) {
pt.addBinding(ed.getDefinition().getBinding()); pt.addBinding(ed.getDefinition().getBinding());
} }
result.addType(pt); if (elementDependencies != null) {
elementDependencies.add(ed.definition);
}
result.addType(ed.definition.unbounded() ? CollectionStatus.ORDERED : CollectionStatus.SINGLETON, pt);
} }
} }
} }