I am often burned by MXMLC: the AS3 compiler. When I am, I find this infuriating and look for the reason why this happened. Today I’ll tip you off about this problem and delve into what it means if you happen to trigger it.

My issue arose when I wrote something similar to this:

function foo(val:int): void
{
	var val:int = 3;
	// use val
}

The problem here is that I have unwittingly overwritten val and have consequently lost the original value of the function parameter. Now don’t get me wrong here: being able to change the value of function parameters can be quite useful, but this was clearly a mistake. Had I declared two local variables named val, the compiler would have warned me about it. Instead, I did something extremely similar and received no warning at all. So let’s delve into the bytecode (provided by the excellent nemo440) and see what you get for the above function:

  function private::foo(int):void	/* disp_id 0*/
  {
    // local_count=2 max_scope=1 max_stack=1 code_len=6
    0       getlocal0     	
    1       pushscope     	
    2       pushbyte      	3
    4       setlocal1     	
    5       returnvoid    	
  }

First of all, notice that “local_count” is 2. The first local (index 0) is this, which refers to the instance of the ArgumentClash class we were called on. For example, for myArgClash.foo() the this local refers to myArgClash. The second local (index 1) is val. This bytecode shows that we’re simply setting the value 3 to the variable val. Let’s confirm this by adding one simple line:

function foo(val:int): void
{
	var val:int = 3;
	val = 4;
}

Which yields this bytecode:

  function private::foo(int):void	/* disp_id 0*/
  {
    // local_count=2 max_scope=1 max_stack=1 code_len=9
    0       getlocal0     	
    1       pushscope     	
    2       pushbyte      	3
    4       setlocal1     	
    5       pushbyte      	4
    7       setlocal1     	
    8       returnvoid    	
  }

Since this is also referencing the second (index 1) local, it seems clear that our local variable declaration is simply reusing the parameter. So from the perspective of typing “val” in the function, you’re using the parameter with its value overwritten. But what would happen if we didn’t type “val” but instead accessed the first parameter’s value via the arguments keyword? Since MXMLC won’t do any dead code removal, it’s easy for us to put in the minimal amount of AS3 code to see what’s going on the bytecode while keeping it simple:

function foo(val:int): void
{
	var val:int = 3;
	arguments[0];
}

Which yields this bytecode:

  function private::foo(int):void	/* disp_id 0*/
  {
    // local_count=3 max_scope=1 max_stack=2 code_len=12
    0       getlocal0     	
    1       pushscope     	
    2       pushbyte      	3
    4       setlocal1     	
    5       getlocal2     	
    6       pushbyte      	0
    8       getproperty   	null
    10      pop           	
    11      returnvoid    	
  }

You’ll notice that “local_count” has now risen from 2 to 3. The line arguments[0] is accomplished by getting the third (index 2) local and accessing its first (index 0) property, then using pop to drop the value since we don’t do anything with it. Given that we only index one in this function, the third local (index 2) must be just that: arguments. Now let’s see if the contents of arguments are changed when we changed val:

function foo(val:int): void
{
	var val:int = 3;
	trace(arguments[0]);
}
foo(2);

We’ve seen in depth above that the local variable val is one and the same with the function parameter val, so arguments[0] should really be the same as val the parameter/local. Strangely enough, trace prints “2” here! It seems as though the original parameters are copied prior to usage in the function as overwriting them doesn’t mean that we can’t still get at the original value. This is actually not the case with AS2 or JavaScript as we see in these examples:

// AS2
function foo(val:Number): Void
{
	val = 3;
	_root.logger.text = "val: " + val + "\narguments[0]: " + arguments[0];
}
 
_root.createTextField("logger", 0, 0, 0, 800, 600);
foo(2);
// JavaScript
function foo(val)
{
	document.write("val: " + val + "<br>");
	document.write("arguments[0]: " + arguments[0]);
}
foo(2);

Now for two related points on this subject. Firstly, the type must match in order for this to work. Consider the following:

function foo(val:int): void
{
	var val:String = "hey";
}

In this case MXMLC (and MTASC for AS2) will give you a compile-time error:

1151: A conflict exists with definition val in namespace internal.

Also, if you don’t define the local variable in the same line it will not overwrite the parameter’s value (named or via arguments):

function foo(val:int): void
{
	var val:int;
	trace(val);
}
foo(2);

This still prints 2. Normally an int would initially have the value 0, but in this case the name val is shared with the parameter so the variable already has a value: whatever was passed. Remember that there is no compiler warning here, so you won’t be warned if your code is assuming that the int will default to 0.

So what have we learned here?

  • MXMLC will not warn you if you declare a local variable with the same name and type as a parameter, regardless of whether or not you define it in the same line or later in the function
  • MXMLC will give you a compiler error if you declare a local variable with the same name and different type
  • arguments adds a local that is not present if you don’t use it
  • The original value of each parameter can be accessed via arguments even after you’ve overwritten them in AS3, but not in AS2 or JavaScript.

So be careful with your variable names!