dswitzer
02/22/2022, 2:26 PMnull
, but in Lucee 5 (and Railo) it returns test
. Since the modelId
is declared within the local
scope, I would expect the isNull(modelId)
to return true if var modelId
exists in the function. However, you have to explicitly specify the local
scope to get it to work (i.e. isNull(local.modelId)
).
I opened up a Lucee issue about this in May 2021, but there's been not traction:
https://luceeserver.atlassian.net/browse/LDEV-3523
Am I wrong? Is this the behavior everyone else would expect?
I see it as a real problem because any UDF in which you're using isNull()
to check a local variable could end up returning drastically different results than what you're expected.rory
02/22/2022, 2:45 PMrory
02/22/2022, 2:47 PMwebsolete
02/22/2022, 2:57 PMdswitzer
02/22/2022, 6:28 PMdswitzer
02/22/2022, 6:30 PMbdw429s
02/22/2022, 6:48 PMisNull(local.modelId)
and your problem is solved.bdw429s
02/22/2022, 6:49 PMwebsolete
02/22/2022, 6:49 PMbdw429s
02/22/2022, 6:49 PMbdw429s
02/22/2022, 6:50 PMurl.something
variable was able to modify an isNull()
code path deep inside the framework. Scoping our check was the fix.websolete
02/22/2022, 6:50 PMwebsolete
02/22/2022, 6:51 PMbdw429s
02/22/2022, 6:51 PMwebsolete
02/22/2022, 6:54 PMbdw429s
02/22/2022, 7:03 PMDefines how the local scope of a function is invoked when a variable with no scope definition is used.
-- Modern - the local scope is always invoked
-- Classic (CFML Default) - the local scope is only invoked when the key already exists in it
bdw429s
02/22/2022, 7:06 PMmodelID
would have defaulted as well to local.modelID
, but running the trycf code locally and toggling on that setting does not change the behavior. Lucee still finds the variable as url.modelID
bdw429s
02/22/2022, 7:08 PMbdw429s
02/22/2022, 7:10 PMdswitzer
02/22/2022, 7:18 PMlocal
variable is declared, then scope cascading should stop. I suspect there's a lot of CFML code that would be affected by this issue based on some searches on Github.dswitzer
02/22/2022, 7:18 PMbdw429s
02/22/2022, 7:19 PMbdw429s
02/22/2022, 7:20 PMbdw429s
02/22/2022, 7:20 PMwriteDUmp(modelId)
bdw429s
02/22/2022, 7:20 PMbdw429s
02/22/2022, 7:20 PMdswitzer
02/22/2022, 7:20 PMbdw429s
02/22/2022, 7:20 PMbdw429s
02/22/2022, 7:21 PMmodelID
, you're thinking in your head, "I mean the one in the local scope", but that's not what the CF engine is thinkingbdw429s
02/22/2022, 7:21 PMmodelID
is not null and has a valuebdw429s
02/22/2022, 7:21 PMbdw429s
02/22/2022, 7:23 PMlocal.modelID
was null, then your could should read
writeDUmp(local.modelId)
right now the code
writeDUmp(modelId)
is just challenging CF to go find a non-null value in any scope in the lookup orderdswitzer
02/22/2022, 7:23 PMtrue
. The variable x
was scoped and is null
. It will return true
unless x
exists in a different scope.
I think at a bare minimum the Lucee docs for isNull() should state you should explicitly declare the local
scope if you do not want cascading scope check to occur.
https://docs.lucee.org/reference/functions/isnull.htmlbdw429s
02/22/2022, 7:24 PMdswitzer
02/22/2022, 7:24 PMbdw429s
02/22/2022, 7:25 PMThe variableNo it isn't scoped. I'm unclear why you would say that.was scopedx
isNull(x);
☝️ That is not a scoped variable. The connection you have to the variable of the same name in the local
scope is coincidentaldswitzer
02/22/2022, 7:25 PMvar x
scopes to the local
scope.bdw429s
02/22/2022, 7:26 PMThe fact that ColdBox, FW/1, DI/1, etc all could be bitten by this issue is concerning.Programming is hard. We have to be careful. Just because people have run afoul of a language design, doesn't necessarily mean it's bad. This only happened a couple times in ColdBox, and we fixed it. Case close. We learned our lesson.
bdw429s
02/22/2022, 7:26 PMlocal.x
is scoped, but the access of x
is not scopedbdw429s
02/22/2022, 7:26 PMdswitzer
02/22/2022, 7:26 PMbdw429s
02/22/2022, 7:27 PMx
is not an absolute canonical reference to a specific variable. it's a guess for the CF engine to go track down for youbdw429s
02/22/2022, 7:28 PMdswitzer
02/22/2022, 7:29 PMdefined
. When I see var x = nullValue();
the variable x
is defined, but the value is null
. The isNull()
function isn't supposed to be checking if a variable has value, but if the value is null
. So the fact that var x
exists and is null
it should return true
.dswitzer
02/22/2022, 7:30 PMbdw429s
02/22/2022, 7:30 PMI think at a bare minimum the Lucee docs for isNull() should state you should explicitly declare theScope hunting is a core part of CFML and Lucee is the most consistent engine. Why should Lucee tell us, "Oh, but the way, we're still following the spec here"? That should be the expectation, not the exception. Instead, I think the Adobe docs should be the ones with the note saying, "oh by the way, we're throwing the spec under the bus here and ignoring it in some cases"scope if you do not want cascading scope check to occur.local
dswitzer
02/22/2022, 7:30 PMbdw429s
02/22/2022, 7:31 PMThis does not seem to be the expected behavior by many.Dunno what to tell you. It's expected to me and it makes perfect since given the rules of CFML.
dswitzer
02/22/2022, 7:31 PMnull
. As soon as it find a variable which is defined and is null
, then it should return true
. It should check local.x
first, see it exists and is null
and then return true
.bdw429s
02/22/2022, 7:32 PMbdw429s
02/22/2022, 7:32 PMbdw429s
02/22/2022, 7:32 PMbdw429s
02/22/2022, 7:32 PMdswitzer
02/22/2022, 7:32 PMnull
does existdswitzer
02/22/2022, 7:32 PMbdw429s
02/22/2022, 7:33 PMbdw429s
02/22/2022, 7:33 PMdswitzer
02/22/2022, 7:33 PMisNotNull()
, it's isNull()
. That should return true
for a variable which contains a Java null value.dswitzer
02/22/2022, 7:34 PMbdw429s
02/22/2022, 7:34 PMbdw429s
02/22/2022, 7:34 PMdswitzer
02/22/2022, 7:34 PMnull
.bdw429s
02/22/2022, 7:35 PMyou can pass it aroundHow? A null variable cannot be passed directly or you will receive an error as shown here https://trycf.com/gist/7a1198e6e5fddfcc885e9a081e2c96e5/lucee5?theme=monokai
dswitzer
02/22/2022, 7:37 PMdswitzer
02/22/2022, 7:37 PMnull
around.bdw429s
02/22/2022, 7:38 PMMy example code wouldn't do that. It would be printingLol, take this example for instance. You want CFML to work this way, which would be bonkers.null
if( isNull(modelId) ) {
writeDump( modelId )
}
☝️ In a well-designed language in which nulls don't exist, that should never be able to run without erroring.bdw429s
02/22/2022, 7:39 PMmodelID
for you and get a non-null value, then isnull() also shuould not return true.bdw429s
02/22/2022, 7:39 PMisNull()
BIF are evaluated before the function is even calledbdw429s
02/22/2022, 7:39 PMwriteDump()
and isNull()
are receiving the same argument!dswitzer
02/22/2022, 7:40 PMbdw429s
02/22/2022, 7:40 PMmodelID
in the same manner regardless of what it's going to do with the value afterward. The fact that it's being passed to isNUll()
does not change how the engine resolves it.bdw429s
02/22/2022, 7:40 PMdswitzer
02/22/2022, 7:41 PMnull
. If there is a variable in the scope chain which is null
, then it should return true
. Not just ignore it. That's what ACF does.dswitzer
02/22/2022, 7:42 PMbdw429s
02/22/2022, 7:43 PMThere should be a difference between isNull() and isDefined()This is perhaps another discussion entirely... The main difference here being that the incoming variable is encased in a string so the indefined method is allowed to resolve it internally, whereas the arg to isNull is evaluated before isNull is ever called
bdw429s
02/22/2022, 7:45 PMvar thingToTest = modelID;
isNull( thingToTest );
the CF engine goes and finds modelID
first and once it's found it, then it passes the result to the isNull()
function. What you're wanting CF to do is to change how the first line works if the second line is going to use the isNull() method to check the value.bdw429s
02/22/2022, 7:46 PMbdw429s
02/22/2022, 7:46 PMdswitzer
02/22/2022, 7:48 PMisNull()
should talk about explicitly scoping variables in a UDF?bdw429s
02/22/2022, 7:48 PMbdw429s
02/22/2022, 7:48 PMbdw429s
02/22/2022, 7:49 PMbdw429s
02/22/2022, 7:49 PMbdw429s
02/22/2022, 7:49 PMbdw429s
02/22/2022, 7:50 PMdswitzer
02/22/2022, 7:50 PMdswitzer
02/22/2022, 7:51 PMisNull()
since the behavior is different from ACF.dswitzer
02/22/2022, 7:52 PMbdw429s
02/22/2022, 7:52 PMurl.modelId = "test";
variables.modelID = javaCast('null','');
// If modelID can be found having a value
writeDump( modelId )
// Then this must return false
writeDump( isNull(modelId) )
bdw429s
02/22/2022, 7:54 PMdswitzer
02/22/2022, 7:55 PMisNull()
was created just for this purpose, because Java libs often return null
values and they wanted a way to check if a local function contained a null to avoid scope bleeds.dswitzer
02/22/2022, 7:55 PMisNull()
was introduced).bdw429s
02/22/2022, 7:55 PMbdw429s
02/22/2022, 7:56 PMbdw429s
02/22/2022, 7:56 PMdswitzer
02/22/2022, 7:57 PMyes
:
url.modelId = "test";
variables.modelID = javaCast('null','');
// If modelID has a value
writeDump( modelId );
// Then this must return false
writeDump( isNull(modelId) );
writeDump( isDefined("modelId") );bdw429s
02/22/2022, 7:58 PMisNull()
and isDefined()
nearly the same, which I thought made sense.dswitzer
02/22/2022, 7:58 PMnull
.bdw429s
02/22/2022, 7:58 PMBut the purpose of isDefined() is differentHow? Why? By definition in CFML, null means not defined. That literally ties the definition of defined-ness to null-ness. Why would they be different?
bdw429s
02/22/2022, 7:59 PMIt's supposed to explicitly check if a value isAnd that's what it does. The issue here is what you are describing as a 'variable'. You're still not seeing the ambiguity betweennull
x
and local.x
dswitzer
02/22/2022, 8:00 PMbdw429s
02/22/2022, 8:00 PMmyBIF( myVar )
the myVar
is first found using the CFML rules of scope hunting and what makes a variable defined. Then, and only then, is the resulting value passed to myBIF()
dswitzer
02/22/2022, 8:00 PMlocal.x
is null
it should stop and return true
.dswitzer
02/22/2022, 8:00 PMbdw429s
02/22/2022, 8:01 PMAs soon as the scope checking finds thatAccording to whom? Null mean not-defined in CFML and has since I can remember. Why should that change now?islocal.x
it should stop and returnnull
.true
dswitzer
02/22/2022, 8:01 PMbdw429s
02/22/2022, 8:01 PMdswitzer
02/22/2022, 8:03 PMbdw429s
02/22/2022, 8:03 PMit should stop and return☝️ And again, you're conflating the action of the isNUll method with the resolution of the variable.true
bdw429s
02/22/2022, 8:03 PMbdw429s
02/22/2022, 8:03 PMbdw429s
02/22/2022, 8:03 PMbdw429s
02/22/2022, 8:04 PMbdw429s
02/22/2022, 8:04 PMwriteDump( modelId )
would also dump null, which would break CFMLbdw429s
02/22/2022, 8:06 PMbdw429s
02/22/2022, 8:06 PMbdw429s
02/22/2022, 8:12 PMurl.foo = "I have a value, I am not null!";
variables.foo = javaCast('null','');
thingtoTest = foo;
writeDump( isNull(foo) );
writeDump( isNull(thingtoTest) );
Both of those isnull checks should return the same thing as they are looking at the same value! Yet Adobe is changing the behavior based on which variable it receives which is wrong.zackster
02/22/2022, 8:18 PMbdw429s
02/22/2022, 8:19 PMbdw429s
02/22/2022, 8:19 PMzackster
02/22/2022, 8:24 PMzackster
02/22/2022, 8:26 PMbdw429s
02/22/2022, 8:32 PMstructKeyList()
that showed the null keys.bdw429s
02/22/2022, 8:33 PMzackster
02/22/2022, 8:34 PMbdw429s
02/22/2022, 8:54 PMdswitzer
02/23/2022, 7:17 PMbdw429s
02/23/2022, 7:30 PMbdw429s
02/23/2022, 7:30 PMAdam Cameron
myVariableWithNullValue = javaCast("null", "")
myVariableWithNullValue
very much is defined there. You can see it. Being defined. It just has "no value" (in the context of CFML not understanding null). This was always two different things, and CFML messed it up.
---
Once CFML acknowledged null is actually a thing, part of that ought to have been to revise the behaviour of the distinction between "define`d-ness" and "null-ness". This would necessarily be a breaking change. So be it: it's the price the language pays for having previously poor design.
Using pseudocode (not intended to be pseudoCFML, don't read anything into the similarity between some CFML functions):
// myProgram.notCfml
isDefined(notEverMentionedPreviously) // false
isNull(notEverMentionedPreviously) // error
var definedButNoValue
isDefined(definedButNoValue) // true
isNull(definedButNoValue) // true
var definedWithExplicitNullValue = null
isDefined(definedWithExplicitNullValue) // true
isNull(definedWithExplicitNullValue) // true
var definedWithExplicitNonNullValue = "some value"
isDefined(definedWithExplicitNonNullValue) // true
isNull(definedWithExplicitNonNullValue) // false
To that end, I'm with @dswitzer: scope-look-up should not come in to play here. In his example modelId
is defined in that function, so there's no need to go looking elsewhere for it. It is irrelevant what its value is; that should not come into it.
I cannot test on Lucee5 ATM cos it seems to be crocked on trycf, but this also seems like a regression in Lucee5, if the Lucee4 behaviour was a) different; b) in-keeping with CF's behaviour; c) which is behaviour that makes more sense anyhow. Why did the behaviour change (sorry if this was explained, it's a long thread and I skimmed)?bdw429s
02/24/2022, 2:50 PMbdw429s
02/24/2022, 2:54 PMstructKeyExists()
checks with isNull()
. The ability to override the engine's null behavior at a CFC level would at least help people juggle 3rd party libs that didn't support it in the mean time 🤔Adam Cameron
bdw429s
02/24/2022, 6:26 PMbdw429s
02/24/2022, 6:26 PMbdw429s
02/24/2022, 6:26 PMbdw429s
02/24/2022, 6:27 PMbdw429s
02/24/2022, 6:27 PMbdw429s
02/24/2022, 6:28 PMAdam Cameron
https://i2.paste.pics/4ee9ac690b197275bb65530c9c0aa2ad.png▾
bdw429s
02/24/2022, 6:29 PMAdam Cameron
bdw429s
02/24/2022, 6:30 PMbdw429s
02/24/2022, 6:30 PMbdw429s
02/24/2022, 6:30 PMbdw429s
02/24/2022, 6:32 PMapplication action="update" NULLSupport=true;
// test stuff with null support on
application action="update" NULLSupport=false;
bdw429s
02/24/2022, 6:32 PMbdw429s
02/24/2022, 6:33 PMAdam Cameron
bdw429s
02/24/2022, 6:34 PMAdam Cameron
bdw429s
02/24/2022, 6:34 PMAdam Cameron
bdw429s
02/24/2022, 6:35 PMbdw429s
02/24/2022, 6:35 PMbdw429s
02/24/2022, 6:35 PMbdw429s
02/24/2022, 6:36 PMAdam Cameron
bdw429s
02/24/2022, 6:36 PMnull
keyword anyhere since that's not backwards compat, but we just don't rely on things like strcutKeyExists()
in our code, opting to use !isnull()
since that works in both casesbdw429s
02/24/2022, 6:37 PMvar results = methodCall();
if( structKeyExists( local, 'results' ) ) {
return results;
}
bdw429s
02/24/2022, 6:38 PMvar results = methodCall();
if( !isNull( local.results ) ) {
return results;
}
instead nowAdam Cameron