This project demonstrates some GDScript "support" for user-defined functions that are evaluated at compile time.
- GDScript supports constants, their initializers must be constant expressions. User-defined functions do not support compile-time evaluations.
- Constant expressions include pass-by-value built-in types1, arrays and dictionaries in a constant context, as well as some objects (resources loaded using
preload()
). Also types are constant expressions: classes (represented by aGDScript
resource or an internal wrapperGDScriptNativeClass
) and enumerations (user-defined named enums represented by a dictionary). Objects are not generally constant expressions. - GDScript performs constant folding during the static analysis. Some expressions are considered compile-time evaluated2, i.e. they can be replaced by a value if all their arguments/elements/operands are also constant expressions. Many operators (
+
,-
,*
, etc.), attribute/index access, pass-by-value built-in type constructors1,@GlobalScope
math functions and some@GDScript
functions are compile-time evaluated.
- Since only objects can have user-defined functions and we need a constant base, we use
preload()
to load a custom resource. We also load a resource rather than a script in order to use non-static class members. - To make the code executable in the editor, we added
@tool
tocompile_time.gd
. This is an optional step if you do not need to execute a code in the editor, see How to use section. - A method call is not considered compile-time evaluated,
const_base.some_method()
is not a constant expression. We could use a property with a getter, but it does not allow us to pass parameters. Therefore, we used index access and the_get()
virtual method. - Since we cannot overload the
[]
operator, and_get()
requires exactly one string argument, we use a string to encode function name and call arguments. For example, the notationsum,1,2
is equivalent tosum(1, 2)
, and the notationsum,"1","2"
is equivalent tosum("1", "2")
. Arguments are written as if they were encoded usingvar_to_str()
.3 - To call a function at compile time, we use
CT["func,args"]
. Before calling the function, the passed arguments are decoded usingstr_to_var()
, after evaluation the result is encoded back to a string usingvar_to_str()
. For example,CT["sum,1,2"]
will return"3"
, not3
. This is done becausevar_to_str()
is not compile-time evaluated. - Since
str_to_var()
is also not compile-time evaluated, there is an exception functionval
that returns the decoded result ofstr_to_var()
. For example,CT["val,1"]
will return1
, andCT['val,"1"']
will return"1"
. - If you made an error in
CT["..."]
, it will be detected at compile time. Note that this may clutter the Output with errors while editing the script.
Of course, in general this use of GDScript is unintended and can only be used as a joke. However, some of this may be useful in practice. For example, you can create a constants.gd
script with export variables and edit their values in the constants.tres
resource using the Inspector. At the same time, you can use these values as constants in other scripts.
# constants.gd
extends Resource
@export var my_value: int
# other.gd
extends Node
const MY_VALUE = preload("./constants.tres").my_value # It's valid!
Footnotes
-
Note that
Callable
andSignal
are not constant expressions if they explicitly or implicitly useself
or another non-constant object as base. ↩ ↩2 -
Typically, constant expressions should be pure, i.e. deterministic and have no side effects. However, in GDScript this is not always true, or at least not always checked. ↩
-
Note that strings can only be enclosed in double quotes, otherwise
str_to_var()
will not parse the expression. ↩