   /// returns true for `v**.c3`
   /// returns true for `**`
   /// returns false for `s0.c3` (because e.g. `s1` would not match)
-  public var matchesAllValueFields: Bool {
+  public var topMatchesAnyValueField: Bool {
     switch top.kind {
       case .anyValueFields, .anything: return true
       default: return false
+    }
+  }
+
+  /// Returns true if the path does not have any class projections.
+  /// For example:
+  /// returns true for `v**`
+  /// returns false for `c0`
+  /// returns false for `**` (because '**' can have any number of class projections)
+  public var hasNoClassProjection: Bool {
+    return matches(pattern: Self(.anyValueFields))
+  }
+
+  /// Returns true if the path has at least one class projection.
+  /// For example:
+  /// returns false for `v**`
+  /// returns true for `v**.c0.s1.v**`
+  /// returns false for `**` (because '**' can have zero class projections)
+  public var hasClassProjection: Bool {
+    var p = self
+    while true {
+      let (k, _, numBits) = p.top
+      if k == .root { return false }
+      if k.isClassField { return true }
+      p = p.pop(numBits: numBits)
     }
   }
 

    assert(kind != .anything || bytes == 0, "'anything' only allowed in last path component")
    var idx = index
    var b = bytes
    if (b >> 56) != 0 { return Self(.anything) }
    b = (b << 8) | UInt64(((idx & 0xf) << 4) | (kind.rawValue << 1))
    idx >>= 4
    while idx != 0 {

      let result = lhs.matches(pattern: rhs)
      precondition(result == expect)
    }

    func predicates() {
      testPredicate("v**.c3", \.topMatchesAnyValueField, expect: true)
      testPredicate("**",     \.topMatchesAnyValueField, expect: true)
      testPredicate("s0.c3",  \.topMatchesAnyValueField, expect: false)

      testPredicate("v**", \.hasNoClassProjection, expect: true)
      testPredicate("c0",  \.hasNoClassProjection, expect: false)
      testPredicate("1",   \.hasNoClassProjection, expect: true)
      testPredicate("**",  \.hasNoClassProjection, expect: false)

      testPredicate("v**",           \.hasClassProjection, expect: false)
      testPredicate("v**.c0.s1.v**", \.hasClassProjection, expect: true)
      testPredicate("c0.**",         \.hasClassProjection, expect: true)
      testPredicate("c0.c1",         \.hasClassProjection, expect: true)
      testPredicate("ct",            \.hasClassProjection, expect: true)
      testPredicate("s0",            \.hasClassProjection, expect: false)
    }

    func testPredicate(_ pathStr: String, _ property: (SmallProjectionPath) -> Bool, expect: Bool) {
      var parser = StringParser(pathStr)
      let path = try! parser.parseProjectionPathFromSIL()
      let result = property(path)
      precondition(result == expect)
    }
    
    func path2path() {
      testPath2Path("s0.e2.3.c4.s1", { $0.popAllValueFields() }, expect: "c4.s1")
      testPath2Path("v**.c4.s1",     { $0.popAllValueFields() }, expect: "c4.s1")
      testPath2Path("**",            { $0.popAllValueFields() }, expect: "**")

      testPath2Path("s0.e2.3.c4.s1.e2.v**.**", { $0.popLastClassAndValuesFromTail() }, expect: "s0.e2.3.c4.s1.e2.v**.**")
      testPath2Path("s0.c2.3.c4.s1",           { $0.popLastClassAndValuesFromTail() }, expect: "s0.c2.3")
      testPath2Path("v**.c*.s1",               { $0.popLastClassAndValuesFromTail() }, expect: "v**")
      testPath2Path("s1.ct.v**",               { $0.popLastClassAndValuesFromTail() }, expect: "s1")
      testPath2Path("c0.c1.c2",                { $0.popLastClassAndValuesFromTail() }, expect: "c0.c1")
      testPath2Path("**",                      { $0.popLastClassAndValuesFromTail() }, expect: "**")
    }

    func testPath2Path(_ pathStr: String, _ transform: (SmallProjectionPath) -> SmallProjectionPath, expect: String) {
      var parser = StringParser(pathStr)
      let path = try! parser.parseProjectionPathFromSIL()
      var expectParser = StringParser(expect)
      let expectPath = try! expectParser.parseProjectionPathFromSIL()
      let result = transform(path)
      precondition(result == expectPath)
    }
  }
}
