GDScript: support variable definitions in if-statement
Another attempt to implement https://github.com/godotengine/godot-proposals/issues/2727 for if-statement. This patch implements two functions: allow variable definition as condition (if var n := foo():) and allow multiple conditions separated by comma in a single if-statement(if foo(), var n := bar(), n > 1:).
The ExpressionNode *condition field of IfNode is replaced with a list of either ExpressionNode or VariableNode. When parsing a single if-statement, a temporary block(SuiteNode) is created for parsing multiple conditions within and then the true block within. For an expression condition, one ExpressionNode is created. For a variable definition, one VariableNode and one ExpressionNode are created. The former one is used for generating code for initial assignment while the later one is used for testing as usual.
Compilation is done like usual except that, all of the not-true-jump-address for each condition will be patched to the same starting address else/elif if any, otherwise the end address of the if statement. Thus, the write_else() interface is changed to write_else(int count).
- Bugsquad edit, closes: https://github.com/godotengine/godot-proposals/issues/2727
I'm not sure about the comma syntax, it's basically a weird and that allows multiple definitions. I think single definition is enough in most cases.
I'm not sure about the comma syntax, it's basically a weird
andthat allows multiple definitions. I think single definition is enough in most cases.
Both the comma syntax and multiple definitions / conditions are borrowed from Swift (optional binding). As a Swift user, the comma syntax appears more readable than and keyword to me personally, but of course it can be changed to other syntax which may be more suitable for GDScript.
As for multiple definitions / conditions, it is mainly for static typing. While GDScript is a dynamic typing language, some people like me actually use it with static typing as much as possible. Static typing will introduce extra code. For example, consider dragging:
Without static typing:
func _input(event: InputEvent) -> void:
if event is InputEventMouseButton && event.button_index == MOUSE_BUTTON_LEFT:
if event.is_pressed():
start_dragging(event)
else:
stop_dragging(event)
elif dragging && event is InputEventMouseMotion:
do_dragging(event)
Is very readable. But if one write it with static typing (maybe for performance and static checking):
func _input(event: InputEvent) -> void:
if event is InputEventMouseButton:
var mouseButtonEvent := event as InputEventMouseButton
if mouseButtonEvent.button_index == MOUSE_BUTTON_LEFT:
if mouseButtonEvent.is_pressed():
start_dragging(mouseButtonEvent)
else:
stop_dragging(mouseButtonEvent)
elif dragging && event is InputEventMouseMotion:
var mouseMotionEvent := event as InputEventMouseMotion
do_dragging(mouseMotionEvent)
If only one definition / condition is allowed, the first if (in the dynamic typing one) has to be split into two if as one can only call button_index on the casted result with static typing.
With static typing and multiple conditions:
func _input(event: InputEvent) -> void:
if var mouseButtonEvent := event as InputEventMouseButton, mouseButtonEvent.button_index == MOUSE_BUTTON_LEFT:
if mouseButtonEvent.is_pressed():
start_dragging(mouseButtonEvent)
else:
stop_dragging(mouseButtonEvent)
elif dragging, var mouseMotionEvent := event as InputEventMouseMotion:
do_dragging(mouseMotionEvent)
This one requires less code lines and is as readable as the first dynamic typing one. Multiple conditions is useful for code with static typing. Code with dynamic typing perhaps do not need it at all.
Still, the comma syntax should be a separate proposal and separate PR IMO. Can you split it?
Sure. I close this PR for now and will reopen it when split is done.
@zjin123 You created two separate commits, but @KoBeWi suggested to create two separate PRs.
@zjin123 You created two separate commits, but @KoBeWi suggested to create two separate PRs.
Thanks for the reminder. I just opened another PR https://github.com/godotengine/godot/pull/98538 for the first commit.
The reason of using comma here is that and or && is not suitable with variable definition. Because when either and or && is used directly after variable definition, they will become part of the variable initializer. For example,
if var x = 1 and y == 2:
print(x) # x is true
if var x = 1 and foo(x): # analyzer error: x is not defined
print(x)
To fix it:
if (var x = 1) and y == 2:
print(x) # x is int
if (var x = 1) and foo(x):
print(x) # x is int
The one works but requires additional parentheses.
With comma syntax, it is simple:
if var x = 1, y == 2:
print(x) # x is int
if var x = 1, foo(x):
print(x) # x is int