diff options
Diffstat (limited to 'modules/gdscript')
101 files changed, 2632 insertions, 861 deletions
diff --git a/modules/gdscript/doc_classes/@GDScript.xml b/modules/gdscript/doc_classes/@GDScript.xml index bc44479f93..4981750b7d 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="""" /> <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. The script must be registered as a global class using the [code]class_name[/code] keyword for this to have a visible effect. The icon specified at [param icon_path] is displayed in the Scene dock for every node of that class, as well as 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 8645aa6f15..0ebdbf090d 100644 --- a/modules/gdscript/editor/gdscript_highlighter.cpp +++ b/modules/gdscript/editor/gdscript_highlighter.cpp @@ -256,7 +256,7 @@ Dictionary GDScriptSyntaxHighlighter::_get_line_syntax_highlighting_impl(int p_l } String word = str.substr(from + 1, to - from); // Keywords need to be exceptions, except for keywords that represent a value. - if (word == "true" || word == "false" || word == "null" || word == "PI" || word == "TAU" || word == "INF" || word == "NAN" || word == "self" || word == "super" || !keywords.has(word)) { + if (word == "true" || word == "false" || word == "null" || word == "PI" || word == "TAU" || word == "INF" || word == "NAN" || word == "self" || word == "super" || !reserved_keywords.has(word)) { if (!is_symbol(str[to]) || str[to] == '"' || str[to] == '\'' || str[to] == ')' || str[to] == ']' || str[to] == '}') { is_binary_op = true; } @@ -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)) { @@ -338,8 +338,10 @@ Dictionary GDScriptSyntaxHighlighter::_get_line_syntax_highlighting_impl(int p_l col = global_function_color; } } - } else if (keywords.has(word)) { - col = keywords[word]; + } else if (class_names.has(word)) { + col = class_names[word]; + } else if (reserved_keywords.has(word)) { + col = reserved_keywords[word]; } else if (member_keywords.has(word)) { col = member_keywords[word]; } @@ -563,7 +565,8 @@ PackedStringArray GDScriptSyntaxHighlighter::_get_supported_languages() const { } void GDScriptSyntaxHighlighter::_update_cache() { - keywords.clear(); + class_names.clear(); + reserved_keywords.clear(); member_keywords.clear(); global_functions.clear(); color_regions.clear(); @@ -580,7 +583,7 @@ void GDScriptSyntaxHighlighter::_update_cache() { List<StringName> types; ClassDB::get_class_list(&types); for (const StringName &E : types) { - keywords[E] = types_color; + class_names[E] = types_color; } /* User types. */ @@ -588,14 +591,14 @@ void GDScriptSyntaxHighlighter::_update_cache() { List<StringName> global_classes; ScriptServer::get_global_class_list(&global_classes); for (const StringName &E : global_classes) { - keywords[E] = usertype_color; + class_names[E] = usertype_color; } /* Autoloads. */ for (const KeyValue<StringName, ProjectSettings::AutoloadInfo> &E : ProjectSettings::get_singleton()->get_autoload_list()) { const ProjectSettings::AutoloadInfo &info = E.value; if (info.is_singleton) { - keywords[info.name] = usertype_color; + class_names[info.name] = usertype_color; } } @@ -606,7 +609,7 @@ void GDScriptSyntaxHighlighter::_update_cache() { List<String> core_types; gdscript->get_core_type_words(&core_types); for (const String &E : core_types) { - keywords[StringName(E)] = basetype_color; + class_names[StringName(E)] = basetype_color; } /* Reserved words. */ @@ -616,9 +619,9 @@ void GDScriptSyntaxHighlighter::_update_cache() { gdscript->get_reserved_words(&keyword_list); for (const String &E : keyword_list) { if (gdscript->is_control_flow_keyword(E)) { - keywords[StringName(E)] = control_flow_keyword_color; + reserved_keywords[StringName(E)] = control_flow_keyword_color; } else { - keywords[StringName(E)] = keyword_color; + reserved_keywords[StringName(E)] = keyword_color; } } @@ -680,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/editor/gdscript_highlighter.h b/modules/gdscript/editor/gdscript_highlighter.h index 60b5b092d4..7c22eb30b1 100644 --- a/modules/gdscript/editor/gdscript_highlighter.h +++ b/modules/gdscript/editor/gdscript_highlighter.h @@ -47,7 +47,8 @@ private: Vector<ColorRegion> color_regions; HashMap<int, int> color_region_cache; - HashMap<StringName, Color> keywords; + HashMap<StringName, Color> class_names; + HashMap<StringName, Color> reserved_keywords; HashMap<StringName, Color> member_keywords; HashSet<StringName> global_functions; diff --git a/modules/gdscript/gdscript.cpp b/modules/gdscript/gdscript.cpp index 0a9dad04c7..91f31174dd 100644 --- a/modules/gdscript/gdscript.cpp +++ b/modules/gdscript/gdscript.cpp @@ -478,7 +478,7 @@ void GDScript::_clear_doc() { void GDScript::_update_doc() { _clear_doc(); - doc.script_path = "\"" + get_path().get_slice("://", 1) + "\""; + doc.script_path = vformat(R"("%s")", get_script_path().get_slice("://", 1)); if (!name.is_empty()) { doc.name = name; } else { @@ -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()); } } } @@ -727,6 +701,7 @@ bool GDScript::_update_exports(bool *r_err, bool p_recursive_call, PlaceHolderSc Variant default_value; if (member.variable->initializer && member.variable->initializer->is_constant) { default_value = member.variable->initializer->reduced_value; + GDScriptCompiler::convert_to_initializer_type(default_value, member.variable); } member_default_values_cache[member.variable->identifier->name] = default_value; } break; @@ -825,22 +800,20 @@ 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() + ")"; + return vformat("%s(%s)", get_name(), get_script_path()); } else { - return get_path(); + return get_script_path(); } } Error GDScript::reload(bool p_keep_state) { + if (reloading) { + return OK; + } + reloading = true; + bool has_instances; { MutexLock lock(GDScriptLanguage::singleton->mutex); @@ -860,9 +833,10 @@ 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())) { + reloading = false; return OK; } #endif @@ -872,11 +846,10 @@ Error GDScript::reload(bool p_keep_state) { if (source_path.is_empty()) { source_path = get_path(); } - if (!source_path.is_empty()) { - MutexLock lock(GDScriptCache::singleton->lock); - if (!GDScriptCache::singleton->shallow_gdscript_cache.has(source_path)) { - GDScriptCache::singleton->shallow_gdscript_cache[source_path] = this; - } + Ref<GDScript> cached_script = GDScriptCache::get_cached_script(source_path); + if (!source_path.is_empty() && cached_script.is_null()) { + MutexLock lock(GDScriptCache::singleton->mutex); + GDScriptCache::singleton->shallow_gdscript_cache[source_path] = Ref<GDScript>(this); } } @@ -889,6 +862,7 @@ Error GDScript::reload(bool p_keep_state) { } // TODO: Show all error messages. _err_print_error("GDScript::reload", path.is_empty() ? "built-in" : (const char *)path.utf8().get_data(), parser.get_errors().front()->get().line, ("Parse Error: " + parser.get_errors().front()->get().message).utf8().get_data(), false, ERR_HANDLER_SCRIPT); + reloading = false; return ERR_PARSE_ERROR; } @@ -905,6 +879,7 @@ Error GDScript::reload(bool p_keep_state) { _err_print_error("GDScript::reload", path.is_empty() ? "built-in" : (const char *)path.utf8().get_data(), e->get().line, ("Parse Error: " + e->get().message).utf8().get_data(), false, ERR_HANDLER_SCRIPT); e = e->next(); } + reloading = false; return ERR_PARSE_ERROR; } @@ -913,18 +888,16 @@ 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()) { GDScriptLanguage::get_singleton()->debug_break_parse(_get_debug_path(), compiler.get_error_line(), "Parser Error: " + compiler.get_error()); } _err_print_error("GDScript::reload", path.is_empty() ? "built-in" : (const char *)path.utf8().get_data(), compiler.get_error_line(), ("Compile Error: " + compiler.get_error()).utf8().get_data(), false, ERR_HANDLER_SCRIPT); + reloading = false; return ERR_COMPILATION_FAILED; } else { + reloading = false; return err; } } @@ -932,19 +905,12 @@ Error GDScript::reload(bool p_keep_state) { for (const GDScriptWarning &warning : parser.get_warnings()) { if (EngineDebugger::is_active()) { Vector<ScriptLanguage::StackInfo> si; - EngineDebugger::get_script_debugger()->send_error("", get_path(), warning.start_line, warning.get_name(), warning.get_message(), false, ERR_HANDLER_WARNING, si); + EngineDebugger::get_script_debugger()->send_error("", get_script_path(), warning.start_line, warning.get_name(), warning.get_message(), false, ERR_HANDLER_WARNING, si); } } #endif - valid = true; - - for (KeyValue<StringName, Ref<GDScript>> &E : subclasses) { - _set_subclass_path(E.value, path); - } - - _init_rpc_methods_properties(); - + reloading = false; return OK; } @@ -1050,7 +1016,27 @@ 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) { + String old_path = path; + if (is_root_script()) { + Script::set_path(p_path, p_take_over); + } + this->path = p_path; + GDScriptCache::move_script(old_path, p_path); + for (KeyValue<StringName, Ref<GDScript>> &kv : subclasses) { + kv.value->set_path(p_path, p_take_over); + } +} + +String GDScript::get_script_path() const { + return path; +} + Error GDScript::load_source_code(const String &p_path) { + if (p_path.is_empty() || ResourceLoader::get_resource_type(p_path.get_slice("::", 0)) == "PackedScene") { + return OK; + } + Vector<uint8_t> sourcef; Error err; Ref<FileAccess> f = FileAccess::open(p_path, FileAccess::READ, &err); @@ -1122,6 +1108,124 @@ 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; +} + +RBSet<GDScript *> GDScript::get_dependencies() { + RBSet<GDScript *> dependencies; + + _get_dependencies(dependencies, this); + dependencies.erase(this); + + return dependencies; +} + +RBSet<GDScript *> GDScript::get_inverted_dependencies() { + RBSet<GDScript *> inverted_dependencies; + + List<GDScript *> scripts; + { + MutexLock lock(GDScriptLanguage::singleton->mutex); + + SelfList<GDScript> *elem = GDScriptLanguage::singleton->script_list.first(); + while (elem) { + scripts.push_back(elem->self()); + elem = elem->next(); + } + } + + for (GDScript *scr : scripts) { + if (scr == nullptr || scr == this || scr->destructing) { + continue; + } + + RBSet<GDScript *> scr_dependencies = scr->get_dependencies(); + if (scr_dependencies.has(this)) { + inverted_dependencies.insert(scr); + } + } + + return inverted_dependencies; +} + +RBSet<GDScript *> GDScript::get_must_clear_dependencies() { + RBSet<GDScript *> dependencies = get_dependencies(); + RBSet<GDScript *> must_clear_dependencies; + HashMap<GDScript *, RBSet<GDScript *>> inverted_dependencies; + + for (GDScript *E : dependencies) { + inverted_dependencies.insert(E, E->get_inverted_dependencies()); + } + + RBSet<GDScript *> cant_clear; + for (KeyValue<GDScript *, RBSet<GDScript *>> &E : inverted_dependencies) { + for (GDScript *F : E.value) { + if (!dependencies.has(F)) { + cant_clear.insert(E.key); + for (GDScript *G : E.key->get_dependencies()) { + cant_clear.insert(G); + } + break; + } + } + } + + for (KeyValue<GDScript *, RBSet<GDScript *>> &E : inverted_dependencies) { + if (cant_clear.has(E.key) || ScriptServer::is_global_class(E.key->get_fully_qualified_name())) { + continue; + } + must_clear_dependencies.insert(E.key); + } + + cant_clear.clear(); + dependencies.clear(); + inverted_dependencies.clear(); + return must_clear_dependencies; +} + bool GDScript::has_script_signal(const StringName &p_signal) const { if (_signals.has(p_signal)) { return true; @@ -1183,15 +1287,76 @@ String GDScript::_get_gdscript_reference_class_name(const GDScript *p_gdscript) return class_name; } +GDScript *GDScript::_get_gdscript_from_variant(const Variant &p_variant) { + Variant::Type type = p_variant.get_type(); + if (type != Variant::Type::OBJECT) + return nullptr; + + Object *obj = p_variant; + if (obj == nullptr) { + return nullptr; + } + + return Object::cast_to<GDScript>(obj); +} + +void GDScript::_get_dependencies(RBSet<GDScript *> &p_dependencies, const GDScript *p_except) { + if (skip_dependencies || p_dependencies.has(this)) { + return; + } + p_dependencies.insert(this); + + for (const KeyValue<StringName, GDScriptFunction *> &E : member_functions) { + if (E.value == nullptr) { + continue; + } + for (const Variant &V : E.value->constants) { + GDScript *scr = _get_gdscript_from_variant(V); + if (scr != nullptr && scr != p_except) { + scr->_get_dependencies(p_dependencies, p_except); + } + } + } + + if (implicit_initializer) { + for (const Variant &V : implicit_initializer->constants) { + GDScript *scr = _get_gdscript_from_variant(V); + if (scr != nullptr && scr != p_except) { + scr->_get_dependencies(p_dependencies, p_except); + } + } + } + + if (implicit_ready) { + for (const Variant &V : implicit_ready->constants) { + GDScript *scr = _get_gdscript_from_variant(V); + if (scr != nullptr && scr != p_except) { + scr->_get_dependencies(p_dependencies, p_except); + } + } + } + + for (KeyValue<StringName, Ref<GDScript>> &E : subclasses) { + if (E.value != p_except) { + E.value->_get_dependencies(p_dependencies, p_except); + } + } + + for (const KeyValue<StringName, Variant> &E : constants) { + GDScript *scr = _get_gdscript_from_variant(E.value); + if (scr != nullptr && scr != p_except) { + scr->_get_dependencies(p_dependencies, p_except); + } + } +} + GDScript::GDScript() : script_list(this) { -#ifdef DEBUG_ENABLED { MutexLock lock(GDScriptLanguage::get_singleton()->mutex); GDScriptLanguage::get_singleton()->script_list.add(&script_list); } -#endif } void GDScript::_save_orphaned_subclasses() { @@ -1233,56 +1398,67 @@ 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; } } } -GDScript::~GDScript() { - { - MutexLock lock(GDScriptLanguage::get_singleton()->mutex); +void GDScript::clear() { + if (clearing) { + return; + } + clearing = true; - while (SelfList<GDScriptFunctionState> *E = pending_func_states.first()) { - // Order matters since clearing the stack may already cause - // the GDSCriptFunctionState to be destroyed and thus removed from the list. - pending_func_states.remove(E); - E->self()->_clear_stack(); + RBSet<GDScript *> must_clear_dependencies = get_must_clear_dependencies(); + HashMap<GDScript *, ObjectID> must_clear_dependencies_objectids; + + // Log the objectids before clearing, as a cascade of clear could + // remove instances that are still in the clear loop + for (GDScript *E : must_clear_dependencies) { + must_clear_dependencies_objectids.insert(E, E->get_instance_id()); + } + + for (GDScript *E : must_clear_dependencies) { + Object *obj = ObjectDB::get_instance(must_clear_dependencies_objectids[E]); + if (obj == nullptr) { + continue; } + + E->skip_dependencies = true; + E->clear(); + E->skip_dependencies = false; + GDScriptCache::remove_script(E->get_path()); } + RBSet<StringName> member_function_names; for (const KeyValue<StringName, GDScriptFunction *> &E : member_functions) { - memdelete(E.value); + member_function_names.insert(E.key); + } + for (const StringName &E : member_function_names) { + if (member_functions.has(E)) { + memdelete(member_functions[E]); + } + } + member_function_names.clear(); + member_functions.clear(); + + for (KeyValue<StringName, GDScript::MemberInfo> &E : member_indices) { + E.value.data_type.script_type_ref = Ref<Script>(); } if (implicit_initializer) { memdelete(implicit_initializer); } + implicit_initializer = nullptr; if (implicit_ready) { memdelete(implicit_ready); } - - if (GDScriptCache::singleton) { // Cache may have been already destroyed at engine shutdown. - GDScriptCache::remove_script(get_path()); - } + implicit_ready = nullptr; _save_orphaned_subclasses(); @@ -1292,14 +1468,37 @@ GDScript::~GDScript() { _clear_doc(); } #endif + clearing = false; +} + +GDScript::~GDScript() { + if (destructing) { + return; + } + destructing = true; + + clear(); + + { + MutexLock lock(GDScriptLanguage::get_singleton()->mutex); + + while (SelfList<GDScriptFunctionState> *E = pending_func_states.first()) { + // Order matters since clearing the stack may already cause + // the GDScriptFunctionState to be destroyed and thus removed from the list. + pending_func_states.remove(E); + E->self()->_clear_stack(); + } + } -#ifdef DEBUG_ENABLED { MutexLock lock(GDScriptLanguage::get_singleton()->mutex); GDScriptLanguage::get_singleton()->script_list.remove(&script_list); } -#endif + + if (GDScriptCache::singleton) { // Cache may have been already destroyed at engine shutdown. + GDScriptCache::remove_script(get_path()); + } } ////////////////////////////// @@ -1821,6 +2020,42 @@ Error GDScriptLanguage::execute_file(const String &p_path) { } void GDScriptLanguage::finish() { + if (_call_stack) { + memdelete_arr(_call_stack); + _call_stack = nullptr; + } + + // Clear the cache before parsing the script_list + GDScriptCache::clear(); + + // Clear dependencies between scripts, to ensure cyclic references are broken + // (to avoid leaks at exit). + SelfList<GDScript> *s = script_list.first(); + while (s) { + // This ensures the current script is not released before we can check + // what's the next one in the list (we can't get the next upfront because we + // don't know if the reference breaking will cause it -or any other after + // it, for that matter- to be released so the next one is not the same as + // before). + Ref<GDScript> scr = s->self(); + if (scr.is_valid()) { + for (KeyValue<StringName, GDScriptFunction *> &E : scr->member_functions) { + GDScriptFunction *func = E.value; + for (int i = 0; i < func->argument_types.size(); i++) { + func->argument_types.write[i].script_type_ref = Ref<Script>(); + } + func->return_type.script_type_ref = Ref<Script>(); + } + for (KeyValue<StringName, GDScript::MemberInfo> &E : scr->member_indices) { + E.value.data_type.script_type_ref = Ref<Script>(); + } + + // Clear backup for scripts that could slip out of the cyclic reference + // check + scr->clear(); + } + s = s->next(); + } } void GDScriptLanguage::profiling_start() { @@ -1930,7 +2165,8 @@ void GDScriptLanguage::reload_all_scripts() { SelfList<GDScript> *elem = script_list.first(); while (elem) { - if (elem->self()->get_path().is_resource_file()) { + // Scripts will reload all subclasses, so only reload root scripts. + if (elem->self()->is_root_script() && elem->self()->get_path().is_resource_file()) { print_verbose("GDScript: Found: " + elem->self()->get_path()); scripts.push_back(Ref<GDScript>(elem->self())); //cast to gdscript to avoid being erased by accident } @@ -1959,7 +2195,8 @@ void GDScriptLanguage::reload_tool_script(const Ref<Script> &p_script, bool p_so SelfList<GDScript> *elem = script_list.first(); while (elem) { - if (elem->self()->get_path().is_resource_file()) { + // Scripts will reload all subclasses, so only reload root scripts. + if (elem->self()->is_root_script() && elem->self()->get_path().is_resource_file()) { scripts.push_back(Ref<GDScript>(elem->self())); //cast to gdscript to avoid being erased by accident } elem = elem->next(); @@ -2332,35 +2569,6 @@ GDScriptLanguage::GDScriptLanguage() { } GDScriptLanguage::~GDScriptLanguage() { - if (_call_stack) { - memdelete_arr(_call_stack); - } - - // Clear dependencies between scripts, to ensure cyclic references are broken (to avoid leaks at exit). - SelfList<GDScript> *s = script_list.first(); - while (s) { - GDScript *scr = s->self(); - // This ensures the current script is not released before we can check what's the next one - // in the list (we can't get the next upfront because we don't know if the reference breaking - // will cause it -or any other after it, for that matter- to be released so the next one - // is not the same as before). - scr->reference(); - - for (KeyValue<StringName, GDScriptFunction *> &E : scr->member_functions) { - GDScriptFunction *func = E.value; - for (int i = 0; i < func->argument_types.size(); i++) { - func->argument_types.write[i].script_type_ref = Ref<Script>(); - } - func->return_type.script_type_ref = Ref<Script>(); - } - for (KeyValue<StringName, GDScript::MemberInfo> &E : scr->member_indices) { - E.value.data_type.script_type_ref = Ref<Script>(); - } - - s = s->next(); - scr->unreference(); - } - singleton = nullptr; } @@ -2382,6 +2590,27 @@ Ref<GDScript> GDScriptLanguage::get_orphan_subclass(const String &p_qualified_na return Ref<GDScript>(Object::cast_to<GDScript>(obj)); } +Ref<GDScript> GDScriptLanguage::get_script_by_fully_qualified_name(const String &p_name) { + { + MutexLock lock(mutex); + + SelfList<GDScript> *elem = script_list.first(); + while (elem) { + GDScript *scr = elem->self(); + scr = scr->find_class(p_name); + if (scr != nullptr) { + return scr; + } + elem = elem->next(); + } + } + + Ref<GDScript> scr; + scr.instantiate(); + scr->fully_qualified_name = p_name; + return scr; +} + /*************** RESOURCE ***************/ Ref<Resource> ResourceFormatLoaderGDScript::load(const String &p_path, const String &p_original_path, Error *r_error, bool p_use_sub_threads, float *r_progress, CacheMode p_cache_mode) { diff --git a/modules/gdscript/gdscript.h b/modules/gdscript/gdscript.h index 0a010e5ad0..7911ea47ec 100644 --- a/modules/gdscript/gdscript.h +++ b/modules/gdscript/gdscript.h @@ -61,6 +61,8 @@ class GDScript : public Script { GDCLASS(GDScript, Script); bool tool = false; bool valid = false; + bool reloading = false; + bool skip_dependencies = false; struct MemberInfo { int index = 0; @@ -124,6 +126,8 @@ class GDScript : public Script { int subclass_count = 0; RBSet<Object *> instances; + bool destructing = false; + bool clearing = false; //exported members String source; String path; @@ -137,7 +141,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 @@ -164,6 +167,9 @@ class GDScript : public Script { // This method will map the class name from "RefCounted" to "MyClass.InnerClass". static String _get_gdscript_reference_class_name(const GDScript *p_gdscript); + GDScript *_get_gdscript_from_variant(const Variant &p_variant); + void _get_dependencies(RBSet<GDScript *> &p_dependencies, const GDScript *p_except); + protected: bool _get(const StringName &p_name, Variant &r_ret) const; bool _set(const StringName &p_name, const Variant &p_value); @@ -174,10 +180,17 @@ protected: static void _bind_methods(); public: + void clear(); + virtual bool is_valid() const override { return valid; } 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; } @@ -189,6 +202,10 @@ public: const Ref<GDScriptNativeClass> &get_native() const { return native; } const String &get_script_class_name() const { return name; } + RBSet<GDScript *> get_dependencies(); + RBSet<GDScript *> get_inverted_dependencies(); + RBSet<GDScript *> get_must_clear_dependencies(); + virtual bool has_script_signal(const StringName &p_signal) const override; virtual void get_script_signal_list(List<MethodInfo> *r_signals) const override; @@ -222,7 +239,8 @@ 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; + String get_script_path() const; Error load_source_code(const String &p_path); Error load_byte_code(const String &p_path); @@ -266,6 +284,7 @@ class GDScriptInstance : public ScriptInstance { friend class GDScriptLambdaCallable; friend class GDScriptLambdaSelfCallable; friend class GDScriptCompiler; + friend class GDScriptCache; friend struct GDScriptUtilityFunctionsDefinitions; ObjectID owner_id; @@ -414,7 +433,7 @@ public: csi.write[_debug_call_stack_pos - i - 1].line = _call_stack[i].line ? *_call_stack[i].line : 0; if (_call_stack[i].function) { csi.write[_debug_call_stack_pos - i - 1].func = _call_stack[i].function->get_name(); - csi.write[_debug_call_stack_pos - i - 1].file = _call_stack[i].function->get_script()->get_path(); + csi.write[_debug_call_stack_pos - i - 1].file = _call_stack[i].function->get_script()->get_script_path(); } } return csi; @@ -514,6 +533,8 @@ public: void add_orphan_subclass(const String &p_qualified_name, const ObjectID &p_subclass); Ref<GDScript> get_orphan_subclass(const String &p_qualified_name); + Ref<GDScript> get_script_by_fully_qualified_name(const String &p_name); + GDScriptLanguage(); ~GDScriptLanguage(); }; diff --git a/modules/gdscript/gdscript_analyzer.cpp b/modules/gdscript/gdscript_analyzer.cpp index e1beb2f374..9ec53b2b66 100644 --- a/modules/gdscript/gdscript_analyzer.cpp +++ b/modules/gdscript/gdscript_analyzer.cpp @@ -32,6 +32,7 @@ #include "core/config/engine.h" #include "core/config/project_settings.h" +#include "core/core_string_names.h" #include "core/io/file_access.h" #include "core/io/resource_loader.h" #include "core/object/class_db.h" @@ -39,6 +40,7 @@ #include "core/templates/hash_map.h" #include "gdscript.h" #include "gdscript_utility_functions.h" +#include "scene/resources/packed_scene.h" static MethodInfo info_from_utility_func(const StringName &p_function) { ERR_FAIL_COND_V(!Variant::has_utility_function(p_function), MethodInfo()); @@ -132,7 +134,7 @@ static GDScriptParser::DataType make_builtin_meta_type(Variant::Type p_type) { return type; } -bool GDScriptAnalyzer::has_member_name_conflict_in_script_class(const StringName &p_member_name, const GDScriptParser::ClassNode *p_class) { +bool GDScriptAnalyzer::has_member_name_conflict_in_script_class(const StringName &p_member_name, const GDScriptParser::ClassNode *p_class, const GDScriptParser::Node *p_member) { if (p_class->members_indices.has(p_member_name)) { int index = p_class->members_indices[p_member_name]; const GDScriptParser::ClassNode::Member *member = &p_class->members[index]; @@ -145,6 +147,9 @@ bool GDScriptAnalyzer::has_member_name_conflict_in_script_class(const StringName member->type == GDScriptParser::ClassNode::Member::SIGNAL) { return true; } + if (p_member->type != GDScriptParser::Node::FUNCTION && member->type == GDScriptParser::ClassNode::Member::FUNCTION) { + return true; + } } return false; @@ -160,6 +165,9 @@ bool GDScriptAnalyzer::has_member_name_conflict_in_native_type(const StringName if (ClassDB::has_integer_constant(p_native_type_string, p_member_name)) { return true; } + if (p_member_name == CoreStringNames::get_singleton()->_script) { + return true; + } return false; } @@ -187,14 +195,18 @@ Error GDScriptAnalyzer::check_class_member_name_conflict(const GDScriptParser::C const GDScriptParser::DataType *current_data_type = &p_class_node->base_type; while (current_data_type && current_data_type->kind == GDScriptParser::DataType::Kind::CLASS) { GDScriptParser::ClassNode *current_class_node = current_data_type->class_type; - if (has_member_name_conflict_in_script_class(p_member_name, current_class_node)) { - push_error(vformat(R"(The member "%s" already exists in a parent class.)", p_member_name), - p_member_node); + if (has_member_name_conflict_in_script_class(p_member_name, current_class_node, p_member_node)) { + String parent_class_name = current_class_node->fqcn; + if (current_class_node->identifier != nullptr) { + parent_class_name = current_class_node->identifier->name; + } + push_error(vformat(R"(The member "%s" already exists in parent class %s.)", p_member_name, parent_class_name), p_member_node); return ERR_PARSE_ERROR; } current_data_type = ¤t_class_node->base_type; } + // No need for native class recursion because Node exposes all Object's properties. if (current_data_type && current_data_type->kind == GDScriptParser::DataType::Kind::NATIVE) { if (current_data_type->native_type != StringName()) { return check_native_member_name_conflict( @@ -207,25 +219,76 @@ Error GDScriptAnalyzer::check_class_member_name_conflict(const GDScriptParser::C return OK; } -Error GDScriptAnalyzer::resolve_inheritance(GDScriptParser::ClassNode *p_class, bool p_recursive) { - if (p_class->base_type.is_set()) { - // Already resolved +void GDScriptAnalyzer::get_class_node_current_scope_classes(GDScriptParser::ClassNode *p_node, List<GDScriptParser::ClassNode *> *p_list) { + if (p_list->find(p_node) != nullptr) { + return; + } + p_list->push_back(p_node); + + // TODO: Try to solve class inheritance if not yet resolving. + + // Prioritize node base type over its outer class + if (p_node->base_type.class_type != nullptr) { + get_class_node_current_scope_classes(p_node->base_type.class_type, p_list); + } + + if (p_node->outer != nullptr) { + get_class_node_current_scope_classes(p_node->outer, p_list); + } +} + +Error GDScriptAnalyzer::resolve_class_inheritance(GDScriptParser::ClassNode *p_class, const GDScriptParser::Node *p_source) { + if (p_source == nullptr && parser->has_class(p_class)) { + p_source = p_class; + } + + if (p_class->base_type.is_resolving()) { + push_error(vformat(R"(Could not resolve class "%s": Cyclic reference.)", type_from_metatype(p_class->get_datatype()).to_string()), p_source); + return ERR_PARSE_ERROR; + } + + if (!p_class->base_type.has_no_type()) { + // Already resolved. 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; + if (!parser->has_class(p_class)) { + String script_path = p_class->get_datatype().script_path; + Ref<GDScriptParserRef> parser_ref = get_parser_for(script_path); + if (parser_ref.is_null()) { + push_error(vformat(R"(Could not find script "%s".)", script_path), p_source); + return ERR_PARSE_ERROR; } - } else { - p_class->fqcn = p_class->outer->fqcn + "::" + String(p_class->identifier->name); + + Error err = parser_ref->raise_status(GDScriptParserRef::PARSED); + if (err) { + push_error(vformat(R"(Could not parse script "%s": %s.)", script_path, error_names[err]), p_source); + return ERR_PARSE_ERROR; + } + + ERR_FAIL_COND_V_MSG(!parser_ref->get_parser()->has_class(p_class), ERR_PARSE_ERROR, R"(Parser bug: Mismatched external parser.)"); + + GDScriptAnalyzer *other_analyzer = parser_ref->get_analyzer(); + GDScriptParser *other_parser = parser_ref->get_parser(); + + int error_count = other_parser->errors.size(); + other_analyzer->resolve_class_inheritance(p_class); + if (other_parser->errors.size() > error_count) { + push_error(vformat(R"(Could not resolve inheritance for class "%s".)", p_class->fqcn), p_source); + return ERR_PARSE_ERROR; + } + + return OK; } + GDScriptParser::ClassNode *previous_class = parser->current_class; + parser->current_class = p_class; + if (p_class->identifier) { StringName class_name = p_class->identifier->name; - if (class_exists(class_name)) { + if (GDScriptParser::get_builtin_type(class_name) < Variant::VARIANT_MAX) { + push_error(vformat(R"(Class "%s" hides a built-in type.)", class_name), p_class->identifier); + } else if (class_exists(class_name)) { push_error(vformat(R"(Class "%s" hides a native class.)", class_name), p_class->identifier); } else if (ScriptServer::is_global_class(class_name) && (ScriptServer::get_global_class_path(class_name) != parser->script_path || p_class != parser->head)) { push_error(vformat(R"(Class "%s" hides a global script class.)", class_name), p_class->identifier); @@ -234,7 +297,9 @@ Error GDScriptAnalyzer::resolve_inheritance(GDScriptParser::ClassNode *p_class, } } - GDScriptParser::DataType result; + GDScriptParser::DataType resolving_datatype; + resolving_datatype.kind = GDScriptParser::DataType::RESOLVING; + p_class->base_type = resolving_datatype; // Set datatype for class. GDScriptParser::DataType class_type; @@ -247,6 +312,7 @@ Error GDScriptAnalyzer::resolve_inheritance(GDScriptParser::ClassNode *p_class, class_type.builtin_type = Variant::OBJECT; p_class->set_datatype(class_type); + GDScriptParser::DataType result; if (!p_class->extends_used) { result.type_source = GDScriptParser::DataType::ANNOTATED_INFERRED; result.kind = GDScriptParser::DataType::NATIVE; @@ -268,7 +334,7 @@ Error GDScriptAnalyzer::resolve_inheritance(GDScriptParser::ClassNode *p_class, return ERR_PARSE_ERROR; } - Error err = ext_parser->raise_status(GDScriptParserRef::INTERFACE_SOLVED); + Error err = ext_parser->raise_status(GDScriptParserRef::INHERITANCE_SOLVED); if (err != OK) { push_error(vformat(R"(Could not resolve super class inheritance from "%s".)", p_class->extends_path), p_class); return err; @@ -295,7 +361,7 @@ Error GDScriptAnalyzer::resolve_inheritance(GDScriptParser::ClassNode *p_class, return ERR_PARSE_ERROR; } - Error err = base_parser->raise_status(GDScriptParserRef::INTERFACE_SOLVED); + Error err = base_parser->raise_status(GDScriptParserRef::INHERITANCE_SOLVED); if (err != OK) { push_error(vformat(R"(Could not resolve super class inheritance from "%s".)", name), p_class); return err; @@ -304,7 +370,7 @@ Error GDScriptAnalyzer::resolve_inheritance(GDScriptParser::ClassNode *p_class, } } else if (ProjectSettings::get_singleton()->has_autoload(name) && ProjectSettings::get_singleton()->get_autoload(name).is_singleton) { const ProjectSettings::AutoloadInfo &info = ProjectSettings::get_singleton()->get_autoload(name); - if (info.path.get_extension().to_lower() != ".gd") { + if (info.path.get_extension().to_lower() != GDScriptLanguage::get_singleton()->get_extension()) { push_error(vformat(R"(Singleton %s is not a GDScript.)", info.name), p_class); return ERR_PARSE_ERROR; } @@ -315,22 +381,24 @@ Error GDScriptAnalyzer::resolve_inheritance(GDScriptParser::ClassNode *p_class, return ERR_PARSE_ERROR; } - Error err = info_parser->raise_status(GDScriptParserRef::INTERFACE_SOLVED); + Error err = info_parser->raise_status(GDScriptParserRef::INHERITANCE_SOLVED); if (err != OK) { push_error(vformat(R"(Could not resolve super class inheritance from "%s".)", name), p_class); return err; } + base = info_parser->get_parser()->head->get_datatype(); } else if (class_exists(name) && ClassDB::can_instantiate(name)) { base.kind = GDScriptParser::DataType::NATIVE; base.native_type = name; } else { // Look for other classes in script. - GDScriptParser::ClassNode *look_class = p_class; bool found = false; - while (look_class != nullptr) { + List<GDScriptParser::ClassNode *> script_classes; + get_class_node_current_scope_classes(p_class, &script_classes); + for (GDScriptParser::ClassNode *look_class : script_classes) { if (look_class->identifier && look_class->identifier->name == name) { if (!look_class->get_datatype().is_set()) { - Error err = resolve_inheritance(look_class, false); + Error err = resolve_class_inheritance(look_class, p_class); if (err) { return err; } @@ -339,19 +407,12 @@ Error GDScriptAnalyzer::resolve_inheritance(GDScriptParser::ClassNode *p_class, found = true; break; } - if (look_class->members_indices.has(name) && look_class->get_member(name).type == GDScriptParser::ClassNode::Member::CLASS) { - GDScriptParser::ClassNode::Member member = look_class->get_member(name); - if (!member.m_class->get_datatype().is_set()) { - Error err = resolve_inheritance(member.m_class, false); - if (err) { - return err; - } - } - base = member.m_class->get_datatype(); + if (look_class->has_member(name)) { + resolve_class_member(look_class, name, p_class); + base = look_class->get_member(name).get_datatype(); found = true; break; } - look_class = look_class->outer; } if (!found) { @@ -384,7 +445,7 @@ Error GDScriptAnalyzer::resolve_inheritance(GDScriptParser::ClassNode *p_class, result = base; } - if (!result.is_set()) { + if (!result.is_set() || result.has_no_type()) { // TODO: More specific error messages. push_error(vformat(R"(Could not resolve inheritance for class "%s".)", p_class->identifier == nullptr ? "<main>" : p_class->identifier->name), p_class); return ERR_PARSE_ERROR; @@ -404,10 +465,21 @@ Error GDScriptAnalyzer::resolve_inheritance(GDScriptParser::ClassNode *p_class, class_type.native_type = result.native_type; p_class->set_datatype(class_type); + parser->current_class = previous_class; + + return OK; +} + +Error GDScriptAnalyzer::resolve_class_inheritance(GDScriptParser::ClassNode *p_class, bool p_recursive) { + Error err = resolve_class_inheritance(p_class); + if (err) { + return err; + } + if (p_recursive) { for (int i = 0; i < p_class->members.size(); i++) { if (p_class->members[i].type == GDScriptParser::ClassNode::Member::CLASS) { - Error err = resolve_inheritance(p_class->members[i].m_class, true); + err = resolve_class_inheritance(p_class->members[i].m_class, true); if (err) { return err; } @@ -419,14 +491,29 @@ Error GDScriptAnalyzer::resolve_inheritance(GDScriptParser::ClassNode *p_class, } GDScriptParser::DataType GDScriptAnalyzer::resolve_datatype(GDScriptParser::TypeNode *p_type) { - GDScriptParser::DataType result; + GDScriptParser::DataType bad_type; + bad_type.kind = GDScriptParser::DataType::VARIANT; + bad_type.type_source = GDScriptParser::DataType::INFERRED; if (p_type == nullptr) { - result.kind = GDScriptParser::DataType::VARIANT; - return result; + return bad_type; } - result.type_source = result.ANNOTATED_EXPLICIT; + if (p_type->get_datatype().is_resolving()) { + push_error(R"(Could not resolve datatype: Cyclic reference.)", p_type); + return bad_type; + } + + if (!p_type->get_datatype().has_no_type()) { + return p_type->get_datatype(); + } + + GDScriptParser::DataType resolving_datatype; + resolving_datatype.kind = GDScriptParser::DataType::RESOLVING; + p_type->set_datatype(resolving_datatype); + + GDScriptParser::DataType result; + result.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT; result.builtin_type = Variant::OBJECT; if (p_type->type_chain.is_empty()) { @@ -440,29 +527,21 @@ GDScriptParser::DataType GDScriptAnalyzer::resolve_datatype(GDScriptParser::Type StringName first = p_type->type_chain[0]->name; if (first == SNAME("Variant")) { - result.kind = GDScriptParser::DataType::VARIANT; if (p_type->type_chain.size() > 1) { - push_error(R"("Variant" type don't contain nested types.)", p_type->type_chain[1]); - return GDScriptParser::DataType(); + // TODO: Variant does actually have a nested Type though. + push_error(R"(Variant doesn't contain nested types.)", p_type->type_chain[1]); + return bad_type; } - return result; - } - - if (first == SNAME("Object")) { + result.kind = GDScriptParser::DataType::VARIANT; + } else if (first == SNAME("Object")) { + // Object is treated like a native type, not a built-in. result.kind = GDScriptParser::DataType::NATIVE; result.native_type = SNAME("Object"); - if (p_type->type_chain.size() > 1) { - push_error(R"("Object" type don't contain nested types.)", p_type->type_chain[1]); - return GDScriptParser::DataType(); - } - return result; - } - - if (GDScriptParser::get_builtin_type(first) < Variant::VARIANT_MAX) { + } else if (GDScriptParser::get_builtin_type(first) < Variant::VARIANT_MAX) { // Built-in types. if (p_type->type_chain.size() > 1) { push_error(R"(Built-in types don't contain nested types.)", p_type->type_chain[1]); - return GDScriptParser::DataType(); + return bad_type; } result.kind = GDScriptParser::DataType::BUILTIN; result.builtin_type = GDScriptParser::get_builtin_type(first); @@ -488,15 +567,15 @@ GDScriptParser::DataType GDScriptAnalyzer::resolve_datatype(GDScriptParser::Type String ext = path.get_extension(); if (ext == GDScriptLanguage::get_singleton()->get_extension()) { Ref<GDScriptParserRef> ref = get_parser_for(path); - if (!ref.is_valid() || ref->raise_status(GDScriptParserRef::INTERFACE_SOLVED) != OK) { + if (!ref.is_valid() || ref->raise_status(GDScriptParserRef::INHERITANCE_SOLVED) != OK) { push_error(vformat(R"(Could not parse global class "%s" from "%s".)", first, ScriptServer::get_global_class_path(first)), p_type); - return GDScriptParser::DataType(); + return bad_type; } result = ref->get_parser()->head->get_datatype(); } else { result.kind = GDScriptParser::DataType::SCRIPT; - result.native_type = ScriptServer::get_global_class_native_base(first); result.script_type = ResourceLoader::load(path, "Script"); + result.native_type = result.script_type->get_instance_base_type(); result.script_path = path; result.is_constant = true; result.is_meta_type = false; @@ -505,9 +584,9 @@ GDScriptParser::DataType GDScriptAnalyzer::resolve_datatype(GDScriptParser::Type } else if (ProjectSettings::get_singleton()->has_autoload(first) && ProjectSettings::get_singleton()->get_autoload(first).is_singleton) { const ProjectSettings::AutoloadInfo &autoload = ProjectSettings::get_singleton()->get_autoload(first); Ref<GDScriptParserRef> ref = get_parser_for(autoload.path); - if (ref->raise_status(GDScriptParserRef::INTERFACE_SOLVED) != OK) { + if (ref->raise_status(GDScriptParserRef::INHERITANCE_SOLVED) != OK) { push_error(vformat(R"(Could not parse singleton "%s" from "%s".)", first, autoload.path), p_type); - return GDScriptParser::DataType(); + return bad_type; } result = ref->get_parser()->head->get_datatype(); } else if (ClassDB::has_enum(parser->current_class->base_type.native_type, first)) { @@ -515,38 +594,36 @@ GDScriptParser::DataType GDScriptAnalyzer::resolve_datatype(GDScriptParser::Type result = make_native_enum_type(parser->current_class->base_type.native_type, first); } else { // Classes in current scope. - GDScriptParser::ClassNode *script_class = parser->current_class; - bool found = false; - while (!found && script_class != nullptr) { + List<GDScriptParser::ClassNode *> script_classes; + get_class_node_current_scope_classes(parser->current_class, &script_classes); + for (GDScriptParser::ClassNode *script_class : script_classes) { if (script_class->identifier && script_class->identifier->name == first) { result = script_class->get_datatype(); - found = true; break; } if (script_class->members_indices.has(first)) { - GDScriptParser::ClassNode::Member member = script_class->members[script_class->members_indices[first]]; + resolve_class_member(script_class, first, p_type); + + GDScriptParser::ClassNode::Member member = script_class->get_member(first); switch (member.type) { case GDScriptParser::ClassNode::Member::CLASS: - result = member.m_class->get_datatype(); - found = true; + result = member.get_datatype(); break; case GDScriptParser::ClassNode::Member::ENUM: - result = member.m_enum->get_datatype(); - found = true; + result = member.get_datatype(); break; case GDScriptParser::ClassNode::Member::CONSTANT: - if (member.constant->get_datatype().is_meta_type) { - result = member.constant->get_datatype(); + if (member.get_datatype().is_meta_type) { + result = member.get_datatype(); result.is_meta_type = false; - found = true; break; } else if (Ref<Script>(member.constant->initializer->reduced_value).is_valid()) { Ref<GDScript> gdscript = member.constant->initializer->reduced_value; if (gdscript.is_valid()) { - Ref<GDScriptParserRef> ref = get_parser_for(gdscript->get_path()); - if (ref->raise_status(GDScriptParserRef::INTERFACE_SOLVED) != OK) { - push_error(vformat(R"(Could not parse script from "%s".)", gdscript->get_path()), p_type); - return GDScriptParser::DataType(); + Ref<GDScriptParserRef> ref = get_parser_for(gdscript->get_script_path()); + if (ref->raise_status(GDScriptParserRef::INHERITANCE_SOLVED) != OK) { + push_error(vformat(R"(Could not parse script from "%s".)", gdscript->get_script_path()), p_type); + return bad_type; } result = ref->get_parser()->head->get_datatype(); result.is_meta_type = false; @@ -564,16 +641,14 @@ GDScriptParser::DataType GDScriptAnalyzer::resolve_datatype(GDScriptParser::Type [[fallthrough]]; default: push_error(vformat(R"("%s" is a %s but does not contain a type.)", first, member.get_type_name()), p_type); - return GDScriptParser::DataType(); + return bad_type; } } - script_class = script_class->outer; } } if (!result.is_set()) { push_error(vformat(R"("%s" was not found in the current scope.)", first), p_type); - result.kind = GDScriptParser::DataType::VARIANT; // Leave Variant anyway so future type check don't use an unresolved type. - return result; + return bad_type; } if (p_type->type_chain.size() > 1) { @@ -584,27 +659,26 @@ GDScriptParser::DataType GDScriptAnalyzer::resolve_datatype(GDScriptParser::Type result = p_type->type_chain[i]->get_datatype(); if (!result.is_set()) { push_error(vformat(R"(Could not find type "%s" under base "%s".)", p_type->type_chain[i]->name, base.to_string()), p_type->type_chain[1]); - result.kind = GDScriptParser::DataType::VARIANT; // Leave Variant anyway so future type check don't use an unresolved type. - return result; + return bad_type; } else if (!result.is_meta_type) { push_error(vformat(R"(Member "%s" under base "%s" is not a valid type.)", p_type->type_chain[i]->name, base.to_string()), p_type->type_chain[1]); - result.kind = GDScriptParser::DataType::VARIANT; // Leave Variant anyway so future type check don't use an unresolved type. - return result; + return bad_type; } } } else if (result.kind == GDScriptParser::DataType::NATIVE) { // Only enums allowed for native. - if (ClassDB::has_enum(result.native_type, p_type->type_chain[1]->name)) { - if (p_type->type_chain.size() > 2) { - push_error(R"(Enums cannot contain nested types.)", p_type->type_chain[2]); - } else { - result = make_native_enum_type(result.native_type, p_type->type_chain[1]->name); - } + if (!ClassDB::has_enum(result.native_type, p_type->type_chain[1]->name)) { + push_error(vformat(R"(Could not find nested type "%s" under base "%s".)", p_type->type_chain[1]->name, result.to_string()), p_type->type_chain[1]); + return bad_type; + } + if (p_type->type_chain.size() > 2) { + push_error(R"(Enums cannot contain nested types.)", p_type->type_chain[2]); + return bad_type; } + result = make_native_enum_type(result.native_type, p_type->type_chain[1]->name); } else { push_error(vformat(R"(Could not find nested type "%s" under base "%s".)", p_type->type_chain[1]->name, result.to_string()), p_type->type_chain[1]); - result.kind = GDScriptParser::DataType::VARIANT; // Leave Variant anyway so future type check don't use an unresolved type. - return result; + return bad_type; } } @@ -616,22 +690,77 @@ GDScriptParser::DataType GDScriptAnalyzer::resolve_datatype(GDScriptParser::Type return result; } -void GDScriptAnalyzer::resolve_class_interface(GDScriptParser::ClassNode *p_class) { - if (p_class->resolved_interface) { +void GDScriptAnalyzer::resolve_class_member(GDScriptParser::ClassNode *p_class, StringName p_name, const GDScriptParser::Node *p_source) { + ERR_FAIL_COND(!p_class->has_member(p_name)); + resolve_class_member(p_class, p_class->members_indices[p_name], p_source); +} + +void GDScriptAnalyzer::resolve_class_member(GDScriptParser::ClassNode *p_class, int p_index, const GDScriptParser::Node *p_source) { + ERR_FAIL_INDEX(p_index, p_class->members.size()); + + GDScriptParser::ClassNode::Member &member = p_class->members.write[p_index]; + if (p_source == nullptr && parser->has_class(p_class)) { + p_source = member.get_source_node(); + } + + if (member.get_datatype().is_resolving()) { + push_error(vformat(R"(Could not resolve member "%s": Cyclic reference.)", member.get_name()), p_source); + return; + } + + if (member.get_datatype().is_set()) { + return; + } + + if (!parser->has_class(p_class)) { + String script_path = p_class->get_datatype().script_path; + Ref<GDScriptParserRef> parser_ref = get_parser_for(script_path); + if (parser_ref.is_null()) { + push_error(vformat(R"(Could not find script "%s" (While resolving "%s").)", script_path, member.get_name()), p_source); + return; + } + + Error err = parser_ref->raise_status(GDScriptParserRef::PARSED); + if (err) { + push_error(vformat(R"(Could not resolve script "%s": %s (While resolving "%s").)", script_path, error_names[err], member.get_name()), p_source); + return; + } + + ERR_FAIL_COND_MSG(!parser_ref->get_parser()->has_class(p_class), R"(Parser bug: Mismatched external parser.)"); + + GDScriptAnalyzer *other_analyzer = parser_ref->get_analyzer(); + GDScriptParser *other_parser = parser_ref->get_parser(); + + int error_count = other_parser->errors.size(); + other_analyzer->resolve_class_member(p_class, p_index); + if (other_parser->errors.size() > error_count) { + push_error(vformat(R"(Could not resolve member "%s".)", member.get_name()), p_source); + } + return; } - p_class->resolved_interface = true; + + // If it's already resolving, that's ok. + if (!p_class->base_type.is_resolving()) { + Error err = resolve_class_inheritance(p_class); + if (err) { + return; + } + } GDScriptParser::ClassNode *previous_class = parser->current_class; parser->current_class = p_class; - for (int i = 0; i < p_class->members.size(); i++) { - GDScriptParser::ClassNode::Member member = p_class->members[i]; + GDScriptParser::DataType resolving_datatype; + resolving_datatype.kind = GDScriptParser::DataType::RESOLVING; + { switch (member.type) { case GDScriptParser::ClassNode::Member::VARIABLE: { check_class_member_name_conflict(p_class, member.variable->identifier->name, member.variable); + member.variable->set_datatype(resolving_datatype); + GDScriptParser::DataType datatype; datatype.kind = GDScriptParser::DataType::VARIANT; datatype.type_source = GDScriptParser::DataType::UNDETECTED; @@ -643,7 +772,6 @@ void GDScriptAnalyzer::resolve_class_interface(GDScriptParser::ClassNode *p_clas } if (member.variable->initializer != nullptr) { - member.variable->set_datatype(datatype); // Allow recursive usage. reduce_expression(member.variable->initializer); if ((member.variable->infer_datatype || (member.variable->datatype_specifier != nullptr && specified_type.has_container_element_type())) && member.variable->initializer->type == GDScriptParser::Node::ARRAY) { // Typed array. @@ -654,18 +782,14 @@ void GDScriptAnalyzer::resolve_class_interface(GDScriptParser::ClassNode *p_clas } } datatype = member.variable->initializer->get_datatype(); + if (datatype.type_source != GDScriptParser::DataType::UNDETECTED) { datatype.type_source = GDScriptParser::DataType::INFERRED; } - } - // Check if initializer is an unset identifier (ie: a variable within scope, but declared below) - if (member.variable->initializer && !member.variable->initializer->get_datatype().is_set()) { - if (member.variable->initializer->type == GDScriptParser::Node::IDENTIFIER) { - GDScriptParser::IdentifierNode *initializer_identifier = static_cast<GDScriptParser::IdentifierNode *>(member.variable->initializer); - push_error(vformat(R"(Identifier "%s" must be declared above current variable.)", initializer_identifier->name), member.variable->initializer); - } else { - ERR_PRINT("Parser bug (please report): tried to assign unset node without an identifier."); + if (!datatype.is_set()) { + push_error(vformat(R"(Could not resolve initializer for member "%s".)", member.variable->identifier->name), member.variable->initializer); + datatype.kind = GDScriptParser::DataType::VARIANT; } } @@ -717,10 +841,9 @@ void GDScriptAnalyzer::resolve_class_interface(GDScriptParser::ClassNode *p_clas case GDScriptParser::ClassNode::Member::CONSTANT: { check_class_member_name_conflict(p_class, member.constant->identifier->name, member.constant); - reduce_expression(member.constant->initializer); + member.constant->set_datatype(resolving_datatype); GDScriptParser::DataType specified_type; - if (member.constant->datatype_specifier != nullptr) { specified_type = resolve_datatype(member.constant->datatype_specifier); specified_type.is_meta_type = false; @@ -728,7 +851,9 @@ void GDScriptAnalyzer::resolve_class_interface(GDScriptParser::ClassNode *p_clas GDScriptParser::DataType datatype; if (member.constant->initializer) { + reduce_expression(member.constant->initializer); datatype = member.constant->initializer->get_datatype(); + if (member.constant->initializer->type == GDScriptParser::Node::ARRAY) { GDScriptParser::ArrayNode *array = static_cast<GDScriptParser::ArrayNode *>(member.constant->initializer); const_fold_array(array); @@ -741,6 +866,11 @@ void GDScriptAnalyzer::resolve_class_interface(GDScriptParser::ClassNode *p_clas const_fold_dictionary(static_cast<GDScriptParser::DictionaryNode *>(member.constant->initializer)); } + if (!datatype.is_set()) { + push_error(vformat(R"(Could not resolve initializer for member "%s".)", member.constant->identifier->name), member.constant->initializer); + datatype.kind = GDScriptParser::DataType::VARIANT; + } + if (!member.constant->initializer->is_constant) { push_error(R"(Initializer for a constant must be a constant expression.)", member.constant->initializer); } @@ -769,6 +899,8 @@ void GDScriptAnalyzer::resolve_class_interface(GDScriptParser::ClassNode *p_clas case GDScriptParser::ClassNode::Member::SIGNAL: { check_class_member_name_conflict(p_class, member.signal->identifier->name, member.signal); + member.signal->set_datatype(resolving_datatype); + for (int j = 0; j < member.signal->parameters.size(); j++) { GDScriptParser::DataType signal_type = resolve_datatype(member.signal->parameters[j]->datatype_specifier); signal_type.is_meta_type = false; @@ -790,6 +922,8 @@ void GDScriptAnalyzer::resolve_class_interface(GDScriptParser::ClassNode *p_clas case GDScriptParser::ClassNode::Member::ENUM: { check_class_member_name_conflict(p_class, member.m_enum->identifier->name, member.m_enum); + member.m_enum->set_datatype(resolving_datatype); + GDScriptParser::DataType enum_type; enum_type.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT; enum_type.kind = GDScriptParser::DataType::ENUM; @@ -799,7 +933,7 @@ void GDScriptAnalyzer::resolve_class_interface(GDScriptParser::ClassNode *p_clas enum_type.is_meta_type = true; enum_type.is_constant = true; - // Enums can't be nested, so we can safely override this. + const GDScriptParser::EnumNode *prev_enum = current_enum; current_enum = member.m_enum; for (int j = 0; j < member.m_enum->values.size(); j++) { @@ -827,7 +961,7 @@ void GDScriptAnalyzer::resolve_class_interface(GDScriptParser::ClassNode *p_clas enum_type.enum_values[element.identifier->name] = element.value; } - current_enum = nullptr; + current_enum = prev_enum; member.m_enum->set_datatype(enum_type); @@ -837,15 +971,18 @@ void GDScriptAnalyzer::resolve_class_interface(GDScriptParser::ClassNode *p_clas } } break; case GDScriptParser::ClassNode::Member::FUNCTION: - resolve_function_signature(member.function); + resolve_function_signature(member.function, p_source); break; case GDScriptParser::ClassNode::Member::ENUM_VALUE: { if (member.enum_value.custom_value) { check_class_member_name_conflict(p_class, member.enum_value.identifier->name, member.enum_value.custom_value); + member.enum_value.identifier->set_datatype(resolving_datatype); + + const GDScriptParser::EnumNode *prev_enum = current_enum; current_enum = member.enum_value.parent_enum; reduce_expression(member.enum_value.custom_value); - current_enum = nullptr; + current_enum = prev_enum; if (!member.enum_value.custom_value->is_constant) { push_error(R"(Enum values must be constant.)", member.enum_value.custom_value); @@ -865,12 +1002,22 @@ void GDScriptAnalyzer::resolve_class_interface(GDScriptParser::ClassNode *p_clas } member.enum_value.resolved = true; } + // Also update the original references. - member.enum_value.parent_enum->values.write[member.enum_value.index] = member.enum_value; - p_class->members.write[i].enum_value = member.enum_value; + member.enum_value.parent_enum->values.set(member.enum_value.index, member.enum_value); + + GDScriptParser::DataType datatype; + datatype.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT; + datatype.kind = GDScriptParser::DataType::BUILTIN; + datatype.builtin_type = Variant::INT; + member.enum_value.identifier->set_datatype(datatype); } break; case GDScriptParser::ClassNode::Member::CLASS: check_class_member_name_conflict(p_class, member.m_class->identifier->name, member.m_class); + // If it's already resolving, that's ok. + if (!member.m_class->base_type.is_resolving()) { + resolve_class_inheritance(member.m_class, p_source); + } break; case GDScriptParser::ClassNode::Member::GROUP: // No-op, but needed to silence warnings. @@ -881,28 +1028,123 @@ void GDScriptAnalyzer::resolve_class_interface(GDScriptParser::ClassNode *p_clas } } - // Recurse nested classes. - for (int i = 0; i < p_class->members.size(); i++) { - GDScriptParser::ClassNode::Member member = p_class->members[i]; - if (member.type != GDScriptParser::ClassNode::Member::CLASS) { - continue; + parser->current_class = previous_class; +} + +void GDScriptAnalyzer::resolve_class_interface(GDScriptParser::ClassNode *p_class, const GDScriptParser::Node *p_source) { + if (p_source == nullptr && parser->has_class(p_class)) { + p_source = p_class; + } + + if (!p_class->resolved_interface) { + if (!parser->has_class(p_class)) { + String script_path = p_class->get_datatype().script_path; + Ref<GDScriptParserRef> parser_ref = get_parser_for(script_path); + if (parser_ref.is_null()) { + push_error(vformat(R"(Could not find script "%s".)", script_path), p_source); + return; + } + + Error err = parser_ref->raise_status(GDScriptParserRef::PARSED); + if (err) { + push_error(vformat(R"(Could not resolve script "%s": %s.)", script_path, error_names[err]), p_source); + return; + } + + ERR_FAIL_COND_MSG(!parser_ref->get_parser()->has_class(p_class), R"(Parser bug: Mismatched external parser.)"); + + GDScriptAnalyzer *other_analyzer = parser_ref->get_analyzer(); + GDScriptParser *other_parser = parser_ref->get_parser(); + + int error_count = other_parser->errors.size(); + other_analyzer->resolve_class_interface(p_class); + if (other_parser->errors.size() > error_count) { + push_error(vformat(R"(Could not resolve class "%s".)", p_class->fqcn), p_source); + } + + return; + } + p_class->resolved_interface = true; + + if (resolve_class_inheritance(p_class) != OK) { + return; } - resolve_class_interface(member.m_class); + GDScriptParser::DataType base_type = p_class->base_type; + if (base_type.kind == GDScriptParser::DataType::CLASS) { + GDScriptParser::ClassNode *base_class = base_type.class_type; + resolve_class_interface(base_class, p_class); + } + + for (int i = 0; i < p_class->members.size(); i++) { + resolve_class_member(p_class, i); + } } +} - parser->current_class = previous_class; +void GDScriptAnalyzer::resolve_class_interface(GDScriptParser::ClassNode *p_class, bool p_recursive) { + resolve_class_interface(p_class); + + if (p_recursive) { + for (int i = 0; i < p_class->members.size(); i++) { + GDScriptParser::ClassNode::Member member = p_class->members[i]; + if (member.type == GDScriptParser::ClassNode::Member::CLASS) { + resolve_class_interface(member.m_class, true); + } + } + } } -void GDScriptAnalyzer::resolve_class_body(GDScriptParser::ClassNode *p_class) { +void GDScriptAnalyzer::resolve_class_body(GDScriptParser::ClassNode *p_class, const GDScriptParser::Node *p_source) { + if (p_source == nullptr && parser->has_class(p_class)) { + p_source = p_class; + } + if (p_class->resolved_body) { return; } + + if (!parser->has_class(p_class)) { + String script_path = p_class->get_datatype().script_path; + Ref<GDScriptParserRef> parser_ref = get_parser_for(script_path); + if (parser_ref.is_null()) { + push_error(vformat(R"(Could not find script "%s".)", script_path), p_source); + return; + } + + Error err = parser_ref->raise_status(GDScriptParserRef::PARSED); + if (err) { + push_error(vformat(R"(Could not resolve script "%s": %s.)", script_path, error_names[err]), p_source); + return; + } + + ERR_FAIL_COND_MSG(!parser_ref->get_parser()->has_class(p_class), R"(Parser bug: Mismatched external parser.)"); + + GDScriptAnalyzer *other_analyzer = parser_ref->get_analyzer(); + GDScriptParser *other_parser = parser_ref->get_parser(); + + int error_count = other_parser->errors.size(); + other_analyzer->resolve_class_body(p_class); + if (other_parser->errors.size() > error_count) { + push_error(vformat(R"(Could not resolve class "%s".)", p_class->fqcn), p_source); + } + + return; + } + p_class->resolved_body = true; GDScriptParser::ClassNode *previous_class = parser->current_class; parser->current_class = p_class; + resolve_class_interface(p_class, p_source); + + GDScriptParser::DataType base_type = p_class->base_type; + if (base_type.kind == GDScriptParser::DataType::CLASS) { + GDScriptParser::ClassNode *base_class = base_type.class_type; + resolve_class_body(base_class, p_class); + } + // Do functions and properties now. for (int i = 0; i < p_class->members.size(); i++) { GDScriptParser::ClassNode::Member member = p_class->members[i]; @@ -945,18 +1187,6 @@ void GDScriptAnalyzer::resolve_class_body(GDScriptParser::ClassNode *p_class) { } } - parser->current_class = previous_class; - - // Recurse nested classes. - for (int i = 0; i < p_class->members.size(); i++) { - GDScriptParser::ClassNode::Member member = p_class->members[i]; - if (member.type != GDScriptParser::ClassNode::Member::CLASS) { - continue; - } - - resolve_class_body(member.m_class); - } - // Check unused variables and datatypes of property getters and setters. for (int i = 0; i < p_class->members.size(); i++) { GDScriptParser::ClassNode::Member member = p_class->members[i]; @@ -985,21 +1215,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 + } } } @@ -1039,17 +1274,34 @@ void GDScriptAnalyzer::resolve_class_body(GDScriptParser::ClassNode *p_class) { } } } + + parser->current_class = previous_class; } -void GDScriptAnalyzer::resolve_node(GDScriptParser::Node *p_node) { +void GDScriptAnalyzer::resolve_class_body(GDScriptParser::ClassNode *p_class, bool p_recursive) { + resolve_class_body(p_class); + + if (p_recursive) { + for (int i = 0; i < p_class->members.size(); i++) { + GDScriptParser::ClassNode::Member member = p_class->members[i]; + if (member.type == GDScriptParser::ClassNode::Member::CLASS) { + resolve_class_body(member.m_class, true); + } + } + } +} + +void GDScriptAnalyzer::resolve_node(GDScriptParser::Node *p_node, bool p_is_root) { ERR_FAIL_COND_MSG(p_node == nullptr, "Trying to resolve type of a null node."); switch (p_node->type) { case GDScriptParser::Node::NONE: break; // Unreachable. case GDScriptParser::Node::CLASS: - resolve_class_interface(static_cast<GDScriptParser::ClassNode *>(p_node)); - resolve_class_body(static_cast<GDScriptParser::ClassNode *>(p_node)); + if (OK == resolve_class_inheritance(static_cast<GDScriptParser::ClassNode *>(p_node), true)) { + resolve_class_interface(static_cast<GDScriptParser::ClassNode *>(p_node), true); + resolve_class_body(static_cast<GDScriptParser::ClassNode *>(p_node), true); + } break; case GDScriptParser::Node::CONSTANT: resolve_constant(static_cast<GDScriptParser::ConstantNode *>(p_node)); @@ -1114,7 +1366,7 @@ void GDScriptAnalyzer::resolve_node(GDScriptParser::Node *p_node) { case GDScriptParser::Node::SUBSCRIPT: case GDScriptParser::Node::TERNARY_OPERATOR: case GDScriptParser::Node::UNARY_OPERATOR: - reduce_expression(static_cast<GDScriptParser::ExpressionNode *>(p_node), true); + reduce_expression(static_cast<GDScriptParser::ExpressionNode *>(p_node), p_is_root); break; case GDScriptParser::Node::BREAK: case GDScriptParser::Node::BREAKPOINT: @@ -1131,7 +1383,16 @@ void GDScriptAnalyzer::resolve_annotation(GDScriptParser::AnnotationNode *p_anno // TODO: Add second validation function for annotations, so they can use checked types. } -void GDScriptAnalyzer::resolve_function_signature(GDScriptParser::FunctionNode *p_function) { +void GDScriptAnalyzer::resolve_function_signature(GDScriptParser::FunctionNode *p_function, const GDScriptParser::Node *p_source) { + if (p_source == nullptr) { + p_source = p_function; + } + + if (p_function->get_datatype().is_resolving()) { + push_error(vformat(R"(Could not resolve function "%s": Cyclic reference.)", p_function->identifier->name), p_source); + return; + } + if (p_function->resolved_signature) { return; } @@ -1140,6 +1401,12 @@ void GDScriptAnalyzer::resolve_function_signature(GDScriptParser::FunctionNode * GDScriptParser::FunctionNode *previous_function = parser->current_function; parser->current_function = p_function; + GDScriptParser::DataType prev_datatype = p_function->get_datatype(); + + GDScriptParser::DataType resolving_datatype; + resolving_datatype.kind = GDScriptParser::DataType::RESOLVING; + p_function->set_datatype(resolving_datatype); + #ifdef TOOLS_ENABLED int default_value_count = 0; #endif // TOOLS_ENABLED @@ -1158,6 +1425,8 @@ void GDScriptAnalyzer::resolve_function_signature(GDScriptParser::FunctionNode * if (p_function->parameters[i]->default_value->is_constant) { p_function->default_arg_values.push_back(p_function->parameters[i]->default_value->reduced_value); + } else { + p_function->default_arg_values.push_back(Variant()); // Prevent shift. } } #endif // TOOLS_ENABLED @@ -1210,11 +1479,7 @@ void GDScriptAnalyzer::resolve_function_signature(GDScriptParser::FunctionNode * if (!valid) { // Compute parent signature as a string to show in the error message. - String parent_signature = parent_return_type.is_hard_type() ? parent_return_type.to_string() : "Variant"; - if (parent_signature == "null") { - parent_signature = "void"; - } - parent_signature += " " + p_function->identifier->name.operator String() + "("; + String parent_signature = p_function->identifier->name.operator String() + "("; int j = 0; for (const GDScriptParser::DataType &par_type : parameters_types) { if (j > 0) { @@ -1231,13 +1496,25 @@ void GDScriptAnalyzer::resolve_function_signature(GDScriptParser::FunctionNode * j++; } - parent_signature += ")"; + parent_signature += ") -> "; + + const String return_type = parent_return_type.is_hard_type() ? parent_return_type.to_string() : "Variant"; + if (return_type == "null") { + parent_signature += "void"; + } else { + parent_signature += return_type; + } + push_error(vformat(R"(The function signature doesn't match the parent. Parent signature is "%s".)", parent_signature), p_function); } } #endif // TOOLS_ENABLED } + if (p_function->get_datatype().is_resolving()) { + p_function->set_datatype(prev_datatype); + } + parser->current_function = previous_function; } @@ -1377,7 +1654,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]); @@ -1411,7 +1688,7 @@ void GDScriptAnalyzer::resolve_for(GDScriptParser::ForNode *p_for) { variable_type.builtin_type = Variant::INT; // Can this ever be a float or something else? p_for->variable->set_datatype(variable_type); } else if (p_for->list) { - resolve_node(p_for->list); + resolve_node(p_for->list, false); if (p_for->list->datatype.has_container_element_type()) { variable_type = p_for->list->datatype.get_container_element_type(); variable_type.type_source = GDScriptParser::DataType::ANNOTATED_INFERRED; @@ -1439,7 +1716,7 @@ void GDScriptAnalyzer::resolve_for(GDScriptParser::ForNode *p_for) { } void GDScriptAnalyzer::resolve_while(GDScriptParser::WhileNode *p_while) { - resolve_node(p_while->condition); + resolve_node(p_while->condition, false); resolve_suite(p_while->loop); p_while->set_datatype(p_while->loop->get_datatype()); @@ -1571,7 +1848,7 @@ void GDScriptAnalyzer::resolve_constant(GDScriptParser::ConstantNode *p_constant } if (p_constant->datatype_specifier != nullptr) { - if (!is_type_compatible(explicit_type, type)) { + if (!is_type_compatible(explicit_type, type, true)) { push_error(vformat(R"(Assigned value for constant "%s" has type %s which is not compatible with defined type %s.)", p_constant->identifier->name, type.to_string(), explicit_type.to_string()), p_constant->initializer); #ifdef DEBUG_ENABLED } else if (explicit_type.builtin_type == Variant::INT && type.builtin_type == Variant::FLOAT) { @@ -1602,8 +1879,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); } } @@ -1721,7 +1998,6 @@ void GDScriptAnalyzer::resolve_parameter(GDScriptParser::ParameterNode *p_parame } else { result.type_source = GDScriptParser::DataType::INFERRED; } - result.is_constant = false; } if (p_parameter->datatype_specifier != nullptr) { @@ -1741,6 +2017,7 @@ void GDScriptAnalyzer::resolve_parameter(GDScriptParser::ParameterNode *p_parame push_error(vformat(R"(Could not infer the type of the variable "%s" because the initial value is "null".)", p_parameter->identifier->name), p_parameter->default_value); } + result.is_constant = false; p_parameter->set_datatype(result); } @@ -1824,7 +2101,7 @@ void GDScriptAnalyzer::reduce_expression(GDScriptParser::ExpressionNode *p_expre reduce_binary_op(static_cast<GDScriptParser::BinaryOpNode *>(p_expression)); break; case GDScriptParser::Node::CALL: - reduce_call(static_cast<GDScriptParser::CallNode *>(p_expression), p_is_root); + reduce_call(static_cast<GDScriptParser::CallNode *>(p_expression), false, p_is_root); break; case GDScriptParser::Node::CAST: reduce_cast(static_cast<GDScriptParser::CastNode *>(p_expression)); @@ -2147,7 +2424,7 @@ void GDScriptAnalyzer::reduce_binary_op(GDScriptParser::BinaryOpNode *p_binary_o GDScriptParser::DataType test_type = right_type; test_type.is_meta_type = false; - if (!is_type_compatible(test_type, p_binary_op->left_operand->get_datatype(), false)) { + if (!is_type_compatible(test_type, left_type, false)) { push_error(vformat(R"(Expression is of type "%s" so it can't be of type "%s".)"), p_binary_op->left_operand); p_binary_op->reduced_value = false; } else { @@ -2181,11 +2458,11 @@ void GDScriptAnalyzer::reduce_binary_op(GDScriptParser::BinaryOpNode *p_binary_o GDScriptParser::DataType test_type = right_type; test_type.is_meta_type = false; - if (!is_type_compatible(test_type, p_binary_op->left_operand->get_datatype(), false)) { + if (!is_type_compatible(test_type, left_type, false)) { // Test reverse as well to consider for subtypes. - if (!is_type_compatible(p_binary_op->left_operand->get_datatype(), test_type, false)) { - if (p_binary_op->left_operand->get_datatype().is_hard_type()) { - push_error(vformat(R"(Expression is of type "%s" so it can't be of type "%s".)", p_binary_op->left_operand->get_datatype().to_string(), test_type.to_string()), p_binary_op->left_operand); + if (!is_type_compatible(left_type, test_type, false)) { + if (left_type.is_hard_type()) { + push_error(vformat(R"(Expression is of type "%s" so it can't be of type "%s".)", left_type.to_string(), test_type.to_string()), p_binary_op->left_operand); } else { // TODO: Warning. mark_node_unsafe(p_binary_op); @@ -2425,9 +2702,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: @@ -2548,6 +2831,22 @@ void GDScriptAnalyzer::reduce_call(GDScriptParser::CallNode *p_call, bool p_is_a mark_lambda_use_self(); } +#ifdef DEBUG_ENABLED + 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 && !(is_self && parser->current_function != nullptr && parser->current_function->is_static)) { + String caller_type = String(base_type.native_type); + + if (caller_type.is_empty()) { + caller_type = base_type.to_string(); + } + + parser->push_warning(p_call, GDScriptWarning::STATIC_CALLED_ON_INSTANCE, p_call->function_name, caller_type); + } +#endif // DEBUG_ENABLED + call_type = return_type; } else { bool found = false; @@ -2641,7 +2940,7 @@ void GDScriptAnalyzer::reduce_cast(GDScriptParser::CastNode *p_cast) { } void GDScriptAnalyzer::reduce_dictionary(GDScriptParser::DictionaryNode *p_dictionary) { - HashMap<Variant, GDScriptParser::ExpressionNode *, VariantHasher, VariantComparator> elements; + HashMap<Variant, GDScriptParser::ExpressionNode *, VariantHasher, StringLikeVariantComparator> elements; for (int i = 0; i < p_dictionary->elements.size(); i++) { const GDScriptParser::DictionaryNode::Pair &element = p_dictionary->elements[i]; @@ -2699,7 +2998,7 @@ GDScriptParser::DataType GDScriptAnalyzer::make_global_class_meta_type(const Str return type; } - Error err = ref->raise_status(GDScriptParserRef::INTERFACE_SOLVED); + Error err = ref->raise_status(GDScriptParserRef::INHERITANCE_SOLVED); if (err) { push_error(vformat(R"(Could not resolve class "%s", because of a parser error.)", p_class_name), p_source); type.type_source = GDScriptParser::DataType::UNDETECTED; @@ -2707,21 +3006,13 @@ GDScriptParser::DataType GDScriptAnalyzer::make_global_class_meta_type(const Str return type; } - type.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT; - type.kind = GDScriptParser::DataType::CLASS; - type.builtin_type = Variant::OBJECT; - type.native_type = ScriptServer::get_global_class_native_base(p_class_name); - type.class_type = ref->get_parser()->head; - type.script_path = ref->get_parser()->script_path; - type.is_constant = true; - type.is_meta_type = true; - return type; + return ref->get_parser()->head->get_datatype(); } else { type.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT; type.kind = GDScriptParser::DataType::SCRIPT; type.builtin_type = Variant::OBJECT; - type.native_type = ScriptServer::get_global_class_native_base(p_class_name); type.script_type = ResourceLoader::load(path, "Script"); + type.native_type = type.script_type->get_instance_base_type(); type.script_path = path; type.is_constant = true; type.is_meta_type = true; @@ -2730,6 +3021,10 @@ GDScriptParser::DataType GDScriptAnalyzer::make_global_class_meta_type(const Str } void GDScriptAnalyzer::reduce_identifier_from_base(GDScriptParser::IdentifierNode *p_identifier, GDScriptParser::DataType *p_base) { + if (!p_identifier->get_datatype().has_no_type()) { + return; + } + GDScriptParser::DataType base; if (p_base == nullptr) { base = type_from_metatype(parser->current_class->get_datatype()); @@ -2822,16 +3117,16 @@ void GDScriptAnalyzer::reduce_identifier_from_base(GDScriptParser::IdentifierNod p_identifier->set_datatype(base_class->get_datatype()); return; } + if (base_class->has_member(name)) { - const GDScriptParser::ClassNode::Member &member = base_class->get_member(name); + resolve_class_member(base_class, name, p_identifier); + + GDScriptParser::ClassNode::Member member = base_class->get_member(name); p_identifier->set_datatype(member.get_datatype()); switch (member.type) { case GDScriptParser::ClassNode::Member::CONSTANT: - // For out-of-order resolution: - reduce_expression(member.constant->initializer); p_identifier->is_constant = true; p_identifier->reduced_value = member.constant->initializer->reduced_value; - p_identifier->set_datatype(member.constant->initializer->get_datatype()); p_identifier->source = GDScriptParser::IdentifierNode::MEMBER_CONSTANT; p_identifier->constant_source = member.constant; break; @@ -2845,15 +3140,12 @@ void GDScriptAnalyzer::reduce_identifier_from_base(GDScriptParser::IdentifierNod p_identifier->variable_source = member.variable; member.variable->usages += 1; break; + case GDScriptParser::ClassNode::Member::SIGNAL: + p_identifier->source = GDScriptParser::IdentifierNode::MEMBER_SIGNAL; + break; case GDScriptParser::ClassNode::Member::FUNCTION: - resolve_function_signature(member.function); p_identifier->set_datatype(make_callable_type(member.function->info)); break; - case GDScriptParser::ClassNode::Member::CLASS: - // For out-of-order resolution: - resolve_class_interface(member.m_class); - p_identifier->set_datatype(member.m_class->get_datatype()); - break; default: break; // Type already set. } @@ -2861,47 +3153,44 @@ void GDScriptAnalyzer::reduce_identifier_from_base(GDScriptParser::IdentifierNod } // Check outer constants. // TODO: Allow outer static functions. - GDScriptParser::ClassNode *outer = base_class->outer; - while (outer != nullptr) { - if (outer->has_member(name)) { - const GDScriptParser::ClassNode::Member &member = outer->get_member(name); - switch (member.type) { - case GDScriptParser::ClassNode::Member::CONSTANT: { - // TODO: Make sure loops won't cause problem. And make special error message for those. - // For out-of-order resolution: - reduce_expression(member.constant->initializer); - p_identifier->set_datatype(member.get_datatype()); - p_identifier->is_constant = true; - p_identifier->reduced_value = member.constant->initializer->reduced_value; - return; - } break; - case GDScriptParser::ClassNode::Member::ENUM_VALUE: { - p_identifier->set_datatype(member.get_datatype()); - p_identifier->is_constant = true; - p_identifier->reduced_value = member.enum_value.value; - return; - } break; - case GDScriptParser::ClassNode::Member::ENUM: { - p_identifier->set_datatype(member.get_datatype()); - p_identifier->is_constant = false; - return; - } break; - case GDScriptParser::ClassNode::Member::CLASS: { - resolve_class_interface(member.m_class); - p_identifier->set_datatype(member.m_class->get_datatype()); - return; - } break; - default: - break; + if (base_class->outer != nullptr) { + List<GDScriptParser::ClassNode *> script_classes; + get_class_node_current_scope_classes(parser->current_class, &script_classes); + for (GDScriptParser::ClassNode *script_class : script_classes) { + if (script_class->has_member(name)) { + resolve_class_member(script_class, name, p_identifier); + + GDScriptParser::ClassNode::Member member = script_class->get_member(name); + switch (member.type) { + case GDScriptParser::ClassNode::Member::CONSTANT: + // TODO: Make sure loops won't cause problem. And make special error message for those. + p_identifier->set_datatype(member.get_datatype()); + p_identifier->is_constant = true; + p_identifier->reduced_value = member.constant->initializer->reduced_value; + return; + case GDScriptParser::ClassNode::Member::ENUM_VALUE: + p_identifier->set_datatype(member.get_datatype()); + p_identifier->is_constant = true; + p_identifier->reduced_value = member.enum_value.value; + return; + case GDScriptParser::ClassNode::Member::ENUM: + p_identifier->set_datatype(member.get_datatype()); + p_identifier->is_constant = false; + return; + case GDScriptParser::ClassNode::Member::CLASS: + p_identifier->set_datatype(member.get_datatype()); + return; + default: + break; + } } } - outer = outer->outer; } base_class = base_class->base_type.class_type; } - // Check native members. + // Check native members. No need for native class recursion because Node exposes all Object's properties. const StringName &native = base.native_type; if (class_exists(native)) { @@ -2989,6 +3278,7 @@ void GDScriptAnalyzer::reduce_identifier(GDScriptParser::IdentifierNode *p_ident p_identifier->reduced_value = p_identifier->constant_source->initializer->reduced_value; found_source = true; break; + case GDScriptParser::IdentifierNode::MEMBER_SIGNAL: case GDScriptParser::IdentifierNode::INHERITED_VARIABLE: mark_lambda_use_self(); break; @@ -3092,14 +3382,31 @@ void GDScriptAnalyzer::reduce_identifier(GDScriptParser::IdentifierNode *p_ident GDScriptParser::DataType result; result.kind = GDScriptParser::DataType::NATIVE; result.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT; - if (autoload.path.to_lower().ends_with(GDScriptLanguage::get_singleton()->get_extension())) { + if (ResourceLoader::get_resource_type(autoload.path) == "GDScript") { Ref<GDScriptParserRef> singl_parser = get_parser_for(autoload.path); if (singl_parser.is_valid()) { - Error err = singl_parser->raise_status(GDScriptParserRef::INTERFACE_SOLVED); + Error err = singl_parser->raise_status(GDScriptParserRef::INHERITANCE_SOLVED); if (err == OK) { result = type_from_metatype(singl_parser->get_parser()->head->get_datatype()); } } + } else if (ResourceLoader::get_resource_type(autoload.path) == "PackedScene") { + if (GDScriptLanguage::get_singleton()->get_named_globals_map().has(name)) { + Variant constant = GDScriptLanguage::get_singleton()->get_named_globals_map()[name]; + Node *node = Object::cast_to<Node>(constant); + if (node != nullptr) { + Ref<GDScript> scr = node->get_script(); + if (scr.is_valid()) { + Ref<GDScriptParserRef> singl_parser = get_parser_for(scr->get_script_path()); + if (singl_parser.is_valid()) { + Error err = singl_parser->raise_status(GDScriptParserRef::INHERITANCE_SOLVED); + if (err == OK) { + result = type_from_metatype(singl_parser->get_parser()->head->get_datatype()); + } + } + } + } + } } result.is_constant = true; p_identifier->set_datatype(result); @@ -3216,12 +3523,37 @@ 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); - if (p_preload->resource.is_null()) { - push_error(vformat(R"(Could not preload resource file "%s".)", p_preload->resolved_path), p_preload->path); + + // Must load GDScript and PackedScenes separately to permit cyclic references + // as ResourceLoader::load() detect and reject those. + if (ResourceLoader::get_resource_type(p_preload->resolved_path) == "GDScript") { + Error err = OK; + Ref<GDScript> res = GDScriptCache::get_shallow_script(p_preload->resolved_path, err, parser->script_path); + p_preload->resource = res; + if (err != OK) { + push_error(vformat(R"(Could not preload resource script "%s".)", p_preload->resolved_path), p_preload->path); + } + } else if (ResourceLoader::get_resource_type(p_preload->resolved_path) == "PackedScene") { + Error err = OK; + Ref<PackedScene> res = GDScriptCache::get_packed_scene(p_preload->resolved_path, err, parser->script_path); + p_preload->resource = res; + if (err != OK) { + push_error(vformat(R"(Could not preload resource scene "%s".)", p_preload->resolved_path), p_preload->path); + } + } else { + p_preload->resource = ResourceLoader::load(p_preload->resolved_path); + if (p_preload->resource.is_null()) { + push_error(vformat(R"(Could not preload resource file "%s".)", p_preload->resolved_path), p_preload->path); + } } } } @@ -3259,43 +3591,44 @@ void GDScriptAnalyzer::reduce_subscript(GDScriptParser::SubscriptNode *p_subscri if (p_subscript->attribute == nullptr) { return; } - if (p_subscript->base->is_constant) { + + GDScriptParser::DataType base_type = p_subscript->base->get_datatype(); + bool valid = false; + // If the base is a metatype, use the analyzer instead. + if (p_subscript->base->is_constant && !base_type.is_meta_type) { // Just try to get it. - bool valid = false; Variant value = p_subscript->base->reduced_value.get_named(p_subscript->attribute->name, valid); - if (!valid) { - push_error(vformat(R"(Cannot get member "%s" from "%s".)", p_subscript->attribute->name, p_subscript->base->reduced_value), p_subscript->index); - result_type.kind = GDScriptParser::DataType::VARIANT; - } else { + if (valid) { p_subscript->is_constant = true; p_subscript->reduced_value = value; result_type = type_from_variant(value, p_subscript); } + } else if (base_type.is_variant() || !base_type.is_hard_type()) { + valid = true; + result_type.kind = GDScriptParser::DataType::VARIANT; + mark_node_unsafe(p_subscript); } else { - GDScriptParser::DataType base_type = p_subscript->base->get_datatype(); - - if (base_type.is_variant() || !base_type.is_hard_type()) { - result_type.kind = GDScriptParser::DataType::VARIANT; - mark_node_unsafe(p_subscript); - } else { - reduce_identifier_from_base(p_subscript->attribute, &base_type); - GDScriptParser::DataType attr_type = p_subscript->attribute->get_datatype(); - if (attr_type.is_set()) { - result_type = attr_type; - p_subscript->is_constant = p_subscript->attribute->is_constant; - p_subscript->reduced_value = p_subscript->attribute->reduced_value; - } else { - if (base_type.kind == GDScriptParser::DataType::BUILTIN) { - push_error(vformat(R"(Cannot find member "%s" in base "%s".)", p_subscript->attribute->name, base_type.to_string()), p_subscript->attribute); + reduce_identifier_from_base(p_subscript->attribute, &base_type); + GDScriptParser::DataType attr_type = p_subscript->attribute->get_datatype(); + if (attr_type.is_set()) { + valid = true; + result_type = attr_type; + p_subscript->is_constant = p_subscript->attribute->is_constant; + p_subscript->reduced_value = p_subscript->attribute->reduced_value; + } else if (!base_type.is_constant) { + valid = base_type.kind != GDScriptParser::DataType::BUILTIN; #ifdef DEBUG_ENABLED - } else { - parser->push_warning(p_subscript, GDScriptWarning::UNSAFE_PROPERTY_ACCESS, p_subscript->attribute->name, base_type.to_string()); -#endif - } - result_type.kind = GDScriptParser::DataType::VARIANT; + if (valid) { + parser->push_warning(p_subscript, GDScriptWarning::UNSAFE_PROPERTY_ACCESS, p_subscript->attribute->name, base_type.to_string()); } +#endif + result_type.kind = GDScriptParser::DataType::VARIANT; } } + if (!valid) { + push_error(vformat(R"(Cannot find member "%s" in base "%s".)", p_subscript->attribute->name, type_from_metatype(base_type).to_string()), p_subscript->attribute); + result_type.kind = GDScriptParser::DataType::VARIANT; + } } else { if (p_subscript->index == nullptr) { return; @@ -3348,7 +3681,7 @@ void GDScriptAnalyzer::reduce_subscript(GDScriptParser::SubscriptNode *p_subscri case Variant::QUATERNION: case Variant::AABB: case Variant::OBJECT: - error = index_type.builtin_type != Variant::STRING; + error = index_type.builtin_type != Variant::STRING && index_type.builtin_type != Variant::STRING_NAME; break; // Expect String or number. case Variant::BASIS: @@ -3362,11 +3695,11 @@ void GDScriptAnalyzer::reduce_subscript(GDScriptParser::SubscriptNode *p_subscri case Variant::TRANSFORM3D: case Variant::PROJECTION: error = index_type.builtin_type != Variant::INT && index_type.builtin_type != Variant::FLOAT && - index_type.builtin_type != Variant::STRING; + index_type.builtin_type != Variant::STRING && index_type.builtin_type != Variant::STRING_NAME; break; // Expect String or int. case Variant::COLOR: - error = index_type.builtin_type != Variant::INT && index_type.builtin_type != Variant::STRING; + error = index_type.builtin_type != Variant::INT && index_type.builtin_type != Variant::STRING && index_type.builtin_type != Variant::STRING_NAME; break; // Don't support indexing, but we will check it later. case Variant::RID: @@ -3542,6 +3875,7 @@ void GDScriptAnalyzer::reduce_ternary_op(GDScriptParser::TernaryOpNode *p_ternar void GDScriptAnalyzer::reduce_unary_op(GDScriptParser::UnaryOpNode *p_unary_op) { reduce_expression(p_unary_op->operand); + GDScriptParser::DataType operand_type = p_unary_op->operand->get_datatype(); GDScriptParser::DataType result; if (p_unary_op->operand == nullptr) { @@ -3554,15 +3888,17 @@ void GDScriptAnalyzer::reduce_unary_op(GDScriptParser::UnaryOpNode *p_unary_op) p_unary_op->is_constant = true; p_unary_op->reduced_value = Variant::evaluate(p_unary_op->variant_op, p_unary_op->operand->reduced_value, Variant()); result = type_from_variant(p_unary_op->reduced_value, p_unary_op); - } else if (p_unary_op->operand->get_datatype().is_variant()) { + } + + if (operand_type.is_variant()) { result.kind = GDScriptParser::DataType::VARIANT; mark_node_unsafe(p_unary_op); } else { bool valid = false; - result = get_operation_type(p_unary_op->variant_op, p_unary_op->operand->get_datatype(), valid, p_unary_op); + result = get_operation_type(p_unary_op->variant_op, operand_type, valid, p_unary_op); if (!valid) { - push_error(vformat(R"(Invalid operand of type "%s" for unary operator "%s".)", p_unary_op->operand->get_datatype().to_string(), Variant::get_operator_name(p_unary_op->variant_op)), p_unary_op->operand); + push_error(vformat(R"(Invalid operand of type "%s" for unary operator "%s".)", operand_type.to_string(), Variant::get_operator_name(p_unary_op->variant_op)), p_unary_op); } } @@ -3630,7 +3966,13 @@ GDScriptParser::DataType GDScriptAnalyzer::type_from_variant(const Variant &p_va result.builtin_type = p_value.get_type(); result.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT; // Constant has explicit type. - if (p_value.get_type() == Variant::OBJECT) { + if (p_value.get_type() == Variant::NIL) { + // A null value is a variant, not void. + result.kind = GDScriptParser::DataType::VARIANT; + } else if (p_value.get_type() == Variant::OBJECT) { + // Object is treated as a native type, not a builtin type. + result.kind = GDScriptParser::DataType::NATIVE; + Object *obj = p_value; if (!obj) { return GDScriptParser::DataType(); @@ -3645,50 +3987,51 @@ GDScriptParser::DataType GDScriptAnalyzer::type_from_variant(const Variant &p_va scr = obj->get_script(); } if (scr.is_valid()) { - if (scr->is_valid()) { - result.script_type = scr; - result.script_path = scr->get_path(); - Ref<GDScript> gds = scr; - if (gds.is_valid()) { - result.kind = GDScriptParser::DataType::CLASS; - // This might be an inner class, so we want to get the parser for the root. - // But still get the inner class from that tree. - GDScript *current = gds.ptr(); - List<StringName> class_chain; - while (current->_owner) { - // Push to front so it's in reverse. - class_chain.push_front(current->name); - current = current->_owner; - } + result.script_path = scr->get_path(); + Ref<GDScript> gds = scr; + if (gds.is_valid()) { + result.kind = GDScriptParser::DataType::CLASS; + // This might be an inner class, so we want to get the parser for the root. + // But still get the inner class from that tree. + GDScript *current = gds.ptr(); + List<StringName> class_chain; + while (current->_owner) { + // Push to front so it's in reverse. + class_chain.push_front(current->name); + current = current->_owner; + } - Ref<GDScriptParserRef> ref = get_parser_for(current->get_path()); - if (ref.is_null()) { - push_error("Could not find script in path.", p_source); - GDScriptParser::DataType error_type; - error_type.kind = GDScriptParser::DataType::VARIANT; - return error_type; - } - ref->raise_status(GDScriptParserRef::INTERFACE_SOLVED); + Ref<GDScriptParserRef> ref = get_parser_for(current->get_path()); + if (ref.is_null()) { + push_error("Could not find script in path.", p_source); + GDScriptParser::DataType error_type; + error_type.kind = GDScriptParser::DataType::VARIANT; + return error_type; + } + ref->raise_status(GDScriptParserRef::INHERITANCE_SOLVED); - GDScriptParser::ClassNode *found = ref->get_parser()->head; + GDScriptParser::ClassNode *found = ref->get_parser()->head; - // It should be okay to assume this exists, since we have a complete script already. - for (const StringName &E : class_chain) { - found = found->get_member(E).m_class; + for (const StringName &E : class_chain) { + if (!found->has_member(E)) { + return GDScriptParser::DataType(); } - result.class_type = found; - result.script_path = ref->get_parser()->script_path; - } else { - result.kind = GDScriptParser::DataType::SCRIPT; + if (found->get_member(E).type != GDScriptParser::ClassNode::Member::CLASS) { + return GDScriptParser::DataType(); + } + + resolve_class_member(found, E, p_source); + + found = found->get_member(E).m_class; } - result.native_type = scr->get_instance_base_type(); + + result = found->get_datatype(); } else { - push_error(vformat(R"(Constant value uses script from "%s" which is loaded but not compiled.)", scr->get_path()), p_source); - result.kind = GDScriptParser::DataType::VARIANT; - result.type_source = GDScriptParser::DataType::UNDETECTED; - result.is_meta_type = false; + result.kind = GDScriptParser::DataType::SCRIPT; + result.native_type = scr->get_instance_base_type(); } + result.script_type = scr; } else { result.kind = GDScriptParser::DataType::NATIVE; if (result.native_type == GDScriptNativeClass::get_class_static()) { @@ -3812,8 +4155,12 @@ bool GDScriptAnalyzer::get_function_signature(GDScriptParser::Node *p_source, bo push_error(vformat(R"(Member "%s" is not a function.)", function_name), p_source); return false; } + + resolve_class_member(base_class, function_name, p_source); found_function = base_class->get_member(function_name).function; } + + resolve_class_inheritance(base_class, p_source); base_class = base_class->base_type.class_type; } @@ -3982,12 +4329,10 @@ bool GDScriptAnalyzer::is_shadowing(GDScriptParser::IdentifierNode *p_local, con base_class = base_class->base_type.class_type; } - StringName base_native = base.native_type; - - ERR_FAIL_COND_V_MSG(!class_exists(base_native), false, "Non-existent native base class."); - - StringName parent = base_native; + StringName parent = base.native_type; while (parent != StringName()) { + ERR_FAIL_COND_V_MSG(!class_exists(parent), false, "Non-existent native base class."); + if (ClassDB::has_method(parent, name, true)) { parser->push_warning(p_local, GDScriptWarning::SHADOWED_VARIABLE_BASE_CLASS, p_context, p_local->name, "method", parent); return true; @@ -4102,7 +4447,7 @@ bool GDScriptAnalyzer::is_type_compatible(const GDScriptParser::DataType &p_targ // Variant array can't be appended to typed array. valid = false; } else { - valid = is_type_compatible(p_target.get_container_element_type(), p_source.get_container_element_type(), false); + valid = is_type_compatible(p_target.get_container_element_type(), p_source.get_container_element_type(), p_allow_implicit_conversion); } } } @@ -4177,6 +4522,7 @@ bool GDScriptAnalyzer::is_type_compatible(const GDScriptParser::DataType &p_targ case GDScriptParser::DataType::VARIANT: case GDScriptParser::DataType::BUILTIN: case GDScriptParser::DataType::ENUM: + case GDScriptParser::DataType::RESOLVING: case GDScriptParser::DataType::UNRESOLVED: break; // Already solved before. } @@ -4213,6 +4559,7 @@ bool GDScriptAnalyzer::is_type_compatible(const GDScriptParser::DataType &p_targ case GDScriptParser::DataType::VARIANT: case GDScriptParser::DataType::BUILTIN: case GDScriptParser::DataType::ENUM: + case GDScriptParser::DataType::RESOLVING: case GDScriptParser::DataType::UNRESOLVED: break; // Already solved before. } @@ -4227,6 +4574,10 @@ void GDScriptAnalyzer::push_error(const String &p_message, const GDScriptParser: void GDScriptAnalyzer::mark_node_unsafe(const GDScriptParser::Node *p_node) { #ifdef DEBUG_ENABLED + if (p_node == nullptr) { + return; + } + for (int i = p_node->start_line; i <= p_node->end_line; i++) { parser->unsafe_lines.insert(i); } @@ -4259,39 +4610,46 @@ Ref<GDScriptParserRef> GDScriptAnalyzer::get_parser_for(const String &p_path) { } Error GDScriptAnalyzer::resolve_inheritance() { - return resolve_inheritance(parser->head); + return resolve_class_inheritance(parser->head, true); } Error GDScriptAnalyzer::resolve_interface() { - resolve_class_interface(parser->head); + resolve_class_interface(parser->head, true); return parser->errors.is_empty() ? OK : ERR_PARSE_ERROR; } Error GDScriptAnalyzer::resolve_body() { - resolve_class_body(parser->head); + resolve_class_body(parser->head, true); return parser->errors.is_empty() ? OK : ERR_PARSE_ERROR; } -Error GDScriptAnalyzer::resolve_program() { - resolve_class_interface(parser->head); - resolve_class_body(parser->head); - +Error GDScriptAnalyzer::resolve_dependencies() { for (KeyValue<String, Ref<GDScriptParserRef>> &K : depended_parsers) { if (K.value.is_null()) { return ERR_PARSE_ERROR; } - K.value->raise_status(GDScriptParserRef::FULLY_SOLVED); + K.value->raise_status(GDScriptParserRef::INHERITANCE_SOLVED); } + return parser->errors.is_empty() ? OK : ERR_PARSE_ERROR; } Error GDScriptAnalyzer::analyze() { parser->errors.clear(); - Error err = resolve_inheritance(parser->head); + Error err = OK; + + err = resolve_inheritance(); if (err) { return err; } - return resolve_program(); + + resolve_interface(); + resolve_body(); + if (!parser->errors.is_empty()) { + return ERR_PARSE_ERROR; + } + + return resolve_dependencies(); } GDScriptAnalyzer::GDScriptAnalyzer(GDScriptParser *p_parser) { diff --git a/modules/gdscript/gdscript_analyzer.h b/modules/gdscript/gdscript_analyzer.h index 3966b81b6e..a4d9efb094 100644 --- a/modules/gdscript/gdscript_analyzer.h +++ b/modules/gdscript/gdscript_analyzer.h @@ -45,25 +45,29 @@ class GDScriptAnalyzer { List<GDScriptParser::LambdaNode *> lambda_stack; // Tests for detecting invalid overloading of script members - static _FORCE_INLINE_ bool has_member_name_conflict_in_script_class(const StringName &p_name, const GDScriptParser::ClassNode *p_current_class_node); + static _FORCE_INLINE_ bool has_member_name_conflict_in_script_class(const StringName &p_name, const GDScriptParser::ClassNode *p_current_class_node, const GDScriptParser::Node *p_member); static _FORCE_INLINE_ bool has_member_name_conflict_in_native_type(const StringName &p_name, const StringName &p_native_type_string); Error check_native_member_name_conflict(const StringName &p_member_name, const GDScriptParser::Node *p_member_node, const StringName &p_native_type_string); Error check_class_member_name_conflict(const GDScriptParser::ClassNode *p_class_node, const StringName &p_member_name, const GDScriptParser::Node *p_member_node); - Error resolve_inheritance(GDScriptParser::ClassNode *p_class, bool p_recursive = true); + void get_class_node_current_scope_classes(GDScriptParser::ClassNode *p_node, List<GDScriptParser::ClassNode *> *p_list); + + Error resolve_class_inheritance(GDScriptParser::ClassNode *p_class, const GDScriptParser::Node *p_source = nullptr); + Error resolve_class_inheritance(GDScriptParser::ClassNode *p_class, bool p_recursive); GDScriptParser::DataType resolve_datatype(GDScriptParser::TypeNode *p_type); void decide_suite_type(GDScriptParser::Node *p_suite, GDScriptParser::Node *p_statement); - // This traverses the tree to resolve all TypeNodes. - Error resolve_program(); - void resolve_annotation(GDScriptParser::AnnotationNode *p_annotation); - void resolve_class_interface(GDScriptParser::ClassNode *p_class); - void resolve_class_body(GDScriptParser::ClassNode *p_class); - void resolve_function_signature(GDScriptParser::FunctionNode *p_function); + void resolve_class_member(GDScriptParser::ClassNode *p_class, StringName p_name, const GDScriptParser::Node *p_source = nullptr); + void resolve_class_member(GDScriptParser::ClassNode *p_class, int p_index, const GDScriptParser::Node *p_source = nullptr); + void resolve_class_interface(GDScriptParser::ClassNode *p_class, const GDScriptParser::Node *p_source = nullptr); + void resolve_class_interface(GDScriptParser::ClassNode *p_class, bool p_recursive); + void resolve_class_body(GDScriptParser::ClassNode *p_class, const GDScriptParser::Node *p_source = nullptr); + void resolve_class_body(GDScriptParser::ClassNode *p_class, bool p_recursive); + void resolve_function_signature(GDScriptParser::FunctionNode *p_function, const GDScriptParser::Node *p_source = nullptr); void resolve_function_body(GDScriptParser::FunctionNode *p_function); - void resolve_node(GDScriptParser::Node *p_node); + void resolve_node(GDScriptParser::Node *p_node, bool p_is_root = true); void resolve_suite(GDScriptParser::SuiteNode *p_suite); void resolve_if(GDScriptParser::IfNode *p_if); void resolve_for(GDScriptParser::ForNode *p_for); @@ -113,7 +117,7 @@ class GDScriptAnalyzer { GDScriptParser::DataType get_operation_type(Variant::Operator p_operation, const GDScriptParser::DataType &p_a, bool &r_valid, const GDScriptParser::Node *p_source); void update_array_literal_element_type(const GDScriptParser::DataType &p_base_type, GDScriptParser::ArrayNode *p_array_literal); bool is_type_compatible(const GDScriptParser::DataType &p_target, const GDScriptParser::DataType &p_source, bool p_allow_implicit_conversion = false, const GDScriptParser::Node *p_source_node = nullptr); - void push_error(const String &p_message, const GDScriptParser::Node *p_origin); + void push_error(const String &p_message, const GDScriptParser::Node *p_origin = nullptr); void mark_node_unsafe(const GDScriptParser::Node *p_node); void mark_lambda_use_self(); bool class_exists(const StringName &p_class) const; @@ -126,6 +130,7 @@ public: Error resolve_inheritance(); Error resolve_interface(); Error resolve_body(); + Error resolve_dependencies(); Error analyze(); GDScriptAnalyzer(GDScriptParser *p_parser); diff --git a/modules/gdscript/gdscript_byte_codegen.cpp b/modules/gdscript/gdscript_byte_codegen.cpp index fa158591fd..1bc83fbbb5 100644 --- a/modules/gdscript/gdscript_byte_codegen.cpp +++ b/modules/gdscript/gdscript_byte_codegen.cpp @@ -164,7 +164,7 @@ void GDScriptByteCodeGenerator::write_start(GDScript *p_script, const StringName function->name = p_function_name; function->_script = p_script; - function->source = p_script->get_path(); + function->source = p_script->get_script_path(); #ifdef DEBUG_ENABLED function->func_cname = (String(function->source) + " - " + String(p_function_name)).utf8(); diff --git a/modules/gdscript/gdscript_cache.cpp b/modules/gdscript/gdscript_cache.cpp index 271296c2f9..6faf2dde73 100644 --- a/modules/gdscript/gdscript_cache.cpp +++ b/modules/gdscript/gdscript_cache.cpp @@ -34,7 +34,9 @@ #include "core/templates/vector.h" #include "gdscript.h" #include "gdscript_analyzer.h" +#include "gdscript_compiler.h" #include "gdscript_parser.h" +#include "scene/resources/packed_scene.h" bool GDScriptParserRef::is_valid() const { return parser != nullptr; @@ -48,6 +50,13 @@ GDScriptParser *GDScriptParserRef::get_parser() const { return parser; } +GDScriptAnalyzer *GDScriptParserRef::get_analyzer() { + if (analyzer == nullptr) { + analyzer = memnew(GDScriptAnalyzer(parser)); + } + return analyzer; +} + Error GDScriptParserRef::raise_status(Status p_new_status) { ERR_FAIL_COND_V(parser == nullptr, ERR_INVALID_DATA); @@ -62,23 +71,22 @@ Error GDScriptParserRef::raise_status(Status p_new_status) { result = parser->parse(GDScriptCache::get_source_code(path), path, false); break; case PARSED: { - analyzer = memnew(GDScriptAnalyzer(parser)); status = INHERITANCE_SOLVED; - Error inheritance_result = analyzer->resolve_inheritance(); + Error inheritance_result = get_analyzer()->resolve_inheritance(); if (result == OK) { result = inheritance_result; } } break; case INHERITANCE_SOLVED: { status = INTERFACE_SOLVED; - Error interface_result = analyzer->resolve_interface(); + Error interface_result = get_analyzer()->resolve_interface(); if (result == OK) { result = interface_result; } } break; case INTERFACE_SOLVED: { status = FULLY_SOLVED; - Error body_result = analyzer->resolve_body(); + Error body_result = get_analyzer()->resolve_body(); if (result == OK) { result = body_result; } @@ -95,27 +103,96 @@ Error GDScriptParserRef::raise_status(Status p_new_status) { return result; } -GDScriptParserRef::~GDScriptParserRef() { +void GDScriptParserRef::clear() { + if (cleared) { + return; + } + cleared = true; + if (parser != nullptr) { memdelete(parser); } + if (analyzer != nullptr) { memdelete(analyzer); } - MutexLock lock(GDScriptCache::singleton->lock); +} + +GDScriptParserRef::~GDScriptParserRef() { + clear(); + + MutexLock lock(GDScriptCache::singleton->mutex); GDScriptCache::singleton->parser_map.erase(path); } GDScriptCache *GDScriptCache::singleton = nullptr; +void GDScriptCache::move_script(const String &p_from, const String &p_to) { + if (singleton == nullptr || p_from == p_to) { + return; + } + + MutexLock lock(singleton->mutex); + + if (singleton->cleared) { + return; + } + + for (KeyValue<String, HashSet<String>> &E : singleton->packed_scene_dependencies) { + if (E.value.has(p_from)) { + E.value.insert(p_to); + E.value.erase(p_from); + } + } + + if (singleton->parser_map.has(p_from) && !p_from.is_empty()) { + singleton->parser_map[p_to] = singleton->parser_map[p_from]; + } + singleton->parser_map.erase(p_from); + + if (singleton->shallow_gdscript_cache.has(p_from) && !p_from.is_empty()) { + singleton->shallow_gdscript_cache[p_to] = singleton->shallow_gdscript_cache[p_from]; + } + singleton->shallow_gdscript_cache.erase(p_from); + + if (singleton->full_gdscript_cache.has(p_from) && !p_from.is_empty()) { + singleton->full_gdscript_cache[p_to] = singleton->full_gdscript_cache[p_from]; + } + singleton->full_gdscript_cache.erase(p_from); +} + void GDScriptCache::remove_script(const String &p_path) { - MutexLock lock(singleton->lock); + if (singleton == nullptr) { + return; + } + + MutexLock lock(singleton->mutex); + + if (singleton->cleared) { + return; + } + + for (KeyValue<String, HashSet<String>> &E : singleton->packed_scene_dependencies) { + if (!E.value.has(p_path)) { + continue; + } + E.value.erase(p_path); + } + + GDScriptCache::clear_unreferenced_packed_scenes(); + + if (singleton->parser_map.has(p_path)) { + singleton->parser_map[p_path]->clear(); + singleton->parser_map.erase(p_path); + } + + singleton->dependencies.erase(p_path); singleton->shallow_gdscript_cache.erase(p_path); singleton->full_gdscript_cache.erase(p_path); } Ref<GDScriptParserRef> GDScriptCache::get_parser(const String &p_path, GDScriptParserRef::Status p_status, Error &r_error, const String &p_owner) { - MutexLock lock(singleton->lock); + MutexLock lock(singleton->mutex); Ref<GDScriptParserRef> ref; if (!p_owner.is_empty()) { singleton->dependencies[p_owner].insert(p_path); @@ -161,8 +238,8 @@ 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) { - MutexLock lock(singleton->lock); +Ref<GDScript> GDScriptCache::get_shallow_script(const String &p_path, Error &r_error, const String &p_owner) { + MutexLock lock(singleton->mutex); if (!p_owner.is_empty()) { singleton->dependencies[p_owner].insert(p_path); } @@ -176,15 +253,19 @@ Ref<GDScript> GDScriptCache::get_shallow_script(const String &p_path, const Stri Ref<GDScript> script; script.instantiate(); script->set_path(p_path, true); - script->set_script_path(p_path); script->load_source_code(p_path); - singleton->shallow_gdscript_cache[p_path] = script.ptr(); + Ref<GDScriptParserRef> parser_ref = get_parser(p_path, GDScriptParserRef::PARSED, r_error); + if (r_error == OK) { + GDScriptCompiler::make_scripts(script.ptr(), parser_ref->get_parser()->get_tree(), true); + } + + singleton->shallow_gdscript_cache[p_path] = script; return script; } Ref<GDScript> GDScriptCache::get_full_script(const String &p_path, Error &r_error, const String &p_owner, bool p_update_from_disk) { - MutexLock lock(singleton->lock); + MutexLock lock(singleton->mutex); if (!p_owner.is_empty()) { singleton->dependencies[p_owner].insert(p_path); @@ -193,38 +274,60 @@ Ref<GDScript> GDScriptCache::get_full_script(const String &p_path, Error &r_erro Ref<GDScript> script; r_error = OK; if (singleton->full_gdscript_cache.has(p_path)) { - script = Ref<GDScript>(singleton->full_gdscript_cache[p_path]); + script = singleton->full_gdscript_cache[p_path]; if (!p_update_from_disk) { return script; } } 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(); + singleton->full_gdscript_cache[p_path] = script; + singleton->shallow_gdscript_cache.erase(p_path); + + r_error = script->reload(true); if (r_error) { + singleton->shallow_gdscript_cache[p_path] = script; + singleton->full_gdscript_cache.erase(p_path); return script; } - singleton->full_gdscript_cache[p_path] = script.ptr(); - singleton->shallow_gdscript_cache.erase(p_path); - return script; } +Ref<GDScript> GDScriptCache::get_cached_script(const String &p_path) { + MutexLock lock(singleton->mutex); + + 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->mutex); + // Mark this as compiled. - Ref<GDScript> script = get_shallow_script(p_owner); - singleton->full_gdscript_cache[p_owner] = script.ptr(); + Ref<GDScript> script = get_cached_script(p_owner); + singleton->full_gdscript_cache[p_owner] = script; singleton->shallow_gdscript_cache.erase(p_owner); HashSet<String> depends = singleton->dependencies[p_owner]; @@ -245,13 +348,98 @@ Error GDScriptCache::finish_compiling(const String &p_owner) { return err; } +Ref<PackedScene> GDScriptCache::get_packed_scene(const String &p_path, Error &r_error, const String &p_owner) { + MutexLock lock(singleton->mutex); + + if (singleton->packed_scene_cache.has(p_path)) { + singleton->packed_scene_dependencies[p_path].insert(p_owner); + return singleton->packed_scene_cache[p_path]; + } + + Ref<PackedScene> scene = ResourceCache::get_ref(p_path); + if (scene.is_valid()) { + singleton->packed_scene_cache[p_path] = scene; + singleton->packed_scene_dependencies[p_path].insert(p_owner); + return scene; + } + scene.instantiate(); + + r_error = OK; + if (p_path.is_empty()) { + r_error = ERR_FILE_BAD_PATH; + return scene; + } + + scene->set_path(p_path); + singleton->packed_scene_cache[p_path] = scene; + singleton->packed_scene_dependencies[p_path].insert(p_owner); + + scene->reload_from_file(); + return scene; +} + +void GDScriptCache::clear_unreferenced_packed_scenes() { + if (singleton == nullptr) { + return; + } + + MutexLock lock(singleton->mutex); + + if (singleton->cleared) { + return; + } + + for (KeyValue<String, HashSet<String>> &E : singleton->packed_scene_dependencies) { + if (E.value.size() > 0 || !ResourceLoader::is_imported(E.key)) { + continue; + } + + singleton->packed_scene_dependencies.erase(E.key); + singleton->packed_scene_cache.erase(E.key); + } +} + +void GDScriptCache::clear() { + if (singleton == nullptr) { + return; + } + + MutexLock lock(singleton->mutex); + + if (singleton->cleared) { + return; + } + singleton->cleared = true; + + RBSet<Ref<GDScriptParserRef>> parser_map_refs; + for (KeyValue<String, GDScriptParserRef *> &E : singleton->parser_map) { + parser_map_refs.insert(E.value); + } + + for (Ref<GDScriptParserRef> &E : parser_map_refs) { + if (E.is_valid()) + E->clear(); + } + + singleton->packed_scene_dependencies.clear(); + singleton->packed_scene_cache.clear(); + + parser_map_refs.clear(); + singleton->parser_map.clear(); + singleton->shallow_gdscript_cache.clear(); + singleton->full_gdscript_cache.clear(); + + singleton->packed_scene_cache.clear(); + singleton->packed_scene_dependencies.clear(); +} + GDScriptCache::GDScriptCache() { singleton = this; } GDScriptCache::~GDScriptCache() { - parser_map.clear(); - shallow_gdscript_cache.clear(); - full_gdscript_cache.clear(); + if (!cleared) { + clear(); + } singleton = nullptr; } diff --git a/modules/gdscript/gdscript_cache.h b/modules/gdscript/gdscript_cache.h index 3d111ea229..43a45bfef6 100644 --- a/modules/gdscript/gdscript_cache.h +++ b/modules/gdscript/gdscript_cache.h @@ -36,6 +36,7 @@ #include "core/templates/hash_map.h" #include "core/templates/hash_set.h" #include "gdscript.h" +#include "scene/resources/packed_scene.h" class GDScriptAnalyzer; class GDScriptParser; @@ -56,6 +57,7 @@ private: Status status = EMPTY; Error result = OK; String path; + bool cleared = false; friend class GDScriptCache; @@ -63,7 +65,9 @@ public: bool is_valid() const; Status get_status() const; GDScriptParser *get_parser() const; + GDScriptAnalyzer *get_analyzer(); Error raise_status(Status p_new_status); + void clear(); GDScriptParserRef() {} ~GDScriptParserRef(); @@ -72,25 +76,37 @@ public: class GDScriptCache { // String key is full path. HashMap<String, GDScriptParserRef *> parser_map; - HashMap<String, GDScript *> shallow_gdscript_cache; - HashMap<String, GDScript *> full_gdscript_cache; + HashMap<String, Ref<GDScript>> shallow_gdscript_cache; + HashMap<String, Ref<GDScript>> full_gdscript_cache; HashMap<String, HashSet<String>> dependencies; + HashMap<String, Ref<PackedScene>> packed_scene_cache; + HashMap<String, HashSet<String>> packed_scene_dependencies; friend class GDScript; friend class GDScriptParserRef; + friend class GDScriptInstance; static GDScriptCache *singleton; - Mutex lock; - static void remove_script(const String &p_path); + bool cleared = false; + + Mutex mutex; public: + static void move_script(const String &p_from, const String &p_to); + static void remove_script(const String &p_path); 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); + static Ref<PackedScene> get_packed_scene(const String &p_path, Error &r_error, const String &p_owner = ""); + static void clear_unreferenced_packed_scenes(); + + static void clear(); + GDScriptCache(); ~GDScriptCache(); }; diff --git a/modules/gdscript/gdscript_compiler.cpp b/modules/gdscript/gdscript_compiler.cpp index 7acd1cdb96..4740b9b5a9 100644 --- a/modules/gdscript/gdscript_compiler.cpp +++ b/modules/gdscript/gdscript_compiler.cpp @@ -43,7 +43,7 @@ bool GDScriptCompiler::_is_class_member_property(CodeGen &codegen, const StringN return false; } - if (codegen.parameters.has(p_name) || codegen.locals.has(p_name)) { + if (_is_local_or_parameter(codegen, p_name)) { return false; //shadowed } @@ -65,6 +65,10 @@ bool GDScriptCompiler::_is_class_member_property(GDScript *owner, const StringNa return ClassDB::has_property(nc->get_name(), p_name); } +bool GDScriptCompiler::_is_local_or_parameter(CodeGen &codegen, const StringName &p_name) { + return codegen.parameters.has(p_name) || codegen.locals.has(p_name); +} + void GDScriptCompiler::_set_error(const String &p_error, const GDScriptParser::Node *p_node) { if (!error.is_empty()) { return; @@ -80,7 +84,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 +107,45 @@ 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); + } + } + + if (script.is_valid()) { + script = Ref<GDScript>(script->find_class(p_datatype.class_type->fqcn)); + } - result.script_type = result.script_type_ref.ptr(); - result.native_type = p_datatype.native_type; + 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); + return GDScriptDataType(); + } 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(); + result.native_type = p_datatype.native_type; } } break; case GDScriptParser::DataType::ENUM: @@ -182,6 +157,7 @@ GDScriptDataType GDScriptCompiler::_gdtype_from_datatype(const GDScriptParser::D result.builtin_type = Variant::INT; } break; + case GDScriptParser::DataType::RESOLVING: case GDScriptParser::DataType::UNRESOLVED: { ERR_PRINT("Parser bug: converting unresolved type."); return GDScriptDataType(); @@ -189,13 +165,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 +337,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; @@ -390,11 +360,22 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &code if (class_node->identifier && class_node->identifier->name == identifier) { res = Ref<GDScript>(main_script); } else { - res = ResourceLoader::load(ScriptServer::get_global_class_path(identifier)); - if (res.is_null()) { - _set_error("Can't load global class " + String(identifier) + ", cyclic reference?", p_expression); - r_error = ERR_COMPILATION_FAILED; - return GDScriptCodeGenerator::Address(); + String global_class_path = ScriptServer::get_global_class_path(identifier); + if (ResourceLoader::get_resource_type(global_class_path) == "GDScript") { + Error err = OK; + res = GDScriptCache::get_full_script(global_class_path, err); + if (err != OK) { + _set_error("Can't load global class " + String(identifier), p_expression); + r_error = ERR_COMPILATION_FAILED; + return GDScriptCodeGenerator::Address(); + } + } else { + res = ResourceLoader::load(global_class_path); + if (res.is_null()) { + _set_error("Can't load global class " + String(identifier) + ", cyclic reference?", p_expression); + r_error = ERR_COMPILATION_FAILED; + return GDScriptCodeGenerator::Address(); + } } } @@ -434,7 +415,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 +492,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 +515,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 +651,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 +667,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 +686,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 +716,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 +754,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 +772,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 +848,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); @@ -944,7 +925,7 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &code StringName var_name = identifier->name; if (_is_class_member_property(codegen, var_name)) { assign_class_member_property = var_name; - } else if (!codegen.locals.has(var_name) && codegen.script->member_indices.has(var_name)) { + } else if (!_is_local_or_parameter(codegen, var_name) && codegen.script->member_indices.has(var_name)) { is_member_property = true; member_property_setter_function = codegen.script->member_indices[var_name].setter; member_property_has_setter = member_property_setter_function != StringName(); @@ -985,7 +966,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 +1005,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 +1111,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. @@ -1155,7 +1136,7 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &code bool is_in_setter = false; StringName setter_function; StringName var_name = static_cast<const GDScriptParser::IdentifierNode *>(assignment->assignee)->name; - if (!codegen.locals.has(var_name) && codegen.script->member_indices.has(var_name)) { + if (!_is_local_or_parameter(codegen, var_name) && codegen.script->member_indices.has(var_name)) { is_member = true; setter_function = codegen.script->member_indices[var_name].setter; has_setter = setter_function != StringName(); @@ -1184,7 +1165,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 +1177,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 +1207,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()); @@ -1276,9 +1257,30 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_match_pattern(CodeGen &c equality_type.kind = GDScriptDataType::BUILTIN; equality_type.builtin_type = Variant::BOOL; + GDScriptCodeGenerator::Address type_string_addr = codegen.add_constant(Variant::STRING); + GDScriptCodeGenerator::Address type_string_name_addr = codegen.add_constant(Variant::STRING_NAME); + // Check type equality. GDScriptCodeGenerator::Address type_equality_addr = codegen.add_temporary(equality_type); codegen.generator->write_binary_operator(type_equality_addr, Variant::OP_EQUAL, p_type_addr, literal_type_addr); + + // Check if StringName <-> String comparison is possible. + GDScriptCodeGenerator::Address type_comp_addr_1 = codegen.add_temporary(equality_type); + GDScriptCodeGenerator::Address type_comp_addr_2 = codegen.add_temporary(equality_type); + + codegen.generator->write_binary_operator(type_comp_addr_1, Variant::OP_EQUAL, p_type_addr, type_string_addr); + codegen.generator->write_binary_operator(type_comp_addr_2, Variant::OP_EQUAL, literal_type_addr, type_string_name_addr); + codegen.generator->write_binary_operator(type_comp_addr_1, Variant::OP_AND, type_comp_addr_1, type_comp_addr_2); + codegen.generator->write_binary_operator(type_equality_addr, Variant::OP_OR, type_equality_addr, type_comp_addr_1); + + codegen.generator->write_binary_operator(type_comp_addr_1, Variant::OP_EQUAL, p_type_addr, type_string_name_addr); + codegen.generator->write_binary_operator(type_comp_addr_2, Variant::OP_EQUAL, literal_type_addr, type_string_addr); + codegen.generator->write_binary_operator(type_comp_addr_1, Variant::OP_AND, type_comp_addr_1, type_comp_addr_2); + codegen.generator->write_binary_operator(type_equality_addr, Variant::OP_OR, type_equality_addr, type_comp_addr_1); + + codegen.generator->pop_temporary(); // Remove type_comp_addr_2 from stack. + codegen.generator->pop_temporary(); // Remove type_comp_addr_1 from stack. + codegen.generator->write_and_left_operand(type_equality_addr); // Get literal. @@ -1645,7 +1647,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 +1677,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 +1786,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 +1899,14 @@ 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); + bool initialized = false; 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,15 +1923,23 @@ 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()) { + initialized = true; + } 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>()); + initialized = true; + } else if (local_type.kind == GDScriptDataType::BUILTIN) { codegen.generator->write_construct(local, local_type.builtin_type, Vector<GDScriptCodeGenerator::Address>()); + initialized = true; } // The `else` branch is for objects, in such case we leave it as `null`. } + + // Assigns a null for the unassigned variables in loops. + if (!initialized && p_block->is_loop) { + codegen.generator->write_construct(local, Variant::NIL, Vector<GDScriptCodeGenerator::Address>()); + } } break; case GDScriptParser::Node::CONSTANT: { // Local constants. @@ -2033,17 +2044,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 +2073,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`. @@ -2107,8 +2118,8 @@ GDScriptFunction *GDScriptCompiler::_parse_function(Error &r_error, GDScript *p_ if (EngineDebugger::is_active()) { String signature; // Path. - if (!p_script->get_path().is_empty()) { - signature += p_script->get_path(); + if (!p_script->get_script_path().is_empty()) { + signature += p_script->get_script_path(); } // Location. if (p_func) { @@ -2195,23 +2206,20 @@ 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); + + p_script->clearing = true; #ifdef TOOLS_ENABLED p_script->doc_functions.clear(); p_script->doc_variables.clear(); @@ -2234,10 +2242,24 @@ Error GDScriptCompiler::_parse_class_level(GDScript *p_script, const GDScriptPar p_script->base = Ref<GDScript>(); p_script->_base = nullptr; p_script->members.clear(); + + // This makes possible to clear script constants and member_functions without heap-use-after-free errors. + HashMap<StringName, Variant> constants; + for (const KeyValue<StringName, Variant> &E : p_script->constants) { + constants.insert(E.key, E.value); + } p_script->constants.clear(); + constants.clear(); + HashMap<StringName, GDScriptFunction *> member_functions; for (const KeyValue<StringName, GDScriptFunction *> &E : p_script->member_functions) { + member_functions.insert(E.key, E.value); + } + p_script->member_functions.clear(); + for (const KeyValue<StringName, GDScriptFunction *> &E : member_functions) { memdelete(E.value); } + member_functions.clear(); + if (p_script->implicit_initializer) { memdelete(p_script->implicit_initializer); } @@ -2252,8 +2274,9 @@ Error GDScriptCompiler::_parse_class_level(GDScript *p_script, const GDScriptPar p_script->implicit_initializer = nullptr; p_script->implicit_ready = nullptr; + p_script->clearing = false; + 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 +2285,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() && !base->reloading, 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; } @@ -2370,6 +2390,7 @@ Error GDScriptCompiler::_parse_class_level(GDScript *p_script, const GDScriptPar #ifdef TOOLS_ENABLED if (variable->initializer != nullptr && variable->initializer->is_constant) { p_script->member_default_values[name] = variable->initializer->reduced_value; + GDScriptCompiler::convert_to_initializer_type(p_script->member_default_values[name], variable); } else { p_script->member_default_values.erase(name); } @@ -2432,9 +2453,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 +2499,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,27 +2511,24 @@ 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; } } #ifdef TOOLS_ENABLED - p_script->member_lines[name] = inner_class->start_line; #endif - p_script->constants.insert(name, subclass); //once parsed, goes to the list of constants } 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 +2632,40 @@ 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::convert_to_initializer_type(Variant &p_variant, const GDScriptParser::VariableNode *p_node) { + // Set p_variant to the value of p_node's initializer, with the type of p_node's variable. + GDScriptParser::DataType member_t = p_node->datatype; + GDScriptParser::DataType init_t = p_node->initializer->datatype; + if (member_t.is_hard_type() && init_t.is_hard_type() && + member_t.kind == GDScriptParser::DataType::BUILTIN && init_t.kind == GDScriptParser::DataType::BUILTIN) { + if (Variant::can_convert_strict(init_t.builtin_type, member_t.builtin_type)) { + Variant *v = &p_node->initializer->reduced_value; + Callable::CallError ce; + Variant::construct(member_t.builtin_type, p_variant, const_cast<const Variant **>(&v), 1, ce); + } + } +} + +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 +2682,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 +2711,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..fc5aa05190 100644 --- a/modules/gdscript/gdscript_compiler.h +++ b/modules/gdscript/gdscript_compiler.h @@ -115,13 +115,14 @@ class GDScriptCompiler { bool _is_class_member_property(CodeGen &codegen, const StringName &p_name); bool _is_class_member_property(GDScript *owner, const StringName &p_name); + bool _is_local_or_parameter(CodeGen &codegen, const StringName &p_name); void _set_error(const String &p_error, const GDScriptParser::Node *p_node); 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 +131,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 +140,8 @@ class GDScriptCompiler { bool within_await = false; public: + static void convert_to_initializer_type(Variant &p_variant, const GDScriptParser::VariableNode *p_node); + 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..79387d1bf6 100644 --- a/modules/gdscript/gdscript_editor.cpp +++ b/modules/gdscript/gdscript_editor.cpp @@ -1612,7 +1612,7 @@ static bool _guess_expression_type(GDScriptParser::CompletionContext &p_context, } } - if (!found) { + if (!found && base.value.get_type() != Variant::NIL) { found = _guess_method_return_type_from_base(c, base, call->function_name, r_type); } } @@ -2272,6 +2272,11 @@ static bool _guess_method_return_type_from_base(GDScriptParser::CompletionContex if (base_type.class_type->has_function(p_method)) { const GDScriptParser::FunctionNode *method = base_type.class_type->get_member(p_method).function; if (!is_static || method->is_static) { + if (method->get_datatype().is_set() && !method->get_datatype().is_variant()) { + r_type.type = method->get_datatype(); + return true; + } + int last_return_line = -1; const GDScriptParser::ExpressionNode *last_returned_value = nullptr; GDScriptParser::CompletionContext c = p_context; @@ -2285,10 +2290,6 @@ static bool _guess_method_return_type_from_base(GDScriptParser::CompletionContex if (_guess_expression_type(c, last_returned_value, r_type)) { return true; } - if (method->get_datatype().is_set() && !method->get_datatype().is_variant()) { - r_type.type = method->get_datatype(); - return true; - } } } } @@ -2511,9 +2512,72 @@ static void _find_call_arguments(GDScriptParser::CompletionContext &p_context, c } } +static bool _get_subscript_type(GDScriptParser::CompletionContext &p_context, const GDScriptParser::SubscriptNode *p_subscript, GDScriptParser::DataType &r_base_type, Variant *r_base = nullptr) { + if (p_context.base == nullptr) { + return false; + } + const GDScriptParser::GetNodeNode *get_node = nullptr; + + switch (p_subscript->base->type) { + case GDScriptParser::Node::GET_NODE: { + get_node = static_cast<GDScriptParser::GetNodeNode *>(p_subscript->base); + } break; + + case GDScriptParser::Node::IDENTIFIER: { + const GDScriptParser::IdentifierNode *identifier_node = static_cast<GDScriptParser::IdentifierNode *>(p_subscript->base); + + switch (identifier_node->source) { + case GDScriptParser::IdentifierNode::Source::MEMBER_VARIABLE: { + if (p_context.current_class != nullptr) { + const StringName &member_name = identifier_node->name; + const GDScriptParser::ClassNode *current_class = p_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; + } + } break; + default: { + } break; + } + + if (get_node != nullptr) { + const Object *node = p_context.base->call("get_node_or_null", NodePath(get_node->full_path)); + if (node != nullptr) { + if (r_base != nullptr) { + *r_base = node; + } + r_base_type.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT; + r_base_type.kind = GDScriptParser::DataType::NATIVE; + r_base_type.native_type = node->get_class_name(); + r_base_type.builtin_type = Variant::OBJECT; + return true; + } + } + + return false; +} + 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); } @@ -2562,12 +2626,16 @@ static void _find_call_arguments(GDScriptParser::CompletionContext &p_context, c } if (subscript->is_attribute) { - GDScriptCompletionIdentifier ci; - if (_guess_expression_type(p_context, subscript->base, ci)) { - base_type = ci.type; - base = ci.value; - } else { - return; + bool found_type = _get_subscript_type(p_context, subscript, base_type, &base); + + if (!found_type) { + GDScriptCompletionIdentifier ci; + if (_guess_expression_type(p_context, subscript->base, ci)) { + base_type = ci.type; + base = ci.value; + } else { + return; + } } _static = base_type.is_meta_type; @@ -2765,7 +2833,8 @@ 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 = _get_subscript_type(completion_context, attr, base.type); + if (!found_type && !_guess_expression_type(completion_context, attr->base, base)) { break; } @@ -2820,7 +2889,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 +3120,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; } @@ -3208,15 +3278,6 @@ static Error _lookup_symbol_from_base(const GDScriptParser::DataType &p_base, co } } - // Need special checks for assert and preload as they are technically - // keywords, so are not registered in GDScriptUtilityFunctions. - if (GDScriptUtilityFunctions::function_exists(p_symbol) || "assert" == p_symbol || "preload" == p_symbol) { - r_result.type = ScriptLanguage::LOOKUP_RESULT_CLASS_METHOD; - r_result.class_name = "@GDScript"; - r_result.class_member = p_symbol; - return OK; - } - if ("PI" == p_symbol || "TAU" == p_symbol || "INF" == p_symbol || "NAN" == p_symbol) { r_result.type = ScriptLanguage::LOOKUP_RESULT_CLASS_CONSTANT; r_result.class_name = "@GDScript"; @@ -3226,10 +3287,24 @@ static Error _lookup_symbol_from_base(const GDScriptParser::DataType &p_base, co GDScriptParser parser; parser.parse(p_code, p_path, true); - GDScriptAnalyzer analyzer(&parser); - analyzer.analyze(); GDScriptParser::CompletionContext context = parser.get_completion_context(); + context.base = p_owner; + + // Allows class functions with the names like built-ins to be handled properly. + if (context.type != GDScriptParser::COMPLETION_ATTRIBUTE) { + // Need special checks for assert and preload as they are technically + // keywords, so are not registered in GDScriptUtilityFunctions. + if (GDScriptUtilityFunctions::function_exists(p_symbol) || "assert" == p_symbol || "preload" == p_symbol) { + r_result.type = ScriptLanguage::LOOKUP_RESULT_CLASS_METHOD; + r_result.class_name = "@GDScript"; + r_result.class_member = p_symbol; + return OK; + } + } + + GDScriptAnalyzer analyzer(&parser); + analyzer.analyze(); if (context.current_class && context.current_class->extends.size() > 0) { bool success = false; @@ -3387,7 +3462,9 @@ static Error _lookup_symbol_from_base(const GDScriptParser::DataType &p_base, co break; } GDScriptCompletionIdentifier base; - if (!_guess_expression_type(context, subscript->base, base)) { + + bool found_type = _get_subscript_type(context, subscript, base.type); + if (!found_type && !_guess_expression_type(context, subscript->base, base)) { break; } diff --git a/modules/gdscript/gdscript_function.cpp b/modules/gdscript/gdscript_function.cpp index 98b3e40f1b..24a614b1ad 100644 --- a/modules/gdscript/gdscript_function.cpp +++ b/modules/gdscript/gdscript_function.cpp @@ -149,10 +149,17 @@ GDScriptFunction::GDScriptFunction() { } GDScriptFunction::~GDScriptFunction() { + get_script()->member_functions.erase(name); + for (int i = 0; i < lambdas.size(); i++) { memdelete(lambdas[i]); } + for (int i = 0; i < argument_types.size(); i++) { + argument_types.write[i].script_type_ref = Ref<Script>(); + } + return_type.script_type_ref = Ref<Script>(); + #ifdef DEBUG_ENABLED MutexLock lock(GDScriptLanguage::get_singleton()->mutex); diff --git a/modules/gdscript/gdscript_function.h b/modules/gdscript/gdscript_function.h index e44038d6da..6e5f7a8520 100644 --- a/modules/gdscript/gdscript_function.h +++ b/modules/gdscript/gdscript_function.h @@ -430,6 +430,7 @@ public: }; private: + friend class GDScript; friend class GDScriptCompiler; friend class GDScriptByteCodeGenerator; diff --git a/modules/gdscript/gdscript_parser.cpp b/modules/gdscript/gdscript_parser.cpp index bdf6fb35b6..103269f0f5 100644 --- a/modules/gdscript/gdscript_parser.cpp +++ b/modules/gdscript/gdscript_parser.cpp @@ -147,6 +147,10 @@ GDScriptParser::GDScriptParser() { register_annotation(MethodInfo("@warning_ignore", PropertyInfo(Variant::STRING, "warning")), AnnotationInfo::CLASS | AnnotationInfo::VARIABLE | AnnotationInfo::SIGNAL | AnnotationInfo::CONSTANT | AnnotationInfo::FUNCTION | AnnotationInfo::STATEMENT, &GDScriptParser::warning_annotations, varray(), true); // Networking. register_annotation(MethodInfo("@rpc", PropertyInfo(Variant::STRING, "mode"), PropertyInfo(Variant::STRING, "sync"), PropertyInfo(Variant::STRING, "transfer_mode"), PropertyInfo(Variant::INT, "transfer_channel")), AnnotationInfo::FUNCTION, &GDScriptParser::rpc_annotation, varray("", "", "", 0), true); + +#ifdef DEBUG_ENABLED + is_ignoring_warnings = !(bool)GLOBAL_GET("debug/gdscript/warnings/enable"); +#endif } GDScriptParser::~GDScriptParser() { @@ -230,7 +234,7 @@ void GDScriptParser::push_warning(const Node *p_source, GDScriptWarning::Code p_ warning.leftmost_column = p_source->leftmost_column; warning.rightmost_column = p_source->rightmost_column; - if (warn_level == GDScriptWarning::WarnLevel::ERROR) { + if (warn_level == GDScriptWarning::WarnLevel::ERROR || bool(GLOBAL_GET("debug/gdscript/warnings/treat_warnings_as_errors"))) { push_error(warning.get_message(), p_source); return; } @@ -534,6 +538,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. @@ -637,6 +642,53 @@ void GDScriptParser::parse_program() { clear_unused_annotations(); } +GDScriptParser::ClassNode *GDScriptParser::find_class(const String &p_qualified_name) const { + String first = p_qualified_name.get_slice("::", 0); + + Vector<String> class_names; + GDScriptParser::ClassNode *result = nullptr; + // Empty initial name means start at the head. + if (first.is_empty() || (head->identifier && first == head->identifier->name)) { + class_names = p_qualified_name.split("::"); + result = head; + } else if (p_qualified_name.begins_with(script_path)) { + // Script path could have a class path separator("::") in it. + class_names = p_qualified_name.trim_prefix(script_path).split("::"); + result = head; + } else if (head->has_member(first)) { + class_names = p_qualified_name.split("::"); + GDScriptParser::ClassNode::Member member = head->get_member(first); + if (member.type == GDScriptParser::ClassNode::Member::CLASS) { + result = member.m_class; + } + } + + // Starts at index 1 because index 0 was handled above. + for (int i = 1; result != nullptr && i < class_names.size(); i++) { + String current_name = class_names[i]; + GDScriptParser::ClassNode *next = nullptr; + if (result->has_member(current_name)) { + GDScriptParser::ClassNode::Member member = result->get_member(current_name); + if (member.type == GDScriptParser::ClassNode::Member::CLASS) { + next = member.m_class; + } + } + result = next; + } + + return result; +} + +bool GDScriptParser::has_class(const GDScriptParser::ClassNode *p_class) const { + if (head->fqcn.is_empty() && p_class->fqcn.get_slice("::", 0).is_empty()) { + return p_class == head; + } else if (p_class->fqcn.begins_with(head->fqcn)) { + return find_class(p_class->fqcn.trim_prefix(head->fqcn)) == p_class; + } + + return false; +} + GDScriptParser::ClassNode *GDScriptParser::parse_class() { ClassNode *n_class = alloc_node<ClassNode>(); @@ -646,6 +698,15 @@ 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) { + String fqcn = n_class->outer->fqcn; + if (fqcn.is_empty()) { + fqcn = script_path; + } + n_class->fqcn = fqcn + "::" + n_class->identifier->name; + } else { + n_class->fqcn = n_class->identifier->name; + } } if (match(GDScriptTokenizer::Token::EXTENDS)) { @@ -684,6 +745,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)) { @@ -1530,7 +1592,7 @@ GDScriptParser::SuiteNode *GDScriptParser::parse_suite(const String &p_context, VariableNode *variable = static_cast<VariableNode *>(statement); const SuiteNode::Local &local = current_suite->get_local(variable->identifier->name); if (local.type != SuiteNode::Local::UNDEFINED) { - push_error(vformat(R"(There is already a %s named "%s" declared in this scope.)", local.get_name(), variable->identifier->name)); + push_error(vformat(R"(There is already a %s named "%s" declared in this scope.)", local.get_name(), variable->identifier->name), variable->identifier); } current_suite->add_local(variable, current_function); break; @@ -1545,7 +1607,7 @@ GDScriptParser::SuiteNode *GDScriptParser::parse_suite(const String &p_context, } else { name = "variable"; } - push_error(vformat(R"(There is already a %s named "%s" declared in this scope.)", name, constant->identifier->name)); + push_error(vformat(R"(There is already a %s named "%s" declared in this scope.)", name, constant->identifier->name), constant->identifier); } current_suite->add_local(constant, current_function); break; @@ -1820,9 +1882,9 @@ GDScriptParser::ForNode *GDScriptParser::parse_for() { } suite->add_local(SuiteNode::Local(n_for->variable, current_function)); } - suite->parent_for = n_for; n_for->loop = parse_suite(R"("for" block)", suite); + n_for->loop->is_loop = true; complete_extents(n_for); // Reset break/continue state. @@ -2154,6 +2216,7 @@ GDScriptParser::WhileNode *GDScriptParser::parse_while() { is_continue_match = false; n_while->loop = parse_suite(R"("while" block)"); + n_while->loop->is_loop = true; complete_extents(n_while); // Reset break/continue state. @@ -2224,7 +2287,7 @@ GDScriptParser::IdentifierNode *GDScriptParser::parse_identifier() { GDScriptParser::ExpressionNode *GDScriptParser::parse_identifier(ExpressionNode *p_previous_operand, bool p_can_assign) { if (!previous.is_identifier()) { - ERR_FAIL_V_MSG(nullptr, "Parser bug: parsing literal node without literal token."); + ERR_FAIL_V_MSG(nullptr, "Parser bug: parsing identifier node without identifier token."); } IdentifierNode *identifier = alloc_node<IdentifierNode>(); complete_extents(identifier); @@ -3725,6 +3788,12 @@ bool GDScriptParser::export_annotations(const AnnotationNode *p_annotation, Node // This is called after the analyzer is done finding the type, so this should be set here. DataType export_type = variable->get_datatype(); + if (p_annotation->name == SNAME("@export_range")) { + if (export_type.builtin_type == Variant::INT) { + variable->export_info.type = Variant::INT; + } + } + if (p_annotation->name == SNAME("@export")) { if (variable->datatype_specifier == nullptr && variable->initializer == nullptr) { push_error(R"(Cannot use simple "@export" annotation with variable without type or initializer, since type can't be inferred.)", p_annotation); @@ -3764,27 +3833,35 @@ 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.to_string(); + } 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.to_string(); + } 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; - if (export_type.script_type != nullptr && export_type.script_type.is_valid()) { + StringName native_base; + if (export_type.script_type.is_valid()) { class_name = export_type.script_type->get_language()->get_global_class_name(export_type.script_type->get_path()); + native_base = export_type.script_type->get_instance_base_type(); } if (class_name == StringName()) { Ref<Script> script = ResourceLoader::load(export_type.script_path, SNAME("Script")); if (script.is_valid()) { class_name = script->get_language()->get_global_class_name(export_type.script_path); + native_base = script->get_instance_base_type(); } } - if (class_name != StringName() && ClassDB::is_parent_class(ScriptServer::get_global_class_native_base(class_name), SNAME("Resource"))) { + if (class_name != StringName() && native_base != StringName() && ClassDB::is_parent_class(native_base, SNAME("Resource"))) { variable->export_info.type = Variant::OBJECT; variable->export_info.hint = PROPERTY_HINT_RESOURCE_TYPE; variable->export_info.hint_string = class_name; @@ -3796,7 +3873,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 { @@ -4012,11 +4089,12 @@ String GDScriptParser::DataType::to_string() const { } case ENUM: return enum_type.operator String() + " (enum)"; + case RESOLVING: case UNRESOLVED: return "<unresolved type>"; } - ERR_FAIL_V_MSG("<unresolved type", "Kind set outside the enum range."); + ERR_FAIL_V_MSG("<unresolved type>", "Kind set outside the enum range."); } static Variant::Type _variant_type_to_typed_array_element_type(Variant::Type p_type) { diff --git a/modules/gdscript/gdscript_parser.h b/modules/gdscript/gdscript_parser.h index 1850a44678..540ef1c561 100644 --- a/modules/gdscript/gdscript_parser.h +++ b/modules/gdscript/gdscript_parser.h @@ -107,6 +107,7 @@ public: CLASS, // GDScript. ENUM, // Enumeration. VARIANT, // Can be any type. + RESOLVING, // Currently resolving. UNRESOLVED, }; Kind kind = UNRESOLVED; @@ -131,11 +132,12 @@ 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 is_set() const { return kind != RESOLVING && kind != UNRESOLVED; } + _FORCE_INLINE_ bool is_resolving() const { return kind == RESOLVING; } _FORCE_INLINE_ bool has_no_type() const { return type_source == UNDETECTED; } - _FORCE_INLINE_ bool is_variant() const { return kind == VARIANT || kind == UNRESOLVED; } + _FORCE_INLINE_ bool is_variant() const { return kind == VARIANT || kind == RESOLVING || kind == UNRESOLVED; } _FORCE_INLINE_ bool is_hard_type() const { return type_source > INFERRED; } String to_string() const; @@ -188,6 +190,7 @@ public: return script_type == p_other.script_type; case CLASS: return class_type == p_other.class_type; + case RESOLVING: case UNRESOLVED: break; } @@ -469,7 +472,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; @@ -516,6 +519,32 @@ public: }; EnumNode::Value enum_value; + String get_name() const { + switch (type) { + case UNDEFINED: + return "<undefined member>"; + case CLASS: + // All class-type members have an id. + return m_class->identifier->name; + case CONSTANT: + return constant->identifier->name; + case FUNCTION: + return function->identifier->name; + case SIGNAL: + return signal->identifier->name; + case VARIABLE: + return variable->identifier->name; + case ENUM: + // All enum-type members have an id. + return m_enum->identifier->name; + case ENUM_VALUE: + return enum_value.identifier->name; + case GROUP: + return annotation->export_info.name; + } + return ""; + } + String get_type_name() const { switch (type) { case UNDEFINED: @@ -576,31 +605,42 @@ public: return variable->get_datatype(); case ENUM: return m_enum->get_datatype(); - case ENUM_VALUE: { - // Always integer. - DataType out_type; - out_type.type_source = DataType::ANNOTATED_EXPLICIT; - out_type.kind = DataType::BUILTIN; - out_type.builtin_type = Variant::INT; - return out_type; - } - case SIGNAL: { - DataType out_type; - out_type.type_source = DataType::ANNOTATED_EXPLICIT; - out_type.kind = DataType::BUILTIN; - out_type.builtin_type = Variant::SIGNAL; - // TODO: Add parameter info. - return out_type; - } - case GROUP: { + case ENUM_VALUE: + return enum_value.identifier->get_datatype(); + case SIGNAL: + return signal->get_datatype(); + case GROUP: return DataType(); - } case UNDEFINED: return DataType(); } ERR_FAIL_V_MSG(DataType(), "Reaching unhandled type."); } + Node *get_source_node() const { + switch (type) { + case CLASS: + return m_class; + case CONSTANT: + return constant; + case FUNCTION: + return function; + case VARIABLE: + return variable; + case ENUM: + return m_enum; + case ENUM_VALUE: + return enum_value.identifier; + case SIGNAL: + return signal; + case GROUP: + return annotation; + case UNDEFINED: + return nullptr; + } + ERR_FAIL_V_MSG(nullptr, "Reaching unhandled type."); + } + Member() {} Member(ClassNode *p_class) { @@ -786,6 +826,7 @@ public: LOCAL_VARIABLE, LOCAL_ITERATOR, // `for` loop iterator. LOCAL_BIND, // Pattern bind. + MEMBER_SIGNAL, MEMBER_VARIABLE, MEMBER_CONSTANT, INHERITED_VARIABLE, @@ -1054,12 +1095,12 @@ public: HashMap<StringName, int> locals_indices; FunctionNode *parent_function = nullptr; - ForNode *parent_for = nullptr; IfNode *parent_if = nullptr; bool has_return = false; bool has_continue = false; bool has_unreachable_code = false; // Just so warnings aren't given more than once per block. + bool is_loop = false; bool has_local(const StringName &p_name) const; const Local &get_local(const StringName &p_name) const; @@ -1216,13 +1257,14 @@ private: bool can_break = false; bool can_continue = false; bool is_continue_match = false; // Whether a `continue` will act on a `match`. - bool is_ignoring_warnings = false; List<bool> multiline_stack; ClassNode *head = nullptr; Node *list = nullptr; List<ParserError> errors; + #ifdef DEBUG_ENABLED + bool is_ignoring_warnings = false; List<GDScriptWarning> warnings; HashSet<String> ignored_warnings; HashSet<uint32_t> ignored_warning_codes; @@ -1428,6 +1470,8 @@ public: Error parse(const String &p_source_code, const String &p_script_path, bool p_for_completion); ClassNode *get_tree() const { return head; } bool is_tool() const { return _is_tool; } + ClassNode *find_class(const String &p_qualified_name) const; + bool has_class(const GDScriptParser::ClassNode *p_class) const; static Variant::Type get_builtin_type(const StringName &p_type); CompletionContext get_completion_context() const { return completion_context; } diff --git a/modules/gdscript/gdscript_utility_functions.cpp b/modules/gdscript/gdscript_utility_functions.cpp index bcbe8b8d2b..27b6792e84 100644 --- a/modules/gdscript/gdscript_utility_functions.cpp +++ b/modules/gdscript/gdscript_utility_functions.cpp @@ -294,6 +294,7 @@ struct GDScriptUtilityFunctionsDefinitions { } GDScript *p = base.ptr(); + String path = p->get_script_path(); Vector<StringName> sname; while (p->_owner) { @@ -302,7 +303,7 @@ struct GDScriptUtilityFunctionsDefinitions { } sname.reverse(); - if (!p->path.is_resource_file()) { + if (!path.is_resource_file()) { r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT; r_error.argument = 0; r_error.expected = Variant::DICTIONARY; @@ -317,7 +318,7 @@ struct GDScriptUtilityFunctionsDefinitions { Dictionary d; d["@subpath"] = cp; - d["@path"] = p->get_path(); + d["@path"] = path; for (const KeyValue<StringName, GDScript::MemberInfo> &E : base->member_indices) { if (!d.has(E.key)) { diff --git a/modules/gdscript/gdscript_vm.cpp b/modules/gdscript/gdscript_vm.cpp index c73ba798aa..fdcc0625d7 100644 --- a/modules/gdscript/gdscript_vm.cpp +++ b/modules/gdscript/gdscript_vm.cpp @@ -2227,7 +2227,7 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a } #ifdef DEBUG_ENABLED gdfs->state.function_name = name; - gdfs->state.script_path = _script->get_path(); + gdfs->state.script_path = _script->get_script_path(); #endif gdfs->state.defarg = defarg; gdfs->function = this; diff --git a/modules/gdscript/gdscript_warning.cpp b/modules/gdscript/gdscript_warning.cpp index 1cae7bdfac..36bc051643 100644 --- a/modules/gdscript/gdscript_warning.cpp +++ b/modules/gdscript/gdscript_warning.cpp @@ -96,7 +96,7 @@ String GDScriptWarning::get_message() const { } break; case RETURN_VALUE_DISCARDED: { CHECK_SYMBOLS(1); - return "The function '" + symbols[0] + "()' returns a value, but this value is never used."; + return "The function '" + symbols[0] + "()' returns a value that will be discarded if not used."; } break; case PROPERTY_USED_AS_FUNCTION: { CHECK_SYMBOLS(2); @@ -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 } @@ -167,6 +171,10 @@ int GDScriptWarning::get_default_value(Code p_code) { if (get_name_from_code(p_code).to_lower().begins_with("unsafe_")) { return WarnLevel::IGNORE; } + // Too spammy by default on common cases (connect, Tween, etc.). + if (p_code == RETURN_VALUE_DISCARDED) { + return WarnLevel::IGNORE; + } return WarnLevel::WARN; } @@ -215,6 +223,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/language_server/gdscript_extend_parser.cpp b/modules/gdscript/language_server/gdscript_extend_parser.cpp index de3becbaf8..e442bf8159 100644 --- a/modules/gdscript/language_server/gdscript_extend_parser.cpp +++ b/modules/gdscript/language_server/gdscript_extend_parser.cpp @@ -844,8 +844,9 @@ Error ExtendGDScriptParser::parse(const String &p_code, const String &p_path) { lines = p_code.split("\n"); Error err = GDScriptParser::parse(p_code, p_path, false); + GDScriptAnalyzer analyzer(this); + if (err == OK) { - GDScriptAnalyzer analyzer(this); err = analyzer.analyze(); } update_diagnostics(); diff --git a/modules/gdscript/language_server/gdscript_language_protocol.cpp b/modules/gdscript/language_server/gdscript_language_protocol.cpp index 39f4c976a4..551973140d 100644 --- a/modules/gdscript/language_server/gdscript_language_protocol.cpp +++ b/modules/gdscript/language_server/gdscript_language_protocol.cpp @@ -32,6 +32,7 @@ #include "core/config/project_settings.h" #include "editor/doc_tools.h" +#include "editor/editor_help.h" #include "editor/editor_log.h" #include "editor/editor_node.h" #include "editor/editor_settings.h" diff --git a/modules/gdscript/tests/gdscript_test_runner.cpp b/modules/gdscript/tests/gdscript_test_runner.cpp index 6c346acb7e..f59983ca90 100644 --- a/modules/gdscript/tests/gdscript_test_runner.cpp +++ b/modules/gdscript/tests/gdscript_test_runner.cpp @@ -71,27 +71,38 @@ void init_autoloads() { continue; } - Ref<Resource> res = ResourceLoader::load(info.path); - ERR_CONTINUE_MSG(res.is_null(), "Can't autoload: " + info.path); Node *n = nullptr; - Ref<PackedScene> scn = res; - Ref<Script> script = res; - if (scn.is_valid()) { - n = scn->instantiate(); - } else if (script.is_valid()) { - StringName ibt = script->get_instance_base_type(); - bool valid_type = ClassDB::is_parent_class(ibt, "Node"); - ERR_CONTINUE_MSG(!valid_type, "Script does not inherit from Node: " + info.path); + if (ResourceLoader::get_resource_type(info.path) == "PackedScene") { + // Cache the scene reference before loading it (for cyclic references) + Ref<PackedScene> scn; + scn.instantiate(); + scn->set_path(info.path); + scn->reload_from_file(); + ERR_CONTINUE_MSG(!scn.is_valid(), vformat("Can't autoload: %s.", info.path)); + + if (scn.is_valid()) { + n = scn->instantiate(); + } + } else { + Ref<Resource> res = ResourceLoader::load(info.path); + ERR_CONTINUE_MSG(res.is_null(), vformat("Can't autoload: %s.", info.path)); - Object *obj = ClassDB::instantiate(ibt); + Ref<Script> scr = res; + if (scr.is_valid()) { + StringName ibt = scr->get_instance_base_type(); + bool valid_type = ClassDB::is_parent_class(ibt, "Node"); + ERR_CONTINUE_MSG(!valid_type, vformat("Script does not inherit from Node: %s.", info.path)); - ERR_CONTINUE_MSG(!obj, "Cannot instance script for autoload, expected 'Node' inheritance, got: " + String(ibt) + "."); + Object *obj = ClassDB::instantiate(ibt); - n = Object::cast_to<Node>(obj); - n->set_script(script); + ERR_CONTINUE_MSG(!obj, vformat("Cannot instance script for Autoload, expected 'Node' inheritance, got: %s.", ibt)); + + n = Object::cast_to<Node>(obj); + n->set_script(scr); + } } - ERR_CONTINUE_MSG(!n, "Path in autoload not a node or script: " + info.path); + ERR_CONTINUE_MSG(!n, vformat("Path in autoload not a node or script: %s.", info.path)); n->set_name(info.name); for (int i = 0; i < ScriptServer::get_language_count(); i++) { @@ -251,7 +262,10 @@ bool GDScriptTestRunner::make_tests_for_dir(const String &p_dir) { return false; } } else { - if (next.get_extension().to_lower() == "gd") { + if (next.ends_with(".notest.gd")) { + next = dir->get_next(); + continue; + } else if (next.get_extension().to_lower() == "gd") { #ifndef DEBUG_ENABLED // On release builds, skip tests marked as debug only. Error open_err = OK; @@ -461,7 +475,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(); @@ -479,9 +492,9 @@ GDScriptTest::TestResult GDScriptTest::execute_test_code(bool p_is_generating) { result.output = get_text_for_status(result.status) + "\n"; const List<GDScriptParser::ParserError> &errors = parser.get_errors(); - for (const GDScriptParser::ParserError &E : errors) { - result.output += E.message + "\n"; // TODO: line, column? - break; // Only the first error since the following might be cascading. + if (!errors.is_empty()) { + // Only the first error since the following might be cascading. + result.output += errors[0].message + "\n"; // TODO: line, column? } if (!p_is_generating) { result.passed = check_output(result.output); @@ -498,9 +511,9 @@ GDScriptTest::TestResult GDScriptTest::execute_test_code(bool p_is_generating) { result.output = get_text_for_status(result.status) + "\n"; const List<GDScriptParser::ParserError> &errors = parser.get_errors(); - for (const GDScriptParser::ParserError &E : errors) { - result.output += E.message + "\n"; // TODO: line, column? - break; // Only the first error since the following might be cascading. + if (!errors.is_empty()) { + // Only the first error since the following might be cascading. + result.output += errors[0].message + "\n"; // TODO: line, column? } if (!p_is_generating) { result.passed = check_output(result.output); @@ -598,6 +611,9 @@ GDScriptTest::TestResult GDScriptTest::execute_test_code(bool p_is_generating) { } enable_stdout(); + + GDScriptCache::remove_script(script->get_path()); + return result; } diff --git a/modules/gdscript/tests/scripts/analyzer/errors/class_name_shadows_builtin_type.out b/modules/gdscript/tests/scripts/analyzer/errors/class_name_shadows_builtin_type.out index 87863baf75..b9a1d301ad 100644 --- a/modules/gdscript/tests/scripts/analyzer/errors/class_name_shadows_builtin_type.out +++ b/modules/gdscript/tests/scripts/analyzer/errors/class_name_shadows_builtin_type.out @@ -1,2 +1,2 @@ GDTEST_ANALYZER_ERROR -The member "Vector2" cannot have the same name as a builtin type. +Class "Vector2" hides a built-in type. diff --git a/modules/gdscript/tests/scripts/analyzer/errors/cyclic_inheritance.gd b/modules/gdscript/tests/scripts/analyzer/errors/cyclic_inheritance.gd new file mode 100644 index 0000000000..d2f6404cd2 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/cyclic_inheritance.gd @@ -0,0 +1,8 @@ +func test(): + print(InnerA.new()) + +class InnerA extends InnerB: + pass + +class InnerB extends InnerA: + pass diff --git a/modules/gdscript/tests/scripts/analyzer/errors/cyclic_inheritance.out b/modules/gdscript/tests/scripts/analyzer/errors/cyclic_inheritance.out new file mode 100644 index 0000000000..75a94baa17 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/cyclic_inheritance.out @@ -0,0 +1,2 @@ +GDTEST_ANALYZER_ERROR +Cyclic inheritance. diff --git a/modules/gdscript/tests/scripts/analyzer/errors/cyclic_ref_const.gd b/modules/gdscript/tests/scripts/analyzer/errors/cyclic_ref_const.gd new file mode 100644 index 0000000000..4292534951 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/cyclic_ref_const.gd @@ -0,0 +1,5 @@ +func test(): + print(c1) + +const c1 = c2 +const c2 = c1 diff --git a/modules/gdscript/tests/scripts/analyzer/errors/cyclic_ref_const.out b/modules/gdscript/tests/scripts/analyzer/errors/cyclic_ref_const.out new file mode 100644 index 0000000000..e71b3fc56a --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/cyclic_ref_const.out @@ -0,0 +1,2 @@ +GDTEST_ANALYZER_ERROR +Could not resolve member "c1": Cyclic reference. diff --git a/modules/gdscript/tests/scripts/analyzer/errors/cyclic_ref_enum.gd b/modules/gdscript/tests/scripts/analyzer/errors/cyclic_ref_enum.gd new file mode 100644 index 0000000000..1caef3d366 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/cyclic_ref_enum.gd @@ -0,0 +1,5 @@ +func test(): + print(E1.V) + +enum E1 {V = E2.V} +enum E2 {V = E1.V} diff --git a/modules/gdscript/tests/scripts/analyzer/errors/cyclic_ref_enum.out b/modules/gdscript/tests/scripts/analyzer/errors/cyclic_ref_enum.out new file mode 100644 index 0000000000..1b6569ba3a --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/cyclic_ref_enum.out @@ -0,0 +1,2 @@ +GDTEST_ANALYZER_ERROR +Could not resolve member "E1": Cyclic reference. diff --git a/modules/gdscript/tests/scripts/analyzer/errors/cyclic_ref_enum_value.gd b/modules/gdscript/tests/scripts/analyzer/errors/cyclic_ref_enum_value.gd new file mode 100644 index 0000000000..237758f340 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/cyclic_ref_enum_value.gd @@ -0,0 +1,5 @@ +func test(): + print(EV1) + +enum {EV1 = EV2} +enum {EV2 = EV1} diff --git a/modules/gdscript/tests/scripts/analyzer/errors/cyclic_ref_enum_value.out b/modules/gdscript/tests/scripts/analyzer/errors/cyclic_ref_enum_value.out new file mode 100644 index 0000000000..233f5fee25 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/cyclic_ref_enum_value.out @@ -0,0 +1,2 @@ +GDTEST_ANALYZER_ERROR +Could not resolve member "EV1": Cyclic reference. diff --git a/modules/gdscript/tests/scripts/analyzer/errors/cyclic_ref_external.gd b/modules/gdscript/tests/scripts/analyzer/errors/cyclic_ref_external.gd new file mode 100644 index 0000000000..52e0d60389 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/cyclic_ref_external.gd @@ -0,0 +1,6 @@ +func test(): + print(v) + +var v = A.v + +const A = preload("cyclic_ref_external_a.notest.gd") diff --git a/modules/gdscript/tests/scripts/analyzer/errors/cyclic_ref_external.out b/modules/gdscript/tests/scripts/analyzer/errors/cyclic_ref_external.out new file mode 100644 index 0000000000..64a6bd417d --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/cyclic_ref_external.out @@ -0,0 +1,2 @@ +GDTEST_ANALYZER_ERROR +Could not resolve member "v". diff --git a/modules/gdscript/tests/scripts/analyzer/errors/cyclic_ref_external_a.notest.gd b/modules/gdscript/tests/scripts/analyzer/errors/cyclic_ref_external_a.notest.gd new file mode 100644 index 0000000000..9ef1769250 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/cyclic_ref_external_a.notest.gd @@ -0,0 +1,3 @@ +const B = preload("cyclic_ref_external.gd") + +var v = B.v diff --git a/modules/gdscript/tests/scripts/analyzer/errors/cyclic_ref_func.gd b/modules/gdscript/tests/scripts/analyzer/errors/cyclic_ref_func.gd new file mode 100644 index 0000000000..b610464c44 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/cyclic_ref_func.gd @@ -0,0 +1,9 @@ +func test(): + print(f1()) + print(f2()) + +static func f1(p := f2()) -> int: + return 1 + +static func f2(p := f1()) -> int: + return 2 diff --git a/modules/gdscript/tests/scripts/analyzer/errors/cyclic_ref_func.out b/modules/gdscript/tests/scripts/analyzer/errors/cyclic_ref_func.out new file mode 100644 index 0000000000..d3ec4b0692 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/cyclic_ref_func.out @@ -0,0 +1,2 @@ +GDTEST_ANALYZER_ERROR +Could not resolve member "f1": Cyclic reference. diff --git a/modules/gdscript/tests/scripts/analyzer/errors/cyclic_ref_override.gd b/modules/gdscript/tests/scripts/analyzer/errors/cyclic_ref_override.gd new file mode 100644 index 0000000000..f750715838 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/cyclic_ref_override.gd @@ -0,0 +1,12 @@ +func test(): + print(v) + +var v := InnerA.new().f() + +class InnerA: + func f(p := InnerB.new().f()) -> int: + return 1 + +class InnerB extends InnerA: + func f(p := 1) -> int: + return super.f() diff --git a/modules/gdscript/tests/scripts/analyzer/errors/cyclic_ref_override.out b/modules/gdscript/tests/scripts/analyzer/errors/cyclic_ref_override.out new file mode 100644 index 0000000000..6bca25b330 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/cyclic_ref_override.out @@ -0,0 +1,2 @@ +GDTEST_ANALYZER_ERROR +Could not resolve member "f": Cyclic reference. diff --git a/modules/gdscript/tests/scripts/analyzer/errors/cyclic_ref_var.gd b/modules/gdscript/tests/scripts/analyzer/errors/cyclic_ref_var.gd new file mode 100644 index 0000000000..6913888724 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/cyclic_ref_var.gd @@ -0,0 +1,5 @@ +func test(): + print(v1) + +var v1 := v2 +var v2 := v1 diff --git a/modules/gdscript/tests/scripts/analyzer/errors/cyclic_ref_var.out b/modules/gdscript/tests/scripts/analyzer/errors/cyclic_ref_var.out new file mode 100644 index 0000000000..c337882d9c --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/cyclic_ref_var.out @@ -0,0 +1,2 @@ +GDTEST_ANALYZER_ERROR +Could not resolve member "v1": Cyclic reference. diff --git a/modules/gdscript/tests/scripts/analyzer/errors/dictionary_string_stringname_equivalent.gd b/modules/gdscript/tests/scripts/analyzer/errors/dictionary_string_stringname_equivalent.gd new file mode 100644 index 0000000000..4dd2b556ee --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/dictionary_string_stringname_equivalent.gd @@ -0,0 +1,9 @@ +# https://github.com/godotengine/godot/issues/62957 + +func test(): + var dict = { + &"key": "StringName", + "key": "String" + } + + print("Invalid dictionary: %s" % dict) diff --git a/modules/gdscript/tests/scripts/analyzer/errors/dictionary_string_stringname_equivalent.out b/modules/gdscript/tests/scripts/analyzer/errors/dictionary_string_stringname_equivalent.out new file mode 100644 index 0000000000..189d8a7955 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/dictionary_string_stringname_equivalent.out @@ -0,0 +1,2 @@ +GDTEST_ANALYZER_ERROR +Key "key" was already used in this dictionary (at line 5). diff --git a/modules/gdscript/tests/scripts/analyzer/errors/function_dont_match_parent_signature_parameter_count_less.out b/modules/gdscript/tests/scripts/analyzer/errors/function_dont_match_parent_signature_parameter_count_less.out index 3baeb17066..4ccd2da381 100644 --- a/modules/gdscript/tests/scripts/analyzer/errors/function_dont_match_parent_signature_parameter_count_less.out +++ b/modules/gdscript/tests/scripts/analyzer/errors/function_dont_match_parent_signature_parameter_count_less.out @@ -1,2 +1,2 @@ GDTEST_ANALYZER_ERROR -The function signature doesn't match the parent. Parent signature is "int my_function(int)". +The function signature doesn't match the parent. Parent signature is "my_function(int) -> int". diff --git a/modules/gdscript/tests/scripts/analyzer/errors/function_dont_match_parent_signature_parameter_count_more.out b/modules/gdscript/tests/scripts/analyzer/errors/function_dont_match_parent_signature_parameter_count_more.out index 3baeb17066..4ccd2da381 100644 --- a/modules/gdscript/tests/scripts/analyzer/errors/function_dont_match_parent_signature_parameter_count_more.out +++ b/modules/gdscript/tests/scripts/analyzer/errors/function_dont_match_parent_signature_parameter_count_more.out @@ -1,2 +1,2 @@ GDTEST_ANALYZER_ERROR -The function signature doesn't match the parent. Parent signature is "int my_function(int)". +The function signature doesn't match the parent. Parent signature is "my_function(int) -> int". diff --git a/modules/gdscript/tests/scripts/analyzer/errors/function_dont_match_parent_signature_parameter_default_values.out b/modules/gdscript/tests/scripts/analyzer/errors/function_dont_match_parent_signature_parameter_default_values.out index 665c229339..c70a1df10d 100644 --- a/modules/gdscript/tests/scripts/analyzer/errors/function_dont_match_parent_signature_parameter_default_values.out +++ b/modules/gdscript/tests/scripts/analyzer/errors/function_dont_match_parent_signature_parameter_default_values.out @@ -1,2 +1,2 @@ GDTEST_ANALYZER_ERROR -The function signature doesn't match the parent. Parent signature is "int my_function(int = default)". +The function signature doesn't match the parent. Parent signature is "my_function(int = default) -> int". diff --git a/modules/gdscript/tests/scripts/analyzer/errors/function_dont_match_parent_signature_parameter_type.out b/modules/gdscript/tests/scripts/analyzer/errors/function_dont_match_parent_signature_parameter_type.out index 3baeb17066..4ccd2da381 100644 --- a/modules/gdscript/tests/scripts/analyzer/errors/function_dont_match_parent_signature_parameter_type.out +++ b/modules/gdscript/tests/scripts/analyzer/errors/function_dont_match_parent_signature_parameter_type.out @@ -1,2 +1,2 @@ GDTEST_ANALYZER_ERROR -The function signature doesn't match the parent. Parent signature is "int my_function(int)". +The function signature doesn't match the parent. Parent signature is "my_function(int) -> int". diff --git a/modules/gdscript/tests/scripts/analyzer/errors/function_dont_match_parent_signature_return_type.out b/modules/gdscript/tests/scripts/analyzer/errors/function_dont_match_parent_signature_return_type.out index 5b22739a93..61004ff627 100644 --- a/modules/gdscript/tests/scripts/analyzer/errors/function_dont_match_parent_signature_return_type.out +++ b/modules/gdscript/tests/scripts/analyzer/errors/function_dont_match_parent_signature_return_type.out @@ -1,2 +1,2 @@ GDTEST_ANALYZER_ERROR -The function signature doesn't match the parent. Parent signature is "int my_function()". +The function signature doesn't match the parent. Parent signature is "my_function() -> int". diff --git a/modules/gdscript/tests/scripts/analyzer/errors/overload_script_variable.gd b/modules/gdscript/tests/scripts/analyzer/errors/overload_script_variable.gd new file mode 100644 index 0000000000..5c8b9fa4ae --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/overload_script_variable.gd @@ -0,0 +1,6 @@ +extends Node + +var script: int + +func test(): + pass diff --git a/modules/gdscript/tests/scripts/analyzer/errors/overload_script_variable.out b/modules/gdscript/tests/scripts/analyzer/errors/overload_script_variable.out new file mode 100644 index 0000000000..8454aaa404 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/overload_script_variable.out @@ -0,0 +1,2 @@ +GDTEST_ANALYZER_ERROR +Member "script" redefined (original in native class 'Node') diff --git a/modules/gdscript/tests/scripts/analyzer/errors/variable_overloads_superclass_function.gd b/modules/gdscript/tests/scripts/analyzer/errors/variable_overloads_superclass_function.gd new file mode 100644 index 0000000000..28561ff94b --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/variable_overloads_superclass_function.gd @@ -0,0 +1,9 @@ +func test(): + pass + +class A: + func overload_me(): + pass + +class B extends A: + var overload_me diff --git a/modules/gdscript/tests/scripts/analyzer/errors/variable_overloads_superclass_function.out b/modules/gdscript/tests/scripts/analyzer/errors/variable_overloads_superclass_function.out new file mode 100644 index 0000000000..32357f9f6a --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/variable_overloads_superclass_function.out @@ -0,0 +1,2 @@ +GDTEST_ANALYZER_ERROR +The member "overload_me" already exists in parent class A. diff --git a/modules/gdscript/tests/scripts/analyzer/features/array_string_stringname_equivalent.gd b/modules/gdscript/tests/scripts/analyzer/features/array_string_stringname_equivalent.gd new file mode 100644 index 0000000000..eb0003eed8 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/array_string_stringname_equivalent.gd @@ -0,0 +1,15 @@ + +var m_string_array: Array[String] = [&"abc"] +var m_stringname_array: Array[StringName] = ["abc"] + +func test(): + print(m_string_array) + print(m_stringname_array) + + # Converted to String when initialized + var string_array: Array[String] = [&"abc"] + print(string_array) + + # Converted to StringName when initialized + var stringname_array: Array[StringName] = ["abc"] + print(stringname_array) diff --git a/modules/gdscript/tests/scripts/analyzer/features/array_string_stringname_equivalent.out b/modules/gdscript/tests/scripts/analyzer/features/array_string_stringname_equivalent.out new file mode 100644 index 0000000000..09c199bde1 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/array_string_stringname_equivalent.out @@ -0,0 +1,5 @@ +GDTEST_OK +["abc"] +[&"abc"] +["abc"] +[&"abc"] diff --git a/modules/gdscript/tests/scripts/analyzer/features/base_outer_resolution.gd b/modules/gdscript/tests/scripts/analyzer/features/base_outer_resolution.gd new file mode 100644 index 0000000000..7881a0feb6 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/base_outer_resolution.gd @@ -0,0 +1,14 @@ +const A: = preload("base_outer_resolution_a.notest.gd") +const B: = preload("base_outer_resolution_b.notest.gd") +const C: = preload("base_outer_resolution_c.notest.gd") + +const Extend: = preload("base_outer_resolution_extend.notest.gd") + +func test() -> void: + Extend.test_a(A.new()) + Extend.test_b(B.new()) + Extend.InnerClass.test_c(C.new()) + Extend.InnerClass.InnerInnerClass.test_a_b_c(A.new(), B.new(), C.new()) + Extend.InnerClass.InnerInnerClass.test_enum(C.TestEnum.HELLO_WORLD) + Extend.InnerClass.InnerInnerClass.test_a_prime(A.APrime.new()) + diff --git a/modules/gdscript/tests/scripts/analyzer/features/base_outer_resolution.out b/modules/gdscript/tests/scripts/analyzer/features/base_outer_resolution.out new file mode 100644 index 0000000000..bd27bd31f6 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/base_outer_resolution.out @@ -0,0 +1,7 @@ +GDTEST_OK +true +true +true +true +true +true diff --git a/modules/gdscript/tests/scripts/analyzer/features/base_outer_resolution_a.notest.gd b/modules/gdscript/tests/scripts/analyzer/features/base_outer_resolution_a.notest.gd new file mode 100644 index 0000000000..966c8bfc8f --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/base_outer_resolution_a.notest.gd @@ -0,0 +1,2 @@ +class APrime: + pass diff --git a/modules/gdscript/tests/scripts/analyzer/features/base_outer_resolution_b.notest.gd b/modules/gdscript/tests/scripts/analyzer/features/base_outer_resolution_b.notest.gd new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/base_outer_resolution_b.notest.gd diff --git a/modules/gdscript/tests/scripts/analyzer/features/base_outer_resolution_base.notest.gd b/modules/gdscript/tests/scripts/analyzer/features/base_outer_resolution_base.notest.gd new file mode 100644 index 0000000000..666b147ced --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/base_outer_resolution_base.notest.gd @@ -0,0 +1,4 @@ +const A: = preload("base_outer_resolution_a.notest.gd") + +class InnerClassInBase: + const C: = preload("base_outer_resolution_c.notest.gd") diff --git a/modules/gdscript/tests/scripts/analyzer/features/base_outer_resolution_c.notest.gd b/modules/gdscript/tests/scripts/analyzer/features/base_outer_resolution_c.notest.gd new file mode 100644 index 0000000000..814be35314 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/base_outer_resolution_c.notest.gd @@ -0,0 +1,3 @@ +enum TestEnum { + HELLO_WORLD +} diff --git a/modules/gdscript/tests/scripts/analyzer/features/base_outer_resolution_extend.notest.gd b/modules/gdscript/tests/scripts/analyzer/features/base_outer_resolution_extend.notest.gd new file mode 100644 index 0000000000..fbd28779d4 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/base_outer_resolution_extend.notest.gd @@ -0,0 +1,23 @@ +extends "base_outer_resolution_base.notest.gd" + +const B: = preload("base_outer_resolution_b.notest.gd") + +static func test_a(a: A) -> void: + print(a is A) + +static func test_b(b: B) -> void: + print(b is B) + +class InnerClass extends InnerClassInBase: + static func test_c(c: C) -> void: + print(c is C) + + class InnerInnerClass: + static func test_a_b_c(a: A, b: B, c: C) -> void: + print(a is A and b is B and c is C) + + static func test_enum(test_enum: C.TestEnum) -> void: + print(test_enum == C.TestEnum.HELLO_WORLD) + + static func test_a_prime(a_prime: A.APrime) -> void: + print(a_prime is A.APrime) 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/gdscript_to_preload.gd b/modules/gdscript/tests/scripts/analyzer/features/gdscript_to_preload.notest.gd index ea744e3027..c3fc176679 100644 --- a/modules/gdscript/tests/scripts/analyzer/features/gdscript_to_preload.gd +++ b/modules/gdscript/tests/scripts/analyzer/features/gdscript_to_preload.notest.gd @@ -1,7 +1,4 @@ const A := 42 -func test(): - pass - func something(): return "OK" 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/analyzer/features/out_of_order.gd b/modules/gdscript/tests/scripts/analyzer/features/out_of_order.gd new file mode 100644 index 0000000000..069e54c528 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/out_of_order.gd @@ -0,0 +1,50 @@ +func test(): + print("v1: ", v1) + print("v1 is String: ", v1 is String) + print("v2: ", v2) + print("v2 is bool: ", v2 is bool) + print("c1: ", c1) + print("c1 is int: ", c1 is int) + print("c2: ", c2) + print("c2 is int: ", c2 is int) + print("E1.V1: ", E1.V1) + print("E1.V2: ", E1.V2) + print("E2.V: ", E2.V) + print("EV1: ", EV1) + print("EV2: ", EV2) + print("EV3: ", EV3) + +var v1 := InnerA.new().fn() + +class InnerA extends InnerAB: + func fn(p2 := E1.V2) -> String: + return "%s, p2=%s" % [super.fn(), p2] + + class InnerAB: + func fn(p1 := c1) -> String: + return "p1=%s" % p1 + +var v2 := f() + +func f() -> bool: + return true + +const c1 := E1.V1 + +enum E1 { + V1 = E2.V + 2, + V2 = V1 - 1 +} + +enum E2 {V = 2} + +const c2 := EV2 + +enum { + EV1 = 42, + EV2 = EV3 + 1 +} + +enum { + EV3 = EV1 + 1 +} diff --git a/modules/gdscript/tests/scripts/analyzer/features/out_of_order.out b/modules/gdscript/tests/scripts/analyzer/features/out_of_order.out new file mode 100644 index 0000000000..b1e75d611d --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/out_of_order.out @@ -0,0 +1,15 @@ +GDTEST_OK +v1: p1=4, p2=3 +v1 is String: true +v2: true +v2 is bool: true +c1: 4 +c1 is int: true +c2: 44 +c2 is int: true +E1.V1: 4 +E1.V2: 3 +E2.V: 2 +EV1: 42 +EV2: 44 +EV3: 43 diff --git a/modules/gdscript/tests/scripts/analyzer/features/out_of_order_external.gd b/modules/gdscript/tests/scripts/analyzer/features/out_of_order_external.gd new file mode 100644 index 0000000000..0b162bdff8 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/out_of_order_external.gd @@ -0,0 +1,39 @@ +const B = preload("out_of_order_external_a.notest.gd") + +func test(): + print("v1: ", v1) + print("v1 is String: ", v1 is String) + print("v2: ", v2) + print("v2 is bool: ", v2 is bool) + print("c1: ", c1) + print("c1 is int: ", c1 is int) + print("c2: ", c2) + print("c2 is int: ", c2 is int) + print("E1.V1: ", E1.V1) + print("E1.V2: ", E1.V2) + print("B.E2.V: ", B.E2.V) + print("EV1: ", EV1) + print("EV2: ", EV2) + print("B.EV3: ", B.EV3) + +var v1 := Inner.new().fn() + +class Inner extends B.Inner: + func fn(p2 := E1.V2) -> String: + return "%s, p2=%s" % [super.fn(), p2] + +var v2 := B.new().f() + +const c1 := E1.V1 + +enum E1 { + V1 = B.E2.V + 2, + V2 = V1 - 1 +} + +const c2 := EV2 + +enum { + EV1 = 42, + EV2 = B.EV3 + 1 +} diff --git a/modules/gdscript/tests/scripts/analyzer/features/out_of_order_external.out b/modules/gdscript/tests/scripts/analyzer/features/out_of_order_external.out new file mode 100644 index 0000000000..437f782fe6 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/out_of_order_external.out @@ -0,0 +1,15 @@ +GDTEST_OK +v1: p1=4, p2=3 +v1 is String: true +v2: true +v2 is bool: true +c1: 4 +c1 is int: true +c2: 44 +c2 is int: true +E1.V1: 4 +E1.V2: 3 +B.E2.V: 2 +EV1: 42 +EV2: 44 +B.EV3: 43 diff --git a/modules/gdscript/tests/scripts/analyzer/features/out_of_order_external_a.notest.gd b/modules/gdscript/tests/scripts/analyzer/features/out_of_order_external_a.notest.gd new file mode 100644 index 0000000000..d276f72fcf --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/out_of_order_external_a.notest.gd @@ -0,0 +1,12 @@ +const A = preload("out_of_order_external.gd") + +class Inner: + func fn(p1 := A.c1) -> String: + return "p1=%s" % p1 + +func f(p := A.c1) -> bool: + return p is int + +enum E2 {V = 2} + +enum {EV3 = A.EV1 + 1} diff --git a/modules/gdscript/tests/scripts/analyzer/features/preload_constant_types_are_inferred.gd b/modules/gdscript/tests/scripts/analyzer/features/preload_constant_types_are_inferred.gd index 276875dd5a..9d0324ead8 100644 --- a/modules/gdscript/tests/scripts/analyzer/features/preload_constant_types_are_inferred.gd +++ b/modules/gdscript/tests/scripts/analyzer/features/preload_constant_types_are_inferred.gd @@ -1,4 +1,4 @@ -const Constants = preload("gdscript_to_preload.gd") +const Constants = preload("gdscript_to_preload.notest.gd") func test(): var a := Constants.A diff --git a/modules/gdscript/tests/scripts/analyzer/features/preload_cyclic_reference.gd b/modules/gdscript/tests/scripts/analyzer/features/preload_cyclic_reference.gd new file mode 100644 index 0000000000..b730453a8a --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/preload_cyclic_reference.gd @@ -0,0 +1,4 @@ +const A = preload("preload_cyclic_reference_a.notest.gd") + +func test(): + A.test_cyclic_reference() diff --git a/modules/gdscript/tests/scripts/analyzer/features/preload_cyclic_reference.out b/modules/gdscript/tests/scripts/analyzer/features/preload_cyclic_reference.out new file mode 100644 index 0000000000..14bb971221 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/preload_cyclic_reference.out @@ -0,0 +1,2 @@ +GDTEST_OK +godot diff --git a/modules/gdscript/tests/scripts/analyzer/features/preload_cyclic_reference_a.notest.gd b/modules/gdscript/tests/scripts/analyzer/features/preload_cyclic_reference_a.notest.gd new file mode 100644 index 0000000000..7a6035ded1 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/preload_cyclic_reference_a.notest.gd @@ -0,0 +1,12 @@ +const B = preload("preload_cyclic_reference_b.notest.gd") + +const WAITING_FOR = "godot" + +static func test_cyclic_reference(): + B.test_cyclic_reference() + +static func test_cyclic_reference_2(): + B.test_cyclic_reference_2() + +static func test_cyclic_reference_3(): + B.test_cyclic_reference_3() diff --git a/modules/gdscript/tests/scripts/analyzer/features/preload_cyclic_reference_b.notest.gd b/modules/gdscript/tests/scripts/analyzer/features/preload_cyclic_reference_b.notest.gd new file mode 100644 index 0000000000..3ea5b01156 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/preload_cyclic_reference_b.notest.gd @@ -0,0 +1,10 @@ +const A = preload("preload_cyclic_reference_a.notest.gd") + +static func test_cyclic_reference(): + A.test_cyclic_reference_2() + +static func test_cyclic_reference_2(): + A.test_cyclic_reference_3() + +static func test_cyclic_reference_3(): + print(A.WAITING_FOR) diff --git a/modules/gdscript/tests/scripts/analyzer/features/use_preload_script_as_type.gd b/modules/gdscript/tests/scripts/analyzer/features/use_preload_script_as_type.gd index 5f73064cc0..beabf3d2e5 100644 --- a/modules/gdscript/tests/scripts/analyzer/features/use_preload_script_as_type.gd +++ b/modules/gdscript/tests/scripts/analyzer/features/use_preload_script_as_type.gd @@ -1,4 +1,4 @@ -const preloaded : GDScript = preload("gdscript_to_preload.gd") +const preloaded : GDScript = preload("gdscript_to_preload.notest.gd") func test(): var preloaded_instance: preloaded = preloaded.new() diff --git a/modules/gdscript/tests/scripts/parser/features/class.gd b/modules/gdscript/tests/scripts/parser/features/class.gd index 6652f85ad9..af24b32322 100644 --- a/modules/gdscript/tests/scripts/parser/features/class.gd +++ b/modules/gdscript/tests/scripts/parser/features/class.gd @@ -21,5 +21,5 @@ func test(): assert(test_sub.number == 25) # From Test. assert(test_sub.other_string == "bye") # From TestSub. - TestConstructor.new() - TestConstructor.new(500) + var _test_constructor = TestConstructor.new() + _test_constructor = TestConstructor.new(500) diff --git a/modules/gdscript/tests/scripts/parser/warnings/return_value_discarded.out b/modules/gdscript/tests/scripts/parser/warnings/return_value_discarded.out index d73c5eb7cd..e89bb9226f 100644 --- a/modules/gdscript/tests/scripts/parser/warnings/return_value_discarded.out +++ b/modules/gdscript/tests/scripts/parser/warnings/return_value_discarded.out @@ -1 +1,5 @@ GDTEST_OK +>> WARNING +>> Line: 6 +>> RETURN_VALUE_DISCARDED +>> The function 'i_return_int()' returns a value that will be discarded if not used. 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/array_string_stringname_equivalent.gd b/modules/gdscript/tests/scripts/runtime/features/array_string_stringname_equivalent.gd new file mode 100644 index 0000000000..5303fb04e2 --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/features/array_string_stringname_equivalent.gd @@ -0,0 +1,35 @@ +# https://github.com/godotengine/godot/issues/63965 + +func test(): + var array_str: Array = [] + array_str.push_back("godot") + print("StringName in Array: ", &"godot" in array_str) + + var array_sname: Array = [] + array_sname.push_back(&"godot") + print("String in Array: ", "godot" in array_sname) + + # Not equal because the values are different types. + print("Arrays not equal: ", array_str != array_sname) + + var string_array: Array[String] = [] + var stringname_array: Array[StringName] = [] + + assert(!string_array.push_back(&"abc")) + print("Array[String] insert converted: ", typeof(string_array[0]) == TYPE_STRING) + + assert(!stringname_array.push_back("abc")) + print("Array[StringName] insert converted: ", typeof(stringname_array[0]) == TYPE_STRING_NAME) + + print("StringName in Array[String]: ", &"abc" in string_array) + print("String in Array[StringName]: ", "abc" in stringname_array) + + var packed_string_array: PackedStringArray = [] + assert(!packed_string_array.push_back("abc")) + print("StringName in PackedStringArray: ", &"abc" in packed_string_array) + + assert(!string_array.push_back("abc")) + print("StringName finds String in Array: ", string_array.find(&"abc")) + + assert(!stringname_array.push_back(&"abc")) + print("String finds StringName in Array: ", stringname_array.find("abc")) diff --git a/modules/gdscript/tests/scripts/runtime/features/array_string_stringname_equivalent.out b/modules/gdscript/tests/scripts/runtime/features/array_string_stringname_equivalent.out new file mode 100644 index 0000000000..98ab78e8f1 --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/features/array_string_stringname_equivalent.out @@ -0,0 +1,11 @@ +GDTEST_OK +StringName in Array: true +String in Array: true +Arrays not equal: true +Array[String] insert converted: true +Array[StringName] insert converted: true +StringName in Array[String]: true +String in Array[StringName]: true +StringName in PackedStringArray: true +StringName finds String in Array: 0 +String finds StringName in Array: 0 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/dictionary_string_stringname_equivalent.gd b/modules/gdscript/tests/scripts/runtime/features/dictionary_string_stringname_equivalent.gd new file mode 100644 index 0000000000..1f15026f17 --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/features/dictionary_string_stringname_equivalent.gd @@ -0,0 +1,17 @@ +# https://github.com/godotengine/godot/issues/62957 + +func test(): + var string_dict = {} + string_dict["abc"] = 42 + var stringname_dict = {} + stringname_dict[&"abc"] = 24 + + print("String key is TYPE_STRING: ", typeof(string_dict.keys()[0]) == TYPE_STRING) + print("StringName key is TYPE_STRING: ", typeof(stringname_dict.keys()[0]) == TYPE_STRING) + + print("StringName gets String: ", string_dict.get(&"abc")) + print("String gets StringName: ", stringname_dict.get("abc")) + + stringname_dict[&"abc"] = 42 + # They compare equal because StringName keys are converted to String. + print("String Dictionary == StringName Dictionary: ", string_dict == stringname_dict) diff --git a/modules/gdscript/tests/scripts/runtime/features/dictionary_string_stringname_equivalent.out b/modules/gdscript/tests/scripts/runtime/features/dictionary_string_stringname_equivalent.out new file mode 100644 index 0000000000..ab5b89d55c --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/features/dictionary_string_stringname_equivalent.out @@ -0,0 +1,6 @@ +GDTEST_OK +String key is TYPE_STRING: true +StringName key is TYPE_STRING: true +StringName gets String: 42 +String gets StringName: 24 +String Dictionary == StringName Dictionary: true diff --git a/modules/gdscript/tests/scripts/runtime/features/match_string_stringname_equivalent.gd b/modules/gdscript/tests/scripts/runtime/features/match_string_stringname_equivalent.gd new file mode 100644 index 0000000000..55be021a90 --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/features/match_string_stringname_equivalent.gd @@ -0,0 +1,14 @@ +# https://github.com/godotengine/godot/issues/60145 + +func test(): + match "abc": + &"abc": + print("String matched StringName") + _: + print("no match") + + match &"abc": + "abc": + print("StringName matched String") + _: + print("no match") diff --git a/modules/gdscript/tests/scripts/runtime/features/match_string_stringname_equivalent.out b/modules/gdscript/tests/scripts/runtime/features/match_string_stringname_equivalent.out new file mode 100644 index 0000000000..9d5a18da3d --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/features/match_string_stringname_equivalent.out @@ -0,0 +1,3 @@ +GDTEST_OK +String matched StringName +StringName matched String diff --git a/modules/gdscript/tests/scripts/runtime/features/parameter_shadowing.gd b/modules/gdscript/tests/scripts/runtime/features/parameter_shadowing.gd new file mode 100644 index 0000000000..f33ba7dffd --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/features/parameter_shadowing.gd @@ -0,0 +1,25 @@ +# https://github.com/godotengine/godot/pull/69620 + +var a: int = 1 + +func shadow_regular_assignment(a: Variant, b: Variant) -> void: + print(a) + print(self.a) + a = b + print(a) + print(self.a) + + +var v := Vector2(0.0, 0.0) + +func shadow_subscript_assignment(v: Vector2, x: float) -> void: + print(v) + print(self.v) + v.x += x + print(v) + print(self.v) + + +func test(): + shadow_regular_assignment('a', 'b') + shadow_subscript_assignment(Vector2(1.0, 1.0), 5.0) diff --git a/modules/gdscript/tests/scripts/runtime/features/parameter_shadowing.out b/modules/gdscript/tests/scripts/runtime/features/parameter_shadowing.out new file mode 100644 index 0000000000..5b981bc8bb --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/features/parameter_shadowing.out @@ -0,0 +1,17 @@ +GDTEST_OK +>> WARNING +>> Line: 5 +>> SHADOWED_VARIABLE +>> The local function parameter "a" is shadowing an already-declared variable at line 3. +>> WARNING +>> Line: 15 +>> SHADOWED_VARIABLE +>> The local function parameter "v" is shadowing an already-declared variable at line 13. +a +1 +b +1 +(1, 1) +(0, 0) +(6, 1) +(0, 0) 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/analyzer/features/gdscript_to_preload.out b/modules/gdscript/tests/scripts/runtime/features/range_optimized_in_for_has_int_iterator.out index d73c5eb7cd..d73c5eb7cd 100644 --- a/modules/gdscript/tests/scripts/analyzer/features/gdscript_to_preload.out +++ b/modules/gdscript/tests/scripts/runtime/features/range_optimized_in_for_has_int_iterator.out 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 diff --git a/modules/gdscript/tests/scripts/runtime/features/string_stringname_equivalent.gd b/modules/gdscript/tests/scripts/runtime/features/string_stringname_equivalent.gd new file mode 100644 index 0000000000..f8bd46523e --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/features/string_stringname_equivalent.gd @@ -0,0 +1,11 @@ +# https://github.com/godotengine/godot/issues/64171 + +func test(): + print("Compare ==: ", "abc" == &"abc") + print("Compare ==: ", &"abc" == "abc") + print("Compare !=: ", "abc" != &"abc") + print("Compare !=: ", &"abc" != "abc") + + print("Concat: ", "abc" + &"def") + print("Concat: ", &"abc" + "def") + print("Concat: ", &"abc" + &"def") diff --git a/modules/gdscript/tests/scripts/runtime/features/string_stringname_equivalent.out b/modules/gdscript/tests/scripts/runtime/features/string_stringname_equivalent.out new file mode 100644 index 0000000000..7e9c364b60 --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/features/string_stringname_equivalent.out @@ -0,0 +1,8 @@ +GDTEST_OK +Compare ==: true +Compare ==: true +Compare !=: false +Compare !=: false +Concat: abcdef +Concat: abcdef +Concat: abcdef |