Godot version:
3.2.1
OS/device including version:
Windows 10, 1903
Issue description:
The bitwise OR Assignment Operator (|=) fails, with "Invalid operands 'bool' and 'bool' in operator '|'" when used with bools. I would expect same behaviour as in C++, since nothing else is stated in the docs.
Steps to reproduce:
var b = false
b |= true # Fails
b |= false # should lead to b == true
Minimal reproduction project:
bitwise or.zip
Quick analysis:
|= is OP_ASSIGN_BIT_OR in the GDScript parser.
The GDScript compiler converts it to assignment and Variant::OP_BIT_OR.
https://github.com/godotengine/godot/blob/2a49798c7b92b332c2198f52b9c5ae10bd944bd0/modules/gdscript/gdscript_compiler.cpp#L188
Variant also has a Variant::OP_OR which is used for booleans. Thus, Variant::OP_BIT_OR fails for everything which is not an int.
https://github.com/godotengine/godot/blob/2a49798c7b92b332c2198f52b9c5ae10bd944bd0/core/variant_op.cpp#L1310-L1319
It seems like the easiest way to implement this would be to make OP_BIT_OR work with booleans as well as with integers. It should not convert operands to booleans automatically, or maybe convert only the second one only if the first operand is a boolean. (Otherwise, 1 | 2.0 would be valid, and will result in true, not an error or 3.0)
if we treat booleans like integers then the below piece of code should also be valid (just like python)
var i = true + true
i += false + 3
imo, this shouldn鈥檛 be feasible and its proper behavior is correct
@xibz I don't agree since there are very valid use cases for doing bitwise ops with bools, but I think @ThakeeNathees proposal is confusing. Since I thought I have a bool and only need a bool (even though it is at least a char and true != 0), but still I don't know a use case where adding something to a bool really helps. In contrast I can imagine (and did) adding a bool to an integer as done when replacing branches with bool math.
@HaSa1002 - No other language does this besides C++ and C#, and that's because of the horrible design of booleans in C++. C# only uses & to mean && minus short circuiting and same for |. Any other higher level language will treat booleans as its own class with values only being true and false, which means any bitwise operation doesn't make any sense.
If you want them to behave like C#, my vote is no. It makes reading the code that much harder, due to now needing to know what an individual function on the RHS may be doing.
Why?
var a = false
a |= false
a == false
var b = false
b |= true
b == true
var c = true
c |= false
c == true
var d = true
d |= true
d == true
Where is that not readable? There are only those four cases. And why do you need to know what an individual function does when you use the bitwise or op and not when you do a "normal" assignment? It shortcuts the following:
var a = false
a = a || false
a == false
var b = false
b = b || true
b == true
var c = true
c = c || false
c == true
var d = true
d = d || true
d == true
In my opinion the latter one is only more verbose than the first.
But the |= operator is not a logical OR assignment operator, so your shortcuts are relying on a property of booleans treated as integers which makes things confusing.
Because yes, a |= false might be equivalent to a = a || false for booleans, simply because (a | false) == (a || false).
But the same is not true of integers, where 5 | 3 and 5 || 3 are not equivalent.
If you want sugar for logical OR, you should be asking for a ||= operator, not for booleans to support bitwise OR assignment to be able to (ab)use it as a logical OR assignment.
@HaSa1002 - Aside from to what akien was talk about, you can do something like this in C#
```C#
if (a | someFunctionThatDoesTooMuch(value)) {
}
You now need to know exactly what that function is doing, because it **WILL** be executed even if `a` is `false`. I'd argue that this is bad programming, but even still having to remember that short-circuting is valid for one versus the another just makes the language that much harder to read. I think C# is a great language, but some of their language design decisions aren't the best, imo.
And then for regular cases if I see,
var c = a | b
```
I have to go back and look at what a and b are. They may be booleans, but they may be integers.
Coming from a background where I learnt that false == 0 and true != 0 I am personally not confused when using bitwise operations on bools. However in a dynamically typed language you have to deduct the type and therefore I agree that it might cause errors in unexpected situations. Nonetheless in your example you have to look up what a and b are since they are not explicitly named. Normally a variable containing an integer is differently named than a variable containing a bool. For the special function it is the same. Yes, you should know what the function returns, but imo you should always know that or at least read that from the functions name. There might be situations where the execution of the function is wanted. I think you should know what your operator does (not in detail, but what output you expect at a given input) otherwise you will end up having trouble.
Anyhow, what I wanted to say, is in a dynamically typed language you as the programmer should stick to a style where you do not cause yourself reading trouble. (Well, that applies to any language...) You can always find a way to write code that is unreadable. When I compare numbers, I always do that explicitly, event though it is not necessary:
var a := 1
if a != 0:
pass
# instead of
if a:
pass
So, for now, I am a bit unsure with what to do with this issue. I see those options:
||= and the other bitwise ops and open another one here since operators are nowhere documented. (I came across a different issue yesterday, where I had to search the archived issues to find out why comparisions on Vector2s do not behave as I thought they do)I vote 3.
- Allow bitwise ops on bools
That makes no sense because bitwise operations are not the same as logical operations (discussed plenty in the thread). Even more if you think like this:
Coming from a background where I learnt that
false == 0andtrue != 0I am personally not confused when using bitwise operations on bools.
Because 2 == true and 4 == true but 2 & 4 == false, since 2 & 4 == 0.
Also, as mentioned, bitwise operators don't short-circuit while logical operators do. So they wouldn't be the same anyway.
- treat bools as integers
That's bad because it leads to many subtle errors when you start doing math with booleans and wrongfully using bitwise operators on what you think are booleans but are actually set as integers. The fact that GDScript uses strong typing avoids these issues, so treating booleans as integers would be a step backwards.
3. close and archive this issue and open one in the proposals repo for
||=and the other bitwise ops and open another one here since operators are nowhere documented. (I came across a different issue yesterday, where I had to search the archived issues to find out why comparisions onVector2s do not behave as I thought they do)
If there's a documentation issue you can open one on https://github.com/godotengine/godot-docs so we can keep track of those. It's not always clear what's missing on the docs for us devs because usually we don't need to read the docs, so we rely on other people to point it out.
For now I'm closing this since it looks like the consensus is that the current behavior is appropriate. You can open a proposal for a new operator if you really need it.
Most helpful comment
That makes no sense because bitwise operations are not the same as logical operations (discussed plenty in the thread). Even more if you think like this:
Because
2 == trueand4 == truebut2 & 4 == false, since2 & 4 == 0.Also, as mentioned, bitwise operators don't short-circuit while logical operators do. So they wouldn't be the same anyway.
That's bad because it leads to many subtle errors when you start doing math with booleans and wrongfully using bitwise operators on what you think are booleans but are actually set as integers. The fact that GDScript uses strong typing avoids these issues, so treating booleans as integers would be a step backwards.
If there's a documentation issue you can open one on https://github.com/godotengine/godot-docs so we can keep track of those. It's not always clear what's missing on the docs for us devs because usually we don't need to read the docs, so we rely on other people to point it out.
For now I'm closing this since it looks like the consensus is that the current behavior is appropriate. You can open a proposal for a new operator if you really need it.