apple/pkl · error · GenericParserError

interpolationInConstant

interpolationInConstant

Error message

String constant cannot have interpolated values.

What it means

Pkl string constants (plain quoted strings) do not support `\(...)` interpolation; only standard strings may interpolate. When the parser encounters INTERPOLATION_START inside a string constant it throws this dedicated error. This distinguishes constant strings (which are interned/exact) from interpolating strings.

Solutions

  1. Use Pkl's standard string interpolation form `"\(expr)"` in a regular (non-constant) string context.
  2. Concatenate values explicitly if the context requires a constant: `"a " + x`.
  3. Remove the interpolation and hardcode the value if the string must remain constant.

Example fix

// before (in a string-constant position)
greeting = "Hello \(name)"

// after
greeting = "Hello " + name
Defensive patterns

Strategy: validation

Validate before calling

function assertNoInterpolationInConstant(str, isConstantContext) {
  if (isConstantContext && /\\\(/.test(str)) {
    throw new Error(`String constant cannot interpolate: ${str}`);
  }
}

Prevention

When it happens

Trigger: Using `\(expr)` inside a string constant produced in parseStringConstant(), e.g. `const = "value \(x)"` in a context where the string is lexed as a constant (such as certain module-level or annotation contexts).

Common situations: Porting Kotlin/Swift-style interpolation habits into Pkl constant strings; moving a string between contexts where the lexer classifies it differently; assuming all Pkl strings interpolate.

Understand the failure class

Background: UnsupportedOperationException and "is not supported" errors: when a library deliberately refuses a call — this error's family across 30 libraries.

Related errors


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

Appendix: source

Thrown at pkl-parser/src/main/java/org/pkl/parser/GenericParserImpl.java:1396

  private Node parseStringConstant() {
    var children = new ArrayList<Node>();
    var startTk = expect(Token.STRING_START, "unexpectedToken", "\"");
    children.add(makeTerminal(startTk));
    while (lookahead != Token.STRING_END) {
      switch (lookahead) {
        case STRING_PART,
            STRING_ESCAPE_NEWLINE,
            STRING_ESCAPE_TAB,
            STRING_ESCAPE_QUOTE,
            STRING_ESCAPE_BACKSLASH,
            STRING_ESCAPE_RETURN,
            STRING_ESCAPE_UNICODE ->
            children.add(makeTerminal(next()));
        case EOF -> {
          var delimiter = new StringBuilder(startTk.text(lexer)).reverse().toString();
          throw parserError("missingDelimiter", delimiter);
        }
        case INTERPOLATION_START -> throw parserError("interpolationInConstant");
        // the lexer makes sure we only get the above tokens inside a string
        default -> throw new RuntimeException("Unreacheable code");
      }
    }
    children.add(makeTerminal(next())); // string end
    return new Node(NodeType.STRING_CHARS, children);
  }

  private FullToken expect(Token type, String errorKey, Object... messageArgs) {
    if (lookahead != type) {
      var span = spanLookahead;
      if (lookahead == Token.EOF || _lookahead.newLinesBetween > 0) {
        // don't point at the EOF or the next line, but at the end of the last token
        span = prev().span.stopSpan();
      }
      var args = messageArgs;
      if (errorKey.startsWith("unexpectedToken")) {
        args = new Object[messageArgs.length + 1];

View on GitHub (pinned to f3efcbfc9b)