The main reason to have a static type system in a programming language is that the compiler can catch your mistakes without having to execute the code. In ABAP, even this is not so straightforward.

Weak, Static Types

ABAP has weak, static types. Implicit type conversions are easy:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
DATA lv_integer TYPE i VALUE 10.
DATA lv_string TYPE string.

ADD 1 TO lv_integer.


lv_string = lv_integer.

ADD 1 TO lv_string.
lv_string = lv_string + 1.


lv_integer = lv_string.

WRITE lv_integer. " Will print 13

An integer easily converts to a string and can still be used like an integer afterwards. As a comparison, Erlang has strong, dynamic types (and also single assignments):

1
2
3
4
5
6
7
Int0 = 10,
Int1 = Int0 + 1,

Str0 = integer_to_list(Int1),

% Trying to sum a list and an integer will produce an exception
{'EXIT', {badarith, _}} = catch (Str0 + 1).

In Erlang, converting a type to another requires a function that takes the source type and returns the destination type, or fails. The possibility that a conversion fails, of course, exists in ABAP also, it’s just not as explicit (ie. does not look like a function call):

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
DATA(lv_string) = |foobar|.
DATA lv_integer TYPE i.

" This will crash
lv_integer = lv_string.


" This will catch the "exception"
CATCH SYSTEM-EXCEPTIONS convt_no_number = 1
                        OTHERS = 2.

  lv_integer = lv_string.

ENDCATCH.

WRITE sy-subrc. " Will print 1


" Class-based exceptions can also be used
TRY.
    lv_integer = lv_string.

  CATCH cx_sy_conversion_no_number.
    WRITE 'Conversion failed'. " This will be printed

ENDTRY.

…Except When Types Are Enforced

However, implicit conversions don’t apply universally. This snippet:

1
2
3
4
5
6
7
8
FORM subroutine USING im_string TYPE string.
ENDFORM.


START-OF-SELECTION.
  DATA lv_integer TYPE i VALUE 10.

  PERFORM subroutine USING lv_integer.

will fail to compile with the following error message:

In PERFORM or CALL FUNCTION “SUBROUTINE”, the actual parameter “LV_INTEGER” is incompatible with the formal parameter “IM_STRING”.

Ie. an integer is not a string and will not be automatically converted to one. This makes sense and is fairly intuitive. What also makes sense, but is less intuitive, is how methods in classes work:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
CLASS class DEFINITION.

  PUBLIC SECTION.
    CLASS-METHODS static_method
      IMPORTING im_string TYPE string.

ENDCLASS.

CLASS class IMPLEMENTATION.

  METHOD static_method.
  ENDMETHOD.

ENDCLASS.


START-OF-SELECTION.
  DATA lv_integer TYPE i VALUE 10.

  class=>static_method( im_string = lv_integer ).

This will fail to compile with the following message:

“LV_INTEGER” is not type-compatible with formal parameter “IM_STRING”.

Ie. an integer is not a string and will not be automatically converted to one, more or less how parameters also work in FORM. The reason why I describe this as less intuitive comes down to the syntax: elsewhere in code = means that an implicit conversion will take place, except here in a method call, parameter = value may look identical to a normal assignment, but will not behave like one. I appreciate that the sanity check happens, but more than once I have been surprised to get the compile error.

…Except When Types Are Not Enforced

It wouldn’t be ABAP if there wasn’t an exception to the above rule. The following compiles:

1
2
3
4
5
DATA lv_integer TYPE i.


PERFORM subroutine IN PROGRAM zreport
  USING lv_integer.

But crashes at runtime:

Type conflict when calling a FORM.

Which makes sense since neither the existence of the subroutine:

Call (PERFORM) to a non-existent external routine.

Nor the existence of the report it should be contained in is checked:

Program “ZREPORT2” not found.

Ie. when the called subroutine exists in a different report, compilation provides no guarantees about anything. Only runtime execution will tell the truth.

Something similar goes on with function modules. The following compiles:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
" Function module signature
"
"FUNCTION zfunction_module
"  IMPORTING im_string TYPE string.


DATA lv_integer TYPE integer VALUE 10.

CALL FUNCTION 'ZFUNCTION_MODULE'
  EXPORTING im_string = lv_integer.

But crashes at runtime:

Type conflict during a function module call

Ie. an integer is not a string and will not automatically be converted to one and we’ll only tell you this at runtime, and only if you happen to execute the function module. The existence of the function module is only checked at runtime, too.

This means that a function module on a rare code path can crash the day it is finally executed for the first time. It also means that changing the type of a variable or an attribute can turn a previously working function module call into a crashing one and that this will only be apparent at runtime (ie. could be too late). And it also means that changing the parameters of a function module can also produce these crashes without any warning.

We have static types, but they are only enforced at runtime.

SAP does have a feature called “Extended Check” that will catch these errors:

Extended Check

But running this check is off the normal “write code-compile-run” path and can be hard to remember to run without a disciplined use of a checklist. And it also doesn’t really effectively catch errors that would be caused by changes to classes or function modules (as the clients are spread far and wide).

Generic Types

There are unfortunately also options for subverting type safety in methods:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
CLASS class DEFINITION.

  PUBLIC SECTION.
    CLASS-METHODS static_method
      IMPORTING im_generic TYPE csequence. " char-type or string
    CLASS-METHODS another_static_method
      IMPORTING im_char TYPE char20.

ENDCLASS.

CLASS class IMPLEMENTATION.

  METHOD static_method.
  
    another_static_method( im_char = im_generic ).
    
  ENDMETHOD.
  
  METHOD another_static_method.
  ENDMETHOD.

ENDCLASS.


START-OF-SELECTION.
  DATA(lv_string) = |foobar|.
  
  class=>static_method( im_generic = lv_string ).

CSEQUENCE is a generic type that accepts a char-type or a string, with other types producing the normal “is not type-compatible with formal parameter” compilation error. another_static_method also accepts it as a parameter, since it could well contain the required type (CHAR20). The obvious question is: “What if im_generic is an incompatible type?”

The answer is that the program will compile and will then crash at runtime:

Type conflict in method call.

The method accepting the generic type should immediately convert the input into some specific type to regain type safety, except in the cases that the methods it calls also explicitly accept the same generic type. Using the parameter in its generic form bypasses type checks and results in a situation where whether the method crashes or not will be entirely up to the parameter that the caller happens to use and these different behaviors will only be apparent at runtime. Even worse, Extended Check will not catch these cases.