karatelabs/karate · error · java.lang.RuntimeException

unexpected logical-assignment operator

Error message

unexpected logical-assignment operator: <operator>

What it means

shouldLogicalAssign maps a logical-assignment operator (||=, &&=, ??=) to its truthiness rule; the default branch throws only if an operator token reached this code path that is not one of the three supported logical-assignment operators. This is an internal invariant guard in PropertyAccess's compound-assignment handling, not a user-facing validation.

Solutions

  1. Check the operator printed in the message; if it is a normal ||=, &&=, ??=, report/upgrade — this indicates a library bug.
  2. Upgrade Karate to the latest patch version where the compound-assign dispatch is complete.
  3. Rewrite the expression explicitly (a = a || b) to avoid the logical-assignment path.

Example fix

// before (script)
foo ||= bar; // unexpected logical-assignment operator
// after
foo = foo || bar;
Defensive patterns

Strategy: validation

Validate before calling

// JS-side pre-check: only use supported logical-assignment operators
// supported: ||=, &&=, ??=
if (!/^(\|\|=|&&=|\?\?=)$/.test(opToken)) {
    throw new Error('unsupported logical-assignment operator: ' + opToken);
}

Type guard

function isLogicalAssignOp(tok) {
    return tok === '||=' || tok === '&&=' || tok === '??=';
}

Try / catch

try {
    return karate.eval(script);
} catch (RuntimeException e) {
    if (e.getMessage().contains("unexpected logical-assignment operator")) {
        // rewrite as explicit binary form and retry
        script = script.replace("||=", "= ...");
    }
    throw e;
}

Prevention

When it happens

Trigger: An assignment expression whose operator token is treated as a logical assignment by the parser but is not PIPE_PIPE_EQ, AMP_AMP_EQ, or QUES_QUES_EQ reaching logicalCompound/logicalCompoundByName/etc. — essentially a parser/grammar bug or an evaluation path misclassifying a compound operator.

Common situations: Rare; encountered when running a Karate version with a grammar/eval mismatch (e.g. a new compound operator added to the parser but not to PropertyAccess), or a malformed script that tickles the wrong dispatch path.

Understand the failure class

Background: "This is a bug, please report it": internal invariant violations, unreachable panics, and SNH errors explained — this error's family across 47 libraries.

Related errors


AI-assisted analysis of karatelabs/karate@a22eb90246 (2026-09-12). Data as JSON: /api/errors/d367fc37ec79edc8. Report an issue: GitHub.

Appendix: source

Thrown at karate-js/src/main/java/io/karatelabs/js/PropertyAccess.java:495

        context.update(name, newValue, trackingNode);
        return newValue;
    }

    private static Object incDecRefByName(Node node, CoreContext context, boolean isIncrement, boolean returnNew) {
        String name = node.getText();
        Object oldValue = context.get(name);
        Object step = Terms.incDecStep(oldValue);
        Object newValue = isIncrement ? Terms.add(oldValue, step, context) : Terms.min(oldValue, step, context);
        context.update(name, newValue);
        return returnNew ? newValue : oldValue;
    }

    private static boolean shouldLogicalAssign(TokenType operator, Object lhsValue) {
        return switch (operator) {
            case PIPE_PIPE_EQ -> !Terms.isTruthy(lhsValue);
            case AMP_AMP_EQ -> Terms.isTruthy(lhsValue);
            case QUES_QUES_EQ -> lhsValue == null || lhsValue == Terms.UNDEFINED;
            default -> throw new RuntimeException("unexpected logical-assignment operator: " + operator);
        };
    }

    private static Object logicalCompoundByIndex(Object object, Object index, TokenType operator, Node rhsNode, CoreContext context, Node trackingNode) {
        if (index instanceof Number n) {
            int i = denseIndex(n);
            if (object instanceof List && i >= 0) {
                List<Object> list = (List<Object>) object;
                Object oldValue = i < list.size() ? list.get(i) : Terms.UNDEFINED;
                if (!shouldLogicalAssign(operator, oldValue)) return oldValue;
                Object newValue = Interpreter.eval(rhsNode, context);
                if (context.isStopped()) return null;
                if (object instanceof JsArray ja) {
                    ja.checkDensePad(i);
                }
                while (list.size() <= i) list.add(Terms.UNDEFINED);
                list.set(i, newValue);
                firePropertySet(context, String.valueOf(i), newValue, oldValue, object, trackingNode);

View on GitHub (pinned to a22eb90246)