You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@daffodil.apache.org by ji...@apache.org on 2024/02/26 17:37:51 UTC

(daffodil) branch main updated: fn:count(non-array) must issue SDE

This is an automated email from the ASF dual-hosted git repository.

jinterrante pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/daffodil.git


The following commit(s) were added to refs/heads/main by this push:
     new 89d02df84 fn:count(non-array) must issue SDE
89d02df84 is described below

commit 89d02df846692cda2cde4467e8bc05b6a14251f2
Author: pkatlic <ka...@ge.com>
AuthorDate: Mon Feb 26 11:40:25 2024 -0500

    fn:count(non-array) must issue SDE
    
    Throw a SDE during compilation if the fn:count function is called on a path
    expression that ends with a unique scalar element.
    
    DAFFODIL-2711
    
    Expression.scala: Remove unused isTypeCorrect and checkTypeCorrectness properties. Add functions to check for Array or Optional elements and combine common code, update naming to include OrOptional. Rename isOptional to isArrayOrOptional.  Check for isLastStep in downwardStep. Add checkArgArrayOrOptional function to check for an array or optional element and call for FNCount and FNExactlyOneExpr expressions.
    
    ElementBaseRuntime1Mixin.scala: Add isOptional property.
    
    dafext.xsd: Remove unused enumeration.
    
    ArrayRelated.scala: Modify FNCount run implementation to handle Optional elements.
    
    DState.scala: Remove isAnArray function since it is now redundant.
    
    FNFunctions.scala: Fix typo.
    
    CompiledExpression1.scala: Add isOptional property.
    
    RuntimeData.scala: Add isOptional to constructor.
    
    testWarnings.tdml: Update error message check.
    
    TestTDMLRunnerWarnings.scala: Update error message check.
    
    PrefixedTests.tdml: Update element to use fn:exists instead of fn:count.
    
    expressions.tdml: Update element to use fn:exists instead of fn:count.
    
    Functions.tdml: Add tests for fn:count, update error message checks and comments. Note that using a group reference for an optional when combined with an array will throw a ClassCastException, this may be a bug where an optional is evaluated as a DownArray instead of a DownElement.
    
    TestDFDLExpressions.scala: Add new test cases for Functions.tdml.
---
 .../apache/daffodil/core/dpath/Expression.scala    | 101 +++--
 .../core/runtime1/ElementBaseRuntime1Mixin.scala   |   1 +
 .../resources/org/apache/daffodil/xsd/dafext.xsd   |   1 -
 .../daffodil/runtime1/dpath/ArrayRelated.scala     |  23 +-
 .../apache/daffodil/runtime1/dpath/DState.scala    |  44 +-
 .../daffodil/runtime1/dpath/FNFunctions.scala      |   2 +-
 .../runtime1/dsom/CompiledExpression1.scala        |   1 +
 .../daffodil/runtime1/processors/RuntimeData.scala |   1 +
 .../src/test/resources/test/tdml/testWarnings.tdml |   2 +-
 .../processor/tdml/TestTDMLRunnerWarnings.scala    |   5 +
 .../section12/lengthKind/PrefixedTests.tdml        |   2 +-
 .../section23/dfdl_expressions/expressions.tdml    |   2 +-
 .../section23/dfdl_functions/Functions.tdml        | 479 +++++++++++++++++++--
 .../dfdl_expressions/TestDFDLExpressions.scala     |  20 +
 14 files changed, 564 insertions(+), 120 deletions(-)

diff --git a/daffodil-core/src/main/scala/org/apache/daffodil/core/dpath/Expression.scala b/daffodil-core/src/main/scala/org/apache/daffodil/core/dpath/Expression.scala
index 3a4871f96..82e52d68b 100644
--- a/daffodil-core/src/main/scala/org/apache/daffodil/core/dpath/Expression.scala
+++ b/daffodil-core/src/main/scala/org/apache/daffodil/core/dpath/Expression.scala
@@ -89,17 +89,6 @@ abstract class Expression extends OOLAGHostImpl() with BasicComponent {
     res
   }
 
-  // This split allows overrides of this lazy val to still reuse this
-  // (super.isTypeCorrect doesn't work inside an overridden lazy val.)
-  lazy val isTypeCorrect: Boolean = checkTypeCorrectness
-  //
-  // override for other checking beyond what is needed to do conversions
-  //
-  protected lazy val checkTypeCorrectness =
-    children.forall { child =>
-      child.isTypeCorrect
-    }
-
   def text: String
 
   def hasReferenceTo(elem: DPathElementCompileInfo): Boolean = {
@@ -744,21 +733,28 @@ abstract class PathExpression() extends Expression {
    * about an array such as its current count.
    */
   lazy val isPathToOneWholeArray: Boolean = {
-    if (steps == Nil) false // root is never an array
-    if (steps == Nil) false // root is never an array
-    else if (isSelf) false // self path is never an array
-    else
-      steps.last.isArray &&
-      !steps.last.pred.isDefined && // last cannot have a [N] pred
-      steps.dropRight(1).forall {
-        // for all the prior steps
-        // if they mention an array element, there
-        // must be a [N] predicate.
-        step =>
-          if (step.isInstanceOf[Up]) true
-          else if (step.isArray) step.pred.isDefined
-          else true
-      }
+    isNotRootOrSelf &&
+    steps.last.isArray &&
+    steps.last.pred.isEmpty
+  }
+
+  /**
+   * Path to a single array element without a [N] predicate qualifying it
+   * or to an optional element.
+   *
+   * That is, the kind of path expression used to access information
+   * about an array or optional such as its current count.
+   */
+  lazy val isPathToOneWholeArrayOrOptional: Boolean = {
+    isNotRootOrSelf &&
+    steps.last.isArrayOrOptional &&
+    steps.last.pred.isEmpty
+  }
+
+  private lazy val isNotRootOrSelf: Boolean = {
+    if (steps == Nil) false
+    else if (isSelf) false
+    else true
   }
 
   def isSelf = steps match {
@@ -969,16 +965,27 @@ sealed abstract class StepExpression(val step: String, val pred: Option[Predicat
   }
 
   final lazy val isArray: Boolean = {
-    val (arrays, scalars) = stepElements.partition { _.isArray }
-    (arrays.length, scalars.length) match {
+    checkAmbiguousPath
+    stepElements.exists(_.isArray)
+  }
+
+  final lazy val isArrayOrOptional: Boolean = {
+    checkAmbiguousPath
+    stepElements.exists(_.isArray) || stepElements.exists(_.isOptional)
+  }
+
+  final lazy val checkAmbiguousPath: Unit = {
+    val (arraysOrOptionals, scalars) = stepElements.partition { se =>
+      se.isArray || se.isOptional
+    }
+    (arraysOrOptionals.length, scalars.length) match {
       case (a, s) if (a > 0 && s > 0) =>
-        arrays.head.SDE(
-          "Path step is ambiguous. It can be to an array or a non-array element.\n" +
+        arraysOrOptionals.head.SDE(
+          "Path step is ambiguous. It can reference both an array or a non-array element, which is not allowed.\n" +
             "One of the non-arrays is %s",
           scalars.head.schemaFileLocation.toString,
         )
-      case (a, s) if (a == 0) => false
-      case _ => true
+      case _ => // do nothing
     }
   }
 
@@ -1215,7 +1222,7 @@ case class NamedStep(s: String, predArg: Option[PredicateExpression])
         Assert.invariant(pred.get.targetType == NodeInfo.ArrayIndex)
         val indexRecipe = pred.get.compiledDPath
         new DownArrayOccurrence(nqn, indexRecipe)
-      } else if (targetType == NodeInfo.Exists) {
+      } else if (isLastStep && targetType == NodeInfo.Exists) {
         new DownArrayExists(nqn)
       } else {
         schemaDefinitionUnless(
@@ -2295,6 +2302,20 @@ abstract class FunctionCallBase(
     res
   }
 
+  protected def checkArgArrayOrOptional(n: Int): Unit = {
+    lazy val isArrayOrOptional = expressions(n) match {
+      case pe: PathExpression => pe.isPathToOneWholeArrayOrOptional
+      case _ => false
+    }
+    if (!isArrayOrOptional) argArrayOrOptionalErr()
+  }
+  protected def argArrayOrOptionalErr() = {
+    SDE(
+      "The %s function requires an array or optional path expression.",
+      functionQName.toPrettyString,
+    )
+  }
+
   final def checkArgCount(n: Int): Unit = {
     if (expressions.length != n) argCountErr(n)
   }
@@ -2335,7 +2356,7 @@ abstract class FunctionCallBase(
 /**
  * Tells the sub-expression that we want an array out of it.
  */
-abstract class FunctionCallArrayBase(
+abstract class FunctionCallArrayOrOptionalBase(
   nameAsParsed: String,
   fnQName: RefQName,
   args: List[Expression],
@@ -2348,11 +2369,6 @@ abstract class FunctionCallArrayBase(
     case _ => subsetError("The %s function must contain a path.", funcName)
   }
 
-  override lazy val isTypeCorrect = {
-    checkArgCount(1)
-    arrPath.isPathToOneWholeArray
-  }
-
   override def targetTypeForSubexpression(subExp: Expression): NodeInfo.Kind = NodeInfo.Array
 
 }
@@ -2361,7 +2377,7 @@ abstract class FunctionCallArrayBase(
  * Tells the sub-expression that we want an array out of it.
  */
 case class FNCountExpr(nameAsParsed: String, fnQName: RefQName, args: List[Expression])
-  extends FunctionCallArrayBase(nameAsParsed, fnQName, args) {
+  extends FunctionCallArrayOrOptionalBase(nameAsParsed, fnQName, args) {
 
   val funcName: String = "fn:count"
 
@@ -2369,6 +2385,7 @@ case class FNCountExpr(nameAsParsed: String, fnQName: RefQName, args: List[Expre
 
   override lazy val compiledDPath = {
     checkArgCount(1)
+    checkArgArrayOrOptional(0)
     val arg0Recipe = args(0).compiledDPath
     val arg0Type = args(0).inherentType
     val c = conversions
@@ -2378,7 +2395,7 @@ case class FNCountExpr(nameAsParsed: String, fnQName: RefQName, args: List[Expre
 }
 
 case class FNExactlyOneExpr(nameAsParsed: String, fnQName: RefQName, args: List[Expression])
-  extends FunctionCallArrayBase(nameAsParsed, fnQName, args) {
+  extends FunctionCallArrayOrOptionalBase(nameAsParsed, fnQName, args) {
 
   val funcName: String = "fn:exactly-one"
 
@@ -2386,6 +2403,7 @@ case class FNExactlyOneExpr(nameAsParsed: String, fnQName: RefQName, args: List[
 
   override lazy val compiledDPath = {
     checkArgCount(1)
+    checkArgArrayOrOptional(0)
     subsetError("fn:exactly-one is not supported.")
   }
 }
@@ -2797,6 +2815,7 @@ sealed abstract class LengthExprBase(
         // $COVERAGE-ON$
       }
     }
+    // checks specifically for an array, but does not apply to an optional
     if (path.isPathToOneWholeArray)
       SDE(s"First argument to ${fnQName} cannot be a path to an array")
     val steps = path.steps
diff --git a/daffodil-core/src/main/scala/org/apache/daffodil/core/runtime1/ElementBaseRuntime1Mixin.scala b/daffodil-core/src/main/scala/org/apache/daffodil/core/runtime1/ElementBaseRuntime1Mixin.scala
index 9920d7d7c..4cc7381a1 100644
--- a/daffodil-core/src/main/scala/org/apache/daffodil/core/runtime1/ElementBaseRuntime1Mixin.scala
+++ b/daffodil-core/src/main/scala/org/apache/daffodil/core/runtime1/ElementBaseRuntime1Mixin.scala
@@ -151,6 +151,7 @@ trait ElementBaseRuntime1Mixin { self: ElementBase =>
       slashPath,
       name,
       isArray,
+      isOptional,
       namedQName,
       optPrimType,
       schemaFileLocation,
diff --git a/daffodil-propgen/src/main/resources/org/apache/daffodil/xsd/dafext.xsd b/daffodil-propgen/src/main/resources/org/apache/daffodil/xsd/dafext.xsd
index 3afc77cf7..4e15b30fd 100644
--- a/daffodil-propgen/src/main/resources/org/apache/daffodil/xsd/dafext.xsd
+++ b/daffodil-propgen/src/main/resources/org/apache/daffodil/xsd/dafext.xsd
@@ -713,7 +713,6 @@
           <xs:enumeration value="namespaceDifferencesOnly" />
           <xs:enumeration value="noEmptyDefault" />
           <xs:enumeration value="nonExpressionPropertyValueLooksLikeExpression" />
-          <xs:enumeration value="pathNotToArray" />
           <xs:enumeration value="patternEncodingSlashW" />
           <xs:enumeration value="queryStylePathExpression" />
           <xs:enumeration value="regexPatternZeroLength" />
diff --git a/daffodil-runtime1/src/main/scala/org/apache/daffodil/runtime1/dpath/ArrayRelated.scala b/daffodil-runtime1/src/main/scala/org/apache/daffodil/runtime1/dpath/ArrayRelated.scala
index 26a0029fb..f2bf39dca 100644
--- a/daffodil-runtime1/src/main/scala/org/apache/daffodil/runtime1/dpath/ArrayRelated.scala
+++ b/daffodil-runtime1/src/main/scala/org/apache/daffodil/runtime1/dpath/ArrayRelated.scala
@@ -27,16 +27,21 @@ case class FNCount(recipe: CompiledDPath, argType: NodeInfo.Kind)
   override def run(dstate: DState): Unit = {
     val res = exists(recipe, dstate)
     if (res) {
-      dstate.mode match {
-        case UnparserBlocking | UnparserNonBlocking => {
-          // we are unparsing, so asking for an array count is asking for the
-          // final count, not however many are in there at this point
-          // so we have to know if the array is closed or not.
-          dstate.setCurrentValue(dstate.finalArrayLength)
-        }
-        case _: ParserMode => {
-          dstate.setCurrentValue(dstate.arrayLength)
+      if (dstate.currentNode.isArray) {
+        dstate.mode match {
+          case UnparserBlocking | UnparserNonBlocking => {
+            // we are unparsing, so asking for an array count is asking for the
+            // final count, not however many are in there at this point
+            // so we have to know if the array is closed or not.
+            dstate.setCurrentValue(dstate.finalArrayLength)
+          }
+          case _: ParserMode => {
+            dstate.setCurrentValue(dstate.arrayLength)
+          }
         }
+      } else {
+        // optional element is known to exist
+        dstate.setCurrentValue(1)
       }
     } else {
       dstate.setCurrentValue(0)
diff --git a/daffodil-runtime1/src/main/scala/org/apache/daffodil/runtime1/dpath/DState.scala b/daffodil-runtime1/src/main/scala/org/apache/daffodil/runtime1/dpath/DState.scala
index e5d4b13b0..c90fcdc52 100644
--- a/daffodil-runtime1/src/main/scala/org/apache/daffodil/runtime1/dpath/DState.scala
+++ b/daffodil-runtime1/src/main/scala/org/apache/daffodil/runtime1/dpath/DState.scala
@@ -41,7 +41,6 @@ import java.math.{ BigInteger => JBigInt }
 
 import org.apache.daffodil.lib.api.DaffodilTunables
 import org.apache.daffodil.lib.api.DataLocation
-import org.apache.daffodil.lib.api.WarnID
 import org.apache.daffodil.runtime1.dsom.DPathCompileInfo
 import org.apache.daffodil.runtime1.infoset.DataValue.DataValuePrimitiveNullable
 
@@ -118,6 +117,7 @@ case class DState(
   tunable: DaffodilTunables,
   val parseOrUnparseState: Maybe[ParseOrUnparseState],
 ) {
+
   import org.apache.daffodil.lib.util.Numbers._
 
   var isCompile = false
@@ -140,6 +140,7 @@ case class DState(
   def setMode(m: EvalMode): Unit = {
     _mode = m
   }
+
   def mode = _mode
 
   //
@@ -203,10 +204,12 @@ case class DState(
     _currentValue = v
     _currentNode = null
   }
+
   def setCurrentValue(v: Long): Unit = {
     _currentValue = v
     _currentNode = null
   }
+
   def setCurrentValue(v: Boolean): Unit = {
     _currentValue = v
     _currentNode = null
@@ -215,45 +218,26 @@ case class DState(
   def booleanValue: Boolean = currentValue.getBoolean
 
   def longValue: Long = asLong(currentValue.getAnyRef)
+
   def intValue: Int = longValue.toInt
+
   def doubleValue: Double = asDouble(currentValue.getAnyRef)
 
   def integerValue: JBigInt = asBigInt(currentValue.getAnyRef)
+
   def decimalValue: JBigDecimal = asBigDecimal(currentValue.getAnyRef)
+
   def stringValue: String = currentValue.getString
 
   def isNilled: Boolean = currentElement.isNilled
 
-  private def isAnArray(): Boolean = {
-    if (!currentNode.isInstanceOf[DIArray]) {
-      Assert.invariant(errorOrWarn.isDefined)
-      if (currentNode.isInstanceOf[DIElement]) {
-        errorOrWarn.get.SDW(
-          WarnID.PathNotToArray,
-          "The specified path to element %s is not to an array. Suggest using fn:exists instead.",
-          currentElement.name,
-        )
-      } else {
-        errorOrWarn.get.SDW(
-          WarnID.PathNotToArray,
-          "The specified path is not to an array. Suggest using fn:exists instead.",
-        )
-      }
-      false
-    } else {
-      true
-    }
-  }
-
   def arrayLength: Long =
-    if (isAnArray()) currentArray.length
-    else 1L
-
-  def finalArrayLength: Long =
-    if (isAnArray()) {
-      currentArray.requireFinal
-      currentArray.length
-    } else 1L
+    currentArray.length
+
+  def finalArrayLength: Long = {
+    currentArray.requireFinal()
+    currentArray.length
+  }
 
   def exists: Boolean = true // we're at a node, so it must exist.
 
diff --git a/daffodil-runtime1/src/main/scala/org/apache/daffodil/runtime1/dpath/FNFunctions.scala b/daffodil-runtime1/src/main/scala/org/apache/daffodil/runtime1/dpath/FNFunctions.scala
index b6b1ee4ae..854bdb076 100644
--- a/daffodil-runtime1/src/main/scala/org/apache/daffodil/runtime1/dpath/FNFunctions.scala
+++ b/daffodil-runtime1/src/main/scala/org/apache/daffodil/runtime1/dpath/FNFunctions.scala
@@ -634,7 +634,7 @@ trait ExistsKind {
     Assert.invariant(dstate.currentNode ne null)
     val res = dstate.mode match {
       case UnparserNonBlocking => {
-        // we are evaluating an expresion while unparsing in non blocking mode.
+        // we are evaluating an expression while unparsing in non blocking mode.
         // This means this throwable must have come from an evaluatable using
         // only backwards references. Backwards references are known to be
         // final (even if isFinal isn't set yet), so because an exception was
diff --git a/daffodil-runtime1/src/main/scala/org/apache/daffodil/runtime1/dsom/CompiledExpression1.scala b/daffodil-runtime1/src/main/scala/org/apache/daffodil/runtime1/dsom/CompiledExpression1.scala
index fa7213bc8..df7b8b8a9 100644
--- a/daffodil-runtime1/src/main/scala/org/apache/daffodil/runtime1/dsom/CompiledExpression1.scala
+++ b/daffodil-runtime1/src/main/scala/org/apache/daffodil/runtime1/dsom/CompiledExpression1.scala
@@ -344,6 +344,7 @@ class DPathElementCompileInfo(
   path: String,
   val name: String,
   val isArray: Boolean,
+  val isOptional: Boolean,
   val namedQName: NamedQName,
   val optPrimType: Option[PrimType],
   sfl: SchemaFileLocation,
diff --git a/daffodil-runtime1/src/main/scala/org/apache/daffodil/runtime1/processors/RuntimeData.scala b/daffodil-runtime1/src/main/scala/org/apache/daffodil/runtime1/processors/RuntimeData.scala
index cef3b0544..684ed8b13 100644
--- a/daffodil-runtime1/src/main/scala/org/apache/daffodil/runtime1/processors/RuntimeData.scala
+++ b/daffodil-runtime1/src/main/scala/org/apache/daffodil/runtime1/processors/RuntimeData.scala
@@ -764,6 +764,7 @@ sealed abstract class ErrorERD(local: String, namespaceURI: String)
       local, // path: String,
       local, // val name: String,
       false, // val isArray: Boolean,
+      false, // val isOptional: Boolean,
       LocalDeclQName(None, local, NS(namespaceURI)), // val namedQName: NamedQName,
       None, // val optPrimType: Option[PrimType],
       null, // sfl: SchemaFileLocation,
diff --git a/daffodil-tdml-processor/src/test/resources/test/tdml/testWarnings.tdml b/daffodil-tdml-processor/src/test/resources/test/tdml/testWarnings.tdml
index 61ba83035..efe1e107f 100644
--- a/daffodil-tdml-processor/src/test/resources/test/tdml/testWarnings.tdml
+++ b/daffodil-tdml-processor/src/test/resources/test/tdml/testWarnings.tdml
@@ -98,7 +98,7 @@
 
   <tdml:errors>
     <tdml:error>Schema Definition Error</tdml:error>
-    <tdml:error>query-style</tdml:error>
+    <tdml:error>ambiguous</tdml:error>
     <tdml:error>AmbigElt</tdml:error>
   </tdml:errors>
 
diff --git a/daffodil-tdml-processor/src/test/scala/org/apache/daffodil/processor/tdml/TestTDMLRunnerWarnings.scala b/daffodil-tdml-processor/src/test/scala/org/apache/daffodil/processor/tdml/TestTDMLRunnerWarnings.scala
index 1e586fe5e..96027482b 100644
--- a/daffodil-tdml-processor/src/test/scala/org/apache/daffodil/processor/tdml/TestTDMLRunnerWarnings.scala
+++ b/daffodil-tdml-processor/src/test/scala/org/apache/daffodil/processor/tdml/TestTDMLRunnerWarnings.scala
@@ -185,6 +185,11 @@ class TestTDMLRunnerWarnings {
               </ex:errUnparsing>
             </tdml:dfdlInfoset>
           </tdml:infoset>
+          <tdml:errors>
+            <tdml:error>Schema Definition Error</tdml:error>
+            <tdml:error>ambiguous</tdml:error>
+            <tdml:error>AmbigElt</tdml:error>
+          </tdml:errors>
           <tdml:warnings>
             <tdml:warning>This will not be found</tdml:warning>
           </tdml:warnings>
diff --git a/daffodil-test/src/test/resources/org/apache/daffodil/section12/lengthKind/PrefixedTests.tdml b/daffodil-test/src/test/resources/org/apache/daffodil/section12/lengthKind/PrefixedTests.tdml
index b1b37deb6..e45f75c5e 100644
--- a/daffodil-test/src/test/resources/org/apache/daffodil/section12/lengthKind/PrefixedTests.tdml
+++ b/daffodil-test/src/test/resources/org/apache/daffodil/section12/lengthKind/PrefixedTests.tdml
@@ -448,7 +448,7 @@
       <xs:restriction base="xs:integer" /> 
     </xs:simpleType>
 
-    <xs:simpleType name="prefixExpression" dfdl:lengthKind="explicit" dfdl:length="{ fn:count(.) }">
+    <xs:simpleType name="prefixExpression" dfdl:lengthKind="explicit" dfdl:length="{ fn:exists(.) }">
       <xs:restriction base="xs:integer" /> 
     </xs:simpleType>
 
diff --git a/daffodil-test/src/test/resources/org/apache/daffodil/section23/dfdl_expressions/expressions.tdml b/daffodil-test/src/test/resources/org/apache/daffodil/section23/dfdl_expressions/expressions.tdml
index 6ca8c3621..a1f634e46 100644
--- a/daffodil-test/src/test/resources/org/apache/daffodil/section23/dfdl_expressions/expressions.tdml
+++ b/daffodil-test/src/test/resources/org/apache/daffodil/section23/dfdl_expressions/expressions.tdml
@@ -213,7 +213,7 @@
           <xs:element name="a" type="xs:int" />
           <xs:element name="a" type="xs:int" />
           <xs:element name="a" type="xs:int" />
-          <xs:element name="ivc" type="xs:int" dfdl:inputValueCalc="{ fn:count(../ex:a) }"/>
+          <xs:element name="ivc" type="xs:int" dfdl:inputValueCalc="{ fn:exists(../ex:a) }"/>
         </xs:sequence>
       </xs:complexType>
     </xs:element>
diff --git a/daffodil-test/src/test/resources/org/apache/daffodil/section23/dfdl_functions/Functions.tdml b/daffodil-test/src/test/resources/org/apache/daffodil/section23/dfdl_functions/Functions.tdml
index a87ce6b4d..b00386f9d 100644
--- a/daffodil-test/src/test/resources/org/apache/daffodil/section23/dfdl_functions/Functions.tdml
+++ b/daffodil-test/src/test/resources/org/apache/daffodil/section23/dfdl_functions/Functions.tdml
@@ -167,7 +167,7 @@
         <xs:appinfo source="http://www.ogf.org/dfdl/">
           <dfdl:assert test="{ fn:count( /ex:more_count1 ) eq 3 }"
             message="Assertion 
-            failed for fn:count( /ex:more_count ) eq 3" />
+            failed for fn:count( /ex:more_count1 ) eq 3" />
         </xs:appinfo>
       </xs:annotation>
       <xs:complexType>
@@ -182,7 +182,7 @@
         <xs:appinfo source="http://www.ogf.org/dfdl/">
           <dfdl:assert test="{ fn:count( /ex:more_count1b ) eq 1 }"
             message="Assertion 
-            failed for fn:count( /ex:more_count ) eq 1" />
+            failed for fn:count( /ex:more_count1b ) eq 1" />
         </xs:appinfo>
       </xs:annotation>
       <xs:complexType>
@@ -3323,14 +3323,11 @@
     <tdml:document>
       <tdml:documentPart type="text"><![CDATA[#:1]]></tdml:documentPart>
     </tdml:document>
-    <tdml:infoset>
-      <tdml:dfdlInfoset xmlns:xs="http://www.w3.org/2001/XMLSchema"
-        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
-        <more_count1b>
-          <i1>1</i1>
-        </more_count1b>
-      </tdml:dfdlInfoset>
-    </tdml:infoset>
+    <tdml:errors>
+      <tdml:error>Schema Definition Error</tdml:error>
+      <tdml:error>fn:count</tdml:error>
+      <tdml:error>array</tdml:error>
+    </tdml:errors>
   </tdml:parserTestCase>
 
 <!--
@@ -5150,6 +5147,145 @@
       </xs:complexType>
     </xs:element>
 
+    <xs:element name="countArray">
+      <xs:complexType>
+        <xs:sequence>
+          <xs:element name="int" type="xs:int" minOccurs="2" maxOccurs="2" dfdl:representation="binary" />
+          <xs:element name="count" type="xs:int" dfdl:representation="binary" dfdl:inputValueCalc="{ fn:count(../ex:int) }" />
+        </xs:sequence>
+      </xs:complexType>
+    </xs:element>
+
+    <xs:element name="countInput">
+      <xs:complexType>
+        <xs:sequence>
+          <xs:element name="int" type="xs:int" dfdl:representation="binary" />
+          <xs:element name="count" type="xs:int" dfdl:representation="binary" dfdl:inputValueCalc="{ fn:count(../ex:int) }" />
+        </xs:sequence>
+      </xs:complexType>
+    </xs:element>
+
+    <xs:element name="countOptional">
+      <xs:complexType>
+        <xs:sequence>
+          <xs:element name="int" type="xs:int" minOccurs="0" maxOccurs="1" dfdl:representation="binary" />
+          <xs:element name="count" type="xs:int" dfdl:representation="binary" dfdl:inputValueCalc="{ fn:count(../ex:int) }" />
+        </xs:sequence>
+      </xs:complexType>
+    </xs:element>
+
+    <xs:element name="countOutput">
+      <xs:complexType>
+        <xs:sequence>
+          <xs:element name="int" type="xs:int" dfdl:representation="binary" />
+          <xs:element name="count" type="xs:int" dfdl:representation="binary" dfdl:outputValueCalc="{ fn:count(../ex:int) }" />
+        </xs:sequence>
+      </xs:complexType>
+    </xs:element>
+
+    <xs:element name="countOccurs">
+      <xs:complexType>
+        <xs:sequence>
+          <xs:element name="int" type="xs:int" dfdl:representation="binary" />
+          <xs:element name="count" type="xs:int" dfdl:representation="binary" dfdl:occursCountKind="expression" dfdl:occursCount="{ fn:count(../ex:int) }" />
+        </xs:sequence>
+      </xs:complexType>
+    </xs:element>
+
+    <xs:element name="countIfArrayOptional">
+      <xs:complexType>
+        <xs:sequence>
+          <xs:element name="int" type="xs:int" minOccurs="2" maxOccurs="2" dfdl:representation="binary" />
+          <xs:element name="int2" type="xs:int" minOccurs="0" maxOccurs="1" dfdl:representation="binary" />
+          <xs:element name="count" type="xs:int" dfdl:representation="binary" dfdl:outputValueCalc="{ fn:count(if (../ex:int[1] ne 0) then ../ex:int else ../ex:int2) }" />
+        </xs:sequence>
+      </xs:complexType>
+    </xs:element>
+
+    <xs:element name="countIfArrayScalar">
+      <xs:complexType>
+        <xs:sequence>
+          <xs:element name="int" type="xs:int" minOccurs="2" maxOccurs="2" dfdl:representation="binary" />
+          <xs:element name="int2" type="xs:int" minOccurs="1" maxOccurs="1" dfdl:representation="binary" />
+          <xs:element name="count" type="xs:int" dfdl:representation="binary" dfdl:outputValueCalc="{ fn:count(if (../ex:int[1] ne 0) then ../ex:int[1] else ../ex:int2) }" />
+        </xs:sequence>
+      </xs:complexType>
+    </xs:element>
+
+    <xs:group name="countGroup">
+      <xs:sequence>
+        <xs:element name="count" type="xs:int" dfdl:representation="binary" dfdl:inputValueCalc="{ fn:count(../ex:foo) }"/>
+      </xs:sequence>
+    </xs:group>
+    <xs:element name="scalarOptional">
+      <xs:complexType>
+        <xs:sequence>
+          <xs:element name="scalar">
+            <xs:complexType>
+              <xs:sequence>
+                <xs:element name="foo" type="xs:int" dfdl:representation="binary"/>
+                <xs:group ref="countGroup"/>
+              </xs:sequence>
+            </xs:complexType>
+          </xs:element>
+          <xs:element name="optional">
+            <xs:complexType>
+              <xs:sequence>
+                <xs:element name="foo" type="xs:int" minOccurs="0" dfdl:representation="binary"/>
+                <xs:group ref="countGroup"/>
+              </xs:sequence>
+            </xs:complexType>
+          </xs:element>
+        </xs:sequence>
+      </xs:complexType>
+    </xs:element>
+    <xs:element name="scalarArray">
+      <xs:complexType>
+        <xs:sequence>
+          <xs:element name="scalar">
+            <xs:complexType>
+              <xs:sequence>
+                <xs:element name="foo" type="xs:int" dfdl:representation="binary"/>
+                <xs:group ref="countGroup"/>
+              </xs:sequence>
+            </xs:complexType>
+          </xs:element>
+          <xs:element name="array">
+            <xs:complexType>
+              <xs:sequence>
+                <xs:element name="foo" type="xs:int" minOccurs="2" maxOccurs="2" dfdl:representation="binary"/>
+                <xs:group ref="countGroup"/>
+              </xs:sequence>
+            </xs:complexType>
+          </xs:element>
+        </xs:sequence>
+      </xs:complexType>
+    </xs:element>
+    <xs:element name="arrayOptional">
+      <xs:complexType>
+        <xs:sequence>
+          <xs:element name="array">
+            <xs:complexType>
+              <xs:sequence>
+                <xs:element name="foo" type="xs:int" minOccurs="2" maxOccurs="2" dfdl:representation="binary"/>
+                <xs:group ref="countGroup"/>
+              </xs:sequence>
+            </xs:complexType>
+          </xs:element>
+          <xs:element name="optional">
+            <xs:complexType>
+              <xs:sequence>
+                <xs:element name="foo" type="xs:int" minOccurs="0" maxOccurs="1" dfdl:representation="binary"/>
+                <!-- throws ClassCastException, possible bug in Daffodil group evaluation -->
+                <!-- <xs:group ref="countGroup"/> -->
+                <xs:element name="count" type="xs:int" dfdl:representation="binary" dfdl:inputValueCalc="{ fn:count(../ex:foo) }"/>
+              </xs:sequence>
+            </xs:complexType>
+          </xs:element>
+        </xs:sequence>
+      </xs:complexType>
+    </xs:element>
+
     <xs:element name="local-name01">
       <xs:complexType>
         <xs:sequence dfdl:separator=",">
@@ -11045,18 +11181,11 @@
     <tdml:document>
       <tdml:documentPart type="text"></tdml:documentPart>
     </tdml:document>
-    <tdml:infoset>
-      <tdml:dfdlInfoset>
-        <count02>
-          <calced>calculated string</calced>
-          <count>1</count>
-        </count02>
-      </tdml:dfdlInfoset>
-    </tdml:infoset>
-    <tdml:warnings>
-      <tdml:warning>Runtime Schema Definition Warning</tdml:warning>
-      <tdml:warning>fn:exists</tdml:warning>
-    </tdml:warnings>
+    <tdml:errors>
+      <tdml:error>Schema Definition Error</tdml:error>
+      <tdml:error>fn:count</tdml:error>
+      <tdml:error>array</tdml:error>
+    </tdml:errors>
   </tdml:parserTestCase>
 
 <!--
@@ -11072,19 +11201,11 @@
     <tdml:document>
       <tdml:documentPart type="text"></tdml:documentPart>
     </tdml:document>
-    <tdml:infoset>
-      <tdml:dfdlInfoset>
-        <count02b>
-          <str>calculated string</str>
-          <calced>calculated string</calced>
-          <count>1</count>
-        </count02b>
-      </tdml:dfdlInfoset>
-    </tdml:infoset>
-    <tdml:warnings>
-      <tdml:warning>Runtime Schema Definition Warning</tdml:warning>
-      <tdml:warning>fn:exists</tdml:warning>
-    </tdml:warnings>
+    <tdml:errors>
+      <tdml:error>Schema Definition Error</tdml:error>
+      <tdml:error>fn:count</tdml:error>
+      <tdml:error>array</tdml:error>
+    </tdml:errors>
   </tdml:parserTestCase>
 
 <!--
@@ -11390,6 +11511,294 @@
     </tdml:infoset>
   </tdml:unparserTestCase>
 
+  <!--
+      Test Name: count_12
+         Schema: XPathFunctions
+           Root: countArray
+           Purpose: This test demonstrates the count() function works as expected
+                    when parsing an array of scalars
+  -->
+
+  <tdml:parserTestCase name="count_12" root="countArray"
+    model="XPathFunctions" description="Section 23 - Functions - fn:count - DFDL-23-122R">
+    <tdml:document>
+      <tdml:documentPart type="byte">00 00 00 01 00 00 00 02</tdml:documentPart>
+    </tdml:document>
+    <tdml:infoset>
+      <tdml:dfdlInfoset>
+        <ex:countArray xmlns:ex="http://example.com">
+          <ex:int>1</ex:int>
+          <ex:int>2</ex:int>
+          <ex:count>2</ex:count>
+        </ex:countArray>
+      </tdml:dfdlInfoset>
+    </tdml:infoset>
+  </tdml:parserTestCase>
+
+  <!--
+      Test Name: count_13
+         Schema: XPathFunctions
+           Root: countInput
+           Purpose: This test demonstrates the count() function works as expected
+                    when parsing a unique scalar element
+  -->
+
+  <tdml:parserTestCase name="count_13" root="countInput" roundTrip="true"
+                       model="XPathFunctions" description="Section 23 - Functions - fn:count - DFDL-23-122R">
+    <tdml:document>
+      <tdml:documentPart type="byte">00 00 00 01</tdml:documentPart>
+    </tdml:document>
+    <tdml:errors>
+      <tdml:error>Schema Definition Error</tdml:error>
+      <tdml:error>fn:count</tdml:error>
+      <tdml:error>array</tdml:error>
+    </tdml:errors>
+  </tdml:parserTestCase>
+
+  <!--
+      Test Name: count_14
+         Schema: XPathFunctions
+           Root: countOptional
+           Purpose: This test demonstrates the count() function works as expected
+                    when parsing an optional element
+  -->
+
+  <tdml:parserTestCase name="count_14" root="countOptional" roundTrip="true"
+                       model="XPathFunctions" description="Section 23 - Functions - fn:count - DFDL-23-122R">
+    <tdml:document>
+      <tdml:documentPart type="byte">00 00 00 01</tdml:documentPart>
+    </tdml:document>
+    <tdml:infoset>
+      <tdml:dfdlInfoset>
+        <ex:countOptional xmlns:ex="http://example.com">
+          <ex:int>1</ex:int>
+          <ex:count>1</ex:count>
+        </ex:countOptional>
+      </tdml:dfdlInfoset>
+    </tdml:infoset>
+  </tdml:parserTestCase>
+
+  <!--
+      Test Name: count_14b
+         Schema: XPathFunctions
+           Root: countOptional
+           Purpose: This test demonstrates the count() function works as expected
+                    when parsing an optional element
+  -->
+
+  <tdml:parserTestCase name="count_14b" root="countOptional" roundTrip="true"
+                       model="XPathFunctions" description="Section 23 - Functions - fn:count - DFDL-23-122R">
+    <tdml:document>
+      <tdml:documentPart type="byte"></tdml:documentPart>
+    </tdml:document>
+    <tdml:infoset>
+      <tdml:dfdlInfoset>
+        <ex:countOptional xmlns:ex="http://example.com">
+          <ex:count>0</ex:count>
+        </ex:countOptional>
+      </tdml:dfdlInfoset>
+    </tdml:infoset>
+  </tdml:parserTestCase>
+
+  <!--
+      Test Name: count_15
+         Schema: XPathFunctions
+           Root: countOutput
+           Purpose: This test demonstrates the count() function works as expected
+                    when parsing a unique scalar element using outputValueCalc
+  -->
+
+  <tdml:parserTestCase name="count_15" root="countOutput" roundTrip="true"
+                       model="XPathFunctions" description="Section 23 - Functions - fn:count - DFDL-23-122R">
+    <tdml:document>
+      <tdml:documentPart type="byte">00 00 00 01</tdml:documentPart>
+    </tdml:document>
+    <tdml:errors>
+      <tdml:error>Schema Definition Error</tdml:error>
+      <tdml:error>fn:count</tdml:error>
+      <tdml:error>array</tdml:error>
+    </tdml:errors>
+  </tdml:parserTestCase>
+
+  <!--
+      Test Name: count_16
+         Schema: XPathFunctions
+           Root: countOccurs
+           Purpose: This test demonstrates the count() function works as expected
+                    when parsing a unique scalar element using an occursCount expression
+  -->
+
+  <tdml:parserTestCase name="count_16" root="countOccurs" roundTrip="true"
+                       model="XPathFunctions" description="Section 23 - Functions - fn:count - DFDL-23-122R">
+    <tdml:document>
+      <tdml:documentPart type="byte">00 00 00 01</tdml:documentPart>
+    </tdml:document>
+    <tdml:errors>
+      <tdml:error>Schema Definition Error</tdml:error>
+      <tdml:error>fn:count</tdml:error>
+      <tdml:error>array</tdml:error>
+    </tdml:errors>
+  </tdml:parserTestCase>
+
+  <!--
+      Test Name: count_17
+         Schema: XPathFunctions
+           Root: countIfArrayOptional
+           Purpose: This test demonstrates the count() function works as expected
+                    when parsing an optional element inside a count expression containing subexpressions
+  -->
+
+  <tdml:parserTestCase name="count_17" root="countIfArrayOptional" roundTrip="true"
+                       model="XPathFunctions" description="Section 23 - Functions - fn:count - DFDL-23-122R">
+    <tdml:document>
+      <tdml:documentPart type="byte">00 00 00 00 00 00 00 00 00 00 00 01 00 00 00 01</tdml:documentPart>
+    </tdml:document>
+    <tdml:errors>
+      <tdml:error>Schema Definition Error</tdml:error>
+      <tdml:error>fn:count</tdml:error>
+      <tdml:error>array</tdml:error>
+    </tdml:errors>
+  </tdml:parserTestCase>
+
+  <!--
+      Test Name: count_18
+         Schema: XPathFunctions
+           Root: countIfArrayScalar
+           Purpose: This test demonstrates the count() function works as expected
+                    when parsing a scalar element inside a count expression containing subexpressions
+  -->
+
+  <tdml:parserTestCase name="count_18" root="countIfArrayScalar" roundTrip="true"
+                       model="XPathFunctions" description="Section 23 - Functions - fn:count - DFDL-23-122R">
+    <tdml:document>
+      <tdml:documentPart type="byte">00 00 00 00 00 00 00 00 00 00 00 01 00 00 00 01</tdml:documentPart>
+    </tdml:document>
+    <tdml:errors>
+      <tdml:error>Schema Definition Error</tdml:error>
+      <tdml:error>fn:count</tdml:error>
+      <tdml:error>array</tdml:error>
+    </tdml:errors>
+  </tdml:parserTestCase>
+
+  <!--
+      Test Name: count_19
+         Schema: XPathFunctions
+           Root: scalarOptional
+           Purpose: This test demonstrates the count() function works as expected
+                    when parsing a pair of scalar and optional elements
+  -->
+
+  <tdml:parserTestCase name="count_19" root="scalarOptional" roundTrip="true"
+                       model="XPathFunctions" description="Section 23 - Functions - fn:count - DFDL-23-122R">
+    <tdml:document>
+      <tdml:documentPart type="byte">00 00 00 01</tdml:documentPart>
+    </tdml:document>
+    <tdml:errors>
+      <tdml:error>Schema Definition Error</tdml:error>
+      <tdml:error>ex:foo</tdml:error>
+      <tdml:error>ambiguous</tdml:error>
+    </tdml:errors>
+  </tdml:parserTestCase>
+
+  <!--
+      Test Name: count_19b
+         Schema: XPathFunctions
+           Root: scalarOptional
+           Purpose: This test demonstrates the count() function works as expected
+                    when parsing a pair of scalar and optional elements
+  -->
+
+  <tdml:parserTestCase name="count_19b" root="scalarOptional" roundTrip="true"
+                       model="XPathFunctions" description="Section 23 - Functions - fn:count - DFDL-23-122R">
+    <tdml:document>
+      <tdml:documentPart type="byte">00 00 00 01 00 00 00 01</tdml:documentPart>
+    </tdml:document>
+    <tdml:errors>
+      <tdml:error>Schema Definition Error</tdml:error>
+      <tdml:error>ex:foo</tdml:error>
+      <tdml:error>ambiguous</tdml:error>
+    </tdml:errors>
+  </tdml:parserTestCase>
+
+  <!--
+      Test Name: count_20
+         Schema: XPathFunctions
+           Root: scalarArray
+           Purpose: This test demonstrates the count() function works as expected
+                    when parsing a pair of scalar and array elements
+  -->
+
+  <tdml:parserTestCase name="count_20" root="scalarArray" roundTrip="true"
+                       model="XPathFunctions" description="Section 23 - Functions - fn:count - DFDL-23-122R">
+    <tdml:document>
+      <tdml:documentPart type="byte">00 00 00 01 00 00 00 01 00 00 00 01</tdml:documentPart>
+    </tdml:document>
+    <tdml:errors>
+      <tdml:error>Schema Definition Error</tdml:error>
+      <tdml:error>ex:foo</tdml:error>
+      <tdml:error>ambiguous</tdml:error>
+    </tdml:errors>
+  </tdml:parserTestCase>
+
+  <!--
+      Test Name: count_21
+         Schema: XPathFunctions
+           Root: arrayOptional
+           Purpose: This test demonstrates the count() function works as expected
+                    when parsing a pair of optional and array elements
+  -->
+
+  <tdml:parserTestCase name="count_21" root="arrayOptional" roundTrip="true"
+                       model="XPathFunctions" description="Section 23 - Functions - fn:count - DFDL-23-122R">
+    <tdml:document>
+      <tdml:documentPart type="byte">00 00 00 01 00 00 00 01</tdml:documentPart>
+    </tdml:document>
+    <tdml:infoset>
+      <tdml:dfdlInfoset>
+        <ex:arrayOptional xmlns:ex="http://example.com">
+          <ex:array>
+            <ex:foo>1</ex:foo>
+            <ex:foo>1</ex:foo>
+            <ex:count>2</ex:count>
+          </ex:array>
+          <ex:optional>
+            <ex:count>0</ex:count>
+          </ex:optional>
+        </ex:arrayOptional>
+      </tdml:dfdlInfoset>
+    </tdml:infoset>
+  </tdml:parserTestCase>
+
+  <!--
+      Test Name: count_21b
+         Schema: XPathFunctions
+           Root: arrayOptional
+           Purpose: This test demonstrates the count() function works as expected
+                    when parsing a pair of optional and array elements
+  -->
+
+  <tdml:parserTestCase name="count_21b" root="arrayOptional" roundTrip="true"
+                       model="XPathFunctions" description="Section 23 - Functions - fn:count - DFDL-23-122R">
+    <tdml:document>
+      <tdml:documentPart type="byte">00 00 00 01 00 00 00 01 00 00 00 01</tdml:documentPart>
+    </tdml:document>
+    <tdml:infoset>
+      <tdml:dfdlInfoset>
+        <ex:arrayOptional xmlns:ex="http://example.com">
+          <ex:array>
+            <ex:foo>1</ex:foo>
+            <ex:foo>1</ex:foo>
+            <ex:count>2</ex:count>
+          </ex:array>
+          <ex:optional>
+            <ex:foo>1</ex:foo>
+            <ex:count>1</ex:count>
+          </ex:optional>
+        </ex:arrayOptional>
+      </tdml:dfdlInfoset>
+    </tdml:infoset>
+  </tdml:parserTestCase>
+
 <!--
     Test Name: local_name_01
        Schema: XPathFunctions
diff --git a/daffodil-test/src/test/scala/org/apache/daffodil/section23/dfdl_expressions/TestDFDLExpressions.scala b/daffodil-test/src/test/scala/org/apache/daffodil/section23/dfdl_expressions/TestDFDLExpressions.scala
index 370786770..74c88421e 100644
--- a/daffodil-test/src/test/scala/org/apache/daffodil/section23/dfdl_expressions/TestDFDLExpressions.scala
+++ b/daffodil-test/src/test/scala/org/apache/daffodil/section23/dfdl_expressions/TestDFDLExpressions.scala
@@ -769,6 +769,26 @@ class TestDFDLExpressions {
   @Test def test_count_10(): Unit = { runner2.runOneTest("count_10") }
   @Test def test_count_11(): Unit = { runner2.runOneTest("count_11") }
 
+  @Test def test_count_12(): Unit = { runner2.runOneTest("count_12") }
+  @Test def test_count_13(): Unit = { runner2.runOneTest("count_13") }
+  @Test def test_count_14(): Unit = { runner2.runOneTest("count_14") }
+  @Test def test_count_14b(): Unit = { runner2.runOneTest("count_14b") }
+  @Test def test_count_15(): Unit = { runner2.runOneTest("count_15") }
+  @Test def test_count_16(): Unit = { runner2.runOneTest("count_16") }
+  @Test def test_count_17(): Unit = { runner2.runOneTest("count_17") }
+
+  @Test def test_count_18(): Unit = { runner2.runOneTest("count_18") }
+
+  @Test def test_count_19(): Unit = { runner2.runOneTest("count_19") }
+
+  @Test def test_count_19b(): Unit = { runner2.runOneTest("count_19b") }
+
+  @Test def test_count_20(): Unit = { runner2.runOneTest("count_20") }
+
+  @Test def test_count_21(): Unit = { runner2.runOneTest("count_21") }
+
+  @Test def test_count_21b(): Unit = { runner2.runOneTest("count_21b") }
+
   @Test def test_local_name_01(): Unit = { runner2.runOneTest("local_name_01") }
   @Test def test_local_name_02(): Unit = { runner2.runOneTest("local_name_02") }
   @Test def test_local_name_03(): Unit = { runner2.runOneTest("local_name_03") }