summaryrefslogtreecommitdiff
path: root/modules/gdscript
diff options
context:
space:
mode:
Diffstat (limited to 'modules/gdscript')
-rw-r--r--modules/gdscript/doc_classes/@GDScript.xml96
-rw-r--r--modules/gdscript/doc_classes/GDScript.xml2
-rw-r--r--modules/gdscript/editor/gdscript_highlighter.cpp4
-rw-r--r--modules/gdscript/gdscript.cpp143
-rw-r--r--modules/gdscript/gdscript.h8
-rw-r--r--modules/gdscript/gdscript_analyzer.cpp65
-rw-r--r--modules/gdscript/gdscript_cache.cpp40
-rw-r--r--modules/gdscript/gdscript_cache.h3
-rw-r--r--modules/gdscript/gdscript_compiler.cpp319
-rw-r--r--modules/gdscript/gdscript_compiler.h8
-rw-r--r--modules/gdscript/gdscript_editor.cpp65
-rw-r--r--modules/gdscript/gdscript_parser.cpp23
-rw-r--r--modules/gdscript/gdscript_parser.h4
-rw-r--r--modules/gdscript/gdscript_warning.cpp5
-rw-r--r--modules/gdscript/gdscript_warning.h1
-rw-r--r--modules/gdscript/tests/gdscript_test_runner.cpp1
-rw-r--r--modules/gdscript/tests/scripts/analyzer/features/external_inner_base.gd4
-rw-r--r--modules/gdscript/tests/scripts/analyzer/features/external_inner_base.out3
-rw-r--r--modules/gdscript/tests/scripts/analyzer/features/inner_base.gd18
-rw-r--r--modules/gdscript/tests/scripts/analyzer/features/inner_base.out4
-rw-r--r--modules/gdscript/tests/scripts/parser/warnings/static_called_on_instance.gd11
-rw-r--r--modules/gdscript/tests/scripts/parser/warnings/static_called_on_instance.out7
-rw-r--r--modules/gdscript/tests/scripts/runtime/features/compare-builtin-equals-null.gd4
-rw-r--r--modules/gdscript/tests/scripts/runtime/features/compare-builtin-equals-null.out1
-rw-r--r--modules/gdscript/tests/scripts/runtime/features/compare-builtin-not-equals-null.gd4
-rw-r--r--modules/gdscript/tests/scripts/runtime/features/compare-builtin-not-equals-null.out1
-rw-r--r--modules/gdscript/tests/scripts/runtime/features/range_optimized_in_for_has_int_iterator.gd60
-rw-r--r--modules/gdscript/tests/scripts/runtime/features/range_optimized_in_for_has_int_iterator.out1
-rw-r--r--modules/gdscript/tests/scripts/runtime/features/range_returns_ints.gd77
-rw-r--r--modules/gdscript/tests/scripts/runtime/features/range_returns_ints.out1
30 files changed, 622 insertions, 361 deletions
diff --git a/modules/gdscript/doc_classes/@GDScript.xml b/modules/gdscript/doc_classes/@GDScript.xml
index bc44479f93..c8eda53a2d 100644
--- a/modules/gdscript/doc_classes/@GDScript.xml
+++ b/modules/gdscript/doc_classes/@GDScript.xml
@@ -18,13 +18,11 @@
<param index="2" name="b8" type="int" />
<param index="3" name="a8" type="int" default="255" />
<description>
- Returns a color constructed from integer red, green, blue, and alpha channels. Each channel should have 8 bits of information ranging from 0 to 255.
- [code]r8[/code] red channel
- [code]g8[/code] green channel
- [code]b8[/code] blue channel
- [code]a8[/code] alpha channel
+ Returns a [Color] constructed from red ([param r8]), green ([param g8]), blue ([param b8]), and optionally alpha ([param a8]) integer channels, each divided by [code]255.0[/code] for their final value.
[codeblock]
- red = Color8(255, 0, 0)
+ var red = Color8(255, 0, 0) # Same as Color(1, 0, 0)
+ var dark_blue = Color8(0, 0, 51) # Same as Color(0, 0, 0.2).
+ var my_color = Color8(306, 255, 0, 102) # Same as Color(1.2, 1, 0, 0.4).
[/codeblock]
</description>
</method>
@@ -33,9 +31,9 @@
<param index="0" name="condition" type="bool" />
<param index="1" name="message" type="String" default="&quot;&quot;" />
<description>
- Asserts that the [code]condition[/code] is [code]true[/code]. If the [code]condition[/code] is [code]false[/code], an error is generated. When running from the editor, the running project will also be paused until you resume it. This can be used as a stronger form of [method @GlobalScope.push_error] for reporting errors to project developers or add-on users.
- [b]Note:[/b] For performance reasons, the code inside [method assert] is only executed in debug builds or when running the project from the editor. Don't include code that has side effects in an [method assert] call. Otherwise, the project will behave differently when exported in release mode.
- The optional [code]message[/code] argument, if given, is shown in addition to the generic "Assertion failed" message. It must be a static string, so format strings can't be used. You can use this to provide additional details about why the assertion failed.
+ Asserts that the [param condition] is [code]true[/code]. If the [param condition] is [code]false[/code], an error is generated. When running from the editor, the running project will also be paused until you resume it. This can be used as a stronger form of [method @GlobalScope.push_error] for reporting errors to project developers or add-on users.
+ An optional [param message] can be shown in addition to the generic "Assertion failed" message. You can use this to provide additional details about why the assertion failed.
+ [b]Warning:[/b] For performance reasons, the code inside [method assert] is only executed in debug builds or when running the project from the editor. Don't include code that has side effects in an [method assert] call. Otherwise, the project will behave differently when exported in release mode.
[codeblock]
# Imagine we always want speed to be between 0 and 20.
var speed = -10
@@ -50,7 +48,7 @@
<return type="String" />
<param index="0" name="char" type="int" />
<description>
- Returns a character as a String of the given Unicode code point (which is compatible with ASCII code).
+ Returns a single character (as a [String]) of the given Unicode code point (which is compatible with ASCII code).
[codeblock]
a = char(65) # a is "A"
a = char(65 + 32) # a is "a"
@@ -63,14 +61,14 @@
<param index="0" name="what" type="Variant" />
<param index="1" name="type" type="int" />
<description>
- Converts from a type to another in the best way possible. The [code]type[/code] parameter uses the [enum Variant.Type] values.
+ Converts [param what] to [param type] in the best way possible. The [param type] uses the [enum Variant.Type] values.
[codeblock]
- a = Vector2(1, 0)
- # Prints 1
- print(a.length())
- a = convert(a, TYPE_STRING)
- # Prints 6 as "(1, 0)" is 6 characters
- print(a.length())
+ var a = [4, 2.5, 1.2]
+ print(a is Array) # Prints true
+
+ var b = convert(a, TYPE_PACKED_BYTE_ARRAY)
+ print(b) # Prints [4, 2, 1]
+ print(b is Array) # Prints false
[/codeblock]
</description>
</method>
@@ -78,7 +76,7 @@
<return type="Object" />
<param index="0" name="dictionary" type="Dictionary" />
<description>
- Converts a [param dictionary] (previously created with [method inst_to_dict]) back to an Object instance. Useful for deserializing.
+ Converts a [param dictionary] (created with [method inst_to_dict]) back to an Object instance. Can be useful for deserializing.
</description>
</method>
<method name="get_stack">
@@ -95,19 +93,19 @@
func bar():
print(get_stack())
[/codeblock]
- would print
+ Starting from [code]_ready()[/code], [code]bar()[/code] would print:
[codeblock]
[{function:bar, line:12, source:res://script.gd}, {function:foo, line:9, source:res://script.gd}, {function:_ready, line:6, source:res://script.gd}]
[/codeblock]
- [b]Note:[/b] [method get_stack] only works if the running instance is connected to a debugging server (i.e. an editor instance). [method get_stack] will not work in projects exported in release mode, or in projects exported in debug mode if not connected to a debugging server.
- [b]Note:[/b] Not supported for calling from threads. Instead, this will return an empty array.
+ [b]Note:[/b] This function only works if the running instance is connected to a debugging server (i.e. an editor instance). [method get_stack] will not work in projects exported in release mode, or in projects exported in debug mode if not connected to a debugging server.
+ [b]Note:[/b] Calling this function from a [Thread] is not supported. Doing so will return an empty array.
</description>
</method>
<method name="inst_to_dict">
<return type="Dictionary" />
<param index="0" name="instance" type="Object" />
<description>
- Returns the passed [param instance] converted to a Dictionary (useful for serializing).
+ Returns the passed [param instance] converted to a Dictionary. Can be useful for serializing.
[codeblock]
var foo = "bar"
func _ready():
@@ -126,11 +124,13 @@
<return type="int" />
<param index="0" name="var" type="Variant" />
<description>
- Returns length of Variant [code]var[/code]. Length is the character count of String, element count of Array, size of Dictionary, etc.
- [b]Note:[/b] Generates a fatal error if Variant can not provide a length.
+ Returns the length of the given Variant [param var]. The length can be the character count of a [String], the element count of any array type or the size of a [Dictionary]. For every other Variant type, a run-time error is generated and execution is stopped.
[codeblock]
a = [1, 2, 3, 4]
len(a) # Returns 4
+
+ b = "Hello!"
+ len(b) # Returns 6
[/codeblock]
</description>
</method>
@@ -138,25 +138,25 @@
<return type="Resource" />
<param index="0" name="path" type="String" />
<description>
- Loads a resource from the filesystem located at [code]path[/code]. The resource is loaded on the method call (unless it's referenced already elsewhere, e.g. in another script or in the scene), which might cause slight delay, especially when loading scenes. To avoid unnecessary delays when loading something multiple times, either store the resource in a variable or use [method preload].
+ Returns a [Resource] from the filesystem located at the absolute [param path]. Unless it's already referenced elsewhere (such as in another script or in the scene), the resource is loaded from disk on function call, which might cause a slight delay, especially when loading large scenes. To avoid unnecessary delays when loading something multiple times, either store the resource in a variable or use [method preload].
[b]Note:[/b] Resource paths can be obtained by right-clicking on a resource in the FileSystem dock and choosing "Copy Path" or by dragging the file from the FileSystem dock into the script.
[codeblock]
- # Load a scene called main located in the root of the project directory and cache it in a variable.
+ # Load a scene called "main" located in the root of the project directory and cache it in a variable.
var main = load("res://main.tscn") # main will contain a PackedScene resource.
[/codeblock]
- [b]Important:[/b] The path must be absolute, a local path will just return [code]null[/code].
- This method is a simplified version of [method ResourceLoader.load], which can be used for more advanced scenarios.
- [b]Note:[/b] You have to import the files into the engine first to load them using [method load]. If you want to load [Image]s at run-time, you may use [method Image.load]. If you want to import audio files, you can use the snippet described in [member AudioStreamMP3.data].
+ [b]Important:[/b] The path must be absolute. A relative path will always return [code]null[/code].
+ This function is a simplified version of [method ResourceLoader.load], which can be used for more advanced scenarios.
+ [b]Note:[/b] Files have to be imported into the engine first to load them using this function. If you want to load [Image]s at run-time, you may use [method Image.load]. If you want to import audio files, you can use the snippet described in [member AudioStreamMP3.data].
</description>
</method>
<method name="preload">
<return type="Resource" />
<param index="0" name="path" type="String" />
<description>
- Returns a [Resource] from the filesystem located at [code]path[/code]. The resource is loaded during script parsing, i.e. is loaded with the script and [method preload] effectively acts as a reference to that resource. Note that the method requires a constant path. If you want to load a resource from a dynamic/variable path, use [method load].
+ Returns a [Resource] from the filesystem located at [param path]. During run-time, the resource is loaded when the script is being parsed. This function effectively acts as a reference to that resource. Note that this function requires [param path] to be a constant [String]. If you want to load a resource from a dynamic/variable path, use [method load].
[b]Note:[/b] Resource paths can be obtained by right clicking on a resource in the Assets Panel and choosing "Copy Path" or by dragging the file from the FileSystem dock into the script.
[codeblock]
- # Instance a scene.
+ # Create instance of a scene.
var diamond = preload("res://diamond.tscn").instantiate()
[/codeblock]
</description>
@@ -165,24 +165,24 @@
<return type="void" />
<description>
Like [method @GlobalScope.print], but includes the current stack frame when running with the debugger turned on.
- Output in the console would look something like this:
+ The output in the console may look like the following:
[codeblock]
Test print
- At: res://test.gd:15:_process()
+ At: res://test.gd:15:_process()
[/codeblock]
- [b]Note:[/b] Not supported for calling from threads. Instead of the stack frame, this will print the thread ID.
+ [b]Note:[/b] Calling this function from a [Thread] is not supported. Doing so will instead print the thread ID.
</description>
</method>
<method name="print_stack">
<return type="void" />
<description>
Prints a stack trace at the current code location. See also [method get_stack].
- Output in the console would look something like this:
+ The output in the console may look like the following:
[codeblock]
Frame 0 - res://test.gd:16 in function '_process'
[/codeblock]
- [b]Note:[/b] [method print_stack] only works if the running instance is connected to a debugging server (i.e. an editor instance). [method print_stack] will not work in projects exported in release mode, or in projects exported in debug mode if not connected to a debugging server.
- [b]Note:[/b] Not supported for calling from threads. Instead of the stack trace, this will print the thread ID.
+ [b]Note:[/b] This function only works if the running instance is connected to a debugging server (i.e. an editor instance). [method print_stack] will not work in projects exported in release mode, or in projects exported in debug mode if not connected to a debugging server.
+ [b]Note:[/b] Calling this function from a [Thread] is not supported. Doing so will instead print the thread ID.
</description>
</method>
<method name="range" qualifiers="vararg">
@@ -229,7 +229,7 @@
<method name="str" qualifiers="vararg">
<return type="String" />
<description>
- Converts one or more arguments to string in the best way possible.
+ Converts one or more arguments to a [String] in the best way possible.
[codeblock]
var a = [10, 20, 30]
var b = str(a);
@@ -242,7 +242,7 @@
<return type="bool" />
<param index="0" name="type" type="StringName" />
<description>
- Returns whether the given [Object]-derived class exists in [ClassDB]. Note that [Variant] data types are not registered in [ClassDB].
+ Returns [code]true[/code] if the given [Object]-derived class exists in [ClassDB]. Note that [Variant] data types are not registered in [ClassDB].
[codeblock]
type_exists("Sprite2D") # Returns true
type_exists("NonExistentClass") # Returns false
@@ -259,11 +259,11 @@
</constant>
<constant name="INF" value="inf">
Positive floating-point infinity. This is the result of floating-point division when the divisor is [code]0.0[/code]. For negative infinity, use [code]-INF[/code]. Dividing by [code]-0.0[/code] will result in negative infinity if the numerator is positive, so dividing by [code]0.0[/code] is not the same as dividing by [code]-0.0[/code] (despite [code]0.0 == -0.0[/code] returning [code]true[/code]).
- [b]Note:[/b] Numeric infinity is only a concept with floating-point numbers, and has no equivalent for integers. Dividing an integer number by [code]0[/code] will not result in [constant INF] and will result in a run-time error instead.
+ [b]Warning:[/b] Numeric infinity is only a concept with floating-point numbers, and has no equivalent for integers. Dividing an integer number by [code]0[/code] will not result in [constant INF] and will result in a run-time error instead.
</constant>
<constant name="NAN" value="nan">
"Not a Number", an invalid floating-point value. [constant NAN] has special properties, including that it is not equal to itself ([code]NAN == NAN[/code] returns [code]false[/code]). It is output by some invalid operations, such as dividing floating-point [code]0.0[/code] by [code]0.0[/code].
- [b]Note:[/b] "Not a Number" is only a concept with floating-point numbers, and has no equivalent for integers. Dividing an integer [code]0[/code] by [code]0[/code] will not result in [constant NAN] and will result in a run-time error instead.
+ [b]Warning:[/b] "Not a Number" is only a concept with floating-point numbers, and has no equivalent for integers. Dividing an integer [code]0[/code] by [code]0[/code] will not result in [constant NAN] and will result in a run-time error instead.
</constant>
</constants>
<annotations>
@@ -294,7 +294,7 @@
<annotation name="@export_color_no_alpha">
<return type="void" />
<description>
- Export a [Color] property without an alpha (fixed as [code]1.0[/code]).
+ Export a [Color] property without transparency (its alpha fixed as [code]1.0[/code]).
See also [constant PROPERTY_HINT_COLOR_NO_ALPHA].
[codeblock]
@export_color_no_alpha var modulate_color: Color
@@ -320,7 +320,7 @@
[codeblock]
@export_enum("Rebecca", "Mary", "Leah") var character_name: String
@export_enum("Warrior", "Magician", "Thief") var character_class: int
- @export_enum("Walking:30", "Running:60", "Riding:200") var character_speed: int
+ @export_enum("Slow:30", "Average:60", "Very Fast:200") var character_speed: int
[/codeblock]
</description>
</annotation>
@@ -451,7 +451,7 @@
<description>
Define a new group for the following exported properties. This helps to organize properties in the Inspector dock. Groups can be added with an optional [param prefix], which would make group to only consider properties that have this prefix. The grouping will break on the first property that doesn't have a prefix. The prefix is also removed from the property's name in the Inspector dock.
If no [param prefix] is provided, the every following property is added to the group. The group ends when then next group or category is defined. You can also force end a group by using this annotation with empty strings for parameters, [code]@export_group("", "")[/code].
- Groups cannot be nested, use [annotation @export_subgroup] to add subgroups to your groups.
+ Groups cannot be nested, use [annotation @export_subgroup] to add subgroups within groups.
See also [constant PROPERTY_USAGE_GROUP].
[codeblock]
@export_group("My Properties")
@@ -473,7 +473,7 @@
Export a [String] property with a large [TextEdit] widget instead of a [LineEdit]. This adds support for multiline content and makes it easier to edit large amount of text stored in the property.
See also [constant PROPERTY_HINT_MULTILINE_TEXT].
[codeblock]
- @export_multiline var character_bio
+ @export_multiline var character_biography
[/codeblock]
</description>
</annotation>
@@ -547,11 +547,11 @@
<return type="void" />
<param index="0" name="icon_path" type="String" />
<description>
- Add a custom icon to the current script. The icon is displayed in the Scene dock for every node that the script is attached to. For named classes the icon is also displayed in various editor dialogs.
+ Add a custom icon to the current script. After loading an icon at [param icon_path], the icon is displayed in the Scene dock for every node that the script is attached to. For named classes, the icon is also displayed in various editor dialogs.
[codeblock]
@icon("res://path/to/class/icon.svg")
[/codeblock]
- [b]Note:[/b] Only the script can have a custom icon. Inner classes are not supported yet.
+ [b]Note:[/b] Only the script can have a custom icon. Inner classes are not supported.
</description>
</annotation>
<annotation name="@onready">
@@ -590,7 +590,7 @@
<return type="void" />
<param index="0" name="warning" type="String" />
<description>
- Mark the following statement to ignore the specified warning. See [url=$DOCS_URL/tutorials/scripting/gdscript/warning_system.html]GDScript warning system[/url].
+ Mark the following statement to ignore the specified [param warning]. See [url=$DOCS_URL/tutorials/scripting/gdscript/warning_system.html]GDScript warning system[/url].
[codeblock]
func test():
print("hello")
diff --git a/modules/gdscript/doc_classes/GDScript.xml b/modules/gdscript/doc_classes/GDScript.xml
index 578e7a34f3..8246c96c15 100644
--- a/modules/gdscript/doc_classes/GDScript.xml
+++ b/modules/gdscript/doc_classes/GDScript.xml
@@ -4,7 +4,7 @@
A script implemented in the GDScript programming language.
</brief_description>
<description>
- A script implemented in the GDScript programming language. The script extends the functionality of all objects that instance it.
+ A script implemented in the GDScript programming language. The script extends the functionality of all objects that instantiate it.
[method new] creates a new instance of the script. [method Object.set_script] extends an existing object, if that object's class matches one of the script's base classes.
</description>
<tutorials>
diff --git a/modules/gdscript/editor/gdscript_highlighter.cpp b/modules/gdscript/editor/gdscript_highlighter.cpp
index 996d323a7f..0ebdbf090d 100644
--- a/modules/gdscript/editor/gdscript_highlighter.cpp
+++ b/modules/gdscript/editor/gdscript_highlighter.cpp
@@ -322,7 +322,7 @@ Dictionary GDScriptSyntaxHighlighter::_get_line_syntax_highlighting_impl(int p_l
}
String word = str.substr(j, to - j);
- Color col = Color();
+ Color col;
if (global_functions.has(word)) {
// "assert" and "preload" are reserved, so highlight even if not followed by a bracket.
if (word == GDScriptTokenizer::get_token_name(GDScriptTokenizer::Token::ASSERT) || word == GDScriptTokenizer::get_token_name(GDScriptTokenizer::Token::PRELOAD)) {
@@ -683,7 +683,7 @@ void GDScriptSyntaxHighlighter::_update_cache() {
}
}
- const String text_edit_color_theme = EditorSettings::get_singleton()->get("text_editor/theme/color_theme");
+ const String text_edit_color_theme = EDITOR_GET("text_editor/theme/color_theme");
const bool godot_2_theme = text_edit_color_theme == "Godot 2";
if (godot_2_theme || EditorSettings::get_singleton()->is_dark_theme()) {
diff --git a/modules/gdscript/gdscript.cpp b/modules/gdscript/gdscript.cpp
index 0a9dad04c7..bd6cef0b6e 100644
--- a/modules/gdscript/gdscript.cpp
+++ b/modules/gdscript/gdscript.cpp
@@ -629,10 +629,6 @@ void GDScript::_update_doc() {
}
}
- for (KeyValue<StringName, Ref<GDScript>> &E : subclasses) {
- E.value->_update_doc();
- }
-
_add_doc(doc);
}
#endif
@@ -674,36 +670,14 @@ bool GDScript::_update_exports(bool *r_err, bool p_recursive_call, PlaceHolderSc
base_cache = Ref<GDScript>();
}
- if (c->extends_used) {
- String ext_path = "";
- if (String(c->extends_path) != "" && String(c->extends_path) != get_path()) {
- ext_path = c->extends_path;
- if (ext_path.is_relative_path()) {
- String base_path = get_path();
- if (base_path.is_empty() || base_path.is_relative_path()) {
- ERR_PRINT(("Could not resolve relative path for parent class: " + ext_path).utf8().get_data());
- } else {
- ext_path = base_path.get_base_dir().path_join(ext_path);
- }
- }
- } else if (c->extends.size() != 0) {
- const StringName &base_class = c->extends[0];
-
- if (ScriptServer::is_global_class(base_class)) {
- ext_path = ScriptServer::get_global_class_path(base_class);
- }
- }
-
- if (!ext_path.is_empty()) {
- if (ext_path != get_path()) {
- Ref<GDScript> bf = ResourceLoader::load(ext_path);
-
- if (bf.is_valid()) {
- base_cache = bf;
- bf->inheriters_cache.insert(get_instance_id());
- }
- } else {
- ERR_PRINT(("Path extending itself in " + ext_path).utf8().get_data());
+ GDScriptParser::DataType base_type = parser.get_tree()->base_type;
+ if (base_type.kind == GDScriptParser::DataType::CLASS) {
+ Ref<GDScript> bf = GDScriptCache::get_full_script(base_type.script_path, err, path);
+ if (err == OK) {
+ bf = Ref<GDScript>(bf->find_class(base_type.class_type->fqcn));
+ if (bf.is_valid()) {
+ base_cache = bf;
+ bf->inheriters_cache.insert(get_instance_id());
}
}
}
@@ -825,13 +799,6 @@ void GDScript::update_exports() {
#endif
}
-void GDScript::_set_subclass_path(Ref<GDScript> &p_sc, const String &p_path) {
- p_sc->path = p_path;
- for (KeyValue<StringName, Ref<GDScript>> &E : p_sc->subclasses) {
- _set_subclass_path(E.value, p_path);
- }
-}
-
String GDScript::_get_debug_path() const {
if (is_built_in() && !get_name().is_empty()) {
return get_name() + " (" + get_path() + ")";
@@ -860,7 +827,7 @@ Error GDScript::reload(bool p_keep_state) {
basedir = basedir.get_base_dir();
}
-// Loading a template, don't parse.
+ // Loading a template, don't parse.
#ifdef TOOLS_ENABLED
if (EditorPaths::get_singleton() && basedir.begins_with(EditorPaths::get_singleton()->get_project_script_templates_dir())) {
return OK;
@@ -913,10 +880,6 @@ Error GDScript::reload(bool p_keep_state) {
GDScriptCompiler compiler;
err = compiler.compile(&parser, this, p_keep_state);
-#ifdef TOOLS_ENABLED
- _update_doc();
-#endif
-
if (err) {
if (can_run) {
if (EngineDebugger::is_active()) {
@@ -937,14 +900,6 @@ Error GDScript::reload(bool p_keep_state) {
}
#endif
- valid = true;
-
- for (KeyValue<StringName, Ref<GDScript>> &E : subclasses) {
- _set_subclass_path(E.value, path);
- }
-
- _init_rpc_methods_properties();
-
return OK;
}
@@ -1050,6 +1005,16 @@ Error GDScript::load_byte_code(const String &p_path) {
return ERR_COMPILATION_FAILED;
}
+void GDScript::set_path(const String &p_path, bool p_take_over) {
+ if (is_root_script()) {
+ Script::set_path(p_path, p_take_over);
+ }
+ this->path = p_path;
+ for (KeyValue<StringName, Ref<GDScript>> &kv : subclasses) {
+ kv.value->set_path(p_path, p_take_over);
+ }
+}
+
Error GDScript::load_source_code(const String &p_path) {
Vector<uint8_t> sourcef;
Error err;
@@ -1122,6 +1087,52 @@ bool GDScript::inherits_script(const Ref<Script> &p_script) const {
return false;
}
+GDScript *GDScript::find_class(const String &p_qualified_name) {
+ String first = p_qualified_name.get_slice("::", 0);
+
+ GDScript *result = nullptr;
+ if (first.is_empty() || first == name) {
+ result = this;
+ } else if (first == get_root_script()->path) {
+ result = get_root_script();
+ } else if (HashMap<StringName, Ref<GDScript>>::Iterator E = subclasses.find(first)) {
+ result = E->value.ptr();
+ } else if (_owner != nullptr) {
+ // Check parent scope.
+ return _owner->find_class(p_qualified_name);
+ }
+
+ int name_count = p_qualified_name.get_slice_count("::");
+ for (int i = 1; result != nullptr && i < name_count; i++) {
+ String current_name = p_qualified_name.get_slice("::", i);
+ if (HashMap<StringName, Ref<GDScript>>::Iterator E = result->subclasses.find(current_name)) {
+ result = E->value.ptr();
+ } else {
+ // Couldn't find inner class.
+ return nullptr;
+ }
+ }
+
+ return result;
+}
+
+bool GDScript::is_subclass(const GDScript *p_script) {
+ String fqn = p_script->fully_qualified_name;
+ if (!fqn.is_empty() && fqn != fully_qualified_name && fqn.begins_with(fully_qualified_name)) {
+ String fqn_rest = fqn.substr(fully_qualified_name.length());
+ return find_class(fqn_rest) == p_script;
+ }
+ return false;
+}
+
+GDScript *GDScript::get_root_script() {
+ GDScript *result = this;
+ while (result->_owner) {
+ result = result->_owner;
+ }
+ return result;
+}
+
bool GDScript::has_script_signal(const StringName &p_signal) const {
if (_signals.has(p_signal)) {
return true;
@@ -1233,25 +1244,11 @@ void GDScript::_init_rpc_methods_properties() {
rpc_config = base->rpc_config.duplicate();
}
- GDScript *cscript = this;
- HashMap<StringName, Ref<GDScript>>::Iterator sub_E = subclasses.begin();
- while (cscript) {
- // RPC Methods
- for (KeyValue<StringName, GDScriptFunction *> &E : cscript->member_functions) {
- Variant config = E.value->get_rpc_config();
- if (config.get_type() != Variant::NIL) {
- rpc_config[E.value->get_name()] = config;
- }
- }
-
- if (cscript != this) {
- ++sub_E;
- }
-
- if (sub_E) {
- cscript = sub_E->value.ptr();
- } else {
- cscript = nullptr;
+ // RPC Methods
+ for (KeyValue<StringName, GDScriptFunction *> &E : member_functions) {
+ Variant config = E.value->get_rpc_config();
+ if (config.get_type() != Variant::NIL) {
+ rpc_config[E.value->get_name()] = config;
}
}
}
diff --git a/modules/gdscript/gdscript.h b/modules/gdscript/gdscript.h
index 0a010e5ad0..61600b1258 100644
--- a/modules/gdscript/gdscript.h
+++ b/modules/gdscript/gdscript.h
@@ -137,7 +137,6 @@ class GDScript : public Script {
void _super_implicit_constructor(GDScript *p_script, GDScriptInstance *p_instance, Callable::CallError &r_error);
GDScriptInstance *_create_instance(const Variant **p_args, int p_argcount, Object *p_owner, bool p_is_ref_counted, Callable::CallError &r_error);
- void _set_subclass_path(Ref<GDScript> &p_sc, const String &p_path);
String _get_debug_path() const;
#ifdef TOOLS_ENABLED
@@ -178,6 +177,11 @@ public:
bool inherits_script(const Ref<Script> &p_script) const override;
+ GDScript *find_class(const String &p_qualified_name);
+ bool is_subclass(const GDScript *p_script);
+ GDScript *get_root_script();
+ bool is_root_script() const { return _owner == nullptr; }
+ String get_fully_qualified_name() const { return fully_qualified_name; }
const HashMap<StringName, Ref<GDScript>> &get_subclasses() const { return subclasses; }
const HashMap<StringName, Variant> &get_constants() const { return constants; }
const HashSet<StringName> &get_members() const { return members; }
@@ -222,7 +226,7 @@ public:
virtual Error reload(bool p_keep_state = false) override;
- void set_script_path(const String &p_path) { path = p_path; } //because subclasses need a path too...
+ virtual void set_path(const String &p_path, bool p_take_over = false) override;
Error load_source_code(const String &p_path);
Error load_byte_code(const String &p_path);
diff --git a/modules/gdscript/gdscript_analyzer.cpp b/modules/gdscript/gdscript_analyzer.cpp
index b3ca8ff00a..8da77b9e5b 100644
--- a/modules/gdscript/gdscript_analyzer.cpp
+++ b/modules/gdscript/gdscript_analyzer.cpp
@@ -213,16 +213,6 @@ Error GDScriptAnalyzer::resolve_inheritance(GDScriptParser::ClassNode *p_class,
return OK;
}
- if (p_class == parser->head) {
- if (p_class->identifier) {
- p_class->fqcn = p_class->identifier->name;
- } else {
- p_class->fqcn = parser->script_path;
- }
- } else {
- p_class->fqcn = p_class->outer->fqcn + "::" + String(p_class->identifier->name);
- }
-
if (p_class->identifier) {
StringName class_name = p_class->identifier->name;
if (class_exists(class_name)) {
@@ -985,21 +975,26 @@ void GDScriptAnalyzer::resolve_class_body(GDScriptParser::ClassNode *p_class) {
if (getter_function == nullptr) {
push_error(vformat(R"(Getter "%s" not found.)", member.variable->getter_pointer->name), member.variable);
-
- } else if (getter_function->parameters.size() != 0 || getter_function->datatype.has_no_type()) {
- push_error(vformat(R"(Function "%s" cannot be used as getter because of its signature.)", getter_function->identifier->name), member.variable);
-
- } else if (!is_type_compatible(member.variable->datatype, getter_function->datatype, true)) {
- push_error(vformat(R"(Function with return type "%s" cannot be used as getter for a property of type "%s".)", getter_function->datatype.to_string(), member.variable->datatype.to_string()), member.variable);
-
} else {
- has_valid_getter = true;
+ GDScriptParser::DataType return_datatype = getter_function->datatype;
+ if (getter_function->return_type != nullptr) {
+ return_datatype = getter_function->return_type->datatype;
+ return_datatype.is_meta_type = false;
+ }
+
+ if (getter_function->parameters.size() != 0 || return_datatype.has_no_type()) {
+ push_error(vformat(R"(Function "%s" cannot be used as getter because of its signature.)", getter_function->identifier->name), member.variable);
+ } else if (!is_type_compatible(member.variable->datatype, return_datatype, true)) {
+ push_error(vformat(R"(Function with return type "%s" cannot be used as getter for a property of type "%s".)", return_datatype.to_string(), member.variable->datatype.to_string()), member.variable);
+ } else {
+ has_valid_getter = true;
#ifdef DEBUG_ENABLED
- if (member.variable->datatype.builtin_type == Variant::INT && getter_function->datatype.builtin_type == Variant::FLOAT) {
- parser->push_warning(member.variable, GDScriptWarning::NARROWING_CONVERSION);
- }
+ if (member.variable->datatype.builtin_type == Variant::INT && return_datatype.builtin_type == Variant::FLOAT) {
+ parser->push_warning(member.variable, GDScriptWarning::NARROWING_CONVERSION);
+ }
#endif
+ }
}
}
@@ -1377,7 +1372,7 @@ void GDScriptAnalyzer::resolve_for(GDScriptParser::ForNode *p_for) {
if (all_is_constant) {
switch (args.size()) {
case 1:
- reduced = args[0];
+ reduced = (int32_t)args[0];
break;
case 2:
reduced = Vector2i(args[0], args[1]);
@@ -1602,8 +1597,8 @@ void GDScriptAnalyzer::resolve_assert(GDScriptParser::AssertNode *p_assert) {
reduce_expression(p_assert->condition);
if (p_assert->message != nullptr) {
reduce_expression(p_assert->message);
- if (!p_assert->message->is_constant || p_assert->message->reduced_value.get_type() != Variant::STRING) {
- push_error(R"(Expected constant string for assert error message.)", p_assert->message);
+ if (!p_assert->message->get_datatype().has_no_type() && (p_assert->message->get_datatype().kind != GDScriptParser::DataType::BUILTIN || p_assert->message->get_datatype().builtin_type != Variant::STRING)) {
+ push_error(R"(Expected string for assert error message.)", p_assert->message);
}
}
@@ -2425,9 +2420,15 @@ void GDScriptAnalyzer::reduce_call(GDScriptParser::CallNode *p_call, bool p_is_a
switch (err.error) {
case Callable::CallError::CALL_ERROR_INVALID_ARGUMENT: {
- PropertyInfo wrong_arg = function_info.arguments[err.argument];
+ String expected_type_name;
+ if (err.argument < function_info.arguments.size()) {
+ expected_type_name = type_from_property(function_info.arguments[err.argument]).to_string();
+ } else {
+ expected_type_name = Variant::get_type_name((Variant::Type)err.expected);
+ }
+
push_error(vformat(R"*(Invalid argument for "%s()" function: argument %d should be %s but is %s.)*", function_name, err.argument + 1,
- type_from_property(wrong_arg).to_string(), p_call->arguments[err.argument]->get_datatype().to_string()),
+ expected_type_name, p_call->arguments[err.argument]->get_datatype().to_string()),
p_call->arguments[err.argument]);
} break;
case Callable::CallError::CALL_ERROR_INVALID_METHOD:
@@ -2552,6 +2553,10 @@ void GDScriptAnalyzer::reduce_call(GDScriptParser::CallNode *p_call, bool p_is_a
if (p_is_root && return_type.kind != GDScriptParser::DataType::UNRESOLVED && return_type.builtin_type != Variant::NIL) {
parser->push_warning(p_call, GDScriptWarning::RETURN_VALUE_DISCARDED, p_call->function_name);
}
+
+ if (is_static && !base_type.is_meta_type && !(callee_type != GDScriptParser::Node::SUBSCRIPT && parser->current_function != nullptr && parser->current_function->is_static)) {
+ parser->push_warning(p_call, GDScriptWarning::STATIC_CALLED_ON_INSTANCE, p_call->function_name, base_type.to_string());
+ }
#endif // DEBUG_ENABLED
call_type = return_type;
@@ -3222,7 +3227,13 @@ void GDScriptAnalyzer::reduce_preload(GDScriptParser::PreloadNode *p_preload) {
}
p_preload->resolved_path = p_preload->resolved_path.simplify_path();
if (!ResourceLoader::exists(p_preload->resolved_path)) {
- push_error(vformat(R"(Preload file "%s" does not exist.)", p_preload->resolved_path), p_preload->path);
+ Ref<FileAccess> file_check = FileAccess::create(FileAccess::ACCESS_RESOURCES);
+
+ if (file_check->file_exists(p_preload->resolved_path)) {
+ push_error(vformat(R"(Preload file "%s" has no resource loaders (unrecognized file extension).)", p_preload->resolved_path), p_preload->path);
+ } else {
+ push_error(vformat(R"(Preload file "%s" does not exist.)", p_preload->resolved_path), p_preload->path);
+ }
} else {
// TODO: Don't load if validating: use completion cache.
p_preload->resource = ResourceLoader::load(p_preload->resolved_path);
diff --git a/modules/gdscript/gdscript_cache.cpp b/modules/gdscript/gdscript_cache.cpp
index 271296c2f9..03a101b9fc 100644
--- a/modules/gdscript/gdscript_cache.cpp
+++ b/modules/gdscript/gdscript_cache.cpp
@@ -34,6 +34,7 @@
#include "core/templates/vector.h"
#include "gdscript.h"
#include "gdscript_analyzer.h"
+#include "gdscript_compiler.h"
#include "gdscript_parser.h"
bool GDScriptParserRef::is_valid() const {
@@ -161,7 +162,7 @@ String GDScriptCache::get_source_code(const String &p_path) {
return source;
}
-Ref<GDScript> GDScriptCache::get_shallow_script(const String &p_path, const String &p_owner) {
+Ref<GDScript> GDScriptCache::get_shallow_script(const String &p_path, Error &r_error, const String &p_owner) {
MutexLock lock(singleton->lock);
if (!p_owner.is_empty()) {
singleton->dependencies[p_owner].insert(p_path);
@@ -173,11 +174,16 @@ Ref<GDScript> GDScriptCache::get_shallow_script(const String &p_path, const Stri
return singleton->shallow_gdscript_cache[p_path];
}
+ Ref<GDScriptParserRef> parser_ref = get_parser(p_path, GDScriptParserRef::PARSED, r_error);
+ if (r_error != OK) {
+ return Ref<GDScript>();
+ }
+
Ref<GDScript> script;
script.instantiate();
script->set_path(p_path, true);
- script->set_script_path(p_path);
script->load_source_code(p_path);
+ GDScriptCompiler::make_scripts(script.ptr(), parser_ref->get_parser()->get_tree(), true);
singleton->shallow_gdscript_cache[p_path] = script.ptr();
return script;
@@ -200,17 +206,21 @@ Ref<GDScript> GDScriptCache::get_full_script(const String &p_path, Error &r_erro
}
if (script.is_null()) {
- script = get_shallow_script(p_path);
- ERR_FAIL_COND_V(script.is_null(), Ref<GDScript>());
+ script = get_shallow_script(p_path, r_error);
+ if (r_error) {
+ return script;
+ }
}
- r_error = script->load_source_code(p_path);
+ if (p_update_from_disk) {
+ r_error = script->load_source_code(p_path);
+ }
if (r_error) {
return script;
}
- r_error = script->reload();
+ r_error = script->reload(true);
if (r_error) {
return script;
}
@@ -221,9 +231,25 @@ Ref<GDScript> GDScriptCache::get_full_script(const String &p_path, Error &r_erro
return script;
}
+Ref<GDScript> GDScriptCache::get_cached_script(const String &p_path) {
+ MutexLock lock(singleton->lock);
+
+ if (singleton->full_gdscript_cache.has(p_path)) {
+ return singleton->full_gdscript_cache[p_path];
+ }
+
+ if (singleton->shallow_gdscript_cache.has(p_path)) {
+ return singleton->shallow_gdscript_cache[p_path];
+ }
+
+ return Ref<GDScript>();
+}
+
Error GDScriptCache::finish_compiling(const String &p_owner) {
+ MutexLock lock(singleton->lock);
+
// Mark this as compiled.
- Ref<GDScript> script = get_shallow_script(p_owner);
+ Ref<GDScript> script = get_cached_script(p_owner);
singleton->full_gdscript_cache[p_owner] = script.ptr();
singleton->shallow_gdscript_cache.erase(p_owner);
diff --git a/modules/gdscript/gdscript_cache.h b/modules/gdscript/gdscript_cache.h
index 3d111ea229..fcd240ba8d 100644
--- a/modules/gdscript/gdscript_cache.h
+++ b/modules/gdscript/gdscript_cache.h
@@ -87,8 +87,9 @@ class GDScriptCache {
public:
static Ref<GDScriptParserRef> get_parser(const String &p_path, GDScriptParserRef::Status status, Error &r_error, const String &p_owner = String());
static String get_source_code(const String &p_path);
- static Ref<GDScript> get_shallow_script(const String &p_path, const String &p_owner = String());
+ static Ref<GDScript> get_shallow_script(const String &p_path, Error &r_error, const String &p_owner = String());
static Ref<GDScript> get_full_script(const String &p_path, Error &r_error, const String &p_owner = String(), bool p_update_from_disk = false);
+ static Ref<GDScript> get_cached_script(const String &p_path);
static Error finish_compiling(const String &p_owner);
GDScriptCache();
diff --git a/modules/gdscript/gdscript_compiler.cpp b/modules/gdscript/gdscript_compiler.cpp
index 7acd1cdb96..aa1356d8c0 100644
--- a/modules/gdscript/gdscript_compiler.cpp
+++ b/modules/gdscript/gdscript_compiler.cpp
@@ -80,7 +80,7 @@ void GDScriptCompiler::_set_error(const String &p_error, const GDScriptParser::N
}
}
-GDScriptDataType GDScriptCompiler::_gdtype_from_datatype(const GDScriptParser::DataType &p_datatype, GDScript *p_owner) const {
+GDScriptDataType GDScriptCompiler::_gdtype_from_datatype(const GDScriptParser::DataType &p_datatype, GDScript *p_owner) {
if (!p_datatype.is_set() || !p_datatype.is_hard_type()) {
return GDScriptDataType();
}
@@ -103,74 +103,43 @@ GDScriptDataType GDScriptCompiler::_gdtype_from_datatype(const GDScriptParser::D
} break;
case GDScriptParser::DataType::SCRIPT: {
result.kind = GDScriptDataType::SCRIPT;
- result.script_type_ref = Ref<Script>(p_datatype.script_type);
+ result.builtin_type = p_datatype.builtin_type;
+ result.script_type_ref = p_datatype.script_type;
result.script_type = result.script_type_ref.ptr();
- result.native_type = result.script_type->get_instance_base_type();
+ result.native_type = p_datatype.native_type;
} break;
case GDScriptParser::DataType::CLASS: {
- // Locate class by constructing the path to it and following that path.
- GDScriptParser::ClassNode *class_type = p_datatype.class_type;
- if (class_type) {
- result.kind = GDScriptDataType::GDSCRIPT;
- result.builtin_type = p_datatype.builtin_type;
-
- String class_name = class_type->fqcn.split("::")[0];
- const bool is_inner_by_path = (!main_script->path.is_empty()) && (class_name == main_script->path);
- const bool is_inner_by_name = (!main_script->name.is_empty()) && (class_name == main_script->name);
- if (is_inner_by_path || is_inner_by_name) {
- // Local class.
- List<StringName> names;
- while (class_type->outer) {
- names.push_back(class_type->identifier->name);
- class_type = class_type->outer;
- }
+ result.kind = GDScriptDataType::GDSCRIPT;
+ result.builtin_type = p_datatype.builtin_type;
+ result.native_type = p_datatype.native_type;
- Ref<GDScript> script = Ref<GDScript>(main_script);
- while (names.back()) {
- if (!script->subclasses.has(names.back()->get())) {
- ERR_PRINT("Parser bug: Cannot locate datatype class.");
- result.has_type = false;
- return GDScriptDataType();
- }
- script = script->subclasses[names.back()->get()];
- names.pop_back();
- }
- result.script_type = script.ptr();
- result.native_type = script->get_instance_base_type();
- } else {
- // Inner class.
- PackedStringArray classes = class_type->fqcn.split("::");
- if (!classes.is_empty()) {
- for (GDScript *script : parsed_classes) {
- // Checking of inheritance structure of inner class to find a correct script link.
- if (script->name == classes[classes.size() - 1]) {
- PackedStringArray classes2 = script->fully_qualified_name.split("::");
- bool valid = true;
- if (classes.size() != classes2.size()) {
- valid = false;
- } else {
- for (int i = 0; i < classes.size(); i++) {
- if (classes[i] != classes2[i]) {
- valid = false;
- break;
- }
- }
- }
- if (!valid) {
- continue;
- }
- result.script_type_ref = Ref<GDScript>(script);
- break;
- }
- }
- }
- if (result.script_type_ref.is_null()) {
- result.script_type_ref = GDScriptCache::get_shallow_script(p_datatype.script_path, main_script->path);
- }
+ String root_name = p_datatype.class_type->fqcn.get_slice("::", 0);
+ bool is_local_class = !root_name.is_empty() && root_name == main_script->fully_qualified_name;
+
+ Ref<GDScript> script;
+ if (is_local_class) {
+ script = Ref<GDScript>(main_script);
+ } else {
+ Error err = OK;
+ script = GDScriptCache::get_shallow_script(p_datatype.script_path, err, p_owner->path);
+ if (err) {
+ _set_error(vformat(R"(Could not find script "%s": %s)", p_datatype.script_path, error_names[err]), nullptr);
+ }
+ }
- result.script_type = result.script_type_ref.ptr();
- result.native_type = p_datatype.native_type;
+ if (script.is_valid()) {
+ script = Ref<GDScript>(script->find_class(p_datatype.class_type->fqcn));
+ }
+
+ if (script.is_null()) {
+ _set_error(vformat(R"(Could not find class "%s" in "%s".)", p_datatype.class_type->fqcn, p_datatype.script_path), nullptr);
+ } else {
+ // Only hold a strong reference if the owner of the element qualified with this type is not local, to avoid cyclic references (leaks).
+ // TODO: Might lead to use after free if script_type is a subclass and is used after its parent is freed.
+ if (!is_local_class) {
+ result.script_type_ref = script;
}
+ result.script_type = script.ptr();
}
} break;
case GDScriptParser::DataType::ENUM:
@@ -189,13 +158,7 @@ GDScriptDataType GDScriptCompiler::_gdtype_from_datatype(const GDScriptParser::D
}
if (p_datatype.has_container_element_type()) {
- result.set_container_element_type(_gdtype_from_datatype(p_datatype.get_container_element_type()));
- }
-
- // Only hold strong reference to the script if it's not the owner of the
- // element qualified with this type, to avoid cyclic references (leaks).
- if (result.script_type && result.script_type == p_owner) {
- result.script_type_ref = Ref<Script>();
+ result.set_container_element_type(_gdtype_from_datatype(p_datatype.get_container_element_type(), p_owner));
}
return result;
@@ -367,7 +330,7 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &code
// This is so one autoload doesn't try to load another before it's compiled.
HashMap<StringName, ProjectSettings::AutoloadInfo> autoloads = ProjectSettings::get_singleton()->get_autoload_list();
if (autoloads.has(identifier) && autoloads[identifier].is_singleton) {
- GDScriptCodeGenerator::Address global = codegen.add_temporary(_gdtype_from_datatype(in->get_datatype()));
+ GDScriptCodeGenerator::Address global = codegen.add_temporary(_gdtype_from_datatype(in->get_datatype(), codegen.script));
int idx = GDScriptLanguage::get_singleton()->get_global_map()[identifier];
gen->write_store_global(global, idx);
return global;
@@ -434,7 +397,7 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &code
Vector<GDScriptCodeGenerator::Address> values;
// Create the result temporary first since it's the last to be killed.
- GDScriptDataType array_type = _gdtype_from_datatype(an->get_datatype());
+ GDScriptDataType array_type = _gdtype_from_datatype(an->get_datatype(), codegen.script);
GDScriptCodeGenerator::Address result = codegen.add_temporary(array_type);
for (int i = 0; i < an->elements.size(); i++) {
@@ -511,7 +474,7 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &code
case GDScriptParser::Node::CAST: {
const GDScriptParser::CastNode *cn = static_cast<const GDScriptParser::CastNode *>(p_expression);
GDScriptParser::DataType og_cast_type = cn->cast_type->get_datatype();
- GDScriptDataType cast_type = _gdtype_from_datatype(og_cast_type);
+ GDScriptDataType cast_type = _gdtype_from_datatype(og_cast_type, codegen.script);
if (og_cast_type.kind == GDScriptParser::DataType::ENUM) {
// Enum types are usually treated as dictionaries, but in this case we want to cast to an integer.
@@ -534,7 +497,7 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &code
} break;
case GDScriptParser::Node::CALL: {
const GDScriptParser::CallNode *call = static_cast<const GDScriptParser::CallNode *>(p_expression);
- GDScriptDataType type = _gdtype_from_datatype(call->get_datatype());
+ GDScriptDataType type = _gdtype_from_datatype(call->get_datatype(), codegen.script);
GDScriptCodeGenerator::Address result = codegen.add_temporary(type);
GDScriptCodeGenerator::Address nil = GDScriptCodeGenerator::Address(GDScriptCodeGenerator::Address::NIL);
@@ -670,7 +633,7 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &code
Vector<GDScriptCodeGenerator::Address> args;
args.push_back(codegen.add_constant(NodePath(get_node->full_path)));
- GDScriptCodeGenerator::Address result = codegen.add_temporary(_gdtype_from_datatype(get_node->get_datatype()));
+ GDScriptCodeGenerator::Address result = codegen.add_temporary(_gdtype_from_datatype(get_node->get_datatype(), codegen.script));
MethodBind *get_node_method = ClassDB::get_method("Node", "get_node");
gen->write_call_ptrcall(result, GDScriptCodeGenerator::Address(GDScriptCodeGenerator::Address::SELF), get_node_method, args);
@@ -686,7 +649,7 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &code
case GDScriptParser::Node::AWAIT: {
const GDScriptParser::AwaitNode *await = static_cast<const GDScriptParser::AwaitNode *>(p_expression);
- GDScriptCodeGenerator::Address result = codegen.add_temporary(_gdtype_from_datatype(p_expression->get_datatype()));
+ GDScriptCodeGenerator::Address result = codegen.add_temporary(_gdtype_from_datatype(p_expression->get_datatype(), codegen.script));
within_await = true;
GDScriptCodeGenerator::Address argument = _parse_expression(codegen, r_error, await->to_await);
within_await = false;
@@ -705,7 +668,7 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &code
// Indexing operator.
case GDScriptParser::Node::SUBSCRIPT: {
const GDScriptParser::SubscriptNode *subscript = static_cast<const GDScriptParser::SubscriptNode *>(p_expression);
- GDScriptCodeGenerator::Address result = codegen.add_temporary(_gdtype_from_datatype(subscript->get_datatype()));
+ GDScriptCodeGenerator::Address result = codegen.add_temporary(_gdtype_from_datatype(subscript->get_datatype(), codegen.script));
GDScriptCodeGenerator::Address base = _parse_expression(codegen, r_error, subscript->base);
if (r_error) {
@@ -735,7 +698,7 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &code
// Remove result temp as we don't need it.
gen->pop_temporary();
// Faster than indexing self (as if no self. had been used).
- return GDScriptCodeGenerator::Address(GDScriptCodeGenerator::Address::MEMBER, MI->value.index, _gdtype_from_datatype(subscript->get_datatype()));
+ return GDScriptCodeGenerator::Address(GDScriptCodeGenerator::Address::MEMBER, MI->value.index, _gdtype_from_datatype(subscript->get_datatype(), codegen.script));
}
}
@@ -773,7 +736,7 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &code
case GDScriptParser::Node::UNARY_OPERATOR: {
const GDScriptParser::UnaryOpNode *unary = static_cast<const GDScriptParser::UnaryOpNode *>(p_expression);
- GDScriptCodeGenerator::Address result = codegen.add_temporary(_gdtype_from_datatype(unary->get_datatype()));
+ GDScriptCodeGenerator::Address result = codegen.add_temporary(_gdtype_from_datatype(unary->get_datatype(), codegen.script));
GDScriptCodeGenerator::Address operand = _parse_expression(codegen, r_error, unary->operand);
if (r_error) {
@@ -791,7 +754,7 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &code
case GDScriptParser::Node::BINARY_OPERATOR: {
const GDScriptParser::BinaryOpNode *binary = static_cast<const GDScriptParser::BinaryOpNode *>(p_expression);
- GDScriptCodeGenerator::Address result = codegen.add_temporary(_gdtype_from_datatype(binary->get_datatype()));
+ GDScriptCodeGenerator::Address result = codegen.add_temporary(_gdtype_from_datatype(binary->get_datatype(), codegen.script));
switch (binary->operation) {
case GDScriptParser::BinaryOpNode::OP_LOGIC_AND: {
@@ -867,7 +830,7 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &code
case GDScriptParser::Node::TERNARY_OPERATOR: {
// x IF a ELSE y operator with early out on failure.
const GDScriptParser::TernaryOpNode *ternary = static_cast<const GDScriptParser::TernaryOpNode *>(p_expression);
- GDScriptCodeGenerator::Address result = codegen.add_temporary(_gdtype_from_datatype(ternary->get_datatype()));
+ GDScriptCodeGenerator::Address result = codegen.add_temporary(_gdtype_from_datatype(ternary->get_datatype(), codegen.script));
gen->write_start_ternary(result);
@@ -985,7 +948,7 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &code
break;
}
const GDScriptParser::SubscriptNode *subscript_elem = E->get();
- GDScriptCodeGenerator::Address value = codegen.add_temporary(_gdtype_from_datatype(subscript_elem->get_datatype()));
+ GDScriptCodeGenerator::Address value = codegen.add_temporary(_gdtype_from_datatype(subscript_elem->get_datatype(), codegen.script));
GDScriptCodeGenerator::Address key;
StringName name;
@@ -1024,8 +987,8 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &code
// Perform operator if any.
if (assignment->operation != GDScriptParser::AssignmentNode::OP_NONE) {
- GDScriptCodeGenerator::Address op_result = codegen.add_temporary(_gdtype_from_datatype(assignment->get_datatype()));
- GDScriptCodeGenerator::Address value = codegen.add_temporary(_gdtype_from_datatype(subscript->get_datatype()));
+ GDScriptCodeGenerator::Address op_result = codegen.add_temporary(_gdtype_from_datatype(assignment->get_datatype(), codegen.script));
+ GDScriptCodeGenerator::Address value = codegen.add_temporary(_gdtype_from_datatype(subscript->get_datatype(), codegen.script));
if (subscript->is_attribute) {
gen->write_get_named(value, name, prev_base);
} else {
@@ -1130,8 +1093,8 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &code
StringName name = static_cast<GDScriptParser::IdentifierNode *>(assignment->assignee)->name;
if (has_operation) {
- GDScriptCodeGenerator::Address op_result = codegen.add_temporary(_gdtype_from_datatype(assignment->get_datatype()));
- GDScriptCodeGenerator::Address member = codegen.add_temporary(_gdtype_from_datatype(assignment->assignee->get_datatype()));
+ GDScriptCodeGenerator::Address op_result = codegen.add_temporary(_gdtype_from_datatype(assignment->get_datatype(), codegen.script));
+ GDScriptCodeGenerator::Address member = codegen.add_temporary(_gdtype_from_datatype(assignment->assignee->get_datatype(), codegen.script));
gen->write_get_member(member, name);
gen->write_binary_operator(op_result, assignment->variant_op, member, assigned_value);
gen->pop_temporary(); // Pop member temp.
@@ -1184,7 +1147,7 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &code
bool has_operation = assignment->operation != GDScriptParser::AssignmentNode::OP_NONE;
if (has_operation) {
// Perform operation.
- GDScriptCodeGenerator::Address op_result = codegen.add_temporary(_gdtype_from_datatype(assignment->get_datatype()));
+ GDScriptCodeGenerator::Address op_result = codegen.add_temporary(_gdtype_from_datatype(assignment->get_datatype(), codegen.script));
GDScriptCodeGenerator::Address og_value = _parse_expression(codegen, r_error, assignment->assignee);
gen->write_binary_operator(op_result, assignment->variant_op, og_value, assigned_value);
to_assign = op_result;
@@ -1196,7 +1159,7 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &code
to_assign = assigned_value;
}
- GDScriptDataType assign_type = _gdtype_from_datatype(assignment->assignee->get_datatype());
+ GDScriptDataType assign_type = _gdtype_from_datatype(assignment->assignee->get_datatype(), codegen.script);
if (has_setter && !is_in_setter) {
// Call setter.
@@ -1226,7 +1189,7 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &code
} break;
case GDScriptParser::Node::LAMBDA: {
const GDScriptParser::LambdaNode *lambda = static_cast<const GDScriptParser::LambdaNode *>(p_expression);
- GDScriptCodeGenerator::Address result = codegen.add_temporary(_gdtype_from_datatype(lambda->get_datatype()));
+ GDScriptCodeGenerator::Address result = codegen.add_temporary(_gdtype_from_datatype(lambda->get_datatype(), codegen.script));
Vector<GDScriptCodeGenerator::Address> captures;
captures.resize(lambda->captures.size());
@@ -1645,7 +1608,7 @@ void GDScriptCompiler::_add_locals_in_block(CodeGen &codegen, const GDScriptPars
// Parameters are added directly from function and loop variables are declared explicitly.
continue;
}
- codegen.add_local(p_block->locals[i].name, _gdtype_from_datatype(p_block->locals[i].get_datatype()));
+ codegen.add_local(p_block->locals[i].name, _gdtype_from_datatype(p_block->locals[i].get_datatype(), codegen.script));
}
}
@@ -1675,7 +1638,7 @@ Error GDScriptCompiler::_parse_block(CodeGen &codegen, const GDScriptParser::Sui
codegen.start_block();
// Evaluate the match expression.
- GDScriptCodeGenerator::Address value = codegen.add_local("@match_value", _gdtype_from_datatype(match->test->get_datatype()));
+ GDScriptCodeGenerator::Address value = codegen.add_local("@match_value", _gdtype_from_datatype(match->test->get_datatype(), codegen.script));
GDScriptCodeGenerator::Address value_expr = _parse_expression(codegen, err, match->test);
if (err) {
return err;
@@ -1784,9 +1747,9 @@ Error GDScriptCompiler::_parse_block(CodeGen &codegen, const GDScriptParser::Sui
const GDScriptParser::ForNode *for_n = static_cast<const GDScriptParser::ForNode *>(s);
codegen.start_block();
- GDScriptCodeGenerator::Address iterator = codegen.add_local(for_n->variable->name, _gdtype_from_datatype(for_n->variable->get_datatype()));
+ GDScriptCodeGenerator::Address iterator = codegen.add_local(for_n->variable->name, _gdtype_from_datatype(for_n->variable->get_datatype(), codegen.script));
- gen->start_for(iterator.type, _gdtype_from_datatype(for_n->list->get_datatype()));
+ gen->start_for(iterator.type, _gdtype_from_datatype(for_n->list->get_datatype(), codegen.script));
GDScriptCodeGenerator::Address list = _parse_expression(codegen, err, for_n->list);
if (err) {
@@ -1897,13 +1860,13 @@ Error GDScriptCompiler::_parse_block(CodeGen &codegen, const GDScriptParser::Sui
const GDScriptParser::VariableNode *lv = static_cast<const GDScriptParser::VariableNode *>(s);
// Should be already in stack when the block began.
GDScriptCodeGenerator::Address local = codegen.locals[lv->identifier->name];
- GDScriptParser::DataType local_type = lv->get_datatype();
+ GDScriptDataType local_type = _gdtype_from_datatype(lv->get_datatype(), codegen.script);
if (lv->initializer != nullptr) {
// For typed arrays we need to make sure this is already initialized correctly so typed assignment work.
- if (local_type.is_hard_type() && local_type.builtin_type == Variant::ARRAY) {
+ if (local_type.has_type && local_type.builtin_type == Variant::ARRAY) {
if (local_type.has_container_element_type()) {
- codegen.generator->write_construct_typed_array(local, _gdtype_from_datatype(local_type.get_container_element_type(), codegen.script), Vector<GDScriptCodeGenerator::Address>());
+ codegen.generator->write_construct_typed_array(local, local_type.get_container_element_type(), Vector<GDScriptCodeGenerator::Address>());
} else {
codegen.generator->write_construct_array(local, Vector<GDScriptCodeGenerator::Address>());
}
@@ -1920,11 +1883,11 @@ Error GDScriptCompiler::_parse_block(CodeGen &codegen, const GDScriptParser::Sui
if (src_address.mode == GDScriptCodeGenerator::Address::TEMPORARY) {
codegen.generator->pop_temporary();
}
- } else if (lv->get_datatype().is_hard_type()) {
+ } else if (local_type.has_type) {
// Initialize with default for type.
if (local_type.has_container_element_type()) {
- codegen.generator->write_construct_typed_array(local, _gdtype_from_datatype(local_type.get_container_element_type(), codegen.script), Vector<GDScriptCodeGenerator::Address>());
- } else if (local_type.kind == GDScriptParser::DataType::BUILTIN) {
+ codegen.generator->write_construct_typed_array(local, local_type.get_container_element_type(), Vector<GDScriptCodeGenerator::Address>());
+ } else if (local_type.kind == GDScriptDataType::BUILTIN) {
codegen.generator->write_construct(local, local_type.builtin_type, Vector<GDScriptCodeGenerator::Address>());
}
// The `else` branch is for objects, in such case we leave it as `null`.
@@ -2033,17 +1996,17 @@ GDScriptFunction *GDScriptCompiler::_parse_function(Error &r_error, GDScript *p_
continue;
}
- GDScriptParser::DataType field_type = field->get_datatype();
+ GDScriptDataType field_type = _gdtype_from_datatype(field->get_datatype(), codegen.script);
- GDScriptCodeGenerator::Address dst_address(GDScriptCodeGenerator::Address::MEMBER, codegen.script->member_indices[field->identifier->name].index, _gdtype_from_datatype(field->get_datatype()));
+ GDScriptCodeGenerator::Address dst_address(GDScriptCodeGenerator::Address::MEMBER, codegen.script->member_indices[field->identifier->name].index, field_type);
if (field->initializer) {
// Emit proper line change.
codegen.generator->write_newline(field->initializer->start_line);
// For typed arrays we need to make sure this is already initialized correctly so typed assignment work.
- if (field_type.is_hard_type() && field_type.builtin_type == Variant::ARRAY) {
+ if (field_type.has_type && field_type.builtin_type == Variant::ARRAY) {
if (field_type.has_container_element_type()) {
- codegen.generator->write_construct_typed_array(dst_address, _gdtype_from_datatype(field_type.get_container_element_type(), codegen.script), Vector<GDScriptCodeGenerator::Address>());
+ codegen.generator->write_construct_typed_array(dst_address, field_type.get_container_element_type(), Vector<GDScriptCodeGenerator::Address>());
} else {
codegen.generator->write_construct_array(dst_address, Vector<GDScriptCodeGenerator::Address>());
}
@@ -2062,13 +2025,13 @@ GDScriptFunction *GDScriptCompiler::_parse_function(Error &r_error, GDScript *p_
if (src_address.mode == GDScriptCodeGenerator::Address::TEMPORARY) {
codegen.generator->pop_temporary();
}
- } else if (field->get_datatype().is_hard_type()) {
+ } else if (field_type.has_type) {
codegen.generator->write_newline(field->start_line);
// Initialize with default for type.
if (field_type.has_container_element_type()) {
- codegen.generator->write_construct_typed_array(dst_address, _gdtype_from_datatype(field_type.get_container_element_type(), codegen.script), Vector<GDScriptCodeGenerator::Address>());
- } else if (field_type.kind == GDScriptParser::DataType::BUILTIN) {
+ codegen.generator->write_construct_typed_array(dst_address, field_type.get_container_element_type(), Vector<GDScriptCodeGenerator::Address>());
+ } else if (field_type.kind == GDScriptDataType::BUILTIN) {
codegen.generator->write_construct(dst_address, field_type.builtin_type, Vector<GDScriptCodeGenerator::Address>());
}
// The `else` branch is for objects, in such case we leave it as `null`.
@@ -2195,23 +2158,19 @@ Error GDScriptCompiler::_parse_setter_getter(GDScript *p_script, const GDScriptP
return err;
}
-Error GDScriptCompiler::_parse_class_level(GDScript *p_script, const GDScriptParser::ClassNode *p_class, bool p_keep_state) {
- parsing_classes.insert(p_script);
+Error GDScriptCompiler::_populate_class_members(GDScript *p_script, const GDScriptParser::ClassNode *p_class, bool p_keep_state) {
+ if (parsed_classes.has(p_script)) {
+ return OK;
+ }
- if (p_class->outer && p_class->outer->outer) {
- // Owner is not root
- if (!parsed_classes.has(p_script->_owner)) {
- if (parsing_classes.has(p_script->_owner)) {
- _set_error("Cyclic class reference for '" + String(p_class->identifier->name) + "'.", p_class);
- return ERR_PARSE_ERROR;
- }
- Error err = _parse_class_level(p_script->_owner, p_class->outer, p_keep_state);
- if (err) {
- return err;
- }
- }
+ if (parsing_classes.has(p_script)) {
+ String class_name = p_class->identifier ? String(p_class->identifier->name) : p_class->fqcn;
+ _set_error(vformat(R"(Cyclic class reference for "%s".)", class_name), p_class);
+ return ERR_PARSE_ERROR;
}
+ parsing_classes.insert(p_script);
+
#ifdef TOOLS_ENABLED
p_script->doc_functions.clear();
p_script->doc_variables.clear();
@@ -2253,7 +2212,6 @@ Error GDScriptCompiler::_parse_class_level(GDScript *p_script, const GDScriptPar
p_script->implicit_ready = nullptr;
p_script->tool = parser->is_tool();
- p_script->name = p_class->identifier ? p_class->identifier->name : "";
if (!p_script->name.is_empty()) {
if (ClassDB::class_exists(p_script->name) && ClassDB::is_class_exposed(p_script->name)) {
@@ -2262,53 +2220,50 @@ Error GDScriptCompiler::_parse_class_level(GDScript *p_script, const GDScriptPar
}
}
- Ref<GDScriptNativeClass> native;
+ GDScriptDataType base_type = _gdtype_from_datatype(p_class->base_type, p_script);
- GDScriptDataType base_type = _gdtype_from_datatype(p_class->base_type);
// Inheritance
switch (base_type.kind) {
case GDScriptDataType::NATIVE: {
int native_idx = GDScriptLanguage::get_singleton()->get_global_map()[base_type.native_type];
- native = GDScriptLanguage::get_singleton()->get_global_array()[native_idx];
- ERR_FAIL_COND_V(native.is_null(), ERR_BUG);
- p_script->native = native;
+ p_script->native = GDScriptLanguage::get_singleton()->get_global_array()[native_idx];
+ ERR_FAIL_COND_V(p_script->native.is_null(), ERR_BUG);
} break;
case GDScriptDataType::GDSCRIPT: {
Ref<GDScript> base = Ref<GDScript>(base_type.script_type);
- p_script->base = base;
- p_script->_base = base.ptr();
+ if (base.is_null()) {
+ return ERR_COMPILATION_FAILED;
+ }
- if (p_class->base_type.kind == GDScriptParser::DataType::CLASS && p_class->base_type.class_type != nullptr) {
- if (p_class->base_type.script_path == main_script->path) {
- if (!parsed_classes.has(p_script->_base)) {
- if (parsing_classes.has(p_script->_base)) {
- String class_name = p_class->identifier ? p_class->identifier->name : "<main>";
- _set_error("Cyclic class reference for '" + class_name + "'.", p_class);
- return ERR_PARSE_ERROR;
- }
- Error err = _parse_class_level(p_script->_base, p_class->base_type.class_type, p_keep_state);
- if (err) {
- return err;
- }
- }
- } else {
- Error err = OK;
- base = GDScriptCache::get_full_script(p_class->base_type.script_path, err, main_script->path);
- if (err) {
- return err;
- }
- if (base.is_null() || !base->is_valid()) {
- return ERR_COMPILATION_FAILED;
- }
+ if (base.ptr() == main_script || main_script->is_subclass(base.ptr())) {
+ Error err = _populate_class_members(base.ptr(), p_class->base_type.class_type, p_keep_state);
+ if (err) {
+ return err;
+ }
+ } else if (!base->is_valid()) {
+ Error err = OK;
+ Ref<GDScript> base_root = GDScriptCache::get_full_script(base->path, err, p_script->path);
+ if (err) {
+ _set_error(vformat(R"(Could not compile base class "%s" from "%s": %s)", base->fully_qualified_name, base->path, error_names[err]), nullptr);
+ return err;
}
+ if (base_root.is_valid()) {
+ base = Ref<GDScript>(base_root->find_class(base->fully_qualified_name));
+ }
+ if (base.is_null()) {
+ _set_error(vformat(R"(Could not find class "%s" in "%s".)", base->fully_qualified_name, base->path), nullptr);
+ return ERR_COMPILATION_FAILED;
+ }
+ ERR_FAIL_COND_V(!base->is_valid(), ERR_BUG);
}
+ p_script->base = base;
+ p_script->_base = base.ptr();
p_script->member_indices = base->member_indices;
- native = base->native;
- p_script->native = native;
+ p_script->native = base->native;
} break;
default: {
- _set_error("Parser bug: invalid inheritance.", p_class);
+ _set_error("Parser bug: invalid inheritance.", nullptr);
return ERR_BUG;
} break;
}
@@ -2432,9 +2387,8 @@ Error GDScriptCompiler::_parse_class_level(GDScript *p_script, const GDScriptPar
// TODO: Make enums not be just a dictionary?
Dictionary new_enum;
for (int j = 0; j < enum_n->values.size(); j++) {
- int value = enum_n->values[j].value;
// Needs to be string because Variant::get will convert to String.
- new_enum[String(enum_n->values[j].identifier->name)] = value;
+ new_enum[String(enum_n->values[j].identifier->name)] = enum_n->values[j].value;
}
p_script->constants.insert(enum_n->identifier->name, new_enum);
@@ -2479,8 +2433,7 @@ Error GDScriptCompiler::_parse_class_level(GDScript *p_script, const GDScriptPar
parsed_classes.insert(p_script);
parsing_classes.erase(p_script);
- //parse sub-classes
-
+ // Populate sub-classes.
for (int i = 0; i < p_class->members.size(); i++) {
const GDScriptParser::ClassNode::Member &member = p_class->members[i];
if (member.type != member.CLASS) {
@@ -2492,8 +2445,8 @@ Error GDScriptCompiler::_parse_class_level(GDScript *p_script, const GDScriptPar
GDScript *subclass_ptr = subclass.ptr();
// Subclass might still be parsing, just skip it
- if (!parsed_classes.has(subclass_ptr) && !parsing_classes.has(subclass_ptr)) {
- Error err = _parse_class_level(subclass_ptr, inner_class, p_keep_state);
+ if (!parsing_classes.has(subclass_ptr)) {
+ Error err = _populate_class_members(subclass_ptr, inner_class, p_keep_state);
if (err) {
return err;
}
@@ -2510,9 +2463,8 @@ Error GDScriptCompiler::_parse_class_level(GDScript *p_script, const GDScriptPar
return OK;
}
-Error GDScriptCompiler::_parse_class_blocks(GDScript *p_script, const GDScriptParser::ClassNode *p_class, bool p_keep_state) {
- //parse methods
-
+Error GDScriptCompiler::_compile_class(GDScript *p_script, const GDScriptParser::ClassNode *p_class, bool p_keep_state) {
+ // Compile member functions, getters, and setters.
for (int i = 0; i < p_class->members.size(); i++) {
const GDScriptParser::ClassNode::Member &member = p_class->members[i];
if (member.type == member.FUNCTION) {
@@ -2616,17 +2568,26 @@ Error GDScriptCompiler::_parse_class_blocks(GDScript *p_script, const GDScriptPa
StringName name = inner_class->identifier->name;
GDScript *subclass = p_script->subclasses[name].ptr();
- Error err = _parse_class_blocks(subclass, inner_class, p_keep_state);
+ Error err = _compile_class(subclass, inner_class, p_keep_state);
if (err) {
return err;
}
}
+#ifdef TOOLS_ENABLED
+ p_script->_update_doc();
+#endif
+
+ p_script->_init_rpc_methods_properties();
+
p_script->valid = true;
return OK;
}
-void GDScriptCompiler::_make_scripts(GDScript *p_script, const GDScriptParser::ClassNode *p_class, bool p_keep_state) {
+void GDScriptCompiler::make_scripts(GDScript *p_script, const GDScriptParser::ClassNode *p_class, bool p_keep_state) {
+ p_script->fully_qualified_name = p_class->fqcn;
+ p_script->name = p_class->identifier ? p_class->identifier->name : "";
+
HashMap<StringName, Ref<GDScript>> old_subclasses;
if (p_keep_state) {
@@ -2643,24 +2604,22 @@ void GDScriptCompiler::_make_scripts(GDScript *p_script, const GDScriptParser::C
StringName name = inner_class->identifier->name;
Ref<GDScript> subclass;
- String fully_qualified_name = p_script->fully_qualified_name + "::" + name;
if (old_subclasses.has(name)) {
subclass = old_subclasses[name];
} else {
- Ref<GDScript> orphan_subclass = GDScriptLanguage::get_singleton()->get_orphan_subclass(fully_qualified_name);
- if (orphan_subclass.is_valid()) {
- subclass = orphan_subclass;
- } else {
- subclass.instantiate();
- }
+ subclass = GDScriptLanguage::get_singleton()->get_orphan_subclass(inner_class->fqcn);
+ }
+
+ if (subclass.is_null()) {
+ subclass.instantiate();
}
subclass->_owner = p_script;
- subclass->fully_qualified_name = fully_qualified_name;
+ subclass->path = p_script->path;
p_script->subclasses.insert(name, subclass);
- _make_scripts(subclass.ptr(), inner_class, false);
+ make_scripts(subclass.ptr(), inner_class, p_keep_state);
}
}
@@ -2674,26 +2633,22 @@ Error GDScriptCompiler::compile(const GDScriptParser *p_parser, GDScript *p_scri
source = p_script->get_path();
- // The best fully qualified name for a base level script is its file path
- p_script->fully_qualified_name = p_script->path;
-
// Create scripts for subclasses beforehand so they can be referenced
- _make_scripts(p_script, root, p_keep_state);
+ make_scripts(p_script, root, p_keep_state);
- p_script->_owner = nullptr;
- Error err = _parse_class_level(p_script, root, p_keep_state);
+ main_script->_owner = nullptr;
+ Error err = _populate_class_members(main_script, parser->get_tree(), p_keep_state);
if (err) {
return err;
}
- err = _parse_class_blocks(p_script, root, p_keep_state);
-
+ err = _compile_class(main_script, root, p_keep_state);
if (err) {
return err;
}
- return GDScriptCache::finish_compiling(p_script->get_path());
+ return GDScriptCache::finish_compiling(main_script->get_path());
}
String GDScriptCompiler::get_error() const {
diff --git a/modules/gdscript/gdscript_compiler.h b/modules/gdscript/gdscript_compiler.h
index e4264ea55b..45ca4fe342 100644
--- a/modules/gdscript/gdscript_compiler.h
+++ b/modules/gdscript/gdscript_compiler.h
@@ -121,7 +121,7 @@ class GDScriptCompiler {
Error _create_binary_operator(CodeGen &codegen, const GDScriptParser::BinaryOpNode *on, Variant::Operator op, bool p_initializer = false, const GDScriptCodeGenerator::Address &p_index_addr = GDScriptCodeGenerator::Address());
Error _create_binary_operator(CodeGen &codegen, const GDScriptParser::ExpressionNode *p_left_operand, const GDScriptParser::ExpressionNode *p_right_operand, Variant::Operator op, bool p_initializer = false, const GDScriptCodeGenerator::Address &p_index_addr = GDScriptCodeGenerator::Address());
- GDScriptDataType _gdtype_from_datatype(const GDScriptParser::DataType &p_datatype, GDScript *p_owner = nullptr) const;
+ GDScriptDataType _gdtype_from_datatype(const GDScriptParser::DataType &p_datatype, GDScript *p_owner);
GDScriptCodeGenerator::Address _parse_assign_right_expression(CodeGen &codegen, Error &r_error, const GDScriptParser::AssignmentNode *p_assignmentint, const GDScriptCodeGenerator::Address &p_index_addr = GDScriptCodeGenerator::Address());
GDScriptCodeGenerator::Address _parse_expression(CodeGen &codegen, Error &r_error, const GDScriptParser::ExpressionNode *p_expression, bool p_root = false, bool p_initializer = false, const GDScriptCodeGenerator::Address &p_index_addr = GDScriptCodeGenerator::Address());
@@ -130,9 +130,8 @@ class GDScriptCompiler {
Error _parse_block(CodeGen &codegen, const GDScriptParser::SuiteNode *p_block, bool p_add_locals = true);
GDScriptFunction *_parse_function(Error &r_error, GDScript *p_script, const GDScriptParser::ClassNode *p_class, const GDScriptParser::FunctionNode *p_func, bool p_for_ready = false, bool p_for_lambda = false);
Error _parse_setter_getter(GDScript *p_script, const GDScriptParser::ClassNode *p_class, const GDScriptParser::VariableNode *p_variable, bool p_is_setter);
- Error _parse_class_level(GDScript *p_script, const GDScriptParser::ClassNode *p_class, bool p_keep_state);
- Error _parse_class_blocks(GDScript *p_script, const GDScriptParser::ClassNode *p_class, bool p_keep_state);
- void _make_scripts(GDScript *p_script, const GDScriptParser::ClassNode *p_class, bool p_keep_state);
+ Error _populate_class_members(GDScript *p_script, const GDScriptParser::ClassNode *p_class, bool p_keep_state);
+ Error _compile_class(GDScript *p_script, const GDScriptParser::ClassNode *p_class, bool p_keep_state);
int err_line = 0;
int err_column = 0;
StringName source;
@@ -140,6 +139,7 @@ class GDScriptCompiler {
bool within_await = false;
public:
+ static void make_scripts(GDScript *p_script, const GDScriptParser::ClassNode *p_class, bool p_keep_state);
Error compile(const GDScriptParser *p_parser, GDScript *p_script, bool p_keep_state = false);
String get_error() const;
diff --git a/modules/gdscript/gdscript_editor.cpp b/modules/gdscript/gdscript_editor.cpp
index 3c68993b36..68508b20da 100644
--- a/modules/gdscript/gdscript_editor.cpp
+++ b/modules/gdscript/gdscript_editor.cpp
@@ -2513,7 +2513,7 @@ static void _find_call_arguments(GDScriptParser::CompletionContext &p_context, c
static void _find_call_arguments(GDScriptParser::CompletionContext &p_context, const GDScriptParser::Node *p_call, int p_argidx, HashMap<String, ScriptLanguage::CodeCompletionOption> &r_result, bool &r_forced, String &r_arghint) {
if (p_call->type == GDScriptParser::Node::PRELOAD) {
- if (p_argidx == 0 && bool(EditorSettings::get_singleton()->get("text_editor/completion/complete_file_paths"))) {
+ if (p_argidx == 0 && bool(EDITOR_GET("text_editor/completion/complete_file_paths"))) {
_get_directory_contents(EditorFileSystem::get_singleton()->get_filesystem(), r_result);
}
@@ -2765,7 +2765,61 @@ static void _find_call_arguments(GDScriptParser::CompletionContext &p_context, c
const GDScriptParser::SubscriptNode *attr = static_cast<const GDScriptParser::SubscriptNode *>(completion_context.node);
if (attr->base) {
GDScriptCompletionIdentifier base;
- if (!_guess_expression_type(completion_context, attr->base, base)) {
+ bool found_type = false;
+
+ if (p_owner != nullptr && attr->base->type == GDScriptParser::Node::IDENTIFIER) {
+ const GDScriptParser::GetNodeNode *get_node = nullptr;
+ const GDScriptParser::IdentifierNode *identifier_node = static_cast<GDScriptParser::IdentifierNode *>(attr->base);
+
+ switch (identifier_node->source) {
+ case GDScriptParser::IdentifierNode::Source::MEMBER_VARIABLE: {
+ if (completion_context.current_class != nullptr) {
+ const StringName &member_name = identifier_node->name;
+ const GDScriptParser::ClassNode *current_class = completion_context.current_class;
+
+ if (current_class->has_member(member_name)) {
+ const GDScriptParser::ClassNode::Member &member = current_class->get_member(member_name);
+
+ if (member.type == GDScriptParser::ClassNode::Member::VARIABLE) {
+ const GDScriptParser::VariableNode *variable = static_cast<GDScriptParser::VariableNode *>(member.variable);
+
+ if (variable->initializer && variable->initializer->type == GDScriptParser::Node::GET_NODE) {
+ get_node = static_cast<GDScriptParser::GetNodeNode *>(variable->initializer);
+ }
+ }
+ }
+ }
+ } break;
+ case GDScriptParser::IdentifierNode::Source::LOCAL_VARIABLE: {
+ if (identifier_node->next != nullptr && identifier_node->next->type == GDScriptParser::ClassNode::Node::GET_NODE) {
+ get_node = static_cast<GDScriptParser::GetNodeNode *>(identifier_node->next);
+ }
+ } break;
+ default:
+ break;
+ }
+
+ if (get_node != nullptr) {
+ const Object *node = p_owner->call("get_node_or_null", NodePath(get_node->full_path));
+ if (node != nullptr) {
+ found_type = true;
+
+ GDScriptParser::DataType type;
+ type.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT;
+ type.kind = GDScriptParser::DataType::NATIVE;
+ type.native_type = node->get_class_name();
+ type.builtin_type = Variant::OBJECT;
+
+ base.type = type;
+ }
+
+ if (!found_type) {
+ break;
+ }
+ }
+ }
+
+ if (!found_type && !_guess_expression_type(completion_context, attr->base, base)) {
break;
}
@@ -2820,7 +2874,7 @@ static void _find_call_arguments(GDScriptParser::CompletionContext &p_context, c
r_forced = true;
} break;
case GDScriptParser::COMPLETION_RESOURCE_PATH: {
- if (EditorSettings::get_singleton()->get("text_editor/completion/complete_file_paths")) {
+ if (EDITOR_GET("text_editor/completion/complete_file_paths")) {
_get_directory_contents(EditorFileSystem::get_singleton()->get_filesystem(), options);
r_forced = true;
}
@@ -3051,8 +3105,9 @@ static Error _lookup_symbol_from_base(const GDScriptParser::DataType &p_base, co
r_result.type = ScriptLanguage::LOOKUP_RESULT_SCRIPT_LOCATION;
r_result.location = base_type.class_type->get_member(p_symbol).get_line();
r_result.class_path = base_type.script_path;
- r_result.script = GDScriptCache::get_shallow_script(r_result.class_path);
- return OK;
+ Error err = OK;
+ r_result.script = GDScriptCache::get_shallow_script(r_result.class_path, err);
+ return err;
}
base_type = base_type.class_type->base_type;
}
diff --git a/modules/gdscript/gdscript_parser.cpp b/modules/gdscript/gdscript_parser.cpp
index bdf6fb35b6..71cedb4f38 100644
--- a/modules/gdscript/gdscript_parser.cpp
+++ b/modules/gdscript/gdscript_parser.cpp
@@ -534,6 +534,7 @@ void GDScriptParser::end_statement(const String &p_context) {
void GDScriptParser::parse_program() {
head = alloc_node<ClassNode>();
+ head->fqcn = script_path;
current_class = head;
// If we happen to parse an annotation before extends or class_name keywords, track it.
@@ -646,6 +647,9 @@ GDScriptParser::ClassNode *GDScriptParser::parse_class() {
if (consume(GDScriptTokenizer::Token::IDENTIFIER, R"(Expected identifier for the class name after "class".)")) {
n_class->identifier = parse_identifier();
+ if (n_class->outer) {
+ n_class->fqcn = n_class->outer->fqcn + "::" + n_class->identifier->name;
+ }
}
if (match(GDScriptTokenizer::Token::EXTENDS)) {
@@ -684,6 +688,7 @@ GDScriptParser::ClassNode *GDScriptParser::parse_class() {
void GDScriptParser::parse_class_name() {
if (consume(GDScriptTokenizer::Token::IDENTIFIER, R"(Expected identifier for the global class name after "class_name".)")) {
current_class->identifier = parse_identifier();
+ current_class->fqcn = String(current_class->identifier->name);
}
if (match(GDScriptTokenizer::Token::EXTENDS)) {
@@ -3765,13 +3770,19 @@ bool GDScriptParser::export_annotations(const AnnotationNode *p_annotation, Node
break;
case GDScriptParser::DataType::CLASS:
// Can assume type is a global GDScript class.
- if (!ClassDB::is_parent_class(export_type.native_type, SNAME("Resource"))) {
- push_error(R"(Exported script type must extend Resource.)");
+ if (ClassDB::is_parent_class(export_type.native_type, SNAME("Resource"))) {
+ variable->export_info.type = Variant::OBJECT;
+ variable->export_info.hint = PROPERTY_HINT_RESOURCE_TYPE;
+ variable->export_info.hint_string = export_type.class_type->identifier->name;
+ } else if (ClassDB::is_parent_class(export_type.native_type, SNAME("Node"))) {
+ variable->export_info.type = Variant::OBJECT;
+ variable->export_info.hint = PROPERTY_HINT_NODE_TYPE;
+ variable->export_info.hint_string = export_type.class_type->identifier->name;
+ } else {
+ push_error(R"(Export type can only be built-in, a resource, a node or an enum.)", variable);
return false;
}
- variable->export_info.type = Variant::OBJECT;
- variable->export_info.hint = PROPERTY_HINT_RESOURCE_TYPE;
- variable->export_info.hint_string = export_type.class_type->identifier->name;
+
break;
case GDScriptParser::DataType::SCRIPT: {
StringName class_name;
@@ -3796,7 +3807,7 @@ bool GDScriptParser::export_annotations(const AnnotationNode *p_annotation, Node
String enum_hint_string;
bool first = true;
- for (const KeyValue<StringName, int> &E : export_type.enum_values) {
+ for (const KeyValue<StringName, int64_t> &E : export_type.enum_values) {
if (!first) {
enum_hint_string += ",";
} else {
diff --git a/modules/gdscript/gdscript_parser.h b/modules/gdscript/gdscript_parser.h
index 1850a44678..d8f5b866aa 100644
--- a/modules/gdscript/gdscript_parser.h
+++ b/modules/gdscript/gdscript_parser.h
@@ -131,7 +131,7 @@ public:
ClassNode *class_type = nullptr;
MethodInfo method_info; // For callable/signals.
- HashMap<StringName, int> enum_values; // For enums.
+ HashMap<StringName, int64_t> enum_values; // For enums.
_FORCE_INLINE_ bool is_set() const { return kind != UNRESOLVED; }
_FORCE_INLINE_ bool has_no_type() const { return type_source == UNDETECTED; }
@@ -469,7 +469,7 @@ public:
EnumNode *parent_enum = nullptr;
int index = -1;
bool resolved = false;
- int value = 0;
+ int64_t value = 0;
int line = 0;
int leftmost_column = 0;
int rightmost_column = 0;
diff --git a/modules/gdscript/gdscript_warning.cpp b/modules/gdscript/gdscript_warning.cpp
index 1cae7bdfac..a0c107aa53 100644
--- a/modules/gdscript/gdscript_warning.cpp
+++ b/modules/gdscript/gdscript_warning.cpp
@@ -155,6 +155,10 @@ String GDScriptWarning::get_message() const {
case INT_ASSIGNED_TO_ENUM: {
return "Integer used when an enum value is expected. If this is intended cast the integer to the enum type.";
}
+ case STATIC_CALLED_ON_INSTANCE: {
+ CHECK_SYMBOLS(2);
+ return vformat(R"(The function '%s()' is a static function but was called from an instance. Instead, it should be directly called from the type: '%s.%s()'.)", symbols[0], symbols[1], symbols[0]);
+ }
case WARNING_MAX:
break; // Can't happen, but silences warning
}
@@ -215,6 +219,7 @@ String GDScriptWarning::get_name_from_code(Code p_code) {
"EMPTY_FILE",
"SHADOWED_GLOBAL_IDENTIFIER",
"INT_ASSIGNED_TO_ENUM",
+ "STATIC_CALLED_ON_INSTANCE",
};
static_assert((sizeof(names) / sizeof(*names)) == WARNING_MAX, "Amount of warning types don't match the amount of warning names.");
diff --git a/modules/gdscript/gdscript_warning.h b/modules/gdscript/gdscript_warning.h
index a639e7b44e..7e4e975510 100644
--- a/modules/gdscript/gdscript_warning.h
+++ b/modules/gdscript/gdscript_warning.h
@@ -78,6 +78,7 @@ public:
EMPTY_FILE, // A script file is empty.
SHADOWED_GLOBAL_IDENTIFIER, // A global class or function has the same name as variable.
INT_ASSIGNED_TO_ENUM, // An integer value was assigned to an enum-typed variable without casting.
+ STATIC_CALLED_ON_INSTANCE, // A static method was called on an instance of a class instead of on the class itself.
WARNING_MAX,
};
diff --git a/modules/gdscript/tests/gdscript_test_runner.cpp b/modules/gdscript/tests/gdscript_test_runner.cpp
index 15131afde7..1ccbf9d150 100644
--- a/modules/gdscript/tests/gdscript_test_runner.cpp
+++ b/modules/gdscript/tests/gdscript_test_runner.cpp
@@ -461,7 +461,6 @@ GDScriptTest::TestResult GDScriptTest::execute_test_code(bool p_is_generating) {
Ref<GDScript> script;
script.instantiate();
script->set_path(source_file);
- script->set_script_path(source_file);
err = script->load_source_code(source_file);
if (err != OK) {
enable_stdout();
diff --git a/modules/gdscript/tests/scripts/analyzer/features/external_inner_base.gd b/modules/gdscript/tests/scripts/analyzer/features/external_inner_base.gd
new file mode 100644
index 0000000000..3f9bfe189c
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/features/external_inner_base.gd
@@ -0,0 +1,4 @@
+extends "inner_base.gd".InnerA.InnerAB
+
+func test():
+ super.test()
diff --git a/modules/gdscript/tests/scripts/analyzer/features/external_inner_base.out b/modules/gdscript/tests/scripts/analyzer/features/external_inner_base.out
new file mode 100644
index 0000000000..62f1383392
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/features/external_inner_base.out
@@ -0,0 +1,3 @@
+GDTEST_OK
+InnerA.InnerAB.test
+InnerB.test
diff --git a/modules/gdscript/tests/scripts/analyzer/features/inner_base.gd b/modules/gdscript/tests/scripts/analyzer/features/inner_base.gd
new file mode 100644
index 0000000000..a825b59255
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/features/inner_base.gd
@@ -0,0 +1,18 @@
+extends InnerA
+
+func test():
+ super.test()
+
+class InnerA extends InnerAB:
+ func test():
+ print("InnerA.test")
+ super.test()
+
+ class InnerAB extends InnerB:
+ func test():
+ print("InnerA.InnerAB.test")
+ super.test()
+
+class InnerB:
+ func test():
+ print("InnerB.test")
diff --git a/modules/gdscript/tests/scripts/analyzer/features/inner_base.out b/modules/gdscript/tests/scripts/analyzer/features/inner_base.out
new file mode 100644
index 0000000000..ddd5ffcfd3
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/features/inner_base.out
@@ -0,0 +1,4 @@
+GDTEST_OK
+InnerA.test
+InnerA.InnerAB.test
+InnerB.test
diff --git a/modules/gdscript/tests/scripts/parser/warnings/static_called_on_instance.gd b/modules/gdscript/tests/scripts/parser/warnings/static_called_on_instance.gd
new file mode 100644
index 0000000000..29d8501b78
--- /dev/null
+++ b/modules/gdscript/tests/scripts/parser/warnings/static_called_on_instance.gd
@@ -0,0 +1,11 @@
+class Player:
+ var x = 3
+
+func test():
+ # These should not emit a warning.
+ var _player = Player.new()
+ print(String.num_uint64(8589934592)) # 2 ^ 33
+
+ # This should emit a warning.
+ var some_string = String()
+ print(some_string.num_uint64(8589934592)) # 2 ^ 33
diff --git a/modules/gdscript/tests/scripts/parser/warnings/static_called_on_instance.out b/modules/gdscript/tests/scripts/parser/warnings/static_called_on_instance.out
new file mode 100644
index 0000000000..3933a35178
--- /dev/null
+++ b/modules/gdscript/tests/scripts/parser/warnings/static_called_on_instance.out
@@ -0,0 +1,7 @@
+GDTEST_OK
+>> WARNING
+>> Line: 11
+>> STATIC_CALLED_ON_INSTANCE
+>> The function 'num_uint64()' is a static function but was called from an instance. Instead, it should be directly called from the type: 'String.num_uint64()'.
+8589934592
+8589934592
diff --git a/modules/gdscript/tests/scripts/runtime/features/compare-builtin-equals-null.gd b/modules/gdscript/tests/scripts/runtime/features/compare-builtin-equals-null.gd
index c6645c2c34..809d0d28a9 100644
--- a/modules/gdscript/tests/scripts/runtime/features/compare-builtin-equals-null.gd
+++ b/modules/gdscript/tests/scripts/runtime/features/compare-builtin-equals-null.gd
@@ -69,6 +69,10 @@ func test():
value = Transform3D()
print(value == null)
+ # Projection
+ value = Projection()
+ print(value == null)
+
# Color
value = Color()
print(value == null)
diff --git a/modules/gdscript/tests/scripts/runtime/features/compare-builtin-equals-null.out b/modules/gdscript/tests/scripts/runtime/features/compare-builtin-equals-null.out
index 639f6027b9..27423ab8e7 100644
--- a/modules/gdscript/tests/scripts/runtime/features/compare-builtin-equals-null.out
+++ b/modules/gdscript/tests/scripts/runtime/features/compare-builtin-equals-null.out
@@ -33,3 +33,4 @@ false
false
false
false
+false
diff --git a/modules/gdscript/tests/scripts/runtime/features/compare-builtin-not-equals-null.gd b/modules/gdscript/tests/scripts/runtime/features/compare-builtin-not-equals-null.gd
index ee622bf22f..f46afb0f18 100644
--- a/modules/gdscript/tests/scripts/runtime/features/compare-builtin-not-equals-null.gd
+++ b/modules/gdscript/tests/scripts/runtime/features/compare-builtin-not-equals-null.gd
@@ -69,6 +69,10 @@ func test():
value = Transform3D()
print(value != null)
+ # Projection
+ value = Projection()
+ print(value != null)
+
# Color
value = Color()
print(value != null)
diff --git a/modules/gdscript/tests/scripts/runtime/features/compare-builtin-not-equals-null.out b/modules/gdscript/tests/scripts/runtime/features/compare-builtin-not-equals-null.out
index d1e332afba..a11c47854a 100644
--- a/modules/gdscript/tests/scripts/runtime/features/compare-builtin-not-equals-null.out
+++ b/modules/gdscript/tests/scripts/runtime/features/compare-builtin-not-equals-null.out
@@ -33,3 +33,4 @@ true
true
true
true
+true
diff --git a/modules/gdscript/tests/scripts/runtime/features/range_optimized_in_for_has_int_iterator.gd b/modules/gdscript/tests/scripts/runtime/features/range_optimized_in_for_has_int_iterator.gd
new file mode 100644
index 0000000000..e24137a20d
--- /dev/null
+++ b/modules/gdscript/tests/scripts/runtime/features/range_optimized_in_for_has_int_iterator.gd
@@ -0,0 +1,60 @@
+func test():
+ # All combinations of 1/2/3 arguments, each being int/float.
+
+ for number in range(5):
+ if typeof(number) != TYPE_INT:
+ print("Number returned from `range` was not an int!")
+
+ for number in range(5.2):
+ if typeof(number) != TYPE_INT:
+ print("Number returned from `range` was not an int!")
+
+
+ for number in range(1, 5):
+ if typeof(number) != TYPE_INT:
+ print("Number returned from `range` was not an int!")
+
+ for number in range(1, 5.2):
+ if typeof(number) != TYPE_INT:
+ print("Number returned from `range` was not an int!")
+
+ for number in range(1.2, 5):
+ if typeof(number) != TYPE_INT:
+ print("Number returned from `range` was not an int!")
+
+ for number in range(1.2, 5.2):
+ if typeof(number) != TYPE_INT:
+ print("Number returned from `range` was not an int!")
+
+
+ for number in range(1, 5, 2):
+ if typeof(number) != TYPE_INT:
+ print("Number returned from `range` was not an int!")
+
+ for number in range(1, 5, 2.2):
+ if typeof(number) != TYPE_INT:
+ print("Number returned from `range` was not an int!")
+
+ for number in range(1, 5.2, 2):
+ if typeof(number) != TYPE_INT:
+ print("Number returned from `range` was not an int!")
+
+ for number in range(1, 5.2, 2.2):
+ if typeof(number) != TYPE_INT:
+ print("Number returned from `range` was not an int!")
+
+ for number in range(1.2, 5, 2):
+ if typeof(number) != TYPE_INT:
+ print("Number returned from `range` was not an int!")
+
+ for number in range(1.2, 5.2, 2):
+ if typeof(number) != TYPE_INT:
+ print("Number returned from `range` was not an int!")
+
+ for number in range(1.2, 5, 2.2):
+ if typeof(number) != TYPE_INT:
+ print("Number returned from `range` was not an int!")
+
+ for number in range(1.2, 5.2, 2.2):
+ if typeof(number) != TYPE_INT:
+ print("Number returned from `range` was not an int!")
diff --git a/modules/gdscript/tests/scripts/runtime/features/range_optimized_in_for_has_int_iterator.out b/modules/gdscript/tests/scripts/runtime/features/range_optimized_in_for_has_int_iterator.out
new file mode 100644
index 0000000000..d73c5eb7cd
--- /dev/null
+++ b/modules/gdscript/tests/scripts/runtime/features/range_optimized_in_for_has_int_iterator.out
@@ -0,0 +1 @@
+GDTEST_OK
diff --git a/modules/gdscript/tests/scripts/runtime/features/range_returns_ints.gd b/modules/gdscript/tests/scripts/runtime/features/range_returns_ints.gd
new file mode 100644
index 0000000000..63c3b84305
--- /dev/null
+++ b/modules/gdscript/tests/scripts/runtime/features/range_returns_ints.gd
@@ -0,0 +1,77 @@
+func test():
+ # All combinations of 1/2/3 arguments, each being int/float.
+ # Store result in variable to ensure actual array is created (avoid `for` + `range` optimization).
+
+ var result
+
+ result = range(5)
+ for number in result:
+ if typeof(number) != TYPE_INT:
+ print("Number returned from `range` was not an int!")
+
+ result = range(5.2)
+ for number in result:
+ if typeof(number) != TYPE_INT:
+ print("Number returned from `range` was not an int!")
+
+
+ result = range(1, 5)
+ for number in result:
+ if typeof(number) != TYPE_INT:
+ print("Number returned from `range` was not an int!")
+
+ result = range(1, 5.2)
+ for number in result:
+ if typeof(number) != TYPE_INT:
+ print("Number returned from `range` was not an int!")
+
+ result = range(1.2, 5)
+ for number in result:
+ if typeof(number) != TYPE_INT:
+ print("Number returned from `range` was not an int!")
+
+ result = range(1.2, 5.2)
+ for number in result:
+ if typeof(number) != TYPE_INT:
+ print("Number returned from `range` was not an int!")
+
+
+ result = range(1, 5, 2)
+ for number in result:
+ if typeof(number) != TYPE_INT:
+ print("Number returned from `range` was not an int!")
+
+ result = range(1, 5, 2.2)
+ for number in result:
+ if typeof(number) != TYPE_INT:
+ print("Number returned from `range` was not an int!")
+
+ result = range(1, 5.2, 2)
+ for number in result:
+ if typeof(number) != TYPE_INT:
+ print("Number returned from `range` was not an int!")
+
+ result = range(1, 5.2, 2.2)
+ for number in result:
+ if typeof(number) != TYPE_INT:
+ print("Number returned from `range` was not an int!")
+
+ result = range(1.2, 5, 2)
+ for number in result:
+ if typeof(number) != TYPE_INT:
+ print("Number returned from `range` was not an int!")
+
+ result = range(1.2, 5.2, 2)
+ for number in result:
+ if typeof(number) != TYPE_INT:
+ print("Number returned from `range` was not an int!")
+
+ result = range(1.2, 5, 2.2)
+ for number in result:
+ if typeof(number) != TYPE_INT:
+ print("Number returned from `range` was not an int!")
+
+ result = range(1.2, 5.2, 2.2)
+ for number in result:
+ if typeof(number) != TYPE_INT:
+ print("Number returned from `range` was not an int!")
diff --git a/modules/gdscript/tests/scripts/runtime/features/range_returns_ints.out b/modules/gdscript/tests/scripts/runtime/features/range_returns_ints.out
new file mode 100644
index 0000000000..d73c5eb7cd
--- /dev/null
+++ b/modules/gdscript/tests/scripts/runtime/features/range_returns_ints.out
@@ -0,0 +1 @@
+GDTEST_OK