I wrote an article last summer about with blocks, but really only touched on basic usage. Today I’ll delve into their internals a bit and discover some surprising aspects.

I was part of a discussion on Grant Skinner’s blog recently that touched on with blocks. Grant correctly pointed out that they are not type-safe and, as a result, do not provide compile time errors or IDE support. Consider the following code:

package
{
	import flash.display.*;
 
	/**
	*   An app to show that "with" blocks are type unsafe
	*   @author Jackson Dunstan
	*/
	public class WithBlocks extends Sprite
	{
		private function empty(obj:Empty): void
		{
			with (obj)
			{
				a = 200;
				b = 300;
			}
		}
 
		private function notEmpty(obj:NotEmpty): void
		{
			obj.a = 200;
			obj.b = 300;
		}
	}
}
class Empty
{
}
class NotEmpty
{
	public var a:Number;
	public var b:Number;
}

At first, these two functions seem like they’re doing the same thing. The version operating on an Empty should be a compile-time error though, since Empty does not have an a or a field. The rules for resolving with a with block are more complicated though:

  1. The object specified in the object parameter in the innermost with statement
    Empty doesn’t have an a or b
  2. The object specified in the object parameter in the outermost with statement
    There is only one with block
  3. The Activation object (a temporary object that is automatically created when the script calls a function that holds the local variables called in the function)
    None of the local variables are named a or b
  4. The object that contains the currently executing script
    The WithBlocks class, the Sprite class, and all of their ancestors don’t have a a or b field.
  5. The Global object (built-in objects such as Math and String)
    There is no global a or b object

It seems to me that the compiler could issue an error based on the above logic and the IDE could report that error like any other. Unfortunately, Flex Builder and Flash Develop don’t do this, probably because MXMLC doesn’t report the error. Let’s look at the generated bytecode, courtesy of the excellent nemo440:

function private::notEmpty(private::NotEmpty):void	/* disp_id 0*/
  {
    // local_count=2 max_scope=1 max_stack=2 code_len=15
    0       getlocal0     	
    1       pushscope     	
    2       getlocal1     	
    3       pushshort     	200
    6       setproperty   	a
    8       getlocal1     	
    9       pushshort     	300
    12      setproperty   	b
    14      returnvoid    	
  }
 
 
  function private::empty(private::Empty):void	/* disp_id 0*/
  {
    activation {
      var obj:private::Empty	/* slot_id 1 */
    }
    // local_count=4 max_scope=3 max_stack=2 code_len=36
    0       getlocal0     	
    1       pushscope     	
    2       newactivation 	
    3       dup           	
    4       setlocal2     	
    5       pushscope     	
    6       getscopeobject	1
    8       getlocal1     	
    9       setslot       	1
    11      getscopeobject	1
    13      getslot       	1
    15      dup           	
    16      setlocal3     	
    17      pushwith      	
    18      findproperty  	a
    20      pushshort     	200
    23      setproperty   	a
    25      findproperty  	b
    27      pushshort     	300
    30      setproperty   	b
    32      popscope      	
    33      kill          	3
    35      returnvoid    	
  }

First off, the notEmpty function is extremely straightforward. It’s simply using setproperty to set the fields which it knows about at compile time. The empty function is much longer and more complicated due to the usage of an “activation” to support the with block. The key part, however, is the usage of findproperty to look up the a and b properties. Since this lookup is deferred to run time rather than compile time, the compiler naturally can’t issue an error. It’s similar to what you get if you indexed the object with the [] operator like this: obj["a"].

with blocks, in addition to being slow, obscure, and possibly leaking memory, are therefore type-unsafe, IDE unfriendly, and convert compile time error handling into run time error handling. Convenience sure comes at a high price sometimes.