Sunday 6 April 2014

CFML: Predictable but perhaps unexpected behaviour with unscoped variables and shorthand operators

G'day:
I've nothing earth-shattering to declare here (so "same ol', same ol'" then perhaps ;-), but here's a follow-up to a puzzling thing I observed during this adventure: "Railo: help me replicate a bug I'm seeing on one of my machines". It turns out there's... um... well I'm not sure what to make of it yet. It could be a bug; it could be expected behaviour.

Consider this code:

URL.firstVariable = 17;
someOtherVariable = firstVariable;

URL.secondVariable = 19;
secondVariable = 23;

URL.thirdVariable = 29;
thirdVariable++;
writeDump(var={URL=URL,variables=variables});    

Here we have three demonstrations.

Firstly if an unscoped variable reference is on the right-hand-side of an assignment expression, then CFML server will look through a number of different scopes to find a variable of this name (see "About scopes: Evaluating unscoped variables" in the docs). So in this case firstVariable will be resolved as being a reference to URL.firstVariable.

Secondly, if an unscoped variable reference is on the left-hand-side of an assignment expression (ie: it's the variable being set to a value), then it's assumed to be a reference to a variable in the variables scope, even if other scopes in the look-up process as per above have a variable of that name. IE: the CFML server does not try to do a look-up in this case.

Thirdly: with a short-hand operator such as ++ or -= etc is used... what happens? Well the results here demonstrate some "unexpected" (kinda) behaviour:

struct
URL
struct
FIRSTVARIABLE17
SECONDVARIABLE19
THIRDVARIABLE29
VARIABLES
struct
SECONDVARIABLE23
SOMEOTHERVARIABLE17
THIRDVARIABLE30

Or, to focus more:

struct
URL.thirdVariable29
variables.thirdVariable30

So here we don't increment URL.thirdVariable, but we create a new variable variables.thirdVariable. Huh? Superficially this seems wrong. But if one things about what happens under the hood, it makes sense (note: I am not sure it's "correct" behaviour, but it makes sense). Because under the hood this expands out to:

thirdVariable = thirdVariable + 1

And if we introduce that statement into our test:

URL.fourthVariable = 31;
fourthVariable = fourthVariable + 1;
writeDump(var={
    "URL.fourthVariable"        = URL.fourthVariable,
    "variables.fourthVariable"  = variables.fourthVariable
});

Output:
struct
URL.fourthVariable31
variables.fourthVariable32

It all becomes a lot clearer as to what is going on. Remember the first two examples above? If the variable reference is on the right-hand-side of the assignment: a scope look-up is done (so fourthVariable resolves to URL.fourthVariable); however on the left-hand-side of the assignment, an unscoped reference simply means "the variables scope".

So if one is to base one's expectations on the under-the-hood logic, then what we see is what we should expected.

However I'm not sure the behaviour we see in CFML (and I checked OpenBD too, just for a laugh, and it behaves the same way as the other two) is what we really expect here?

It's one of these situations in which I find myself thinking I really wish CFML stopped trying to be 'helpful' and do all this scope-look-up nonsense. It was a bloody stupid and unnecessary thing to add to the language from the outset. Fortunately at least in Railo there's a way to switch it off.

Anyway... I'm divided as to whether this should be considered a bug or simply "expected behaviour". Before I thought sufficiently about it, I raised it as a bug (3737577), although I'm now not sure it is. I think the position should be not to worry about how the logic expands out under the hood, but it should concern itself with what "expected behaviour" is, in the given situation. And I'm not so sure what my expectations here are any more.

This impacts all of the prefix (++myVar), postfix (myVar--) and compound assignment (eg: +=) operators.

What do you think?

--
Adam