Adjust local names for multiplicity and typing
The problem addressed in this issue can be highlighted by a simple example:
According to the rules of Alf 1.0.1, the local name line is given the multiplicity 0..1. This is allowable in the string concatenation but, since line has a multiplicity lower bound of 0, the derived multiplicity lower bound for the result of the concatenation is also 0. Again, this is currently statically allowable for an argument to the WriteLine activity, even though the declared multiplicity of the in parameter for that activity is 1..1.
However, the return parameter of the GetNextLine is declared to be 0..1, meaning that it may return null. If it does, line is then assigned null, but there is no “null pointer exception” when line is used in the next statement. Instead, due to the UML semantics of null as meaning “no value”, the null value for line essentially gets propagated. Because the string concatenation operator receives no value ("null") for its second operand, it cannot execute, so the concatenation expression also does not produce a value. The call to WriteLine thus receives no input value and, because its input parameter has multiplicity 1..1, it cannot execute, either. The result is that nothing is printed, not “Next line:”, not even a carriage return. Most people seem to find this behavior unexpected.
There are a number of possible ways to avoid this behavior by specifically testing whether line is null. Here are three of the possible alternatives:
Each of these alternatives represents a common paradigm for avoiding this problem, similar to the approaches used to avoid a null pointer exception in other languages. However, since the original version produces subtle and unexpected behavior at runtime, rather than some obvious exception, it would be preferable to make the original version statically illegal by disallowing an expression with multiplicity lower bound of 0 from being an argument for a mandatory parameter. Unfortunately, this would also make all three of the above alternative versions illegal.
A null-coalescing operator, as proposed in issue ALF11-44, could be used to make the above alternatives legal again. However, this would require modifying working code for no functional change. It would also be particularly pointless in the third alternative, in which an entirely different message is written if line is null.
What would be more desirable instead would be to track more carefully the statically known multiplicity of a local name after each assignment/reassignment and based on simple tests such as line == null. In this case, it could be determined that, in each of the above alternatives, the second operand expression in the string concatenation expression could never actually be null at that point, and so that operand expression can be considered to have a multiplicity lower bound of 1. In this case, it would be possible to strengthen the multiplicity conformance rules such that the original version of this example would be illegal, while allowing each of the presented alternatives to remain legal.
In addition, since casting in Alf "filters out" values that cannot be cast, rather than producing a cast exception, cast expressions can also result in null (if everything is filtered out). For example, consider the following:
This is currently legal, even though the cast expressions (B)a and (C)a have multiplicity lower bounds of 0 (because, e.g., (B)a would result in null if the value assigned to a was actually of type C), while the in parameters to doSomethingB and doSomethingC have multiplicity 1..1 (by default). But, with tighter multiplicity rules, both of the doSomethingB and doSomethingC calls would become illegal, unless it was also tracked that a is actually known to have the subtype B in the first clause of the if statement and subtype C in the second clause. Indeed, if this were tracked, then it would be possible to allow the use of a in each of the calls without having to do a cast at all.
Finally, it should be possible to track local name multiplicity and type classification information based on compound conditions using, at least, conditional-logical and Boolean negation operators. For instance, it should be possible to deduce from a != null && a instanceof B both that a is not null and that a has the subtype B. (Note that the UML object classification action in UML presumes a non-null object input.) This should be a sufficient level of analytical capability for the language at this time. More sophisticated analysis could be added in the future, if experiences shows its worth.