Lilienthal
Lilienthal

Reputation: 4378

Unexpected behaviour in nested COND statement

I'm using the conditional operator COND in a string template to create a string to concatenate a variable Alpha and Beta. If Alpha is initial the string is initial but if it isn't the result should be 'Alpha' if Beta is initial but 'Alpha-Beta' if Beta is not initial.

This is the code:

DATA:
  lv_alpha  TYPE string VALUE 'Alpha',
  lv_beta   TYPE string VALUE 'Beta',
  lv_result TYPE string.

lv_result = COND #( WHEN lv_alpha IS INITIAL THEN '' ELSE
  |{ lv_alpha }{ COND #( WHEN lv_beta IS INITIAL THEN '' ELSE |-{ lv_beta }| ) }|
).

The problem now is that when I run this, lv_result actually returns: 'Alpha-', so the value of lv_beta is not included, even though I know that the code entered the second part of the inner COND statement. What is causing this?

Upvotes: 1

Views: 1157

Answers (1)

Lilienthal
Lilienthal

Reputation: 4378

You see this behaviour when you use # as the operand type and the result of the type inference doesn't match what you want it to.

If you look at the documentation on Type Inference for Actual Parameters it explains:

If a constructor expression COND|SWITCH #( ... THEN ... ) is passed to generically typed formal parameters as an actual parameter using the character # as a symbol for the operand type, the following type inference is performed for the character #:

  • If the data type of the operand after the first THEN is identifiable statically and matches the generic type of the formal parameter, this data type is used.

  • If the data type of the operand after the first THEN is identifiable statically and does not match the generic type of the formal parameter or of it is not identifiable statically, the type is derived from the generic type as follows:

The fact that you're seeing this on the inner COND and not the outer one is because the outer type inference looks to the type of lv_result, a string. The inner COND has no direct type to look at so instead uses the statically identifiable type '', defaulting to a CHAR(1).

You can easily observe this behaviour by running the following commands. The output of each is behind the ".

DATA(lv_test) = 'ABCDEFG'.
WRITE :/ COND #(      WHEN lv_alpha IS INITIAL THEN ''    ELSE lv_test ). " A
WRITE :/ COND #(      WHEN lv_alpha IS INITIAL THEN '123' ELSE lv_test ). " ABC
WRITE :/ COND string( WHEN lv_alpha IS INITIAL THEN ''    ELSE lv_test ). " ABCDEFG

In both of the first commands, the type is derived from the first operand after the THEN and as a result the length of the output is limited based on the length of that first operand. The last line correctly specifies that we want to see a string as the result and ignores whatever type the first operand has. You're more likely to see this in nested statements because of how the type is inferred. Most programmers always use fully typed variables which means that errors like in the first two statements here are rare.

The # operand type is very convenient and certainly speeds up programming but you need to keep in mind what it's actually doing or risk running into strange bugs like these. These last examples show what happens when you rely on an unspecified type and you're passing in a value of a different type than what you're expecting, but the result won't always be this obviously incorrect, especially since this can happen with all data types, not just CLIKEs. Especially in nested statements where you can't view the type of the intermediate objects, this can be very difficult to trace.

DATA: lv_date TYPE dats.
WRITE :/ COND #(      WHEN lv_alpha IS INITIAL THEN lv_date ELSE lv_test ). " G .EF.ABCD
WRITE :/ COND dats(   WHEN lv_alpha IS INITIAL THEN ''      ELSE lv_test ). " G .EF.ABCD
WRITE :/ COND string( WHEN lv_alpha IS INITIAL THEN lv_date ELSE lv_test ). " ABCDEFG
WRITE :/ COND string( WHEN lv_alpha IS INITIAL THEN ''      ELSE lv_test ). " ABCDEFG

Upvotes: 1

Related Questions