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
- Validate `0 <= start <= str.length` (Pkl code-point length) before calling substring.
- Use take/drop, takeLast/dropLast or substringOrNull when bounds may be uncertain — they clamp or return null instead of throwing.
- Compute offsets with code-point-aware APIs (e.g. indexOf results) rather than byte/UTF-16 indices.
- 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
- Use substringOrNull/take/drop when bounds may be out of range
- Compute offsets in code points, never bytes or UTF-16 units
- Be extra careful with emoji/astral characters when slicing
- Clamp external offsets to 0..str.length before slicing
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
- charIndexOutOfRange
- Cannot convert pkl.base#String
- doesNotContainLiteralMatch
- doesNotContainRegexMatch
- elementIndexOutOfRange
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)