'Class of record' catch

Here’s a little “gotcha” I stumbled upon recently. If you’re writing a handler that has to check if its input’s a record, you might reasonably expect to do something like this:

on myHandler(x)
	if (class of x is record) then
		-- x is a record (or a reference to one).
	end if
end myHandler

But besides having the intrinsic property of being a record, a record can sometimes contain a ‘class’ property, which might have some other value. In such cases, ‘class of’ returns the value of the contained property rather than the intrinsic one:

tell application "TextEdit"
	set x to properties of front document
end tell
--> {modified:false, path:missing value, text:"", class:document, name:missing value}

class of x
--> document

Similarly (although admittedly less likely) a record might give a false positive when you’re expecting some other class value:

set x to {class:integer, b:"aardvark"}

if (class of x is integer) then set x to x + 1
--> !

One quite fast way to avoid this ambiguity is to put the item into a list and count how many records the list contains:

tell application "TextEdit"
	set x to properties of front document
end tell

(count {x}'s records) is 1
--> true

This trick distinguishes between records and other data types ” including references. So if you want to treat records and references to records as equivalent, you have to include some dereferencing. The normal instrument for this would be the ‘contents’ operator, but this has a couple of disadvantages here. If the item being tested is the first in a chain of references, ‘contents of’ only returns the next reference in the chain, so a repeat loop would be needed to dereference the entire chain. The other thing is that, although ‘contents of’ normally returns the item itself when the item isn’t a reference, if the item happens to be a record containing a ‘contents’ property, the value of that property is returned instead of the record!

The simplest and fastest dereferencing solution I’ve found for the current purpose is to coerce the unknown item to ‘item’. Some application references (if your handler is unfortunate enough to receive them) don’t take kindly to being so coerced, so a try block is needed as well:

on myHandler(x)
	try
		if ((count {x as item}'s records) is 1) then
			-- x is a record (or a reference to one).
		end if
	end try
end myHandler

Groovy one!