ID:2364859
 
Not a bug
BYOND Version:511
Operating System:Windows 10 Pro 64-bit
Web Browser:Firefox 56.0
Applies to:Dream Daemon
Status: Not a bug

This is not a bug. It may be an incorrect use of syntax or a limitation in the software. For further discussion on the matter, please consult the BYOND forums.
Descriptive Problem Summary:
You cannot pass null explicitly in a proc-call with default values on it's arguments. Attempting to do so will result in the null being discarded (regardless of argument specification method) and the default value taking over it.

This is a problem because it means that there is no way to tell if a proc was called with null as an explicit argument vs a proc called with no arguments (or no value specified for the argument in question)

Numbered Steps to Reproduce Problem:
1. Make a proc that has an argument with a default value, `bug(z = 1)`.
2. Call the proc however you want with the argument `null`.
3. Observe that accessing `z` in the proc will always return `1`.


Code Snippet (if applicable) to Reproduce Problem:
/proc/bug(z = 1)
world << "Actual result: [isnull(z) ? "null" : z]"

/client/verb/test()
world << "T1: Expected result: null"
bug(null) // "Actual result: 1"
world << "T2: Expected result: null"
bug(z = null) // "Actual result: 1"
world << "T3: Expected result: null"
call(/proc/bug)(null) // "Actual result: 1"


Expected Results:
Explicitly specified nulls would override default arguments.

Actual Results:
Default arguments override explicitly specified nulls.

Does the problem occur:
Every time? Or how often? Every time.
In other games? Yes.
In other user accounts? Yes.
On other computers? Yes.

When does the problem NOT occur?
Unknown. I couldn't find any way to make it not occur.

Did the problem NOT occur in any earlier versions? If so, what was the last version that worked? (Visit http://www.byond.com/download/build to download old versions for testing.) I tested the last version of every major build from 512.1422 to 508.1299. This bug is present in all of them.

Workarounds:
None known.
Lummox JR resolved issue (Not a bug)
This isn't a bug, but expected behavior. Null is interpreted as the lack of an argument in this case and the default is filled in. There's really no other way to do that, short of having the internal code check the arguments list length--except that list will always expand to the length of your defined arguments anyway. Also, if you want to fall back on a default value for one argument but not another, null is how you would have to do it.

The compiler literally adds in if() statements for filling in the default values at the beginning of the proc:

if(z == null) z = 1

The bottom line is that if you want null to be an allowable argument value, you can't assign a default to that argument.
I totally understand if this doesn't matter, but I'd like to point out that the way this works isn't harmless-

It means that you can't make any optional arguments that accept null as a value, because there's no way to differentiate between "no value provided" and "null" provided. This came up when we were working on our lighting system, with a proc that looked like this:
/atom/proc/set_light(light_range, light_power, light_color)


Because BYOND doesn't provide any way to access the arguments as they were originally presented in the call, there's no way to tell if someone just didn't provide light_color (meaning that the intended behavior is that it stays static while the range and or the power changes) or if they explicitly want to reset it to the default value, null.


The workaround we tried to do was making it
#define ARBITRARY_NOT_NULL_VALUE -99999
/atom/proc/set_light(light_range, light_power, light_color = ARBITRARY_NOT_NULL_VALUE

So that later in the proc, we can check `if(light_color != ARBITRARY_NOT_NULL_VALUE) set_light_color(light_color)`, knowing that they explicitly passed null as an argument. This obviously doesn't work because of the way this behaves.

The only other workaround to make a proc like this function that I can think of is to make a magic string or number that resets the color to white.
In response to Tigercat2000
The other option would have been to not use null as the reset value and use "#fff" instead.
In response to Tigercat2000
Tigercat2000 wrote:
Because BYOND doesn't provide any way to access the arguments as they were originally presented in the call, there's no way to tell if someone just didn't provide light_color (meaning that the intended behavior is that it stays static while the range and or the power changes) or if they explicitly want to reset it to the default value, null.

You're missing the point, though. There will always be some value for that argument, be it null or something else. What you're asking for is to have something sent in place of null that's exactly like null but isn't treated the same in this one and only instance.

There's no such concept as calling proc(a,b,c) and only passing along b and c. That isn't possible. There is such a concept as passing only a and b, but internally the arguments list is always padded out to the number of args provided--and while I agree that's not ideal, there are some internal reasons why it's done that way that wouldn't make a lot of sense to change.

Ultimately the problem you're running into is that there's not much good reason to want a default-value argument in a proc that can take null as a value. It's a design error.

What would make more sense for you as a workaround would be to have some arbitrary value in your #defines that means "use null" and explicitly send that to the proc. When your proc sees that value it knows null was sent, and when it sees null it knows nothing was sent.

Or, if you're calling set_light() in such a way that light_color is taken from atom.color, just use atom.color||"#fff" which solves the issue entirely.

Or, if you don't care about using named arguments, you could leave the light_color argument out, and then if args.len > 2, you know args[3] is your light_color value and otherwise it was not sent. I use this method in some places in stddef.dm.