Odd CFLoop Condition Runtime Compilation Bug
Yesterday (or was it the day before), Mark Mandel brought up a very odd CFLoop / Condition runtime compilation error on Twitter. It involved evaluating explicit True / False data types in order to prepare the Condition attribute of the CFLoop tag. To demonstrate this, take a look at the following code snippets and their associated output:
<!--- Store true in a variable. --->
<cfset isTrue = true />
<!--- Demonstrate that TRUE can be used in hashes and parens. --->
Is True: #(true)# | #true#<br />
Is True: true | true
<!--- Loop while true. --->
<cfloop condition="isTrue">
Loop True (delayed variable evaluation).<br />
<cfbreak />
</cfloop>
Loop True (delayed variable evaluation).
<!--- Loop while true (with hash). --->
<cfloop condition="#isTrue#">
Loop True (immediate variable evaluation).<br />
<cfbreak />
</cfloop>
Loop True (immediate variable evaluation).
<!--- Loop while true (without variable). --->
<cfloop condition="#true#">
Loop true (immediate evaluation).<br />
<cfbreak />
</cfloop>
Invalid CFML construct found on line 1 at column 2. ColdFusion was looking at the following text: true The CFML compiler was processing: An expression beginning with #.
<!--- Loop while true as string. --->
<cfloop condition="#toString( true )#">
Loop true (immediate evaluation as string).<br />
<cfbreak />
</cfloop>
Loop true (immediate evaluation as string).
<!--- Loop while true as string (delayed evaluation). --->
<cfloop condition="toString( true )">
Loop true (delayed evaluation as string).<br />
<cfbreak />
</cfloop>
Loop true (delayed evaluation as string).
This is too odd. It's like the immediate evaluation of the data type True (False is the same) simply doesn't translate into a string value that can be used in the delayed-evaluation of the Condition attribute. However, you'll notice that if we run the True data type through the ToString() method first, even with immediate evaluation, it works fine. This has got to be a bug?? Can anyone offer any insight that I'm not thinking about?
Want to use code from this post? Check out the license.
Reader Comments
does look like a bug...
especially considering delayed evaluation works fine
<cfloop condition="true">
This reminds me of a problem I encountered yesterday (and solved with your help). If you create an xml with cfxml, you cant create a cffile on MX7 (CF9 can), except you parse it toString. I know, not really the same, but this may be a bug that some variables are not parsed automatically.
Did you do this on CF9?
@Roman,
I actually ran this on CF8. I still do most of my R&D on CF8 since we have no CF9 production box yet.
Okay, if I find some time this evening, I'll run this script on CF9.. just in case. And to satisfy my curiosity ;-)
@Roman,
Awesome - sounds like a plan.
Ah okay, I tried some things out and I got the same result on CF9. So it doesn't have anything with my problem I had.
After experimenting some time I can't add anything to your discovered problem. This is strange and could be called a bug. I tried about 10 ways to set true as a variable (like <cfset true = 1 /> and so on (yea I know, I'm crazy and it is senseless to try this... but I wanted to :-P)) but this is impossible. So, yea, why doesn't Coldfusion automatically parse the 'true' string?
This reminds me of a problem I ran into in CF8 where I used the Java method indexOf() to do array searching in CFML.
arr.indexOf("findMe")
Passing in simple values as seen above would work fine, but when I passed in a field from a query variable, it would always fail.
arr.indexOf(qry.col1) - always returns -1
I had to escape anything within parenthesis using toString() like you did here.
arry.indexOf( toString(qry.col1) )
I think deep down it has something to do with typing. Just when you thought toString() was useless, huh?
@Roman,
Yeah, the fact that this works:
<cfset x = true />
<cfloop condition="#x#">
... and this fails:
<cfloop condition="#true#">
... seems funky, right? It's just a simple substitution. Unless, the CFSet is actually not copying the actual data type "true", but is, instead converting it to a string value?? Perhaps I'll try CFDumping out the underlying class to see what's going on.
@Jose,
Talking between ColdFusion and Java can definitely hiccup when it comes to data types. I think technically, the query.col reference actually returns the underlying Java record set column object... I know that query[ "col" ] returns an object that can take array functions:
www.bennadel.com/blog/167-Calling-Array-Functions-on-ColdFusion-Query-Columns.htm
@Ben,
Hi Ben,
First thanks for all your posts. They bring light and clarity to a lot of CF topics.
About this issue, I don't think it's a bug. CF is funky that way. It's just a matter of type and evaluation order which in CF is translated to how we use '', ## or both.
In this case the condition accepts a string or an expression and that's why these will work:
<cfset x = true />
<cfloop condition="x">
<cfloop condition="#x#">
<cfloop condition="'#x#'">
or
<cfloop condition="true">
<cfloop condition="'#true#'">
The fact that <cfloop condition="#true#"> this doesn't work means that we are dobleevaluating the expression and it also means what you said that CF will accept the result of #x# as a string.
This is much like the Evaluate() function. If you run these scenarios with the Evaluate() function it kind of starts to make sense.
The whole topic of the evaluation order and the use of "" and ## is probably the most confusing in CF. There is no clear cut when where and how you use or combine them. Many times the whole process is a black box.
For example since we are talking about cfloop:
<cfset x = StructNew()>
This will work:
<cfloop collection="#x#" item="item">
</cfloop>
and this will not:
<cfloop collection="x" item="item">
</cfloop>
however this will work in a condition loop:
<cfloop condition="x"> (x being an expression/variable that evaluates to true or false or a number or a 'true' 'false' string)
I guess that's the price we have to pay for CF being typeless.
@Geno,
If you run this code:
<cfset t = true />
<cfdump var="#getMetaData( true ).getName()#" />
<cfdump var="#getMetaData( t ).getName()#" />
... you get the following output:
java.lang.String
java.lang.String
As you can see, both the data type, "true", and the copy of, "true" - t - are being stored as a string value. They are the same data type. As such, I don't think it should follow that:
<cfloop condition="#t#">
... works and:
<cfloop condition="#true#">
... does not.
Maybe there is just something I am not wrapping my head around properly.
@Ben,
Good test but the getMetaData( t ).getName() can't be trusted.
If you ran this code:
<cfset t = 1 />
<cfparam name="x" type="boolean" default="true">
<cfdump var="#getMetaData( t ).getName()#" />
<cfdump var="#getMetaData( x ).getName()#" />
You get:
java.lang.String
java.lang.String
Even if you substitute t for 1 as in:
<cfdump var="#getMetaData( 1 ).getName()#" />
You still get:
java.lang.String
However, and this makes this whole thing more interesting, when you ran this:
<cfdump var="#getMetaData( IsBoolean(x) ).getName()#" />
You get:
java.lang.Boolean
If you output the value of the function as:
<cfoutput>#IsBoolean(x)#</cfoutput>
CF returns 'YES'.
Anyway I hope this will be helpful in some way.
@Geno,
Hmmm. I'm not sure what to even make out of this :) It's like ColdFusion keeps trying to convert all simple data values to String when possible.
I have no answers at this point :)