P4049

Code

P4049

Message

Operator is not defined for the operand types

This error occurs when an operator is applied to operand types it is not defined for. The message names the operator and the operand types.

The arithmetic operators +, -, * and / are defined for two numeric operands (SINT through ULINT, REAL and LREAL) where one widens to the other, and for the time and date combinations listed on the ADD, SUB, MUL and DIV pages, such as a TIME plus a TIME or a DATE minus a DATE. MOD is defined for two integer operands only. The function forms ADD, SUB, MUL and DIV are checked the same way and report this error too, naming the two operand types of the step that fails; MOD(a, b) is checked against its signature and reports P4026 instead.

Each arithmetic expression is reported once, naming both operand types. When an operand of an expression is itself an expression that failed, the enclosing expression may be reported as well.

The selection functions MIN, MAX, LIMIT, SEL and MUX, and ATAN2, compute at one type too, and report this error when their inputs have no common type, naming the function and the type of each input.

Mixed numeric types

The following code will generate error P4049:

PROGRAM main
VAR
    count : DINT := 7;
    factor : REAL := 1.5;
    scaled : REAL;
END_VAR
    scaled := factor * count;
END_PROGRAM

A DINT does not widen to REAL without loss, because REAL cannot hold every 32-bit integer exactly, and a REAL does not widen to DINT. Neither operand can take the other’s type, so the operator is not defined for the pair. The same applies to a signed and an unsigned integer of the same width, such as DINT and UDINT. A bare real literal such as 1.5 is a REAL, so count * 1.5 is reported for the same reason, while an integer of 16 bits or fewer, such as INT, widens to REAL and is accepted next to a REAL or a real literal.

To fix this error, convert one operand explicitly, type the literal as an LREAL, which a DINT widens to (count * LREAL#1.5), or use a type that holds both:

PROGRAM main
VAR
    count : DINT := 7;
    factor : REAL := 1.5;
    scaled : REAL;
END_VAR
    scaled := factor * DINT_TO_REAL(count);   (* 10.5 *)
END_PROGRAM

See Type Conversions for which types widen to which.

Selection functions with inputs of different types

MIN, MAX, LIMIT, SEL and MUX compare or select between their inputs, and ATAN2 combines its two, so each computes at one type: a narrower input widens to a wider one, and the result has the wider type. When no input’s type holds every other input, the call is reported, naming the function as the operator and the type of each input by its parameter name. The selector G of SEL and K of MUX choose between the other inputs and take no part.

The following code will generate error P4049:

PROGRAM main
VAR
    lowest : DINT := -1;
    count : UDINT := 4000000000;
    largest : LINT;
END_VAR
    largest := MAX(lowest, count);
END_PROGRAM

A UDINT above 2147483647 does not fit a DINT, and a negative DINT does not fit a UDINT, so neither type holds every value of the other and there is no type at which to compare them.

To fix this error, convert the inputs to a type that holds both:

PROGRAM main
VAR
    lowest : DINT := -1;
    count : UDINT := 4000000000;
    largest : LINT;
END_VAR
    largest := MAX(DINT_TO_LINT(lowest), UDINT_TO_LINT(count));   (* 4000000000 *)
END_PROGRAM

A further input of such a type makes the conversion implicit: LIMIT(lowest, count, ceiling) with ceiling : LINT compares at LINT, because a DINT and a UDINT both widen to it.

A bare real literal is a REAL, and is accepted or reported with an integer input as it is with an integer operand: MAX(lowest, 1.5) is this error, because a DINT does not widen to REAL, while MIN(reading, 1.5) with reading : INT compares two REALs, and MAX(lowest, 1) two DINTs, because a bare integer literal takes the other input’s type.

Time and date operands

The following code will generate error P4049:

PROGRAM main
VAR
    interval : TIME := T#2s;
    repeats : TIME := T#3s;
    total : TIME;
END_VAR
    total := interval * repeats;
END_PROGRAM

A duration can be multiplied or divided by a number, but not by another duration, and two dates can be subtracted but not added.

To fix this error, use a number for the factor:

PROGRAM main
VAR
    interval : TIME := T#2s;
    repeats : DINT := 3;
    total : TIME;
END_VAR
    total := interval * repeats;   (* T#6s *)
END_PROGRAM

Negating a time of day or a date

The following code will generate error P4049:

PROGRAM main
VAR
    opened : TIME_OF_DAY := TOD#08:00:00;
    closed : TIME_OF_DAY;
END_VAR
    closed := -opened;
END_PROGRAM

The unary - negates a number or a duration (TIME, LTIME). A time of day, a date and a date and time are points in time, not amounts, so they have no negative. The same applies to DATE, DATE_AND_TIME and their long forms.

To fix this error, negate the duration you mean, such as the time since midnight:

PROGRAM main
VAR
    opened : TIME_OF_DAY := TOD#08:00:00;
    before : TIME;
END_VAR
    before := -(opened - TOD#00:00:00);   (* T#-8h *)
END_PROGRAM

Mixing a BOOL with a bit string

The following code will generate error P4049:

PROGRAM main
VAR
    enabled : BOOL := TRUE;
    flags : WORD := 16#00F0;
    masked : WORD;
END_VAR
    masked := flags AND enabled;
END_PROGRAM

AND, OR and XOR compute on operands of one bit type. A narrower bit string widens to a wider one, so a BYTE with a WORD computes as a WORD. A BOOL is a truth value rather than a one-bit string, and it widens to no bit string, in every dialect. The same holds for AND_THEN and OR_ELSE, and for the function forms AND, OR and XOR.

To fix this error, choose the bit string the BOOL stands for with SEL:

PROGRAM main
VAR
    enabled : BOOL := TRUE;
    flags : WORD := 16#00F0;
    masked : WORD;
END_VAR
    masked := flags AND SEL(enabled, WORD#0, WORD#16#FFFF);   (* 16#00F0 *)
END_PROGRAM

Comparing a reference with a number

The following code will generate error P4049:

PROGRAM main
VAR
    count : INT;
    target : REF_TO INT;
    unset : BOOL;
END_VAR
    unset := target = 0;
END_PROGRAM

A reference equals NULL or another reference, never a number. This applies to a CODESYS POINTER TO as well: p <> 0 is an error, in every dialect.

To fix this error, compare the reference with NULL:

PROGRAM main
VAR
    count : INT;
    target : REF_TO INT;
    unset : BOOL;
END_VAR
    unset := target = NULL;
END_PROGRAM

Negating a BOOL

The following code will generate error P4049:

PROGRAM main
VAR
    running : BOOL := TRUE;
    stopped : BOOL;
END_VAR
    stopped := -running;
END_PROGRAM

The unary - negates a number or a duration. A BOOL is a truth value, not a number, so it has no negative.

To fix this error, invert the BOOL with NOT:

PROGRAM main
VAR
    running : BOOL := TRUE;
    stopped : BOOL;
END_VAR
    stopped := NOT running;   (* FALSE *)
END_PROGRAM

Remainder of a real number

The following code will generate error P4049:

PROGRAM main
VAR
    angle : REAL := 400.5;
    wrapped : REAL;
END_VAR
    wrapped := angle MOD 360.0;
END_PROGRAM

MOD accepts only integer operands. There is no floating-point remainder operator.

To fix this error, convert the operands to an integer type when an integer remainder is what you want:

PROGRAM main
VAR
    angle : REAL := 400.5;
    wrapped : DINT;
END_VAR
    wrapped := REAL_TO_DINT(angle) MOD 360;
END_PROGRAM

For a floating-point remainder, subtract the truncated quotient. TRUNC drops the fractional part of the quotient, and the <INT>_TO_<REAL> conversion brings it back to the operand type:

PROGRAM main
VAR
    angle : REAL := 400.5;
    wrapped : REAL;
END_VAR
    wrapped := angle - DINT_TO_REAL(TRUNC(angle / 360.0)) * 360.0;  (* 40.5 *)
END_PROGRAM

The result has the sign of the dividend, as MOD does for integers.

Arithmetic on bit strings

The following code will generate error P4049 unless bit-string arithmetic is enabled:

PROGRAM main
VAR
    counter : BYTE;
END_VAR
    counter := counter + 1;
END_PROGRAM

IEC 61131-3 defines no arithmetic on the bit-string types. CODESYS, TwinCAT and RuSTy treat a bit string as the unsigned integer of its width, and IronPLC does the same with --allow-bit-string-arithmetic. See Enabling Dialects and Features for how to enable it.

Without the flag, declare the variable as an unsigned integer of the same width:

PROGRAM main
VAR
    counter : USINT;
END_VAR
    counter := counter + 1;
END_PROGRAM

Comparisons

The comparison operators =, <>, <, <=, > and >=, and their function forms EQ, NE, LT, LE, GT and GE, are defined for two operands where one widens to the other, the same pairs as the arithmetic operators. The comparison is computed at the wider type.

The following code will generate error P4049:

PROGRAM main
VAR
    offset : DINT := -1;
    limit : UDINT := 4000000000;
    below : BOOL;
END_VAR
    below := offset < limit;
END_PROGRAM

Neither DINT nor UDINT holds every value of the other, so there is no type to compare them at: comparing at DINT reads limit as a negative number, and comparing at UDINT reads offset as a large positive one. An untyped literal takes the type of the other operand when that type’s category holds it (limit > 3), and is reported when it does not (offset < 1.5).

To fix this error, convert one operand to a type that holds both, here LINT, which UDINT widens to:

PROGRAM main
VAR
    offset : DINT := -1;
    limit : UDINT := 4000000000;
    below : BOOL;
END_VAR
    below := DINT_TO_LINT(offset) < limit;   (* TRUE *)
END_PROGRAM

Bit-string operators

The bit-string operators AND, OR, XOR and NOT are defined over the bit-string types (BOOL, BYTE, WORD, DWORD, LWORD). For these operators, each operand outside that set is reported separately, naming the expected category and the actual type. The short-circuit operators AND_THEN and OR_ELSE, which some dialects enable, are checked as AND and OR are.

The following code will generate error P4049:

PROGRAM main
VAR
    flags : DINT := 10;
    masked : DINT;
END_VAR
    masked := flags AND 3;
END_PROGRAM

The variable flags is a DINT, but AND accepts only bit-string operands. x AND 3 where x is a DINT does not mean a bitwise AND of the two numbers.

To fix this error, declare the operands as a bit-string type of the width you want:

PROGRAM main
VAR
    flags : BYTE := 16#0A;
    masked : BYTE;
END_VAR
    masked := flags AND BYTE#16#03;   (* 16#02 *)
END_PROGRAM

The same applies to NOT, whose operand is a single bit string:

PROGRAM main
VAR
    flags : BYTE := 16#0F;
    inverted : BYTE;
END_VAR
    inverted := NOT flags;   (* 16#F0 *)
END_PROGRAM

Think IronPLC is wrong about this?

If you believe this diagnostic is incorrect, open an issue on GitHub with a small sample that demonstrates the problem.