docs: public DataType resolveProperty(DataType sourceType, String identifier, boolean mustResolve) {
          DataType currentType = sourceType;
          while (currentType != null) {
              if (currentType instanceof ClassType) {
                  ClassType classType = (ClassType)currentType;
                  for (ClassTypeElement e : classType.getElements()) {
                      if (e.getName().equals(identifier)) {
                          if (e.isProhibited()) {
                              throw new IllegalArgumentException(String.format("Element %s cannot be referenced because it is marked prohibited in type %s.", e.getName(), ((ClassType) currentType).getName()));
                          }
  
                          return e.getType();
                      }
                  }
              }
              else if (currentType instanceof TupleType) {
                  TupleType tupleType = (TupleType)currentType;
                  for (TupleTypeElement e : tupleType.getElements()) {
                      if (e.getName().equals(identifier)) {
                          return e.getType();
                      }
                  }
              }
              else if (currentType instanceof IntervalType) {
                  IntervalType intervalType = (IntervalType)currentType;
                  switch (identifier) {
                      case "low":
                      case "high":
                          return intervalType.getPointType();
                      case "lowClosed":
                      case "highClosed":
                          return resolveTypeName("System", "Boolean");
                      default:
                          // ERROR:
                          throw new IllegalArgumentException(String.format("Invalid interval property name %s.", identifier));
                  }
              }
              else if (currentType instanceof ChoiceType) {
                  ChoiceType choiceType = (ChoiceType)currentType;
                  // TODO: Issue a warning if the property does not resolve against every type in the choice
  
                  // Resolve the property against each type in the choice
                  Set<DataType> resultTypes = new HashSet<>();
                  for (DataType choice : choiceType.getTypes()) {
                      DataType resultType = resolveProperty(choice, identifier, false);
                      if (resultType != null) {
                          resultTypes.add(resultType);
                      }
                  }
  
                  // The result type is a choice of all the resolved types
                  if (resultTypes.size() > 1) {
                      return new ChoiceType(resultTypes);
                  }
  
                  if (resultTypes.size() == 1) {
                      for (DataType resultType : resultTypes) {
                          return resultType;
                      }
                  }
              }
              else if (currentType instanceof ListType && listTraversal) {
                  // NOTE: FHIRPath path traversal support
                  // Resolve property as a list of items of property of the element type
                  ListType listType = (ListType)currentType;
                  DataType resultType = resolveProperty(listType.getElementType(), identifier);
                  return new ListType(resultType);
  
              }
  
              if (currentType.getBaseType() != null) {
                  currentType = currentType.getBaseType();
              }
              else {
                  break;
              }
          }
  
          if (mustResolve) {
              // ERROR:
              throw new IllegalArgumentException(String.format("Member %s not found for type %s.", identifier, sourceType != null ? sourceType.toLabel() : null));
          }
  
          return null;
      }