Yes - it does look like it's treating the function call as REDUCIBLE.
However, we have similar behavior from a BUILTIN (which can't be so marked.) Consider this example where we are invoking the DATETIME builtin with a bad string; which should raise the ERROR condition.
TEST: PROC OPTIONS(MAIN);
DCL D CHAR(100) VARYING;
DCL L FIXED BIN(31);
DCL lab LABEL;
DCL bad_pat char(5);
dcl err_count fixed bin(31);
dcl rc fixed bin(31);
ON ERROR BEGIN;
DCL code fixed bin(31);
code = oncode();
DISPLAY('ERROR condition #' || code);
err_count = err_count + 1;
GOTO lab;
END;
/* Check to see that DATETIME is actually invoked */
/* when only the length is involved. */
err_count = 0;
lab = rest;
bad_pat = 'abcde';
L = LENGTH(datetime(bad_pat));
rest:;
display('err_count is ' || err_count);
END;
When you run that, you get:
err_count is 0
If you change the declaration of "bad_pat" to VARYING; then the program works as expected, and you get:
ERROR condition # 2104
err_count is 1
I think the LENGTH builtin is incorrectly short-circuiting the evaluation of its operand if the the operand is NONVARYING.
And - for completeness, I did the original test with the INNER function declared this way:
inner: proc returns(char(20)) options(irreducible);
call_count = call_count + 1;
return ('a string');
end;
and still got only one call to INNER.
Lastly - I checked on the PL/I standard's definition of the LENGTH builtin-in function (length-bif) to see that it requires a complete evaluation of its argument, so that any possible side-effects have been accomplished. There didn't seem to be a caveat that says you could drop the evaluation of the argument to LENGTH if it is a NONVARYING string.
Clearly though, there are reasons to apply this simple optimization when possible (e.g. the LENGTH of a reference with no side-effects, or a constant.). I think, perhaps, the compiler is just a little over-zealous?
tdr