apple/pkl · error

charIndexOutOfRange

charIndexOutOfRange

Error message

charIndexOutOfRange

What it means

String.substring(start, exclusiveEnd) takes code-point offsets. This error is thrown when the `start` offset does not fall on a code-point boundary or is beyond the string's code-point count, i.e. the start index is out of the valid range 0..length. The error message includes the offending start, the valid maximum, and the string itself.

Solutions

  1. Validate `0 <= start <= str.length` (Pkl code-point length) before calling substring.
  2. Use take/drop, takeLast/dropLast or substringOrNull when bounds may be uncertain — they clamp or return null instead of throwing.
  3. Compute offsets with code-point-aware APIs (e.g. indexOf results) rather than byte/UTF-16 indices.
  4. If input is external, sanitize/trim it first so offsets derive from the final string.

Example fix

// before
name.substring(start, start + 3) // start may exceed length
// after
name.substringOrNull(start, start + 3) ?? "" // null-safe slice
Defensive patterns

Strategy: validation

Validate before calling

// Pkl
function validStart(s: String, start: Int): Boolean = start >= 0 && start <= s.length
// call site
if (validStart(str, start)) str.substringOrNull(start, end) else ""

Type guard

function inRange(i: Int, len: Int): Boolean = i >= 0 && i <= len

Try / catch

try { str.substring(start, end) } catch (e: PklError) { fallbackSlice(str, start, end) }

Prevention

When it happens

Trigger: Calling substring with start < 0, start > string length, or start landing inside a surrogate pair (slicing computed from UTF-16/byte indices instead of code points, e.g. after using .length on a UTF-16 view of the value).

Common situations: Slicing strings containing emoji or other astral characters using byte or UTF-16 positions; off-by-one loops computing start from endIndex of a previous substring; config-driven offsets that exceed the actual value length.

Related errors


AI-assisted analysis of apple/pkl@f3efcbfc9b (2026-09-08). Data as JSON: /api/errors/efb19c621b306751. Report an issue: GitHub.

Appendix: source

Thrown at pkl-core/src/main/java/org/pkl/core/stdlib/base/StringNodes.java:241

      var charIndex = VmUtils.codePointOffsetToCharOffset(self, index);
      if (charIndex == -1 || charIndex == self.length()) return VmNull.withoutDefault();

      if (Character.isHighSurrogate(self.charAt(charIndex))
          && charIndex < self.length() - 1
          && Character.isLowSurrogate(self.charAt(charIndex + 1))) {
        return self.substring(charIndex, charIndex + 2);
      }
      return self.substring(charIndex, charIndex + 1);
    }
  }

  public abstract static class substring extends ExternalMethod2Node {
    @TruffleBoundary
    @Specialization
    protected String eval(String self, long start, long exclusiveEnd) {
      var charStart = VmUtils.codePointOffsetToCharOffset(self, start);
      if (charStart == -1) {
        throw exceptionBuilder()
            .evalError("charIndexOutOfRange", start, 0, self.codePointCount(0, self.length()))
            .withProgramValue("String", self)
            .build();
      }

      var charExclusiveEnd =
          VmUtils.codePointOffsetToCharOffset(self, exclusiveEnd - start, charStart);
      if (charExclusiveEnd < charStart) {
        throw exceptionBuilder()
            .evalError(
                "charIndexOutOfRange", exclusiveEnd, start, self.codePointCount(0, self.length()))
            .withProgramValue("String", self)
            .build();
      }

      return self.substring(charStart, charExclusiveEnd);
    }
  }

View on GitHub (pinned to f3efcbfc9b)