apple/pkl · error

operatorNotDefined2

operatorNotDefined2

Error message

operatorNotDefined2 ${op} ${leftClass} ${rightClass}

What it means

Generic binary-operator fallback in Truffle DSL specializations: when no typed specialization matches the operand types, Pkl throws `operatorNotDefined2` naming the operator and both operand classes. It means the operator is simply not defined for that type combination. This is a type error discovered at evaluation time rather than a runtime bug.

Source

Thrown at pkl-core/src/main/java/org/pkl/core/ast/expression/binary/BinaryExpressionNode.java:39

import com.oracle.truffle.api.source.SourceSection;
import org.pkl.core.ast.ExpressionNode;
import org.pkl.core.runtime.VmUtils;

@NodeChild(value = "leftNode", type = ExpressionNode.class)
@NodeChild(value = "rightNode", type = ExpressionNode.class)
public abstract class BinaryExpressionNode extends ExpressionNode {
  protected BinaryExpressionNode(SourceSection sourceSection) {
    super(sourceSection);
  }

  protected abstract ExpressionNode getLeftNode();

  protected abstract ExpressionNode getRightNode();

  @Fallback
  @TruffleBoundary
  protected Object fallback(Object left, Object right) {
    throw exceptionBuilder()
        .evalError(
            "operatorNotDefined2", getShortName(), VmUtils.getClass(left), VmUtils.getClass(right))
        .withProgramValue("Left operand", left)
        .withProgramValue("Right operand", right)
        .build();
  }
}

View on GitHub (pinned to f3efcbfc9b)

Solutions

  1. Check the dynamic types of both operands and fix the expression or its declared type
  2. Use explicit conversions (`toInt()`, `toString()`, string interpolation) instead of relying on coercion
  3. Add a type annotation on the property so the mistake is caught earlier

Example fix

// before (Pkl)
val x = "count: " + 5 - 1
// after
val x = "count: " + (5 - 1).toString()
Defensive patterns

Strategy: type-guard

Validate before calling

function operandsCompatible(op, l, r) {
  if (typeof l === 'string' || typeof r === 'string') return op === '+' || op === '==';
  return typeof l === 'number' && typeof r === 'number';
}

Type guard

const bothNumeric = (l, r) => typeof l === 'number' && typeof r === 'number';
const bothString = (l, r) => typeof l === 'string' && typeof r === 'string';

Prevention

When it happens

Trigger: Applying a binary operator (e.g. `+`, `-`, `<`) to operand types with no matching specialization, such as `"a" - "b"` or `[1] * 2` where no overload exists.

Common situations: Typos in intended operator, mixing String and Number assuming implicit coercion, or calling an operator on a value typed `any`/`unknown` whose dynamic type lacks the operator.

Understand the failure class

Background: Type mismatch errors: IllegalArgumentException, TypeError and type guards across 150 open-source libraries — this error's family across 150 libraries.

Related errors


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