Edit: The original idea was to do the optimisations when compiling GDScript to bytecode but I realised that it makes more sense to instead do conditional AST generation to allow for optimisation even when interpreted.
I would like to propose adding version as a keyword in GDScript to allow conditional code execution and compilation based on the OS and custom features that are available.
At first glance
version (âfeatureâ):
appears to just be syntactic sugar for
if OS.has_feature (âfeatureâ):
and when interpreted this would be the case.
The true advantage of the version keyword would be when GDScript is compiled to gdc bytecode.
The version keyword could be detected at compile time and used to conditionally compile blocks of code depending on the feature set of the chosen export preset/template.
This would result in faster runtime speeds, smaller bytecodes and potentially faster compile times as code that is never used by your exported game will be skipped.
It also (in my opinion) results in better looking code that is arguably easier to maintain.
Here are some psudocode examples of use cases along with their current, run time counterparts:
Debug statements:
Run time
func my_function (value):
if OS.is_debug_build():
print(âSome debug text: Arg=â, str(value))
#Function body here
if OS.is_debug_build():
print(âResult: â, result)
return result
Compile time
func my_function (value):
version(âdebugâ):
print(âSome debug text: Arg=â, str(value))
#Function body here
version(âdebugâ):
print(âResult: â, result)
return result
Different code for different platforms:
Run time
#Platform independent code here
if OS.has_feature(âandroidâ):
#Android code here
if OS.has_feature(âiosâ):
#iOS code here
if OS.has_feature(âwebâ):
#Web code here
Compile time
#Platform independent code here
version(âandroidâ):
#Android code here
version(âiosâ):
#iOS code here
version(âwebâ):
#Web code here
Custom features & else:
Run time
if OS.has_feature(âmy_custom_featureâ):
#Do something
else:
#Do something else
Compile time
version(âmy_custom_featureâ):
#Do something
else:
#Do something else
Tool scripts:
Run time
func _draw ():
if Engine.editor_hint:
#Draw lines and stuff
Compile time
func _draw ():
version(âeditorâ):
#Draw lines and stuff
or
version(âeditorâ): # _draw function not included at all so it doesn't even get called
func _draw ():
#Draw lines and stuff
At compile time when a version block is found the compiler can check if the specified feature is available on the export platform (OS.has_feature) and either include the code in the block or ignore it meaning no runtime checks are required.
e.g when exporting for android
#Platform independent code here
version(âandroidâ):
#Android code here
version(âiosâ):
#iOS code here
version(âwebâ):
#Web code here
compiles as
#Platform independent code here
#Android code here
completely omitting the iOS and web code from the compiled bytecode.
This proposal is based on the version keyword featured in D https://dlang.org/spec/version.html
Was also proposed/discussed in the now-closed #6340.
I didn't spot that one when searching. Not much point keeping this open then so closing.
Reopening. Note the other issue is closed; there can still be merit in discussing this one.
So... that's basically a specialized #ifdef. Conditional script parsing is a recurring theme, notably in https://github.com/godotengine/godot/issues/26177 as well.
I like the debug-only tag, that occurs a lot in my projects and it can be tedious to chase after all debug-only code to ensure to turn off or remove before export, not to mention the fact these debug-only if sections should be carefully placed so they are not evaluated too much like in tight loops.
Also, expecting only one tag in version won't cut cases like "Not Android", "Mobile only", "OSX and Linux", or "Godot version from 3.1 to 3.2.1". I know these use cases exist because I saw them in existing games I worked on, and particularly plugins and libraries which have to support wider ranges of versions (I myself do that too with regular ifs and has_method at the moment).
I also had to break compatibility in one of my plugins because a virtual function signature started including a default argument, which made the script fail to even parse. Conditional compilation isn't pretty in that case, but at least allows to maintain compatibility.
Perhaps something like D's static if would be more appropriate then. It would allow of more complex conditions such as static if not OS.has_feature(âandroidâ) or static if OS.has_feature(âOSXâ) or OS.has_feature(âX11â) although there is no reason that we couldnât have something like this:
version(âandroidâ) and not version(âx86â):
#somecode
vs
static if OS.has_version(âandroidâ) and not OS.has_version(âx86â)
but static if does allow for stuff like this:
static if false:
#Doesnât matter whatâs here; It will not be compiled
I think that version is a better solution than static if because it can specifically focus on the availability features rather than arbitrary conditions that would require compile time code execution, is less verbose, is less likely to cause confusion (âwhatâs the difference between if and static if?â asked every new user for all of time) and should not result in issues caused by values not being known at compile time as the only valid argument type is a string constant.
As for checking the engine version that could be added to OS.has_feature i.e. OS.has_feature(â3.1â) or OS.has_feature(â>=3.0â) or something like that and assuming that version is just a compile time check of OS.has_feature it could be used like so:
version(â>=3.1â) and version(â<=3.2.1â):
#Code only for Godot Versions 3.1 to 3.2.1
In comparison to #ifdef and #ifndef an important difference between the C preprocessor and Dâs version and static if is that the C preprocessor has to run over all of the code first before compiling happens while D does it all in place during the compilation step AFAIK. Iâm proposing to do this the D way, in a single pass as part of the compilation step with no preprocessor required. If the condition isn't met then just skip the block entirely.
Now that I think about it there would need to be warnings for when code outside of a version block references code inside the block as that code may not be included in the build and the version block likely wouldnât have its own scope.
version (âandroidâ):
var x = 10
print(x) # Error. Variable x undefined on non-android platforms.
After some thinking I realised that this could work by skipping unused branches when building the AST. This would make version more efficient than if even when interpreted and not just when the code is compiled to bytecode as if the feature specified in the version statement isnât supported on that platform then that entire branch of the AST is never generated.
I would prefer a shorter syntax, for example:
$$debug:
print("debug")
$$mobile:
print("mobile")
@dalexeev In GDScript, we tend to favor easy-to-read constructs. Code is read way more often than it is written :slightly_smiling_face:
@Calinou There is already a short syntax for get_node(). The dollar sign is often used in programming languages to denote a variable. Feature tags are also kind of variables: the variables that are used when exporting a build. Why not use two dollar signs?
Also easiness is a relative concept:
#!ifdef debug
func _ready():
# ...
#!endif
In any case, the functionality is primary, not the name.
ADD. @Calinou Which is easier to read: typeof(a) == typeof(b) and a == b OR a == b (#26375)?
At the very least, it would be nice to give up quotes and brackets. And it is advisable to choose a shorter keyword.
version debug:
print("debug")
only debug:
print("debug")
at debug:
print("debug")
Nim uses the when keyword as a compile-time if statement, we could borrow that to use a more generic keyword.
I think that we should focus on clarity rather than verbosity. Personally, I think that version provides the clearest explanation of what is happening in the code (only works too). If a symbol were to be used then it should be shorthand for a keyword and not similar to an existing symbol. I think that $$ is too similar to $ and would make code confusing for people who are unfamiliar with it. I don't think @ is used anywhere, that could be used as a shorthand if there is demand for it.
version Windows:
Windows Stuff
version X11:
Linux and BSD Stuff
version OSX:
Mac Stuff
or
@32:
const MAX_INT = 2147483647
@64:
const MAX_INT = 9223372036854775807
I don't think
@is used anywhere
Actually, it's used to create NodePaths :slightly_smiling_face:
I don't think
@is used anywhereActually, it's used to create NodePaths slightly_smiling_face
You learn something new everyday.
How about ?
?debug:
print("debug")
?release:
print("release")
@matthew1006 We should prefer using fully spelled-out keywords to symbols for language features that aren't used very often.
@Calinou I agree. I was just humouring some of the other suggestions for an alternative shorthand.
$ and @ are precedents for such shorthands in GDScript. That's why I suggested it. But

Maybe I was wrong, but getting rid of the extra brackets and quotes is also right.
Feature and improvement proposals for the Godot Engine are now being discussed and reviewed in a dedicated Godot Improvement Proposals (GIP) (godotengine/godot-proposals) issue tracker. The GIP tracker has a detailed issue template designed so that proposals include all the relevant information to start a productive discussion and help the community assess the validity of the proposal for the engine.
The main (godotengine/godot) tracker is now solely dedicated to bug reports and Pull Requests, enabling contributors to have a better focus on bug fixing work. Therefore, we are now closing all older feature proposals on the main issue tracker.
If you are interested in this feature proposal, please open a new proposal on the GIP tracker following the given issue template (after checking that it doesn't exist already). Be sure to reference this closed issue if it includes any relevant discussion (which you are also encouraged to summarize in the new proposal). Thanks in advance!