feat: updated engine version to 4.4-rc1

This commit is contained in:
Sara 2025-02-23 14:38:14 +01:00
parent ee00efde1f
commit 21ba8e33af
5459 changed files with 1128836 additions and 198305 deletions

View file

@ -1,8 +0,0 @@
[*.gd]
indent_style = tab
indent_size = 4
insert_final_newline = true
trim_trailing_whitespace = true
[*.out]
insert_final_newline = true

View file

@ -1,4 +1,5 @@
#!/usr/bin/env python
from misc.utility.scons_hints import *
Import("env")
Import("env_modules")

View file

@ -11,6 +11,7 @@ def get_doc_classes():
return [
"@GDScript",
"GDScript",
"GDScriptSyntaxHighlighter",
]

View file

@ -4,14 +4,14 @@
Built-in GDScript constants, functions, and annotations.
</brief_description>
<description>
A list of GDScript-specific utility functions and annotations accessible from any script.
For the list of the global functions and constants see [@GlobalScope].
A list of utility functions and annotations accessible from any script written in GDScript.
For the list of global functions and constants that can be accessed in any scripting language, see [@GlobalScope].
</description>
<tutorials>
<link title="GDScript exports">$DOCS_URL/tutorials/scripting/gdscript/gdscript_exports.html</link>
</tutorials>
<methods>
<method name="Color8">
<method name="Color8" deprecated="Use [method Color.from_rgba8] instead.">
<return type="Color" />
<param index="0" name="r8" type="int" />
<param index="1" name="g8" type="int" />
@ -52,16 +52,16 @@
<description>
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"
a = char(8364) # a is "€"
var upper = char(65) # upper is "A"
var lower = char(65 + 32) # lower is "a"
var euro = char(8364) # euro is "€"
[/codeblock]
</description>
</method>
<method name="convert" deprecated="Use [method @GlobalScope.type_convert] instead.">
<return type="Variant" />
<param index="0" name="what" type="Variant" />
<param index="1" name="type" type="int" />
<param index="1" name="type" type="int" enum="Variant.Type" />
<description>
Converts [param what] to [param type] in the best way possible. The [param type] uses the [enum Variant.Type] values.
[codeblock]
@ -74,7 +74,7 @@
[/codeblock]
</description>
</method>
<method name="dict_to_inst">
<method name="dict_to_inst" deprecated="Consider using [method JSON.to_native] or [method Object.get_property_list] instead.">
<return type="Object" />
<param index="0" name="dictionary" type="Dictionary" />
<description>
@ -103,12 +103,11 @@
[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">
<method name="inst_to_dict" deprecated="Consider using [method JSON.from_native] or [method Object.get_property_list] instead.">
<return type="Dictionary" />
<param index="0" name="instance" type="Object" />
<description>
Returns the passed [param instance] converted to a Dictionary. Can be useful for serializing.
[b]Note:[/b] Cannot be used to serialize objects with built-in scripts attached or objects allocated within built-in scripts.
[codeblock]
var foo = "bar"
func _ready():
@ -121,6 +120,8 @@
[@subpath, @path, foo]
[, res://test.gd, bar]
[/codeblock]
[b]Note:[/b] This function can only be used to serialize objects with an attached [GDScript] stored in a separate file. Objects without an attached script, with a script written in another language, or with a built-in script are not supported.
[b]Note:[/b] This function is not recursive, which means that nested objects will not be represented as dictionaries. Also, properties passed by reference ([Object], [Dictionary], [Array], and packed arrays) are copied by reference, not duplicated.
</description>
</method>
<method name="is_instance_of">
@ -133,7 +134,7 @@
- An [Object]-derived class which exists in [ClassDB], for example [Node].
- A [Script] (you can use any class, including inner one).
Unlike the right operand of the [code]is[/code] operator, [param type] can be a non-constant value. The [code]is[/code] operator supports more features (such as typed arrays). Use the operator instead of this method if you do not need dynamic type checking.
Examples:
[b]Examples:[/b]
[codeblock]
print(is_instance_of(a, TYPE_INT))
print(is_instance_of(a, Node))
@ -150,10 +151,10 @@
<description>
Returns the length of the given Variant [param var]. The length can be the character count of a [String] or [StringName], 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]
var a = [1, 2, 3, 4]
len(a) # Returns 4
b = "Hello!"
var b = "Hello!"
len(b) # Returns 6
[/codeblock]
</description>
@ -220,7 +221,7 @@
[code]range(b: int, n: int, s: int)[/code]: Starts from [code]b[/code], increases/decreases by steps of [code]s[/code], and stops [i]before[/i] [code]n[/code]. The arguments [code]b[/code] and [code]n[/code] are [b]inclusive[/b] and [b]exclusive[/b], respectively. The argument [code]s[/code] [b]can[/b] be negative, but not [code]0[/code]. If [code]s[/code] is [code]0[/code], an error message is printed.
[method range] converts all arguments to [int] before processing.
[b]Note:[/b] Returns an empty array if no value meets the value constraint (e.g. [code]range(2, 5, -1)[/code] or [code]range(5, 5, 1)[/code]).
Examples:
[b]Examples:[/b]
[codeblock]
print(range(4)) # Prints [0, 1, 2, 3]
print(range(2, 5)) # Prints [2, 3, 4]
@ -666,7 +667,54 @@
@export var car_label = "Speedy"
@export var car_number = 3
[/codeblock]
[b]Note:[/b] Subgroups cannot be nested, they only provide one extra level of depth. Just like the next group ends the previous group, so do the subsequent subgroups.
[b]Note:[/b] Subgroups cannot be nested, but you can use the slash separator ([code]/[/code]) to achieve the desired effect:
[codeblock]
@export_group("Car Properties")
@export_subgroup("Wheels", "wheel_")
@export_subgroup("Wheels/Front", "front_wheel_")
@export var front_wheel_strength = 10
@export var front_wheel_mobility = 5
@export_subgroup("Wheels/Rear", "rear_wheel_")
@export var rear_wheel_strength = 8
@export var rear_wheel_mobility = 3
@export_subgroup("Wheels", "wheel_")
@export var wheel_material: PhysicsMaterial
[/codeblock]
</description>
</annotation>
<annotation name="@export_tool_button">
<return type="void" />
<param index="0" name="text" type="String" />
<param index="1" name="icon" type="String" default="&quot;&quot;" />
<description>
Export a [Callable] property as a clickable button with the label [param text]. When the button is pressed, the callable is called.
If [param icon] is specified, it is used to fetch an icon for the button via [method Control.get_theme_icon], from the [code]"EditorIcons"[/code] theme type. If [param icon] is omitted, the default [code]"Callable"[/code] icon is used instead.
Consider using the [EditorUndoRedoManager] to allow the action to be reverted safely.
See also [constant PROPERTY_HINT_TOOL_BUTTON].
[codeblock]
@tool
extends Sprite2D
@export_tool_button("Hello") var hello_action = hello
@export_tool_button("Randomize the color!", "ColorRect")
var randomize_color_action = randomize_color
func hello():
print("Hello world!")
func randomize_color():
var undo_redo = EditorInterface.get_editor_undo_redo()
undo_redo.create_action("Randomized Sprite2D Color")
undo_redo.add_do_property(self, &amp;"self_modulate", Color(randf(), randf(), randf()))
undo_redo.add_undo_property(self, &amp;"self_modulate", self_modulate)
undo_redo.commit_action()
[/codeblock]
[b]Note:[/b] The property is exported without the [constant PROPERTY_USAGE_STORAGE] flag because a [Callable] cannot be properly serialized and stored in a file.
[b]Note:[/b] In an exported project neither [EditorInterface] nor [EditorUndoRedoManager] exist, which may cause some scripts to break. To prevent this, you can use [method Engine.get_singleton] and omit the static type from the variable declaration:
[codeblock]
var undo_redo = Engine.get_singleton(&amp;"EditorInterface").get_editor_undo_redo()
[/codeblock]
[b]Note:[/b] Avoid storing lambda callables in member variables of [RefCounted]-based classes (e.g. resources), as this can lead to memory leaks. Use only method callables and optionally [method Callable.bind] or [method Callable.unbind].
</description>
</annotation>
<annotation name="@icon">
@ -679,7 +727,7 @@
[/codeblock]
[b]Note:[/b] Only the script can have a custom icon. Inner classes are not supported.
[b]Note:[/b] As annotations describe their subject, the [annotation @icon] annotation must be placed before the class definition and inheritance.
[b]Note:[/b] Unlike other annotations, the argument of the [annotation @icon] annotation must be a string literal (constant expressions are not supported).
[b]Note:[/b] Unlike most other annotations, the argument of the [annotation @icon] annotation must be a string literal (constant expressions are not supported).
</description>
</annotation>
<annotation name="@onready">
@ -687,7 +735,7 @@
<description>
Mark the following property as assigned when the [Node] is ready. Values for these properties are not assigned immediately when the node is initialized ([method Object._init]), and instead are computed and stored right before [method Node._ready].
[codeblock]
@onready var character_name: Label = $Label
@onready var character_name = $Label
[/codeblock]
</description>
</annotation>
@ -747,6 +795,33 @@
@warning_ignore("unreachable_code")
print("unreachable")
[/codeblock]
See also [annotation @warning_ignore_start] and [annotation @warning_ignore_restore].
</description>
</annotation>
<annotation name="@warning_ignore_restore" qualifiers="vararg">
<return type="void" />
<param index="0" name="warning" type="String" />
<description>
Stops ignoring the listed warning types after [annotation @warning_ignore_start]. Ignoring the specified warning types will be reset to Project Settings. This annotation can be omitted to ignore the warning types until the end of the file.
[b]Note:[/b] Unlike most other annotations, arguments of the [annotation @warning_ignore_restore] annotation must be string literals (constant expressions are not supported).
</description>
</annotation>
<annotation name="@warning_ignore_start" qualifiers="vararg">
<return type="void" />
<param index="0" name="warning" type="String" />
<description>
Starts ignoring the listed warning types until the end of the file or the [annotation @warning_ignore_restore] annotation with the given warning type.
[codeblock]
func test():
var a = 1 # Warning (if enabled in the Project Settings).
@warning_ignore_start("unused_variable")
var b = 2 # No warning.
var c = 3 # No warning.
@warning_ignore_restore("unused_variable")
var d = 4 # Warning (if enabled in the Project Settings).
[/codeblock]
[b]Note:[/b] To suppress a single warning, use [annotation @warning_ignore] instead.
[b]Note:[/b] Unlike most other annotations, arguments of the [annotation @warning_ignore_start] annotation must be string literals (constant expressions are not supported).
</description>
</annotation>
</annotations>

View file

@ -16,11 +16,10 @@
<return type="Variant" />
<description>
Returns a new instance of the script.
For example:
[codeblock]
var MyClass = load("myclass.gd")
var instance = MyClass.new()
assert(instance.get_script() == MyClass)
print(instance.get_script() == MyClass) # Prints true
[/codeblock]
</description>
</method>

View file

@ -0,0 +1,23 @@
<?xml version="1.0" encoding="UTF-8" ?>
<class name="GDScriptSyntaxHighlighter" inherits="EditorSyntaxHighlighter" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../doc/class.xsd">
<brief_description>
A GDScript syntax highlighter that can be used with [TextEdit] and [CodeEdit] nodes.
</brief_description>
<description>
[b]Note:[/b] This class can only be used for editor plugins because it relies on editor settings.
[codeblocks]
[gdscript]
var code_preview = TextEdit.new()
var highlighter = GDScriptSyntaxHighlighter.new()
code_preview.syntax_highlighter = highlighter
[/gdscript]
[csharp]
var codePreview = new TextEdit();
var highlighter = new GDScriptSyntaxHighlighter();
codePreview.SyntaxHighlighter = highlighter;
[/csharp]
[/codeblocks]
</description>
<tutorials>
</tutorials>
</class>

View file

@ -84,6 +84,15 @@ void GDScriptDocGen::_doctype_from_gdtype(const GDType &p_gdtype, String &r_type
return;
}
}
if (p_gdtype.builtin_type == Variant::DICTIONARY && p_gdtype.has_container_element_types()) {
String key, value;
_doctype_from_gdtype(p_gdtype.get_container_element_type_or_variant(0), key, r_enum);
_doctype_from_gdtype(p_gdtype.get_container_element_type_or_variant(1), value, r_enum);
if (key != "Variant" || value != "Variant") {
r_type = "Dictionary[" + key + ", " + value + "]";
return;
}
}
r_type = Variant::get_type_name(p_gdtype.builtin_type);
return;
case GDType::NATIVE:
@ -130,10 +139,11 @@ void GDScriptDocGen::_doctype_from_gdtype(const GDType &p_gdtype, String &r_type
r_type = "int";
r_enum = String(p_gdtype.native_type).replace("::", ".");
if (r_enum.begins_with("res://")) {
r_enum = r_enum.trim_prefix("res://");
int dot_pos = r_enum.rfind(".");
int dot_pos = r_enum.rfind_char('.');
if (dot_pos >= 0) {
r_enum = r_enum.left(dot_pos).quote() + r_enum.substr(dot_pos);
r_enum = _get_script_name(r_enum.left(dot_pos)) + r_enum.substr(dot_pos);
} else {
r_enum = _get_script_name(r_enum);
}
}
return;
@ -155,34 +165,82 @@ String GDScriptDocGen::_docvalue_from_variant(const Variant &p_variant, int p_re
return "<Object>";
case Variant::DICTIONARY: {
const Dictionary dict = p_variant;
String result;
if (dict.is_typed()) {
result += "Dictionary[";
Ref<Script> key_script = dict.get_typed_key_script();
if (key_script.is_valid()) {
if (key_script->get_global_name() != StringName()) {
result += key_script->get_global_name();
} else if (!key_script->get_path().get_file().is_empty()) {
result += key_script->get_path().get_file();
} else {
result += dict.get_typed_key_class_name();
}
} else if (dict.get_typed_key_class_name() != StringName()) {
result += dict.get_typed_key_class_name();
} else if (dict.is_typed_key()) {
result += Variant::get_type_name((Variant::Type)dict.get_typed_key_builtin());
} else {
result += "Variant";
}
result += ", ";
Ref<Script> value_script = dict.get_typed_value_script();
if (value_script.is_valid()) {
if (value_script->get_global_name() != StringName()) {
result += value_script->get_global_name();
} else if (!value_script->get_path().get_file().is_empty()) {
result += value_script->get_path().get_file();
} else {
result += dict.get_typed_value_class_name();
}
} else if (dict.get_typed_value_class_name() != StringName()) {
result += dict.get_typed_value_class_name();
} else if (dict.is_typed_value()) {
result += Variant::get_type_name((Variant::Type)dict.get_typed_value_builtin());
} else {
result += "Variant";
}
result += "](";
}
if (dict.is_empty()) {
return "{}";
}
result += "{}";
} else if (p_recursion_level > MAX_RECURSION_LEVEL) {
result += "{...}";
} else {
result += "{";
if (p_recursion_level > MAX_RECURSION_LEVEL) {
return "{...}";
}
List<Variant> keys;
dict.get_key_list(&keys);
keys.sort_custom<StringLikeVariantOrder>();
List<Variant> keys;
dict.get_key_list(&keys);
keys.sort();
String data;
for (List<Variant>::Element *E = keys.front(); E; E = E->next()) {
if (E->prev()) {
data += ", ";
for (List<Variant>::Element *E = keys.front(); E; E = E->next()) {
if (E->prev()) {
result += ", ";
}
result += _docvalue_from_variant(E->get(), p_recursion_level + 1) + ": " + _docvalue_from_variant(dict[E->get()], p_recursion_level + 1);
}
data += _docvalue_from_variant(E->get(), p_recursion_level + 1) + ": " + _docvalue_from_variant(dict[E->get()], p_recursion_level + 1);
result += "}";
}
return "{" + data + "}";
if (dict.is_typed()) {
result += ")";
}
return result;
} break;
case Variant::ARRAY: {
const Array array = p_variant;
String result;
if (array.get_typed_builtin() != Variant::NIL) {
if (array.is_typed()) {
result += "Array[";
Ref<Script> script = array.get_typed_script();
@ -209,16 +267,18 @@ String GDScriptDocGen::_docvalue_from_variant(const Variant &p_variant, int p_re
result += "[...]";
} else {
result += "[";
for (int i = 0; i < array.size(); i++) {
if (i > 0) {
result += ", ";
}
result += _docvalue_from_variant(array[i], p_recursion_level + 1);
}
result += "]";
}
if (array.get_typed_builtin() != Variant::NIL) {
if (array.is_typed()) {
result += ")";
}
@ -229,7 +289,7 @@ String GDScriptDocGen::_docvalue_from_variant(const Variant &p_variant, int p_re
}
}
String GDScriptDocGen::_docvalue_from_expression(const GDP::ExpressionNode *p_expression) {
String GDScriptDocGen::docvalue_from_expression(const GDP::ExpressionNode *p_expression) {
ERR_FAIL_NULL_V(p_expression, String());
if (p_expression->is_constant) {
@ -325,6 +385,7 @@ void GDScriptDocGen::_generate_docs(GDScript *p_script, const GDP::ClassNode *p_
const_doc.name = const_name;
const_doc.value = _docvalue_from_variant(m_const->initializer->reduced_value);
const_doc.is_value_valid = true;
_doctype_from_gdtype(m_const->get_datatype(), const_doc.type, const_doc.enumeration);
const_doc.description = m_const->doc_data.description;
const_doc.is_deprecated = m_const->doc_data.is_deprecated;
const_doc.deprecated_message = m_const->doc_data.deprecated_message;
@ -348,7 +409,9 @@ void GDScriptDocGen::_generate_docs(GDScript *p_script, const GDP::ClassNode *p_
method_doc.experimental_message = m_func->doc_data.experimental_message;
method_doc.qualifiers = m_func->is_static ? "static" : "";
if (m_func->return_type) {
if (func_name == "_init") {
method_doc.return_type = "void";
} else if (m_func->return_type) {
// `m_func->return_type->get_datatype()` is a metatype.
_doctype_from_gdtype(m_func->get_datatype(), method_doc.return_type, method_doc.return_enum, true);
} else if (!m_func->body->has_return) {
@ -363,7 +426,7 @@ void GDScriptDocGen::_generate_docs(GDScript *p_script, const GDP::ClassNode *p_
arg_doc.name = p->identifier->name;
_doctype_from_gdtype(p->get_datatype(), arg_doc.type, arg_doc.enumeration);
if (p->initializer != nullptr) {
arg_doc.default_value = _docvalue_from_expression(p->initializer);
arg_doc.default_value = docvalue_from_expression(p->initializer);
}
method_doc.arguments.push_back(arg_doc);
}
@ -432,7 +495,7 @@ void GDScriptDocGen::_generate_docs(GDScript *p_script, const GDP::ClassNode *p_
}
if (m_var->initializer != nullptr) {
prop_doc.default_value = _docvalue_from_expression(m_var->initializer);
prop_doc.default_value = docvalue_from_expression(m_var->initializer);
}
prop_doc.overridden = false;
@ -459,6 +522,7 @@ void GDScriptDocGen::_generate_docs(GDScript *p_script, const GDP::ClassNode *p_
const_doc.name = val.identifier->name;
const_doc.value = _docvalue_from_variant(val.value);
const_doc.is_value_valid = true;
const_doc.type = "int";
const_doc.enumeration = name;
const_doc.description = val.doc_data.description;
const_doc.is_deprecated = val.doc_data.is_deprecated;
@ -481,6 +545,7 @@ void GDScriptDocGen::_generate_docs(GDScript *p_script, const GDP::ClassNode *p_
const_doc.name = name;
const_doc.value = _docvalue_from_variant(m_enum_val.value);
const_doc.is_value_valid = true;
const_doc.type = "int";
const_doc.enumeration = "@unnamed_enums";
const_doc.description = m_enum_val.doc_data.description;
const_doc.is_deprecated = m_enum_val.doc_data.is_deprecated;
@ -508,3 +573,14 @@ void GDScriptDocGen::generate_docs(GDScript *p_script, const GDP::ClassNode *p_c
_generate_docs(p_script, p_class);
singletons.clear();
}
// This method is needed for the editor, since during autocompletion the script is not compiled, only analyzed.
void GDScriptDocGen::doctype_from_gdtype(const GDType &p_gdtype, String &r_type, String &r_enum, bool p_is_return) {
for (const KeyValue<StringName, ProjectSettings::AutoloadInfo> &E : ProjectSettings::get_singleton()->get_autoload_list()) {
if (E.value.is_singleton) {
singletons[E.value.path] = E.key;
}
}
_doctype_from_gdtype(p_gdtype, r_type, r_enum, p_is_return);
singletons.clear();
}

View file

@ -45,11 +45,12 @@ class GDScriptDocGen {
static String _get_class_name(const GDP::ClassNode &p_class);
static void _doctype_from_gdtype(const GDType &p_gdtype, String &r_type, String &r_enum, bool p_is_return = false);
static String _docvalue_from_variant(const Variant &p_variant, int p_recursion_level = 1);
static String _docvalue_from_expression(const GDP::ExpressionNode *p_expression);
static void _generate_docs(GDScript *p_script, const GDP::ClassNode *p_class);
public:
static void generate_docs(GDScript *p_script, const GDP::ClassNode *p_class);
static void doctype_from_gdtype(const GDType &p_gdtype, String &r_type, String &r_enum, bool p_is_return = false);
static String docvalue_from_expression(const GDP::ExpressionNode *p_expression);
};
#endif // GDSCRIPT_DOCGEN_H

View file

@ -36,6 +36,7 @@
#include "core/config/project_settings.h"
#include "editor/editor_settings.h"
#include "editor/themes/editor_theme_manager.h"
#include "scene/gui/text_edit.h"
Dictionary GDScriptSyntaxHighlighter::_get_line_syntax_highlighting_impl(int p_line) {
Dictionary color_map;
@ -93,7 +94,7 @@ Dictionary GDScriptSyntaxHighlighter::_get_line_syntax_highlighting_impl(int p_l
in_region = color_region_cache[p_line - 1];
}
const String &str = text_edit->get_line(p_line);
const String &str = text_edit->get_line_with_ime(p_line);
const int line_length = str.length();
Color prev_color;
@ -163,7 +164,7 @@ Dictionary GDScriptSyntaxHighlighter::_get_line_syntax_highlighting_impl(int p_l
}
if (from + end_key_length > line_length) {
// If it's key length and there is a '\', dont skip to highlight esc chars.
if (str.find("\\", from) >= 0) {
if (str.find_char('\\', from) >= 0) {
break;
}
}
@ -236,7 +237,7 @@ Dictionary GDScriptSyntaxHighlighter::_get_line_syntax_highlighting_impl(int p_l
for (; from < line_length; from++) {
if (line_length - from < end_key_length) {
// Don't break if '\' to highlight esc chars.
if (str.find("\\", from) < 0) {
if (str.find_char('\\', from) < 0) {
break;
}
}
@ -350,15 +351,15 @@ Dictionary GDScriptSyntaxHighlighter::_get_line_syntax_highlighting_impl(int p_l
// Special cases for numbers.
if (in_number && !is_a_digit) {
if (str[j] == 'b' && str[j - 1] == '0') {
if ((str[j] == 'b' || str[j] == 'B') && str[j - 1] == '0') {
is_bin_notation = true;
} else if (str[j] == 'x' && str[j - 1] == '0') {
} else if ((str[j] == 'x' || str[j] == 'X') && str[j - 1] == '0') {
is_hex_notation = true;
} else if (!((str[j] == '-' || str[j] == '+') && str[j - 1] == 'e' && !prev_is_digit) &&
!(str[j] == '_' && (prev_is_digit || str[j - 1] == 'b' || str[j - 1] == 'x' || str[j - 1] == '.')) &&
!(str[j] == 'e' && (prev_is_digit || str[j - 1] == '_')) &&
} else if (!((str[j] == '-' || str[j] == '+') && (str[j - 1] == 'e' || str[j - 1] == 'E') && !prev_is_digit) &&
!(str[j] == '_' && (prev_is_digit || str[j - 1] == 'b' || str[j - 1] == 'B' || str[j - 1] == 'x' || str[j - 1] == 'X' || str[j - 1] == '.')) &&
!((str[j] == 'e' || str[j] == 'E') && (prev_is_digit || str[j - 1] == '_')) &&
!(str[j] == '.' && (prev_is_digit || (!prev_is_binary_op && (j > 0 && (str[j - 1] == '_' || str[j - 1] == '-' || str[j - 1] == '+' || str[j - 1] == '~'))))) &&
!((str[j] == '-' || str[j] == '+' || str[j] == '~') && !is_binary_op && !prev_is_binary_op && str[j - 1] != 'e')) {
!((str[j] == '-' || str[j] == '+' || str[j] == '~') && !is_binary_op && !prev_is_binary_op && str[j - 1] != 'e' && str[j - 1] != 'E')) {
/* This condition continues number highlighting in special cases.
1st row: '+' or '-' after scientific notation (like 3e-4);
2nd row: '_' as a numeric separator;
@ -560,12 +561,17 @@ Dictionary GDScriptSyntaxHighlighter::_get_line_syntax_highlighting_impl(int p_l
}
}
// Keep symbol color for binary '&&'. In the case of '&&&' use StringName color for the last ampersand.
// Set color of StringName, keeping symbol color for binary '&&' and '&'.
if (!in_string_name && in_region == -1 && str[j] == '&' && !is_binary_op) {
if (j >= 2 && str[j - 1] == '&' && str[j - 2] != '&' && prev_is_binary_op) {
is_binary_op = true;
} else if (j == 0 || (j > 0 && str[j - 1] != '&') || prev_is_binary_op) {
if (j + 1 <= line_length - 1 && (str[j + 1] == '\'' || str[j + 1] == '"')) {
in_string_name = true;
// Cover edge cases of i.e. '+&""' and '&&&""', so the StringName is properly colored.
if (prev_is_binary_op && j >= 2 && str[j - 1] == '&' && str[j - 2] != '&') {
in_string_name = false;
is_binary_op = true;
}
} else {
is_binary_op = true;
}
} else if (in_region != -1 || is_a_symbol) {
in_string_name = false;
@ -701,7 +707,9 @@ void GDScriptSyntaxHighlighter::_update_cache() {
List<StringName> types;
ClassDB::get_class_list(&types);
for (const StringName &E : types) {
class_names[E] = types_color;
if (ClassDB::is_class_exposed(E)) {
class_names[E] = types_color;
}
}
/* User types. */
@ -813,7 +821,7 @@ void GDScriptSyntaxHighlighter::_update_cache() {
if (E.usage & PROPERTY_USAGE_CATEGORY || E.usage & PROPERTY_USAGE_GROUP || E.usage & PROPERTY_USAGE_SUBGROUP) {
continue;
}
if (prop_name.contains("/")) {
if (prop_name.contains_char('/')) {
continue;
}
member_keywords[prop_name] = member_variable_color;
@ -852,6 +860,7 @@ void GDScriptSyntaxHighlighter::_update_cache() {
comment_marker_colors[COMMENT_MARKER_NOTICE] = Color(0.24, 0.54, 0.09);
}
// TODO: Move to editor_settings.cpp
EDITOR_DEF("text_editor/theme/highlighting/gdscript/function_definition_color", function_definition_color);
EDITOR_DEF("text_editor/theme/highlighting/gdscript/global_function_color", global_function_color);
EDITOR_DEF("text_editor/theme/highlighting/gdscript/node_path_color", node_path_color);

View file

@ -32,7 +32,6 @@
#define GDSCRIPT_HIGHLIGHTER_H
#include "editor/plugins/script_editor_plugin.h"
#include "scene/gui/text_edit.h"
class GDScriptSyntaxHighlighter : public EditorSyntaxHighlighter {
GDCLASS(GDScriptSyntaxHighlighter, EditorSyntaxHighlighter)

View file

@ -39,7 +39,7 @@ void GDScriptEditorTranslationParserPlugin::get_recognized_extensions(List<Strin
GDScriptLanguage::get_singleton()->get_recognized_extensions(r_extensions);
}
Error GDScriptEditorTranslationParserPlugin::parse_file(const String &p_path, Vector<String> *r_ids, Vector<Vector<String>> *r_ids_ctx_plural) {
Error GDScriptEditorTranslationParserPlugin::parse_file(const String &p_path, Vector<Vector<String>> *r_translations) {
// Extract all translatable strings using the parsed tree from GDScriptParser.
// The strategy is to find all ExpressionNode and AssignmentNode from the tree and extract strings if relevant, i.e
// Search strings in ExpressionNode -> CallNode -> tr(), set_text(), set_placeholder() etc.
@ -49,8 +49,8 @@ Error GDScriptEditorTranslationParserPlugin::parse_file(const String &p_path, Ve
Ref<Resource> loaded_res = ResourceLoader::load(p_path, "", ResourceFormatLoader::CACHE_MODE_REUSE, &err);
ERR_FAIL_COND_V_MSG(err, err, "Failed to load " + p_path);
ids = r_ids;
ids_ctx_plural = r_ids_ctx_plural;
translations = r_translations;
Ref<GDScript> gdscript = loaded_res;
String source_code = gdscript->get_source_code();
@ -62,16 +62,81 @@ Error GDScriptEditorTranslationParserPlugin::parse_file(const String &p_path, Ve
err = analyzer.analyze();
ERR_FAIL_COND_V_MSG(err, err, "Failed to analyze GDScript with GDScriptAnalyzer.");
comment_data = &parser.comment_data;
// Traverse through the parsed tree from GDScriptParser.
GDScriptParser::ClassNode *c = parser.get_tree();
_traverse_class(c);
comment_data = nullptr;
return OK;
}
bool GDScriptEditorTranslationParserPlugin::_is_constant_string(const GDScriptParser::ExpressionNode *p_expression) {
ERR_FAIL_NULL_V(p_expression, false);
return p_expression->is_constant && (p_expression->reduced_value.get_type() == Variant::STRING || p_expression->reduced_value.get_type() == Variant::STRING_NAME);
return p_expression->is_constant && p_expression->reduced_value.is_string();
}
String GDScriptEditorTranslationParserPlugin::_parse_comment(int p_line, bool &r_skip) const {
// Parse inline comment.
if (comment_data->has(p_line)) {
const String stripped_comment = comment_data->get(p_line).comment.trim_prefix("#").strip_edges();
if (stripped_comment.begins_with("TRANSLATORS:")) {
return stripped_comment.trim_prefix("TRANSLATORS:").strip_edges(true, false);
}
if (stripped_comment == "NO_TRANSLATE" || stripped_comment.begins_with("NO_TRANSLATE:")) {
r_skip = true;
return String();
}
}
// Parse multiline comment.
String multiline_comment;
for (int line = p_line - 1; comment_data->has(line) && comment_data->get(line).new_line; line--) {
const String stripped_comment = comment_data->get(line).comment.trim_prefix("#").strip_edges();
if (stripped_comment.is_empty()) {
continue;
}
if (multiline_comment.is_empty()) {
multiline_comment = stripped_comment;
} else {
multiline_comment = stripped_comment + "\n" + multiline_comment;
}
if (stripped_comment.begins_with("TRANSLATORS:")) {
return multiline_comment.trim_prefix("TRANSLATORS:").strip_edges(true, false);
}
if (stripped_comment == "NO_TRANSLATE" || stripped_comment.begins_with("NO_TRANSLATE:")) {
r_skip = true;
return String();
}
}
return String();
}
void GDScriptEditorTranslationParserPlugin::_add_id(const String &p_id, int p_line) {
bool skip = false;
const String comment = _parse_comment(p_line, skip);
if (skip) {
return;
}
translations->push_back({ p_id, String(), String(), comment });
}
void GDScriptEditorTranslationParserPlugin::_add_id_ctx_plural(const Vector<String> &p_id_ctx_plural, int p_line) {
bool skip = false;
const String comment = _parse_comment(p_line, skip);
if (skip) {
return;
}
translations->push_back({ p_id_ctx_plural[0], p_id_ctx_plural[1], p_id_ctx_plural[2], comment });
}
void GDScriptEditorTranslationParserPlugin::_traverse_class(const GDScriptParser::ClassNode *p_class) {
@ -253,7 +318,7 @@ void GDScriptEditorTranslationParserPlugin::_assess_assignment(const GDScriptPar
if (assignee_name != StringName() && assignment_patterns.has(assignee_name) && _is_constant_string(p_assignment->assigned_value)) {
// If the assignment is towards one of the extract patterns (text, tooltip_text etc.), and the value is a constant string, we collect the string.
ids->push_back(p_assignment->assigned_value->reduced_value);
_add_id(p_assignment->assigned_value->reduced_value, p_assignment->assigned_value->start_line);
} else if (assignee_name == fd_filters) {
// Extract from `get_node("FileDialog").filters = <filter array>`.
_extract_fd_filter_array(p_assignment->assigned_value);
@ -287,7 +352,7 @@ void GDScriptEditorTranslationParserPlugin::_assess_call(const GDScriptParser::C
}
}
if (extract_id_ctx_plural) {
ids_ctx_plural->push_back(id_ctx_plural);
_add_id_ctx_plural(id_ctx_plural, p_call->start_line);
}
} else if (function_name == trn_func || function_name == atrn_func) {
// Extract from `tr_n(id, plural, n, ctx)` or `atr_n(id, plural, n, ctx)`.
@ -307,20 +372,20 @@ void GDScriptEditorTranslationParserPlugin::_assess_call(const GDScriptParser::C
}
}
if (extract_id_ctx_plural) {
ids_ctx_plural->push_back(id_ctx_plural);
_add_id_ctx_plural(id_ctx_plural, p_call->start_line);
}
} else if (first_arg_patterns.has(function_name)) {
if (!p_call->arguments.is_empty() && _is_constant_string(p_call->arguments[0])) {
ids->push_back(p_call->arguments[0]->reduced_value);
_add_id(p_call->arguments[0]->reduced_value, p_call->arguments[0]->start_line);
}
} else if (second_arg_patterns.has(function_name)) {
if (p_call->arguments.size() > 1 && _is_constant_string(p_call->arguments[1])) {
ids->push_back(p_call->arguments[1]->reduced_value);
_add_id(p_call->arguments[1]->reduced_value, p_call->arguments[1]->start_line);
}
} else if (function_name == fd_add_filter) {
// Extract the 'JPE Images' in this example - get_node("FileDialog").add_filter("*.jpg; JPE Images").
if (!p_call->arguments.is_empty()) {
_extract_fd_filter_string(p_call->arguments[0]);
_extract_fd_filter_string(p_call->arguments[0], p_call->arguments[0]->start_line);
}
} else if (function_name == fd_set_filter) {
// Extract from `get_node("FileDialog").set_filters(<filter array>)`.
@ -330,12 +395,12 @@ void GDScriptEditorTranslationParserPlugin::_assess_call(const GDScriptParser::C
}
}
void GDScriptEditorTranslationParserPlugin::_extract_fd_filter_string(const GDScriptParser::ExpressionNode *p_expression) {
void GDScriptEditorTranslationParserPlugin::_extract_fd_filter_string(const GDScriptParser::ExpressionNode *p_expression, int p_line) {
// Extract the name in "extension ; name".
if (_is_constant_string(p_expression)) {
PackedStringArray arr = p_expression->reduced_value.operator String().split(";", true);
ERR_FAIL_COND_MSG(arr.size() != 2, "Argument for setting FileDialog has bad format.");
ids->push_back(arr[1].strip_edges());
_add_id(arr[1].strip_edges(), p_line);
}
}
@ -355,7 +420,7 @@ void GDScriptEditorTranslationParserPlugin::_extract_fd_filter_array(const GDScr
if (array_node) {
for (int i = 0; i < array_node->elements.size(); i++) {
_extract_fd_filter_string(array_node->elements[i]);
_extract_fd_filter_string(array_node->elements[i], array_node->elements[i]->start_line);
}
}
}

View file

@ -32,15 +32,18 @@
#define GDSCRIPT_TRANSLATION_PARSER_PLUGIN_H
#include "../gdscript_parser.h"
#include "../gdscript_tokenizer.h"
#include "core/templates/hash_map.h"
#include "core/templates/hash_set.h"
#include "editor/editor_translation_parser.h"
class GDScriptEditorTranslationParserPlugin : public EditorTranslationParserPlugin {
GDCLASS(GDScriptEditorTranslationParserPlugin, EditorTranslationParserPlugin);
Vector<String> *ids = nullptr;
Vector<Vector<String>> *ids_ctx_plural = nullptr;
const HashMap<int, GDScriptTokenizer::CommentData> *comment_data = nullptr;
Vector<Vector<String>> *translations = nullptr;
// List of patterns used for extracting translation strings.
StringName tr_func = "tr";
@ -57,6 +60,11 @@ class GDScriptEditorTranslationParserPlugin : public EditorTranslationParserPlug
static bool _is_constant_string(const GDScriptParser::ExpressionNode *p_expression);
String _parse_comment(int p_line, bool &r_skip) const;
void _add_id(const String &p_id, int p_line);
void _add_id_ctx_plural(const Vector<String> &p_id_ctx_plural, int p_line);
void _traverse_class(const GDScriptParser::ClassNode *p_class);
void _traverse_function(const GDScriptParser::FunctionNode *p_func);
void _traverse_block(const GDScriptParser::SuiteNode *p_suite);
@ -65,11 +73,11 @@ class GDScriptEditorTranslationParserPlugin : public EditorTranslationParserPlug
void _assess_assignment(const GDScriptParser::AssignmentNode *p_assignment);
void _assess_call(const GDScriptParser::CallNode *p_call);
void _extract_fd_filter_string(const GDScriptParser::ExpressionNode *p_expression);
void _extract_fd_filter_string(const GDScriptParser::ExpressionNode *p_expression, int p_line);
void _extract_fd_filter_array(const GDScriptParser::ExpressionNode *p_expression);
public:
virtual Error parse_file(const String &p_path, Vector<String> *r_ids, Vector<Vector<String>> *r_ids_ctx_plural) override;
virtual Error parse_file(const String &p_path, Vector<Vector<String>> *r_translations) override;
virtual void get_recognized_extensions(List<String> *r_extensions) const override;
GDScriptEditorTranslationParserPlugin();

View file

@ -1,16 +1,8 @@
#!/usr/bin/env python
from misc.utility.scons_hints import *
Import("env")
import editor.template_builders as build_template_gd
env["BUILDERS"]["MakeGDTemplateBuilder"] = Builder(
action=env.Run(build_template_gd.make_templates),
suffix=".h",
src_suffix=".gd",
)
# Template files
templates_sources = Glob("*/*.gd")
env.Alias("editor_template_gd", [env.MakeGDTemplateBuilder("templates.gen.h", templates_sources)])
env.CommandNoCache("templates.gen.h", Glob("*/*.gd"), env.Run(build_template_gd.make_templates))

View file

@ -50,12 +50,12 @@
#include "core/config/project_settings.h"
#include "core/core_constants.h"
#include "core/io/file_access.h"
#include "core/io/file_access_encrypted.h"
#include "core/os/os.h"
#include "scene/resources/packed_scene.h"
#include "scene/scene_string_names.h"
#ifdef TOOLS_ENABLED
#include "core/extension/gdextension_manager.h"
#include "editor/editor_paths.h"
#endif
@ -74,9 +74,16 @@ bool GDScriptNativeClass::_get(const StringName &p_name, Variant &r_ret) const {
if (ok) {
r_ret = v;
return true;
} else {
return false;
}
MethodBind *method = ClassDB::get_method(name, p_name);
if (method && method->is_static()) {
// Native static method.
r_ret = Callable(this, p_name);
return true;
}
return false;
}
void GDScriptNativeClass::_bind_methods() {
@ -136,7 +143,7 @@ void GDScript::_super_implicit_constructor(GDScript *p_script, GDScriptInstance
}
}
ERR_FAIL_NULL(p_script->implicit_initializer);
if (likely(valid)) {
if (likely(p_script->valid)) {
p_script->implicit_initializer->call(p_instance, nullptr, 0, r_error);
} else {
r_error.error = Callable::CallError::CALL_ERROR_INVALID_METHOD;
@ -247,7 +254,7 @@ Variant GDScript::_new(const Variant **p_args, int p_argcount, Callable::CallErr
bool GDScript::can_instantiate() const {
#ifdef TOOLS_ENABLED
return valid && (tool || ScriptServer::is_scripting_enabled());
return valid && (tool || ScriptServer::is_scripting_enabled()) && !Engine::get_singleton()->is_recovery_mode_hint();
#else
return valid;
#endif
@ -470,23 +477,25 @@ void GDScript::_update_exports_values(HashMap<StringName, Variant> &values, List
}
}
void GDScript::_add_doc(const DocData::ClassDoc &p_inner_class) {
if (_owner) { // Only the top-level class stores doc info
_owner->_add_doc(p_inner_class);
} else { // Remove old docs, add new
void GDScript::_add_doc(const DocData::ClassDoc &p_doc) {
doc_class_name = p_doc.name;
if (_owner) { // Only the top-level class stores doc info.
_owner->_add_doc(p_doc);
} else { // Remove old docs, add new.
for (int i = 0; i < docs.size(); i++) {
if (docs[i].name == p_inner_class.name) {
if (docs[i].name == p_doc.name) {
docs.remove_at(i);
break;
}
}
docs.append(p_inner_class);
docs.append(p_doc);
}
}
void GDScript::_clear_doc() {
docs.clear();
doc_class_name = StringName();
doc = DocData::ClassDoc();
docs.clear();
}
String GDScript::get_class_icon_path() const {
@ -691,10 +700,16 @@ void GDScript::_static_default_init() {
continue;
}
if (type.builtin_type == Variant::ARRAY && type.has_container_element_type(0)) {
const GDScriptDataType element_type = type.get_container_element_type(0);
Array default_value;
const GDScriptDataType &element_type = type.get_container_element_type(0);
default_value.set_typed(element_type.builtin_type, element_type.native_type, element_type.script_type);
static_variables.write[E.value.index] = default_value;
} else if (type.builtin_type == Variant::DICTIONARY && type.has_container_element_types()) {
const GDScriptDataType key_type = type.get_container_element_type_or_variant(0);
const GDScriptDataType value_type = type.get_container_element_type_or_variant(1);
Dictionary default_value;
default_value.set_typed(key_type.builtin_type, key_type.native_type, key_type.script_type, value_type.builtin_type, value_type.native_type, value_type.script_type);
static_variables.write[E.value.index] = default_value;
} else {
Variant default_value;
Callable::CallError err;
@ -878,6 +893,11 @@ Error GDScript::reload(bool p_keep_state) {
if (can_run && p_keep_state) {
_restore_old_static_data();
}
if (p_keep_state) {
// Update the properties in the inspector.
update_exports();
}
#endif
reloading = false;
@ -904,7 +924,7 @@ void GDScript::get_members(HashSet<StringName> *p_members) {
}
}
const Variant GDScript::get_rpc_config() const {
Variant GDScript::get_rpc_config() const {
return rpc_config;
}
@ -952,7 +972,8 @@ bool GDScript::_get(const StringName &p_name, Variant &r_ret) const {
if (E) {
if (likely(top->valid) && E->value.getter) {
Callable::CallError ce;
r_ret = const_cast<GDScript *>(this)->callp(E->value.getter, nullptr, 0, ce);
const Variant ret = const_cast<GDScript *>(this)->callp(E->value.getter, nullptr, 0, ce);
r_ret = (ce.error == Callable::CallError::CALL_OK) ? ret : Variant();
return true;
}
r_ret = top->static_variables[E->value.index];
@ -1102,7 +1123,7 @@ Error GDScript::load_source_code(const String &p_path) {
w[len] = 0;
String s;
if (s.parse_utf8((const char *)w) != OK) {
if (s.parse_utf8((const char *)w, len) != OK) {
ERR_FAIL_V_MSG(ERR_INVALID_DATA, "Script '" + p_path + "' contains invalid unicode (UTF-8), so it was not loaded. Please ensure that scripts are saved in valid UTF-8 unicode.");
}
@ -1725,10 +1746,9 @@ bool GDScriptInstance::get(const StringName &p_name, Variant &r_ret) const {
if (E) {
if (likely(script->valid) && E->value.getter) {
Callable::CallError err;
r_ret = const_cast<GDScriptInstance *>(this)->callp(E->value.getter, nullptr, 0, err);
if (err.error == Callable::CallError::CALL_OK) {
return true;
}
const Variant ret = const_cast<GDScriptInstance *>(this)->callp(E->value.getter, nullptr, 0, err);
r_ret = (err.error == Callable::CallError::CALL_OK) ? ret : Variant();
return true;
}
r_ret = members[E->value.index];
return true;
@ -1750,7 +1770,8 @@ bool GDScriptInstance::get(const StringName &p_name, Variant &r_ret) const {
if (E) {
if (likely(sptr->valid) && E->value.getter) {
Callable::CallError ce;
r_ret = const_cast<GDScript *>(sptr)->callp(E->value.getter, nullptr, 0, ce);
const Variant ret = const_cast<GDScript *>(sptr)->callp(E->value.getter, nullptr, 0, ce);
r_ret = (ce.error == Callable::CallError::CALL_OK) ? ret : Variant();
return true;
}
r_ret = sptr->static_variables[E->value.index];
@ -1793,7 +1814,7 @@ bool GDScriptInstance::get(const StringName &p_name, Variant &r_ret) const {
const Variant *args[1] = { &name };
Callable::CallError err;
Variant ret = const_cast<GDScriptFunction *>(E->value)->call(const_cast<GDScriptInstance *>(this), (const Variant **)args, 1, err);
Variant ret = E->value->call(const_cast<GDScriptInstance *>(this), (const Variant **)args, 1, err);
if (err.error == Callable::CallError::CALL_OK && ret.get_type() != Variant::NIL) {
r_ret = ret;
return true;
@ -1821,14 +1842,14 @@ Variant::Type GDScriptInstance::get_property_type(const StringName &p_name, bool
}
void GDScriptInstance::validate_property(PropertyInfo &p_property) const {
Variant property = (Dictionary)p_property;
const Variant *args[1] = { &property };
const GDScript *sptr = script.ptr();
while (sptr) {
if (likely(sptr->valid)) {
HashMap<StringName, GDScriptFunction *>::ConstIterator E = sptr->member_functions.find(GDScriptLanguage::get_singleton()->strings._validate_property);
if (E) {
Variant property = (Dictionary)p_property;
const Variant *args[1] = { &property };
Callable::CallError err;
Variant ret = E->value->call(const_cast<GDScriptInstance *>(this), args, 1, err);
if (err.error == Callable::CallError::CALL_OK) {
@ -1852,7 +1873,7 @@ void GDScriptInstance::get_property_list(List<PropertyInfo> *p_properties) const
HashMap<StringName, GDScriptFunction *>::ConstIterator E = sptr->member_functions.find(GDScriptLanguage::get_singleton()->strings._get_property_list);
if (E) {
Callable::CallError err;
Variant ret = const_cast<GDScriptFunction *>(E->value)->call(const_cast<GDScriptInstance *>(this), nullptr, 0, err);
Variant ret = E->value->call(const_cast<GDScriptInstance *>(this), nullptr, 0, err);
if (err.error == Callable::CallError::CALL_OK) {
ERR_FAIL_COND_MSG(ret.get_type() != Variant::ARRAY, "Wrong type for _get_property_list, must be an array of dictionaries.");
@ -2176,9 +2197,26 @@ void GDScriptLanguage::_add_global(const StringName &p_name, const Variant &p_va
global_array.write[globals[p_name]] = p_value;
return;
}
globals[p_name] = global_array.size();
global_array.push_back(p_value);
_global_array = global_array.ptrw();
if (global_array_empty_indexes.size()) {
int index = global_array_empty_indexes[global_array_empty_indexes.size() - 1];
globals[p_name] = index;
global_array.write[index] = p_value;
global_array_empty_indexes.resize(global_array_empty_indexes.size() - 1);
} else {
globals[p_name] = global_array.size();
global_array.push_back(p_value);
_global_array = global_array.ptrw();
}
}
void GDScriptLanguage::_remove_global(const StringName &p_name) {
if (!globals.has(p_name)) {
return;
}
global_array_empty_indexes.push_back(globals[p_name]);
global_array.write[globals[p_name]] = Variant::NIL;
globals.erase(p_name);
}
void GDScriptLanguage::add_global_constant(const StringName &p_variable, const Variant &p_value) {
@ -2236,11 +2274,40 @@ void GDScriptLanguage::init() {
_add_global(E.name, E.ptr);
}
#ifdef TOOLS_ENABLED
if (Engine::get_singleton()->is_editor_hint()) {
GDExtensionManager::get_singleton()->connect("extension_loaded", callable_mp(this, &GDScriptLanguage::_extension_loaded));
GDExtensionManager::get_singleton()->connect("extension_unloading", callable_mp(this, &GDScriptLanguage::_extension_unloading));
}
#endif
#ifdef TESTS_ENABLED
GDScriptTests::GDScriptTestRunner::handle_cmdline();
#endif
}
#ifdef TOOLS_ENABLED
void GDScriptLanguage::_extension_loaded(const Ref<GDExtension> &p_extension) {
List<StringName> class_list;
ClassDB::get_extension_class_list(p_extension, &class_list);
for (const StringName &n : class_list) {
if (globals.has(n)) {
continue;
}
Ref<GDScriptNativeClass> nc = memnew(GDScriptNativeClass(n));
_add_global(n, nc);
}
}
void GDScriptLanguage::_extension_unloading(const Ref<GDExtension> &p_extension) {
List<StringName> class_list;
ClassDB::get_extension_class_list(p_extension, &class_list);
for (const StringName &n : class_list) {
_remove_global(n);
}
}
#endif
String GDScriptLanguage::get_type() const {
return "GDScript";
}
@ -2486,11 +2553,11 @@ void GDScriptLanguage::reload_all_scripts() {
}
}
}
#endif
#endif // TOOLS_ENABLED
}
reload_scripts(scripts, true);
#endif
#endif // DEBUG_ENABLED
}
void GDScriptLanguage::reload_scripts(const Array &p_scripts, bool p_soft_reload) {
@ -2503,7 +2570,7 @@ void GDScriptLanguage::reload_scripts(const Array &p_scripts, bool p_soft_reload
SelfList<GDScript> *elem = script_list.first();
while (elem) {
// Scripts will reload all subclasses, so only reload root scripts.
if (elem->self()->is_root_script() && elem->self()->get_path().is_resource_file()) {
if (elem->self()->is_root_script() && !elem->self()->get_path().is_empty()) {
scripts.push_back(Ref<GDScript>(elem->self())); //cast to gdscript to avoid being erased by accident
}
elem = elem->next();
@ -2560,7 +2627,7 @@ void GDScriptLanguage::reload_scripts(const Array &p_scripts, bool p_soft_reload
}
}
#endif
#endif // TOOLS_ENABLED
for (const KeyValue<ObjectID, List<Pair<StringName, Variant>>> &F : scr->pending_reload_state) {
map[F.key] = F.value; //pending to reload, use this one instead
@ -2571,7 +2638,19 @@ void GDScriptLanguage::reload_scripts(const Array &p_scripts, bool p_soft_reload
for (KeyValue<Ref<GDScript>, HashMap<ObjectID, List<Pair<StringName, Variant>>>> &E : to_reload) {
Ref<GDScript> scr = E.key;
print_verbose("GDScript: Reloading: " + scr->get_path());
scr->load_source_code(scr->get_path());
if (scr->is_built_in()) {
// TODO: It would be nice to do it more efficiently than loading the whole scene again.
Ref<PackedScene> scene = ResourceLoader::load(scr->get_path().get_slice("::", 0), "", ResourceFormatLoader::CACHE_MODE_IGNORE_DEEP);
ERR_CONTINUE(scene.is_null());
Ref<SceneState> state = scene->get_state();
Ref<GDScript> fresh = state->get_sub_resource(scr->get_path());
ERR_CONTINUE(fresh.is_null());
scr->set_source_code(fresh->get_source_code());
} else {
scr->load_source_code(scr->get_path());
}
scr->reload(p_soft_reload);
//restore state if saved
@ -2616,7 +2695,7 @@ void GDScriptLanguage::reload_scripts(const Array &p_scripts, bool p_soft_reload
//if instance states were saved, set them!
}
#endif
#endif // DEBUG_ENABLED
}
void GDScriptLanguage::reload_tool_script(const Ref<Script> &p_script, bool p_soft_reload) {
@ -2626,8 +2705,6 @@ void GDScriptLanguage::reload_tool_script(const Ref<Script> &p_script, bool p_so
}
void GDScriptLanguage::frame() {
calls = 0;
#ifdef DEBUG_ENABLED
if (profiling) {
MutexLock lock(mutex);
@ -2734,7 +2811,7 @@ bool GDScriptLanguage::handles_global_class_type(const String &p_type) const {
return p_type == "GDScript";
}
String GDScriptLanguage::get_global_class_name(const String &p_path, String *r_base_type, String *r_icon_path) const {
String GDScriptLanguage::get_global_class_name(const String &p_path, String *r_base_type, String *r_icon_path, bool *r_is_abstract, bool *r_is_tool) const {
Error err;
Ref<FileAccess> f = FileAccess::open(p_path, FileAccess::READ, &err);
if (err) {
@ -2835,13 +2912,18 @@ String GDScriptLanguage::get_global_class_name(const String &p_path, String *r_b
if (r_icon_path) {
*r_icon_path = c->simplified_icon_path;
}
if (r_is_abstract) {
*r_is_abstract = false;
}
if (r_is_tool) {
*r_is_tool = parser.is_tool();
}
return c->identifier != nullptr ? String(c->identifier->name) : String();
}
thread_local GDScriptLanguage::CallStack GDScriptLanguage::_call_stack;
GDScriptLanguage::GDScriptLanguage() {
calls = 0;
ERR_FAIL_COND(singleton);
singleton = this;
strings._init = StaticCString::create("_init");
@ -2857,8 +2939,11 @@ GDScriptLanguage::GDScriptLanguage() {
_debug_parse_err_line = -1;
_debug_parse_err_file = "";
#ifdef DEBUG_ENABLED
profiling = false;
profile_native_calls = false;
script_frame_time = 0;
#endif
int dmcs = GLOBAL_DEF(PropertyInfo(Variant::INT, "debug/settings/gdscript/max_call_stack", PROPERTY_HINT_RANGE, "512," + itos(GDScriptFunction::MAX_CALL_DEPTH - 1) + ",1"), 1024);
@ -2873,6 +2958,7 @@ GDScriptLanguage::GDScriptLanguage() {
#ifdef DEBUG_ENABLED
GLOBAL_DEF("debug/gdscript/warnings/enable", true);
GLOBAL_DEF("debug/gdscript/warnings/exclude_addons", true);
GLOBAL_DEF("debug/gdscript/warnings/renamed_in_godot_4_hint", true);
for (int i = 0; i < (int)GDScriptWarning::WARNING_MAX; i++) {
GDScriptWarning::Code code = (GDScriptWarning::Code)i;
Variant default_enabled = GDScriptWarning::get_default_value(code);

View file

@ -110,11 +110,13 @@ class GDScript : public Script {
HashMap<StringName, MethodInfo> _signals;
Dictionary rpc_config;
public:
struct LambdaInfo {
int capture_count;
bool use_self;
};
private:
HashMap<GDScriptFunction *, LambdaInfo> lambda_info;
public:
@ -157,16 +159,18 @@ private:
bool placeholder_fallback_enabled = false;
void _update_exports_values(HashMap<StringName, Variant> &values, List<PropertyInfo> &propnames);
StringName doc_class_name;
DocData::ClassDoc doc;
Vector<DocData::ClassDoc> docs;
void _add_doc(const DocData::ClassDoc &p_doc);
void _clear_doc();
void _add_doc(const DocData::ClassDoc &p_inner_class);
#endif
GDScriptFunction *implicit_initializer = nullptr;
GDScriptFunction *initializer = nullptr; //direct pointer to new , faster to locate
GDScriptFunction *implicit_ready = nullptr;
GDScriptFunction *static_initializer = nullptr;
GDScriptFunction *initializer = nullptr; // Direct pointer to `new()`/`_init()` member function, faster to locate.
GDScriptFunction *implicit_initializer = nullptr; // `@implicit_new()` special function.
GDScriptFunction *implicit_ready = nullptr; // `@implicit_ready()` special function.
GDScriptFunction *static_initializer = nullptr; // `@static_initializer()` special function.
Error _static_init();
void _static_default_init(); // Initialize static variables with default values based on their types.
@ -257,9 +261,15 @@ public:
CRASH_COND(!member_indices.has(p_member));
return member_indices[p_member].data_type;
}
const HashMap<StringName, GDScriptFunction *> &get_member_functions() const { return member_functions; }
const Ref<GDScriptNativeClass> &get_native() const { return native; }
_FORCE_INLINE_ const HashMap<StringName, GDScriptFunction *> &get_member_functions() const { return member_functions; }
_FORCE_INLINE_ const HashMap<GDScriptFunction *, LambdaInfo> &get_lambda_info() const { return lambda_info; }
_FORCE_INLINE_ const GDScriptFunction *get_implicit_initializer() const { return implicit_initializer; }
_FORCE_INLINE_ const GDScriptFunction *get_implicit_ready() const { return implicit_ready; }
_FORCE_INLINE_ const GDScriptFunction *get_static_initializer() const { return static_initializer; }
RBSet<GDScript *> get_dependencies();
HashMap<GDScript *, RBSet<GDScript *>> get_all_dependencies();
RBSet<GDScript *> get_must_clear_dependencies();
@ -292,9 +302,8 @@ public:
virtual void update_exports() override;
#ifdef TOOLS_ENABLED
virtual Vector<DocData::ClassDoc> get_documentation() const override {
return docs;
}
virtual StringName get_doc_class_name() const override { return doc_class_name; }
virtual Vector<DocData::ClassDoc> get_documentation() const override { return docs; }
virtual String get_class_icon_path() const override;
#endif // TOOLS_ENABLED
@ -334,7 +343,7 @@ public:
virtual void get_constants(HashMap<StringName, Variant> *p_constants) override;
virtual void get_members(HashSet<StringName> *p_members) override;
virtual const Variant get_rpc_config() const override;
virtual Variant get_rpc_config() const override;
void unload_static() const;
@ -417,6 +426,7 @@ class GDScriptLanguage : public ScriptLanguage {
Vector<Variant> global_array;
HashMap<StringName, int> globals;
HashMap<StringName, Variant> named_globals;
Vector<int> global_array_empty_indexes;
struct CallLevel {
Variant *stack = nullptr;
@ -448,6 +458,7 @@ class GDScriptLanguage : public ScriptLanguage {
int _debug_max_call_stack = 0;
void _add_global(const StringName &p_name, const Variant &p_value);
void _remove_global(const StringName &p_name);
friend class GDScriptInstance;
@ -459,15 +470,20 @@ class GDScriptLanguage : public ScriptLanguage {
friend class GDScriptFunction;
SelfList<GDScriptFunction>::List function_list;
#ifdef DEBUG_ENABLED
bool profiling;
bool profile_native_calls;
uint64_t script_frame_time;
#endif
HashMap<String, ObjectID> orphan_subclasses;
public:
int calls;
#ifdef TOOLS_ENABLED
void _extension_loaded(const Ref<GDExtension> &p_extension);
void _extension_unloading(const Ref<GDExtension> &p_extension);
#endif
public:
bool debug_break(const String &p_error, bool p_allow_continue = true);
bool debug_break_parse(const String &p_file, int p_line, const String &p_error);
@ -621,7 +637,7 @@ public:
/* GLOBAL CLASSES */
virtual bool handles_global_class_type(const String &p_type) const override;
virtual String get_global_class_name(const String &p_path, String *r_base_type = nullptr, String *r_icon_path = nullptr) const override;
virtual String get_global_class_name(const String &p_path, String *r_base_type = nullptr, String *r_icon_path = nullptr, bool *r_is_abstract = nullptr, bool *r_is_tool = nullptr) const override;
void add_orphan_subclass(const String &p_qualified_name, const ObjectID &p_subclass);
Ref<GDScript> get_orphan_subclass(const String &p_qualified_name);

View file

@ -42,7 +42,7 @@
#include "core/object/class_db.h"
#include "core/object/script_language.h"
#include "core/templates/hash_map.h"
#include "scene/resources/packed_scene.h"
#include "scene/main/node.h"
#if defined(TOOLS_ENABLED) && !defined(DISABLE_DEPRECATED)
#define SUGGEST_GODOT4_RENAMES
@ -148,6 +148,15 @@ static GDScriptParser::DataType make_enum_type(const StringName &p_enum_name, co
return type;
}
static GDScriptParser::DataType make_class_enum_type(const StringName &p_enum_name, GDScriptParser::ClassNode *p_class, const String &p_script_path, bool p_meta = true) {
GDScriptParser::DataType type = make_enum_type(p_enum_name, p_class->fqcn, p_meta);
type.class_type = p_class;
type.script_path = p_script_path;
return type;
}
static GDScriptParser::DataType make_native_enum_type(const StringName &p_enum_name, const StringName &p_native_class, bool p_meta = true) {
// Find out which base class declared the enum, so the name is always the same even when coming from other contexts.
StringName native_base = p_native_class;
@ -160,7 +169,9 @@ static GDScriptParser::DataType make_native_enum_type(const StringName &p_enum_n
GDScriptParser::DataType type = make_enum_type(p_enum_name, native_base, p_meta);
if (p_meta) {
type.builtin_type = Variant::NIL; // Native enum types are not Dictionaries.
// Native enum types are not dictionaries.
type.builtin_type = Variant::NIL;
type.is_pseudo_type = true;
}
List<StringName> enum_values;
@ -173,10 +184,29 @@ static GDScriptParser::DataType make_native_enum_type(const StringName &p_enum_n
return type;
}
static GDScriptParser::DataType make_builtin_enum_type(const StringName &p_enum_name, Variant::Type p_type, bool p_meta = true) {
GDScriptParser::DataType type = make_enum_type(p_enum_name, Variant::get_type_name(p_type), p_meta);
if (p_meta) {
// Built-in enum types are not dictionaries.
type.builtin_type = Variant::NIL;
type.is_pseudo_type = true;
}
List<StringName> enum_values;
Variant::get_enumerations_for_enum(p_type, p_enum_name, &enum_values);
for (const StringName &E : enum_values) {
type.enum_values[E] = Variant::get_enum_value(p_type, p_enum_name, E);
}
return type;
}
static GDScriptParser::DataType make_global_enum_type(const StringName &p_enum_name, const StringName &p_base, bool p_meta = true) {
GDScriptParser::DataType type = make_enum_type(p_enum_name, p_base, p_meta);
if (p_meta) {
type.builtin_type = Variant::NIL; // Native enum types are not Dictionaries.
// Global enum types are not dictionaries.
type.builtin_type = Variant::NIL;
type.is_pseudo_type = true;
}
@ -418,6 +448,12 @@ Error GDScriptAnalyzer::resolve_class_inheritance(GDScriptParser::ClassNode *p_c
return err;
}
#ifdef DEBUG_ENABLED
if (!parser->_is_tool && ext_parser->get_parser()->_is_tool) {
parser->push_warning(p_class, GDScriptWarning::MISSING_TOOL);
}
#endif
base = ext_parser->get_parser()->head->get_datatype();
} else {
if (p_class->extends.is_empty()) {
@ -445,6 +481,13 @@ Error GDScriptAnalyzer::resolve_class_inheritance(GDScriptParser::ClassNode *p_c
push_error(vformat(R"(Could not resolve super class inheritance from "%s".)", name), id);
return err;
}
#ifdef DEBUG_ENABLED
if (!parser->_is_tool && base_parser->get_parser()->_is_tool) {
parser->push_warning(p_class, GDScriptWarning::MISSING_TOOL);
}
#endif
base = base_parser->get_parser()->head->get_datatype();
}
} else if (ProjectSettings::get_singleton()->has_autoload(name) && ProjectSettings::get_singleton()->get_autoload(name).is_singleton) {
@ -465,6 +508,13 @@ Error GDScriptAnalyzer::resolve_class_inheritance(GDScriptParser::ClassNode *p_c
push_error(vformat(R"(Could not resolve super class inheritance from "%s".)", name), id);
return err;
}
#ifdef DEBUG_ENABLED
if (!parser->_is_tool && info_parser->get_parser()->_is_tool) {
parser->push_warning(p_class, GDScriptWarning::MISSING_TOOL);
}
#endif
base = info_parser->get_parser()->head->get_datatype();
} else if (class_exists(name)) {
if (Engine::get_singleton()->has_singleton(name)) {
@ -674,8 +724,8 @@ GDScriptParser::DataType GDScriptAnalyzer::resolve_datatype(GDScriptParser::Type
if (first == SNAME("Variant")) {
if (p_type->type_chain.size() == 2) {
// May be nested enum.
StringName enum_name = p_type->type_chain[1]->name;
StringName qualified_name = String(first) + ENUM_SEPARATOR + String(p_type->type_chain[1]->name);
const StringName enum_name = p_type->type_chain[1]->name;
const StringName qualified_name = String(first) + ENUM_SEPARATOR + String(p_type->type_chain[1]->name);
if (CoreConstants::is_global_enum(qualified_name)) {
result = make_global_enum_type(enum_name, first, true);
return result;
@ -690,20 +740,45 @@ GDScriptParser::DataType GDScriptAnalyzer::resolve_datatype(GDScriptParser::Type
result.kind = GDScriptParser::DataType::VARIANT;
} 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]);
const Variant::Type builtin_type = GDScriptParser::get_builtin_type(first);
if (p_type->type_chain.size() == 2) {
// May be nested enum.
const StringName enum_name = p_type->type_chain[1]->name;
if (Variant::has_enum(builtin_type, enum_name)) {
result = make_builtin_enum_type(enum_name, builtin_type, true);
return result;
} else {
push_error(vformat(R"(Name "%s" is not a nested type of "%s".)", enum_name, first), p_type->type_chain[1]);
return bad_type;
}
} else if (p_type->type_chain.size() > 2) {
push_error(R"(Built-in types only contain enum types, which do not have nested types.)", p_type->type_chain[2]);
return bad_type;
}
result.kind = GDScriptParser::DataType::BUILTIN;
result.builtin_type = GDScriptParser::get_builtin_type(first);
if (result.builtin_type == Variant::ARRAY) {
result.kind = GDScriptParser::DataType::BUILTIN;
result.builtin_type = builtin_type;
if (builtin_type == Variant::ARRAY) {
GDScriptParser::DataType container_type = type_from_metatype(resolve_datatype(p_type->get_container_type_or_null(0)));
if (container_type.kind != GDScriptParser::DataType::VARIANT) {
container_type.is_constant = false;
result.set_container_element_type(0, container_type);
}
}
if (builtin_type == Variant::DICTIONARY) {
GDScriptParser::DataType key_type = type_from_metatype(resolve_datatype(p_type->get_container_type_or_null(0)));
if (key_type.kind != GDScriptParser::DataType::VARIANT) {
key_type.is_constant = false;
result.set_container_element_type(0, key_type);
}
GDScriptParser::DataType value_type = type_from_metatype(resolve_datatype(p_type->get_container_type_or_null(1)));
if (value_type.kind != GDScriptParser::DataType::VARIANT) {
value_type.is_constant = false;
result.set_container_element_type(1, value_type);
}
}
} else if (class_exists(first)) {
// Native engine classes.
result.kind = GDScriptParser::DataType::NATIVE;
@ -717,7 +792,7 @@ GDScriptParser::DataType GDScriptAnalyzer::resolve_datatype(GDScriptParser::Type
String ext = path.get_extension();
if (ext == GDScriptLanguage::get_singleton()->get_extension()) {
Ref<GDScriptParserRef> ref = parser->get_depended_parser_for(path);
if (!ref.is_valid() || ref->raise_status(GDScriptParserRef::INHERITANCE_SOLVED) != OK) {
if (ref.is_null() || 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 bad_type;
}
@ -864,11 +939,16 @@ GDScriptParser::DataType GDScriptAnalyzer::resolve_datatype(GDScriptParser::Type
if (!p_type->container_types.is_empty()) {
if (result.builtin_type == Variant::ARRAY) {
if (p_type->container_types.size() != 1) {
push_error("Arrays require exactly one collection element type.", p_type);
push_error(R"(Typed arrays require exactly one collection element type.)", p_type);
return bad_type;
}
} else if (result.builtin_type == Variant::DICTIONARY) {
if (p_type->container_types.size() != 2) {
push_error(R"(Typed dictionaries require exactly two collection element types.)", p_type);
return bad_type;
}
} else {
push_error("Only arrays can specify collection element types.", p_type);
push_error(R"(Only arrays and dictionaries can specify collection element types.)", p_type);
return bad_type;
}
}
@ -894,8 +974,8 @@ void GDScriptAnalyzer::resolve_class_member(GDScriptParser::ClassNode *p_class,
Finally finally([&]() {
ensure_cached_external_parser_for_class(member.get_datatype().class_type, p_class, "Trying to resolve datatype of class member", p_source);
GDScriptParser::DataType member_type = member.get_datatype();
if (member_type.has_container_element_type(0)) {
ensure_cached_external_parser_for_class(member_type.get_container_element_type(0).class_type, p_class, "Trying to resolve datatype of class member", p_source);
for (int i = 0; i < member_type.get_container_element_type_count(); ++i) {
ensure_cached_external_parser_for_class(member_type.get_container_element_type(i).class_type, p_class, "Trying to resolve datatype of class member", p_source);
}
});
@ -1013,7 +1093,12 @@ void GDScriptAnalyzer::resolve_class_member(GDScriptParser::ClassNode *p_class,
}
}
if (is_get_node) {
parser->push_warning(member.variable, GDScriptWarning::GET_NODE_DEFAULT_WITHOUT_ONREADY, is_using_shorthand ? "$" : "get_node()");
String offending_syntax = "get_node()";
if (is_using_shorthand) {
GDScriptParser::GetNodeNode *get_node_node = static_cast<GDScriptParser::GetNodeNode *>(expr);
offending_syntax = get_node_node->use_dollar ? "$" : "%";
}
parser->push_warning(member.variable, GDScriptWarning::GET_NODE_DEFAULT_WITHOUT_ONREADY, offending_syntax);
}
}
}
@ -1064,7 +1149,7 @@ void GDScriptAnalyzer::resolve_class_member(GDScriptParser::ClassNode *p_class,
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 = make_enum_type(member.m_enum->identifier->name, p_class->fqcn, true);
GDScriptParser::DataType enum_type = make_class_enum_type(member.m_enum->identifier->name, p_class, parser->script_path, true);
const GDScriptParser::EnumNode *prev_enum = current_enum;
current_enum = member.m_enum;
@ -1157,7 +1242,7 @@ void GDScriptAnalyzer::resolve_class_member(GDScriptParser::ClassNode *p_class,
// Also update the original references.
member.enum_value.parent_enum->values.set(member.enum_value.index, member.enum_value);
member.enum_value.identifier->set_datatype(make_enum_type(UNNAMED_ENUM, p_class->fqcn, false));
member.enum_value.identifier->set_datatype(make_class_enum_type(UNNAMED_ENUM, p_class, parser->script_path, false));
} break;
case GDScriptParser::ClassNode::Member::CLASS:
check_class_member_name_conflict(p_class, member.m_class->identifier->name, member.m_class);
@ -1906,6 +1991,11 @@ void GDScriptAnalyzer::resolve_assignable(GDScriptParser::AssignableNode *p_assi
if (has_specified_type && specified_type.has_container_element_type(0)) {
update_array_literal_element_type(array, specified_type.get_container_element_type(0));
}
} else if (p_assignable->initializer->type == GDScriptParser::Node::DICTIONARY) {
GDScriptParser::DictionaryNode *dictionary = static_cast<GDScriptParser::DictionaryNode *>(p_assignable->initializer);
if (has_specified_type && specified_type.has_container_element_types()) {
update_dictionary_literal_element_type(dictionary, specified_type.get_container_element_type_or_variant(0), specified_type.get_container_element_type_or_variant(1));
}
}
if (is_constant && !p_assignable->initializer->is_constant) {
@ -1967,7 +2057,7 @@ void GDScriptAnalyzer::resolve_assignable(GDScriptParser::AssignableNode *p_assi
} else {
push_error(vformat(R"(Cannot assign a value of type %s to %s "%s" with specified type %s.)", initializer_type.to_string(), p_kind, p_assignable->identifier->name, specified_type.to_string()), p_assignable->initializer);
}
} else if (specified_type.has_container_element_type(0) && !initializer_type.has_container_element_type(0)) {
} else if ((specified_type.has_container_element_type(0) && !initializer_type.has_container_element_type(0)) || (specified_type.has_container_element_type(1) && !initializer_type.has_container_element_type(1))) {
mark_node_unsafe(p_assignable->initializer);
#ifdef DEBUG_ENABLED
} else if (specified_type.builtin_type == Variant::INT && initializer_type.builtin_type == Variant::FLOAT) {
@ -2209,8 +2299,12 @@ void GDScriptAnalyzer::resolve_for(GDScriptParser::ForNode *p_for) {
} else if (!is_type_compatible(specified_type, variable_type)) {
p_for->use_conversion_assign = true;
}
if (p_for->list && p_for->list->type == GDScriptParser::Node::ARRAY) {
update_array_literal_element_type(static_cast<GDScriptParser::ArrayNode *>(p_for->list), specified_type);
if (p_for->list) {
if (p_for->list->type == GDScriptParser::Node::ARRAY) {
update_array_literal_element_type(static_cast<GDScriptParser::ArrayNode *>(p_for->list), specified_type);
} else if (p_for->list->type == GDScriptParser::Node::DICTIONARY) {
update_dictionary_literal_element_type(static_cast<GDScriptParser::DictionaryNode *>(p_for->list), specified_type, GDScriptParser::DataType::get_variant_type());
}
}
}
p_for->variable->set_datatype(specified_type);
@ -2412,6 +2506,9 @@ void GDScriptAnalyzer::resolve_return(GDScriptParser::ReturnNode *p_return) {
} else {
if (p_return->return_value->type == GDScriptParser::Node::ARRAY && has_expected_type && expected_type.has_container_element_type(0)) {
update_array_literal_element_type(static_cast<GDScriptParser::ArrayNode *>(p_return->return_value), expected_type.get_container_element_type(0));
} else if (p_return->return_value->type == GDScriptParser::Node::DICTIONARY && has_expected_type && expected_type.has_container_element_types()) {
update_dictionary_literal_element_type(static_cast<GDScriptParser::DictionaryNode *>(p_return->return_value),
expected_type.get_container_element_type_or_variant(0), expected_type.get_container_element_type_or_variant(1));
}
if (has_expected_type && expected_type.is_hard_type() && p_return->return_value->is_constant) {
update_const_expression_builtin_type(p_return->return_value, expected_type, "return");
@ -2658,6 +2755,54 @@ void GDScriptAnalyzer::update_array_literal_element_type(GDScriptParser::ArrayNo
p_array->set_datatype(array_type);
}
// When a dictionary literal is stored (or passed as function argument) to a typed context, we then assume the dictionary is typed.
// This function determines which type is that (if any).
void GDScriptAnalyzer::update_dictionary_literal_element_type(GDScriptParser::DictionaryNode *p_dictionary, const GDScriptParser::DataType &p_key_element_type, const GDScriptParser::DataType &p_value_element_type) {
GDScriptParser::DataType expected_key_type = p_key_element_type;
GDScriptParser::DataType expected_value_type = p_value_element_type;
expected_key_type.container_element_types.clear(); // Nested types (like `Dictionary[String, Array[int]]`) are not currently supported.
expected_value_type.container_element_types.clear();
for (int i = 0; i < p_dictionary->elements.size(); i++) {
GDScriptParser::ExpressionNode *key_element_node = p_dictionary->elements[i].key;
if (key_element_node->is_constant) {
update_const_expression_builtin_type(key_element_node, expected_key_type, "include");
}
const GDScriptParser::DataType &actual_key_type = key_element_node->get_datatype();
if (actual_key_type.has_no_type() || actual_key_type.is_variant() || !actual_key_type.is_hard_type()) {
mark_node_unsafe(key_element_node);
} else if (!is_type_compatible(expected_key_type, actual_key_type, true, p_dictionary)) {
if (is_type_compatible(actual_key_type, expected_key_type)) {
mark_node_unsafe(key_element_node);
} else {
push_error(vformat(R"(Cannot have a key of type "%s" in a dictionary of type "Dictionary[%s, %s]".)", actual_key_type.to_string(), expected_key_type.to_string(), expected_value_type.to_string()), key_element_node);
return;
}
}
GDScriptParser::ExpressionNode *value_element_node = p_dictionary->elements[i].value;
if (value_element_node->is_constant) {
update_const_expression_builtin_type(value_element_node, expected_value_type, "include");
}
const GDScriptParser::DataType &actual_value_type = value_element_node->get_datatype();
if (actual_value_type.has_no_type() || actual_value_type.is_variant() || !actual_value_type.is_hard_type()) {
mark_node_unsafe(value_element_node);
} else if (!is_type_compatible(expected_value_type, actual_value_type, true, p_dictionary)) {
if (is_type_compatible(actual_value_type, expected_value_type)) {
mark_node_unsafe(value_element_node);
} else {
push_error(vformat(R"(Cannot have a value of type "%s" in a dictionary of type "Dictionary[%s, %s]".)", actual_value_type.to_string(), expected_key_type.to_string(), expected_value_type.to_string()), value_element_node);
return;
}
}
}
GDScriptParser::DataType dictionary_type = p_dictionary->get_datatype();
dictionary_type.set_container_element_type(0, expected_key_type);
dictionary_type.set_container_element_type(1, expected_value_type);
p_dictionary->set_datatype(dictionary_type);
}
void GDScriptAnalyzer::reduce_assignment(GDScriptParser::AssignmentNode *p_assignment) {
reduce_expression(p_assignment->assigned_value);
@ -2750,9 +2895,12 @@ void GDScriptAnalyzer::reduce_assignment(GDScriptParser::AssignmentNode *p_assig
}
}
// Check if assigned value is an array literal, so we can make it a typed array too if appropriate.
// Check if assigned value is an array/dictionary literal, so we can make it a typed container too if appropriate.
if (p_assignment->assigned_value->type == GDScriptParser::Node::ARRAY && assignee_type.is_hard_type() && assignee_type.has_container_element_type(0)) {
update_array_literal_element_type(static_cast<GDScriptParser::ArrayNode *>(p_assignment->assigned_value), assignee_type.get_container_element_type(0));
} else if (p_assignment->assigned_value->type == GDScriptParser::Node::DICTIONARY && assignee_type.is_hard_type() && assignee_type.has_container_element_types()) {
update_dictionary_literal_element_type(static_cast<GDScriptParser::DictionaryNode *>(p_assignment->assigned_value),
assignee_type.get_container_element_type_or_variant(0), assignee_type.get_container_element_type_or_variant(1));
}
if (p_assignment->operation == GDScriptParser::AssignmentNode::OP_NONE && assignee_type.is_hard_type() && p_assignment->assigned_value->is_constant) {
@ -2830,8 +2978,8 @@ void GDScriptAnalyzer::reduce_assignment(GDScriptParser::AssignmentNode *p_assig
// weak non-variant assignee and incompatible result
downgrades_assignee = true;
}
} else if (assignee_type.has_container_element_type(0) && !op_type.has_container_element_type(0)) {
// typed array assignee and untyped array result
} else if ((assignee_type.has_container_element_type(0) && !op_type.has_container_element_type(0)) || (assignee_type.has_container_element_type(1) && !op_type.has_container_element_type(1))) {
// Typed assignee and untyped result.
mark_node_unsafe(p_assignment);
}
}
@ -3029,10 +3177,13 @@ const char *check_for_renamed_identifier(String identifier, GDScriptParser::Node
void GDScriptAnalyzer::reduce_call(GDScriptParser::CallNode *p_call, bool p_is_await, bool p_is_root) {
bool all_is_constant = true;
HashMap<int, GDScriptParser::ArrayNode *> arrays; // For array literal to potentially type when passing.
HashMap<int, GDScriptParser::DictionaryNode *> dictionaries; // Same, but for dictionaries.
for (int i = 0; i < p_call->arguments.size(); i++) {
reduce_expression(p_call->arguments[i]);
if (p_call->arguments[i]->type == GDScriptParser::Node::ARRAY) {
arrays[i] = static_cast<GDScriptParser::ArrayNode *>(p_call->arguments[i]);
} else if (p_call->arguments[i]->type == GDScriptParser::Node::DICTIONARY) {
dictionaries[i] = static_cast<GDScriptParser::DictionaryNode *>(p_call->arguments[i]);
}
all_is_constant = all_is_constant && p_call->arguments[i]->is_constant;
}
@ -3225,6 +3376,26 @@ void GDScriptAnalyzer::reduce_call(GDScriptParser::CallNode *p_call, bool p_is_a
push_error(vformat(R"(No constructor of "%s" matches the signature "%s".)", Variant::get_type_name(builtin_type), signature), p_call);
}
}
#ifdef DEBUG_ENABLED
// Consider `Signal(self, "my_signal")` as an implicit use of the signal.
if (builtin_type == Variant::SIGNAL && p_call->arguments.size() >= 2) {
const GDScriptParser::ExpressionNode *object_arg = p_call->arguments[0];
if (object_arg && object_arg->type == GDScriptParser::Node::SELF) {
const GDScriptParser::ExpressionNode *signal_arg = p_call->arguments[1];
if (signal_arg && signal_arg->is_constant) {
const StringName &signal_name = signal_arg->reduced_value;
if (parser->current_class->has_member(signal_name)) {
const GDScriptParser::ClassNode::Member &member = parser->current_class->get_member(signal_name);
if (member.type == GDScriptParser::ClassNode::Member::SIGNAL) {
member.signal->usages++;
}
}
}
}
}
#endif
p_call->set_datatype(call_type);
return;
} else if (GDScriptUtilityFunctions::function_exists(function_name)) {
@ -3417,6 +3588,14 @@ void GDScriptAnalyzer::reduce_call(GDScriptParser::CallNode *p_call, bool p_is_a
update_array_literal_element_type(E.value, par_types.get(index).get_container_element_type(0));
}
}
for (const KeyValue<int, GDScriptParser::DictionaryNode *> &E : dictionaries) {
int index = E.key;
if (index < par_types.size() && par_types.get(index).is_hard_type() && par_types.get(index).has_container_element_types()) {
GDScriptParser::DataType key = par_types.get(index).get_container_element_type_or_variant(0);
GDScriptParser::DataType value = par_types.get(index).get_container_element_type_or_variant(1);
update_dictionary_literal_element_type(E.value, key, value);
}
}
validate_call_arg(par_types, default_arg_count, method_flags.has_flag(METHOD_FLAG_VARARG), p_call);
if (base_type.kind == GDScriptParser::DataType::ENUM && base_type.is_meta_type) {
@ -3459,6 +3638,20 @@ void GDScriptAnalyzer::reduce_call(GDScriptParser::CallNode *p_call, bool p_is_a
parser->push_warning(p_call, GDScriptWarning::STATIC_CALLED_ON_INSTANCE, p_call->function_name, caller_type);
}
// Consider `emit_signal()`, `connect()`, and `disconnect()` as implicit uses of the signal.
if (is_self && (p_call->function_name == SNAME("emit_signal") || p_call->function_name == SNAME("connect") || p_call->function_name == SNAME("disconnect")) && !p_call->arguments.is_empty()) {
const GDScriptParser::ExpressionNode *signal_arg = p_call->arguments[0];
if (signal_arg && signal_arg->is_constant) {
const StringName &signal_name = signal_arg->reduced_value;
if (parser->current_class->has_member(signal_name)) {
const GDScriptParser::ClassNode::Member &member = parser->current_class->get_member(signal_name);
if (member.type == GDScriptParser::ClassNode::Member::SIGNAL) {
member.signal->usages++;
}
}
}
}
#endif // DEBUG_ENABLED
call_type = return_type;
@ -3502,7 +3695,7 @@ void GDScriptAnalyzer::reduce_call(GDScriptParser::CallNode *p_call, bool p_is_a
String base_name = is_self && !p_call->is_super ? "self" : base_type.to_string();
#ifdef SUGGEST_GODOT4_RENAMES
String rename_hint;
if (GLOBAL_GET(GDScriptWarning::get_settings_path_from_code(GDScriptWarning::RENAMED_IN_GODOT_4_HINT)).booleanize()) {
if (GLOBAL_GET("debug/gdscript/warnings/renamed_in_godot_4_hint")) {
const char *renamed_function_name = check_for_renamed_identifier(p_call->function_name, p_call->type);
if (renamed_function_name) {
rename_hint = " " + vformat(R"(Did you mean to use "%s"?)", String(renamed_function_name) + "()");
@ -3547,6 +3740,11 @@ void GDScriptAnalyzer::reduce_cast(GDScriptParser::CastNode *p_cast) {
update_array_literal_element_type(static_cast<GDScriptParser::ArrayNode *>(p_cast->operand), cast_type.get_container_element_type(0));
}
if (p_cast->operand->type == GDScriptParser::Node::DICTIONARY && cast_type.has_container_element_types()) {
update_dictionary_literal_element_type(static_cast<GDScriptParser::DictionaryNode *>(p_cast->operand),
cast_type.get_container_element_type_or_variant(0), cast_type.get_container_element_type_or_variant(1));
}
if (!cast_type.is_variant()) {
GDScriptParser::DataType op_type = p_cast->operand->get_datatype();
if (op_type.is_variant() || !op_type.is_hard_type()) {
@ -3657,6 +3855,12 @@ GDScriptParser::DataType GDScriptAnalyzer::make_global_class_meta_type(const Str
}
Ref<GDScriptParserRef> GDScriptAnalyzer::ensure_cached_external_parser_for_class(const GDScriptParser::ClassNode *p_class, const GDScriptParser::ClassNode *p_from_class, const char *p_context, const GDScriptParser::Node *p_source) {
// Delicate piece of code that intentionally doesn't use the GDScript cache or `get_depended_parser_for`.
// Search dependencies for the parser that owns `p_class` and make a cache entry for it.
// Required for how we store pointers to classes owned by other parser trees and need to call `resolve_class_member` and such on the same parser tree.
// Since https://github.com/godotengine/godot/pull/94871 there can technically be multiple parsers for the same script in the same parser tree.
// Even if unlikely, getting the wrong parser could lead to strange undefined behavior without errors.
if (p_class == nullptr) {
return nullptr;
}
@ -3673,8 +3877,6 @@ Ref<GDScriptParserRef> GDScriptAnalyzer::ensure_cached_external_parser_for_class
p_from_class = parser->head;
}
String script_path = p_class->get_datatype().script_path;
Ref<GDScriptParserRef> parser_ref;
for (const GDScriptParser::ClassNode *look_class = p_from_class; look_class != nullptr; look_class = look_class->base_type.class_type) {
if (parser->has_class(look_class)) {
@ -3755,8 +3957,9 @@ Ref<GDScriptParserRef> GDScriptAnalyzer::find_cached_external_parser_for_class(c
Ref<GDScript> GDScriptAnalyzer::get_depended_shallow_script(const String &p_path, Error &r_error) {
// To keep a local cache of the parser for resolving external nodes later.
parser->get_depended_parser_for(p_path);
Ref<GDScript> scr = GDScriptCache::get_shallow_script(p_path, r_error, parser->script_path);
const String path = ResourceUID::ensure_path(p_path);
parser->get_depended_parser_for(path);
Ref<GDScript> scr = GDScriptCache::get_shallow_script(path, r_error, parser->script_path);
return scr;
}
@ -3807,24 +4010,47 @@ void GDScriptAnalyzer::reduce_identifier_from_base(GDScriptParser::IdentifierNod
if (base.kind == GDScriptParser::DataType::BUILTIN) {
if (base.is_meta_type) {
bool valid = true;
Variant result = Variant::get_constant_value(base.builtin_type, name, &valid);
if (valid) {
bool valid = false;
if (Variant::has_constant(base.builtin_type, name)) {
valid = true;
const Variant constant_value = Variant::get_constant_value(base.builtin_type, name);
p_identifier->is_constant = true;
p_identifier->reduced_value = result;
p_identifier->set_datatype(type_from_variant(result, p_identifier));
} else if (base.is_hard_type()) {
p_identifier->reduced_value = constant_value;
p_identifier->set_datatype(type_from_variant(constant_value, p_identifier));
}
if (!valid) {
const StringName enum_name = Variant::get_enum_for_enumeration(base.builtin_type, name);
if (enum_name != StringName()) {
valid = true;
p_identifier->is_constant = true;
p_identifier->reduced_value = Variant::get_enum_value(base.builtin_type, enum_name, name);
p_identifier->set_datatype(make_builtin_enum_type(enum_name, base.builtin_type, false));
}
}
if (!valid && Variant::has_enum(base.builtin_type, name)) {
valid = true;
p_identifier->set_datatype(make_builtin_enum_type(name, base.builtin_type, true));
}
if (!valid && base.is_hard_type()) {
#ifdef SUGGEST_GODOT4_RENAMES
String rename_hint;
if (GLOBAL_GET(GDScriptWarning::get_settings_path_from_code(GDScriptWarning::RENAMED_IN_GODOT_4_HINT)).booleanize()) {
if (GLOBAL_GET("debug/gdscript/warnings/renamed_in_godot_4_hint")) {
const char *renamed_identifier_name = check_for_renamed_identifier(name, p_identifier->type);
if (renamed_identifier_name) {
rename_hint = " " + vformat(R"(Did you mean to use "%s"?)", renamed_identifier_name);
}
}
push_error(vformat(R"(Cannot find constant "%s" on base "%s".%s)", name, base.to_string(), rename_hint), p_identifier);
push_error(vformat(R"(Cannot find member "%s" in base "%s".%s)", name, base.to_string(), rename_hint), p_identifier);
#else
push_error(vformat(R"(Cannot find constant "%s" on base "%s".)", name, base.to_string()), p_identifier);
push_error(vformat(R"(Cannot find member "%s" in base "%s".)", name, base.to_string()), p_identifier);
#endif // SUGGEST_GODOT4_RENAMES
}
} else {
@ -3860,15 +4086,15 @@ void GDScriptAnalyzer::reduce_identifier_from_base(GDScriptParser::IdentifierNod
if (base.is_hard_type()) {
#ifdef SUGGEST_GODOT4_RENAMES
String rename_hint;
if (GLOBAL_GET(GDScriptWarning::get_settings_path_from_code(GDScriptWarning::RENAMED_IN_GODOT_4_HINT)).booleanize()) {
if (GLOBAL_GET("debug/gdscript/warnings/renamed_in_godot_4_hint")) {
const char *renamed_identifier_name = check_for_renamed_identifier(name, p_identifier->type);
if (renamed_identifier_name) {
rename_hint = " " + vformat(R"(Did you mean to use "%s"?)", renamed_identifier_name);
}
}
push_error(vformat(R"(Cannot find property "%s" on base "%s".%s)", name, base.to_string(), rename_hint), p_identifier);
push_error(vformat(R"(Cannot find member "%s" in base "%s".%s)", name, base.to_string(), rename_hint), p_identifier);
#else
push_error(vformat(R"(Cannot find property "%s" on base "%s".)", name, base.to_string()), p_identifier);
push_error(vformat(R"(Cannot find member "%s" in base "%s".)", name, base.to_string()), p_identifier);
#endif // SUGGEST_GODOT4_RENAMES
}
}
@ -4099,7 +4325,7 @@ void GDScriptAnalyzer::reduce_identifier(GDScriptParser::IdentifierNode *p_ident
const GDScriptParser::EnumNode::Value &element = current_enum->values[i];
if (element.identifier->name == p_identifier->name) {
StringName enum_name = current_enum->identifier ? current_enum->identifier->name : UNNAMED_ENUM;
GDScriptParser::DataType type = make_enum_type(enum_name, parser->current_class->fqcn, false);
GDScriptParser::DataType type = make_class_enum_type(enum_name, parser->current_class, parser->script_path, false);
if (element.parent_enum->identifier) {
type.enum_type = element.parent_enum->identifier->name;
}
@ -4288,11 +4514,11 @@ void GDScriptAnalyzer::reduce_identifier(GDScriptParser::IdentifierNode *p_ident
result.builtin_type = Variant::OBJECT;
result.native_type = SNAME("Node");
if (ResourceLoader::get_resource_type(autoload.path) == "GDScript") {
Ref<GDScriptParserRef> singl_parser = parser->get_depended_parser_for(autoload.path);
if (singl_parser.is_valid()) {
Error err = singl_parser->raise_status(GDScriptParserRef::INHERITANCE_SOLVED);
Ref<GDScriptParserRef> single_parser = parser->get_depended_parser_for(autoload.path);
if (single_parser.is_valid()) {
Error err = single_parser->raise_status(GDScriptParserRef::INHERITANCE_SOLVED);
if (err == OK) {
result = type_from_metatype(singl_parser->get_parser()->head->get_datatype());
result = type_from_metatype(single_parser->get_parser()->head->get_datatype());
}
}
} else if (ResourceLoader::get_resource_type(autoload.path) == "PackedScene") {
@ -4302,11 +4528,11 @@ void GDScriptAnalyzer::reduce_identifier(GDScriptParser::IdentifierNode *p_ident
if (node != nullptr) {
Ref<GDScript> scr = node->get_script();
if (scr.is_valid()) {
Ref<GDScriptParserRef> singl_parser = parser->get_depended_parser_for(scr->get_script_path());
if (singl_parser.is_valid()) {
Error err = singl_parser->raise_status(GDScriptParserRef::INHERITANCE_SOLVED);
Ref<GDScriptParserRef> single_parser = parser->get_depended_parser_for(scr->get_script_path());
if (single_parser.is_valid()) {
Error err = single_parser->raise_status(GDScriptParserRef::INHERITANCE_SOLVED);
if (err == OK) {
result = type_from_metatype(singl_parser->get_parser()->head->get_datatype());
result = type_from_metatype(single_parser->get_parser()->head->get_datatype());
}
}
}
@ -4376,7 +4602,7 @@ void GDScriptAnalyzer::reduce_identifier(GDScriptParser::IdentifierNode *p_ident
// Not found.
#ifdef SUGGEST_GODOT4_RENAMES
String rename_hint;
if (GLOBAL_GET(GDScriptWarning::get_settings_path_from_code(GDScriptWarning::RENAMED_IN_GODOT_4_HINT)).booleanize()) {
if (GLOBAL_GET("debug/gdscript/warnings/renamed_in_godot_4_hint")) {
const char *renamed_identifier_name = check_for_renamed_identifier(name, p_identifier->type);
if (renamed_identifier_name) {
rename_hint = " " + vformat(R"(Did you mean to use "%s"?)", renamed_identifier_name);
@ -4571,10 +4797,23 @@ void GDScriptAnalyzer::reduce_subscript(GDScriptParser::SubscriptNode *p_subscri
reduce_identifier_from_base(p_subscript->attribute, &base_type);
GDScriptParser::DataType attr_type = p_subscript->attribute->get_datatype();
if (attr_type.is_set()) {
valid = !attr_type.is_pseudo_type || p_can_be_pseudo_type;
result_type = attr_type;
p_subscript->is_constant = p_subscript->attribute->is_constant;
p_subscript->reduced_value = p_subscript->attribute->reduced_value;
if (base_type.builtin_type == Variant::DICTIONARY && base_type.has_container_element_types()) {
Variant::Type key_type = base_type.get_container_element_type_or_variant(0).builtin_type;
valid = key_type == Variant::NIL || key_type == Variant::STRING || key_type == Variant::STRING_NAME;
if (base_type.has_container_element_type(1)) {
result_type = base_type.get_container_element_type(1);
result_type.type_source = base_type.type_source;
} else {
result_type.builtin_type = Variant::NIL;
result_type.kind = GDScriptParser::DataType::VARIANT;
result_type.type_source = GDScriptParser::DataType::UNDETECTED;
}
} else {
valid = !attr_type.is_pseudo_type || p_can_be_pseudo_type;
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_meta_type || !base_type.is_constant) {
valid = base_type.kind != GDScriptParser::DataType::BUILTIN;
#ifdef DEBUG_ENABLED
@ -4681,8 +4920,40 @@ void GDScriptAnalyzer::reduce_subscript(GDScriptParser::SubscriptNode *p_subscri
case Variant::SIGNAL:
case Variant::STRING_NAME:
break;
// Here for completeness.
// Support depends on if the dictionary has a typed key, otherwise anything is valid.
case Variant::DICTIONARY:
if (base_type.has_container_element_type(0)) {
GDScriptParser::DataType key_type = base_type.get_container_element_type(0);
switch (index_type.builtin_type) {
// Null value will be treated as an empty object, allow.
case Variant::NIL:
error = key_type.builtin_type != Variant::OBJECT;
break;
// Objects are parsed for validity in a similar manner to container types.
case Variant::OBJECT:
if (key_type.builtin_type == Variant::OBJECT) {
error = !key_type.can_reference(index_type);
} else {
error = key_type.builtin_type != Variant::NIL;
}
break;
// String and StringName interchangeable in this context.
case Variant::STRING:
case Variant::STRING_NAME:
error = key_type.builtin_type != Variant::STRING_NAME && key_type.builtin_type != Variant::STRING;
break;
// Ints are valid indices for floats, but not the other way around.
case Variant::INT:
error = key_type.builtin_type != Variant::INT && key_type.builtin_type != Variant::FLOAT;
break;
// All other cases require the types to match exactly.
default:
error = key_type.builtin_type != index_type.builtin_type;
break;
}
}
break;
// Here for completeness.
case Variant::VARIANT_MAX:
break;
}
@ -4771,7 +5042,6 @@ void GDScriptAnalyzer::reduce_subscript(GDScriptParser::SubscriptNode *p_subscri
case Variant::PROJECTION:
case Variant::PLANE:
case Variant::COLOR:
case Variant::DICTIONARY:
case Variant::OBJECT:
result_type.kind = GDScriptParser::DataType::VARIANT;
result_type.type_source = GDScriptParser::DataType::UNDETECTED;
@ -4786,6 +5056,16 @@ void GDScriptAnalyzer::reduce_subscript(GDScriptParser::SubscriptNode *p_subscri
result_type.type_source = GDScriptParser::DataType::UNDETECTED;
}
break;
// Can have two element types, but we only care about the value.
case Variant::DICTIONARY:
if (base_type.has_container_element_type(1)) {
result_type = base_type.get_container_element_type(1);
result_type.type_source = base_type.type_source;
} else {
result_type.kind = GDScriptParser::DataType::VARIANT;
result_type.type_source = GDScriptParser::DataType::UNDETECTED;
}
break;
// Here for completeness.
case Variant::VARIANT_MAX:
break;
@ -4965,7 +5245,9 @@ Variant GDScriptAnalyzer::make_array_reduced_value(GDScriptParser::ArrayNode *p_
}
Variant GDScriptAnalyzer::make_dictionary_reduced_value(GDScriptParser::DictionaryNode *p_dictionary, bool &is_reduced) {
Dictionary dictionary;
Dictionary dictionary = p_dictionary->get_datatype().has_container_element_types()
? make_dictionary_from_element_datatype(p_dictionary->get_datatype().get_container_element_type_or_variant(0), p_dictionary->get_datatype().get_container_element_type_or_variant(1))
: Dictionary();
for (int i = 0; i < p_dictionary->elements.size(); i++) {
const GDScriptParser::DictionaryNode::Pair &element = p_dictionary->elements[i];
@ -5052,6 +5334,49 @@ Array GDScriptAnalyzer::make_array_from_element_datatype(const GDScriptParser::D
return array;
}
Dictionary GDScriptAnalyzer::make_dictionary_from_element_datatype(const GDScriptParser::DataType &p_key_element_datatype, const GDScriptParser::DataType &p_value_element_datatype, const GDScriptParser::Node *p_source_node) {
Dictionary dictionary;
StringName key_name;
Variant key_script;
StringName value_name;
Variant value_script;
if (p_key_element_datatype.builtin_type == Variant::OBJECT) {
Ref<Script> script_type = p_key_element_datatype.script_type;
if (p_key_element_datatype.kind == GDScriptParser::DataType::CLASS && script_type.is_null()) {
Error err = OK;
Ref<GDScript> scr = get_depended_shallow_script(p_key_element_datatype.script_path, err);
if (err) {
push_error(vformat(R"(Error while getting cache for script "%s".)", p_key_element_datatype.script_path), p_source_node);
return dictionary;
}
script_type.reference_ptr(scr->find_class(p_key_element_datatype.class_type->fqcn));
}
key_name = p_key_element_datatype.native_type;
key_script = script_type;
}
if (p_value_element_datatype.builtin_type == Variant::OBJECT) {
Ref<Script> script_type = p_value_element_datatype.script_type;
if (p_value_element_datatype.kind == GDScriptParser::DataType::CLASS && script_type.is_null()) {
Error err = OK;
Ref<GDScript> scr = get_depended_shallow_script(p_value_element_datatype.script_path, err);
if (err) {
push_error(vformat(R"(Error while getting cache for script "%s".)", p_value_element_datatype.script_path), p_source_node);
return dictionary;
}
script_type.reference_ptr(scr->find_class(p_value_element_datatype.class_type->fqcn));
}
value_name = p_value_element_datatype.native_type;
value_script = script_type;
}
dictionary.set_typed(p_key_element_datatype.builtin_type, key_name, key_script, p_value_element_datatype.builtin_type, value_name, value_script);
return dictionary;
}
Variant GDScriptAnalyzer::make_variable_default_value(GDScriptParser::VariableNode *p_variable) {
Variant result = Variant();
@ -5067,6 +5392,10 @@ Variant GDScriptAnalyzer::make_variable_default_value(GDScriptParser::VariableNo
if (datatype.kind == GDScriptParser::DataType::BUILTIN && datatype.builtin_type != Variant::OBJECT) {
if (datatype.builtin_type == Variant::ARRAY && datatype.has_container_element_type(0)) {
result = make_array_from_element_datatype(datatype.get_container_element_type(0));
} else if (datatype.builtin_type == Variant::DICTIONARY && datatype.has_container_element_types()) {
GDScriptParser::DataType key = datatype.get_container_element_type_or_variant(0);
GDScriptParser::DataType value = datatype.get_container_element_type_or_variant(1);
result = make_dictionary_from_element_datatype(key, value);
} else {
VariantInternal::initialize(&result, datatype.builtin_type);
}
@ -5095,6 +5424,22 @@ GDScriptParser::DataType GDScriptAnalyzer::type_from_variant(const Variant &p_va
} else if (array.get_typed_builtin() != Variant::NIL) {
result.set_container_element_type(0, type_from_metatype(make_builtin_meta_type((Variant::Type)array.get_typed_builtin())));
}
} else if (p_value.get_type() == Variant::DICTIONARY) {
const Dictionary &dict = p_value;
if (dict.get_typed_key_script()) {
result.set_container_element_type(0, type_from_metatype(make_script_meta_type(dict.get_typed_key_script())));
} else if (dict.get_typed_key_class_name()) {
result.set_container_element_type(0, type_from_metatype(make_native_meta_type(dict.get_typed_key_class_name())));
} else if (dict.get_typed_key_builtin() != Variant::NIL) {
result.set_container_element_type(0, type_from_metatype(make_builtin_meta_type((Variant::Type)dict.get_typed_key_builtin())));
}
if (dict.get_typed_value_script()) {
result.set_container_element_type(1, type_from_metatype(make_script_meta_type(dict.get_typed_value_script())));
} else if (dict.get_typed_value_class_name()) {
result.set_container_element_type(1, type_from_metatype(make_native_meta_type(dict.get_typed_value_class_name())));
} else if (dict.get_typed_value_builtin() != Variant::NIL) {
result.set_container_element_type(1, type_from_metatype(make_builtin_meta_type((Variant::Type)dict.get_typed_value_builtin())));
}
} else if (p_value.get_type() == Variant::OBJECT) {
// Object is treated as a native type, not a builtin type.
result.kind = GDScriptParser::DataType::NATIVE;
@ -5227,6 +5572,60 @@ GDScriptParser::DataType GDScriptAnalyzer::type_from_property(const PropertyInfo
}
elem_type.is_constant = false;
result.set_container_element_type(0, elem_type);
} else if (p_property.type == Variant::DICTIONARY && p_property.hint == PROPERTY_HINT_DICTIONARY_TYPE) {
// Check element type.
StringName key_elem_type_name = p_property.hint_string.get_slice(";", 0);
GDScriptParser::DataType key_elem_type;
key_elem_type.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT;
Variant::Type key_elem_builtin_type = GDScriptParser::get_builtin_type(key_elem_type_name);
if (key_elem_builtin_type < Variant::VARIANT_MAX) {
// Builtin type.
key_elem_type.kind = GDScriptParser::DataType::BUILTIN;
key_elem_type.builtin_type = key_elem_builtin_type;
} else if (class_exists(key_elem_type_name)) {
key_elem_type.kind = GDScriptParser::DataType::NATIVE;
key_elem_type.builtin_type = Variant::OBJECT;
key_elem_type.native_type = key_elem_type_name;
} else if (ScriptServer::is_global_class(key_elem_type_name)) {
// Just load this as it shouldn't be a GDScript.
Ref<Script> script = ResourceLoader::load(ScriptServer::get_global_class_path(key_elem_type_name));
key_elem_type.kind = GDScriptParser::DataType::SCRIPT;
key_elem_type.builtin_type = Variant::OBJECT;
key_elem_type.native_type = script->get_instance_base_type();
key_elem_type.script_type = script;
} else {
ERR_FAIL_V_MSG(result, "Could not find element type from property hint of a typed dictionary.");
}
key_elem_type.is_constant = false;
StringName value_elem_type_name = p_property.hint_string.get_slice(";", 1);
GDScriptParser::DataType value_elem_type;
value_elem_type.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT;
Variant::Type value_elem_builtin_type = GDScriptParser::get_builtin_type(value_elem_type_name);
if (value_elem_builtin_type < Variant::VARIANT_MAX) {
// Builtin type.
value_elem_type.kind = GDScriptParser::DataType::BUILTIN;
value_elem_type.builtin_type = value_elem_builtin_type;
} else if (class_exists(value_elem_type_name)) {
value_elem_type.kind = GDScriptParser::DataType::NATIVE;
value_elem_type.builtin_type = Variant::OBJECT;
value_elem_type.native_type = value_elem_type_name;
} else if (ScriptServer::is_global_class(value_elem_type_name)) {
// Just load this as it shouldn't be a GDScript.
Ref<Script> script = ResourceLoader::load(ScriptServer::get_global_class_path(value_elem_type_name));
value_elem_type.kind = GDScriptParser::DataType::SCRIPT;
value_elem_type.builtin_type = Variant::OBJECT;
value_elem_type.native_type = script->get_instance_base_type();
value_elem_type.script_type = script;
} else {
ERR_FAIL_V_MSG(result, "Could not find element type from property hint of a typed dictionary.");
}
value_elem_type.is_constant = false;
result.set_container_element_type(0, key_elem_type);
result.set_container_element_type(1, value_elem_type);
} else if (p_property.type == Variant::INT) {
// Check if it's enum.
if ((p_property.usage & PROPERTY_USAGE_CLASS_IS_ENUM) && p_property.class_name != StringName()) {
@ -5236,7 +5635,7 @@ GDScriptParser::DataType GDScriptAnalyzer::type_from_property(const PropertyInfo
} else {
Vector<String> names = String(p_property.class_name).split(ENUM_SEPARATOR);
if (names.size() == 2) {
result = make_native_enum_type(names[1], names[0], false);
result = make_enum_type(names[1], names[0], false);
result.is_constant = false;
}
}
@ -5473,8 +5872,6 @@ void GDScriptAnalyzer::validate_call_arg(const List<GDScriptParser::DataType> &p
#ifdef DEBUG_ENABLED
void GDScriptAnalyzer::is_shadowing(GDScriptParser::IdentifierNode *p_identifier, const String &p_context, const bool p_in_local_scope) {
const StringName &name = p_identifier->name;
GDScriptParser::DataType base = parser->current_class->get_datatype();
GDScriptParser::ClassNode *base_class = base.class_type;
{
List<MethodInfo> gdscript_funcs;
@ -5502,40 +5899,56 @@ void GDScriptAnalyzer::is_shadowing(GDScriptParser::IdentifierNode *p_identifier
}
}
const GDScriptParser::DataType current_class_type = parser->current_class->get_datatype();
if (p_in_local_scope) {
while (base_class != nullptr) {
GDScriptParser::ClassNode *base_class = current_class_type.class_type;
if (base_class != nullptr) {
if (base_class->has_member(name)) {
parser->push_warning(p_identifier, GDScriptWarning::SHADOWED_VARIABLE, p_context, p_identifier->name, base_class->get_member(name).get_type_name(), itos(base_class->get_member(name).get_line()));
return;
}
base_class = base_class->base_type.class_type;
}
while (base_class != nullptr) {
if (base_class->has_member(name)) {
String base_class_name = base_class->get_global_name();
if (base_class_name.is_empty()) {
base_class_name = base_class->fqcn;
}
parser->push_warning(p_identifier, GDScriptWarning::SHADOWED_VARIABLE_BASE_CLASS, p_context, p_identifier->name, base_class->get_member(name).get_type_name(), itos(base_class->get_member(name).get_line()), base_class_name);
return;
}
base_class = base_class->base_type.class_type;
}
}
StringName parent = base.native_type;
while (parent != StringName()) {
ERR_FAIL_COND_MSG(!class_exists(parent), "Non-existent native base class.");
StringName native_base_class = current_class_type.native_type;
while (native_base_class != StringName()) {
ERR_FAIL_COND_MSG(!class_exists(native_base_class), "Non-existent native base class.");
if (ClassDB::has_method(parent, name, true)) {
parser->push_warning(p_identifier, GDScriptWarning::SHADOWED_VARIABLE_BASE_CLASS, p_context, p_identifier->name, "method", parent);
if (ClassDB::has_method(native_base_class, name, true)) {
parser->push_warning(p_identifier, GDScriptWarning::SHADOWED_VARIABLE_BASE_CLASS, p_context, p_identifier->name, "method", native_base_class);
return;
} else if (ClassDB::has_signal(parent, name, true)) {
parser->push_warning(p_identifier, GDScriptWarning::SHADOWED_VARIABLE_BASE_CLASS, p_context, p_identifier->name, "signal", parent);
} else if (ClassDB::has_signal(native_base_class, name, true)) {
parser->push_warning(p_identifier, GDScriptWarning::SHADOWED_VARIABLE_BASE_CLASS, p_context, p_identifier->name, "signal", native_base_class);
return;
} else if (ClassDB::has_property(parent, name, true)) {
parser->push_warning(p_identifier, GDScriptWarning::SHADOWED_VARIABLE_BASE_CLASS, p_context, p_identifier->name, "property", parent);
} else if (ClassDB::has_property(native_base_class, name, true)) {
parser->push_warning(p_identifier, GDScriptWarning::SHADOWED_VARIABLE_BASE_CLASS, p_context, p_identifier->name, "property", native_base_class);
return;
} else if (ClassDB::has_integer_constant(parent, name, true)) {
parser->push_warning(p_identifier, GDScriptWarning::SHADOWED_VARIABLE_BASE_CLASS, p_context, p_identifier->name, "constant", parent);
} else if (ClassDB::has_integer_constant(native_base_class, name, true)) {
parser->push_warning(p_identifier, GDScriptWarning::SHADOWED_VARIABLE_BASE_CLASS, p_context, p_identifier->name, "constant", native_base_class);
return;
} else if (ClassDB::has_enum(parent, name, true)) {
parser->push_warning(p_identifier, GDScriptWarning::SHADOWED_VARIABLE_BASE_CLASS, p_context, p_identifier->name, "enum", parent);
} else if (ClassDB::has_enum(native_base_class, name, true)) {
parser->push_warning(p_identifier, GDScriptWarning::SHADOWED_VARIABLE_BASE_CLASS, p_context, p_identifier->name, "enum", native_base_class);
return;
}
parent = ClassDB::get_parent_class(parent);
native_base_class = ClassDB::get_parent_class(native_base_class);
}
}
#endif
#endif // DEBUG_ENABLED
GDScriptParser::DataType GDScriptAnalyzer::get_operation_type(Variant::Operator p_operation, const GDScriptParser::DataType &p_a, bool &r_valid, const GDScriptParser::Node *p_source) {
// Unary version.
@ -5647,6 +6060,15 @@ bool GDScriptAnalyzer::check_type_compatibility(const GDScriptParser::DataType &
valid = p_target.get_container_element_type(0) == p_source.get_container_element_type(0);
}
}
if (valid && p_target.builtin_type == Variant::DICTIONARY && p_source.builtin_type == Variant::DICTIONARY) {
// Check the element types.
if (p_target.has_container_element_type(0) && p_source.has_container_element_type(0)) {
valid = p_target.get_container_element_type(0) == p_source.get_container_element_type(0);
}
if (valid && p_target.has_container_element_type(1) && p_source.has_container_element_type(1)) {
valid = p_target.get_container_element_type(1) == p_source.get_container_element_type(1);
}
}
return valid;
}

View file

@ -36,7 +36,6 @@
#include "core/object/object.h"
#include "core/object/ref_counted.h"
#include "core/templates/hash_set.h"
class GDScriptAnalyzer {
GDScriptParser *parser = nullptr;
@ -125,8 +124,8 @@ class GDScriptAnalyzer {
// Helpers.
Array make_array_from_element_datatype(const GDScriptParser::DataType &p_element_datatype, const GDScriptParser::Node *p_source_node = nullptr);
Dictionary make_dictionary_from_element_datatype(const GDScriptParser::DataType &p_key_element_datatype, const GDScriptParser::DataType &p_value_element_datatype, const GDScriptParser::Node *p_source_node = nullptr);
GDScriptParser::DataType type_from_variant(const Variant &p_value, const GDScriptParser::Node *p_source);
static GDScriptParser::DataType type_from_metatype(const GDScriptParser::DataType &p_meta_type);
GDScriptParser::DataType type_from_property(const PropertyInfo &p_property, bool p_is_arg = false, bool p_is_readonly = false) const;
GDScriptParser::DataType make_global_class_meta_type(const StringName &p_class_name, const GDScriptParser::Node *p_source);
bool get_function_signature(GDScriptParser::Node *p_source, bool p_is_constructor, GDScriptParser::DataType base_type, const StringName &p_function, GDScriptParser::DataType &r_return_type, List<GDScriptParser::DataType> &r_par_types, int &r_default_arg_count, BitField<MethodFlags> &r_method_flags, StringName *r_native_class = nullptr);
@ -137,6 +136,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_const_expression_builtin_type(GDScriptParser::ExpressionNode *p_expression, const GDScriptParser::DataType &p_type, const char *p_usage, bool p_is_cast = false);
void update_array_literal_element_type(GDScriptParser::ArrayNode *p_array, const GDScriptParser::DataType &p_element_type);
void update_dictionary_literal_element_type(GDScriptParser::DictionaryNode *p_dictionary, const GDScriptParser::DataType &p_key_element_type, const GDScriptParser::DataType &p_value_element_type);
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 = nullptr);
void mark_node_unsafe(const GDScriptParser::Node *p_node);
@ -161,7 +161,9 @@ public:
Error analyze();
Variant make_variable_default_value(GDScriptParser::VariableNode *p_variable);
static bool check_type_compatibility(const GDScriptParser::DataType &p_target, const GDScriptParser::DataType &p_source, bool p_allow_implicit_conversion = false, const GDScriptParser::Node *p_source_node = nullptr);
static GDScriptParser::DataType type_from_metatype(const GDScriptParser::DataType &p_meta_type);
GDScriptAnalyzer(GDScriptParser *p_parser);
};

View file

@ -585,8 +585,25 @@ void GDScriptByteCodeGenerator::write_unary_operator(const Address &p_target, Va
}
void GDScriptByteCodeGenerator::write_binary_operator(const Address &p_target, Variant::Operator p_operator, const Address &p_left_operand, const Address &p_right_operand) {
// Avoid validated evaluator for modulo and division when operands are int, since there's no check for division by zero.
if (HAS_BUILTIN_TYPE(p_left_operand) && HAS_BUILTIN_TYPE(p_right_operand) && ((p_operator != Variant::OP_DIVIDE && p_operator != Variant::OP_MODULE) || p_left_operand.type.builtin_type != Variant::INT || p_right_operand.type.builtin_type != Variant::INT)) {
bool valid = HAS_BUILTIN_TYPE(p_left_operand) && HAS_BUILTIN_TYPE(p_right_operand);
// Avoid validated evaluator for modulo and division when operands are int or integer vector, since there's no check for division by zero.
if (valid && (p_operator == Variant::OP_DIVIDE || p_operator == Variant::OP_MODULE)) {
switch (p_left_operand.type.builtin_type) {
case Variant::INT:
valid = p_right_operand.type.builtin_type != Variant::INT;
break;
case Variant::VECTOR2I:
case Variant::VECTOR3I:
case Variant::VECTOR4I:
valid = p_right_operand.type.builtin_type != Variant::INT && p_right_operand.type.builtin_type != p_left_operand.type.builtin_type;
break;
default:
break;
}
}
if (valid) {
if (p_target.mode == Address::TEMPORARY) {
Variant::Type result_type = Variant::get_operator_return_type(p_operator, p_left_operand.type.builtin_type, p_right_operand.type.builtin_type);
Variant::Type temp_type = temporaries[p_target.address].type;
@ -634,6 +651,18 @@ void GDScriptByteCodeGenerator::write_type_test(const Address &p_target, const A
append(get_constant_pos(element_type.script_type) | (GDScriptFunction::ADDR_TYPE_CONSTANT << GDScriptFunction::ADDR_BITS));
append(element_type.builtin_type);
append(element_type.native_type);
} else if (p_type.builtin_type == Variant::DICTIONARY && p_type.has_container_element_types()) {
const GDScriptDataType &key_element_type = p_type.get_container_element_type_or_variant(0);
const GDScriptDataType &value_element_type = p_type.get_container_element_type_or_variant(1);
append_opcode(GDScriptFunction::OPCODE_TYPE_TEST_DICTIONARY);
append(p_target);
append(p_source);
append(get_constant_pos(key_element_type.script_type) | (GDScriptFunction::ADDR_TYPE_CONSTANT << GDScriptFunction::ADDR_BITS));
append(get_constant_pos(value_element_type.script_type) | (GDScriptFunction::ADDR_TYPE_CONSTANT << GDScriptFunction::ADDR_BITS));
append(key_element_type.builtin_type);
append(key_element_type.native_type);
append(value_element_type.builtin_type);
append(value_element_type.native_type);
} else {
append_opcode(GDScriptFunction::OPCODE_TYPE_TEST_BUILTIN);
append(p_target);
@ -889,6 +918,18 @@ void GDScriptByteCodeGenerator::write_assign_with_conversion(const Address &p_ta
append(get_constant_pos(element_type.script_type) | (GDScriptFunction::ADDR_TYPE_CONSTANT << GDScriptFunction::ADDR_BITS));
append(element_type.builtin_type);
append(element_type.native_type);
} else if (p_target.type.builtin_type == Variant::DICTIONARY && p_target.type.has_container_element_types()) {
const GDScriptDataType &key_type = p_target.type.get_container_element_type_or_variant(0);
const GDScriptDataType &value_type = p_target.type.get_container_element_type_or_variant(1);
append_opcode(GDScriptFunction::OPCODE_ASSIGN_TYPED_DICTIONARY);
append(p_target);
append(p_source);
append(get_constant_pos(key_type.script_type) | (GDScriptFunction::ADDR_TYPE_CONSTANT << GDScriptFunction::ADDR_BITS));
append(get_constant_pos(value_type.script_type) | (GDScriptFunction::ADDR_TYPE_CONSTANT << GDScriptFunction::ADDR_BITS));
append(key_type.builtin_type);
append(key_type.native_type);
append(value_type.builtin_type);
append(value_type.native_type);
} else {
append_opcode(GDScriptFunction::OPCODE_ASSIGN_TYPED_BUILTIN);
append(p_target);
@ -935,6 +976,18 @@ void GDScriptByteCodeGenerator::write_assign(const Address &p_target, const Addr
append(get_constant_pos(element_type.script_type) | (GDScriptFunction::ADDR_TYPE_CONSTANT << GDScriptFunction::ADDR_BITS));
append(element_type.builtin_type);
append(element_type.native_type);
} else if (p_target.type.kind == GDScriptDataType::BUILTIN && p_target.type.builtin_type == Variant::DICTIONARY && p_target.type.has_container_element_types()) {
const GDScriptDataType &key_type = p_target.type.get_container_element_type_or_variant(0);
const GDScriptDataType &value_type = p_target.type.get_container_element_type_or_variant(1);
append_opcode(GDScriptFunction::OPCODE_ASSIGN_TYPED_DICTIONARY);
append(p_target);
append(p_source);
append(get_constant_pos(key_type.script_type) | (GDScriptFunction::ADDR_TYPE_CONSTANT << GDScriptFunction::ADDR_BITS));
append(get_constant_pos(value_type.script_type) | (GDScriptFunction::ADDR_TYPE_CONSTANT << GDScriptFunction::ADDR_BITS));
append(key_type.builtin_type);
append(key_type.native_type);
append(value_type.builtin_type);
append(value_type.native_type);
} else if (p_target.type.kind == GDScriptDataType::BUILTIN && p_source.type.kind == GDScriptDataType::BUILTIN && p_target.type.builtin_type != p_source.type.builtin_type) {
// Need conversion.
append_opcode(GDScriptFunction::OPCODE_ASSIGN_TYPED_BUILTIN);
@ -1434,6 +1487,23 @@ void GDScriptByteCodeGenerator::write_construct_dictionary(const Address &p_targ
ct.cleanup();
}
void GDScriptByteCodeGenerator::write_construct_typed_dictionary(const Address &p_target, const GDScriptDataType &p_key_type, const GDScriptDataType &p_value_type, const Vector<Address> &p_arguments) {
append_opcode_and_argcount(GDScriptFunction::OPCODE_CONSTRUCT_TYPED_DICTIONARY, 3 + p_arguments.size());
for (int i = 0; i < p_arguments.size(); i++) {
append(p_arguments[i]);
}
CallTarget ct = get_call_target(p_target);
append(ct.target);
append(get_constant_pos(p_key_type.script_type) | (GDScriptFunction::ADDR_TYPE_CONSTANT << GDScriptFunction::ADDR_BITS));
append(get_constant_pos(p_value_type.script_type) | (GDScriptFunction::ADDR_TYPE_CONSTANT << GDScriptFunction::ADDR_BITS));
append(p_arguments.size() / 2); // This is number of key-value pairs, so only half of actual arguments.
append(p_key_type.builtin_type);
append(p_key_type.native_type);
append(p_value_type.builtin_type);
append(p_value_type.native_type);
ct.cleanup();
}
void GDScriptByteCodeGenerator::write_await(const Address &p_target, const Address &p_operand) {
append_opcode(GDScriptFunction::OPCODE_AWAIT);
append(p_operand);
@ -1711,6 +1781,19 @@ void GDScriptByteCodeGenerator::write_return(const Address &p_return_value) {
append(get_constant_pos(element_type.script_type) | (GDScriptFunction::ADDR_TYPE_CONSTANT << GDScriptFunction::ADDR_BITS));
append(element_type.builtin_type);
append(element_type.native_type);
} else if (function->return_type.kind == GDScriptDataType::BUILTIN && function->return_type.builtin_type == Variant::DICTIONARY &&
function->return_type.has_container_element_types()) {
// Typed dictionary.
const GDScriptDataType &key_type = function->return_type.get_container_element_type_or_variant(0);
const GDScriptDataType &value_type = function->return_type.get_container_element_type_or_variant(1);
append_opcode(GDScriptFunction::OPCODE_RETURN_TYPED_DICTIONARY);
append(p_return_value);
append(get_constant_pos(key_type.script_type) | (GDScriptFunction::ADDR_TYPE_CONSTANT << GDScriptFunction::ADDR_BITS));
append(get_constant_pos(value_type.script_type) | (GDScriptFunction::ADDR_TYPE_CONSTANT << GDScriptFunction::ADDR_BITS));
append(key_type.builtin_type);
append(key_type.native_type);
append(value_type.builtin_type);
append(value_type.native_type);
} else if (function->return_type.kind == GDScriptDataType::BUILTIN && p_return_value.type.kind == GDScriptDataType::BUILTIN && function->return_type.builtin_type != p_return_value.type.builtin_type) {
// Add conversion.
append_opcode(GDScriptFunction::OPCODE_RETURN_TYPED_BUILTIN);
@ -1735,6 +1818,17 @@ void GDScriptByteCodeGenerator::write_return(const Address &p_return_value) {
append(get_constant_pos(element_type.script_type) | (GDScriptFunction::ADDR_TYPE_CONSTANT << GDScriptFunction::ADDR_BITS));
append(element_type.builtin_type);
append(element_type.native_type);
} else if (function->return_type.builtin_type == Variant::DICTIONARY && function->return_type.has_container_element_types()) {
const GDScriptDataType &key_type = function->return_type.get_container_element_type_or_variant(0);
const GDScriptDataType &value_type = function->return_type.get_container_element_type_or_variant(1);
append_opcode(GDScriptFunction::OPCODE_RETURN_TYPED_DICTIONARY);
append(p_return_value);
append(get_constant_pos(key_type.script_type) | (GDScriptFunction::ADDR_TYPE_CONSTANT << GDScriptFunction::ADDR_BITS));
append(get_constant_pos(value_type.script_type) | (GDScriptFunction::ADDR_TYPE_CONSTANT << GDScriptFunction::ADDR_BITS));
append(key_type.builtin_type);
append(key_type.native_type);
append(value_type.builtin_type);
append(value_type.native_type);
} else {
append_opcode(GDScriptFunction::OPCODE_RETURN_TYPED_BUILTIN);
append(p_return_value);
@ -1785,7 +1879,7 @@ void GDScriptByteCodeGenerator::end_block() {
void GDScriptByteCodeGenerator::clear_temporaries() {
for (int slot_idx : temporaries_pending_clear) {
// The temporary may have been re-used as something else since it was added to the list.
// The temporary may have been reused as something else since it was added to the list.
// In that case, there's **no** need to clear it.
if (temporaries[slot_idx].can_contain_object) {
clear_address(Address(Address::TEMPORARY, slot_idx)); // Can contain `RefCounted`, so clear it.
@ -1803,6 +1897,13 @@ void GDScriptByteCodeGenerator::clear_address(const Address &p_address) {
case Variant::BOOL:
write_assign_false(p_address);
break;
case Variant::DICTIONARY:
if (p_address.type.has_container_element_types()) {
write_construct_typed_dictionary(p_address, p_address.type.get_container_element_type_or_variant(0), p_address.type.get_container_element_type_or_variant(1), Vector<GDScriptCodeGenerator::Address>());
} else {
write_construct(p_address, p_address.type.builtin_type, Vector<GDScriptCodeGenerator::Address>());
}
break;
case Variant::ARRAY:
if (p_address.type.has_container_element_type(0)) {
write_construct_typed_array(p_address, p_address.type.get_container_element_type(0), Vector<GDScriptCodeGenerator::Address>());
@ -1827,7 +1928,7 @@ void GDScriptByteCodeGenerator::clear_address(const Address &p_address) {
}
}
// Returns `true` if the local has been re-used and not cleaned up with `clear_address()`.
// Returns `true` if the local has been reused and not cleaned up with `clear_address()`.
bool GDScriptByteCodeGenerator::is_local_dirty(const Address &p_address) const {
ERR_FAIL_COND_V(p_address.mode != Address::LOCAL_VARIABLE, false);
return dirty_locals.has(p_address.address);

View file

@ -529,6 +529,7 @@ public:
virtual void write_construct_array(const Address &p_target, const Vector<Address> &p_arguments) override;
virtual void write_construct_typed_array(const Address &p_target, const GDScriptDataType &p_element_type, const Vector<Address> &p_arguments) override;
virtual void write_construct_dictionary(const Address &p_target, const Vector<Address> &p_arguments) override;
virtual void write_construct_typed_dictionary(const Address &p_target, const GDScriptDataType &p_key_type, const GDScriptDataType &p_value_type, const Vector<Address> &p_arguments) override;
virtual void write_await(const Address &p_target, const Address &p_operand) override;
virtual void write_if(const Address &p_condition) override;
virtual void write_else() override;

View file

@ -144,6 +144,14 @@ GDScriptParserRef::~GDScriptParserRef() {
GDScriptCache *GDScriptCache::singleton = nullptr;
SafeBinaryMutex<GDScriptCache::BINARY_MUTEX_TAG> &_get_gdscript_cache_mutex() {
return GDScriptCache::mutex;
}
template <>
thread_local SafeBinaryMutex<GDScriptCache::BINARY_MUTEX_TAG>::TLSData SafeBinaryMutex<GDScriptCache::BINARY_MUTEX_TAG>::tls_data(_get_gdscript_cache_mutex());
SafeBinaryMutex<GDScriptCache::BINARY_MUTEX_TAG> GDScriptCache::mutex;
void GDScriptCache::move_script(const String &p_from, const String &p_to) {
if (singleton == nullptr || p_from == p_to) {
return;
@ -267,7 +275,7 @@ String GDScriptCache::get_source_code(const String &p_path) {
source_file.write[len] = 0;
String source;
if (source.parse_utf8((const char *)source_file.ptr()) != OK) {
if (source.parse_utf8((const char *)source_file.ptr(), len) != OK) {
ERR_FAIL_V_MSG("", "Script '" + p_path + "' contains invalid unicode (UTF-8), so it was not loaded. Please ensure that scripts are saved in valid UTF-8 unicode.");
}
return source;
@ -289,6 +297,7 @@ Vector<uint8_t> GDScriptCache::get_binary_tokens(const String &p_path) {
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);
}
@ -299,7 +308,7 @@ Ref<GDScript> GDScriptCache::get_shallow_script(const String &p_path, Error &r_e
return singleton->shallow_gdscript_cache[p_path];
}
String remapped_path = ResourceLoader::path_remap(p_path);
const String remapped_path = ResourceLoader::path_remap(p_path);
Ref<GDScript> script;
script.instantiate();
@ -324,6 +333,7 @@ Ref<GDScript> GDScriptCache::get_shallow_script(const String &p_path, Error &r_e
}
singleton->shallow_gdscript_cache[p_path] = script;
return script;
}
@ -351,16 +361,18 @@ Ref<GDScript> GDScriptCache::get_full_script(const String &p_path, Error &r_erro
}
}
const String remapped_path = ResourceLoader::path_remap(p_path);
if (p_update_from_disk) {
if (p_path.get_extension().to_lower() == "gdc") {
Vector<uint8_t> buffer = get_binary_tokens(p_path);
if (remapped_path.get_extension().to_lower() == "gdc") {
Vector<uint8_t> buffer = get_binary_tokens(remapped_path);
if (buffer.is_empty()) {
r_error = ERR_FILE_CANT_READ;
return script;
}
script->set_binary_tokens_source(buffer);
} else {
r_error = script->load_source_code(p_path);
r_error = script->load_source_code(remapped_path);
if (r_error) {
return script;
}
@ -369,7 +381,7 @@ Ref<GDScript> GDScriptCache::get_full_script(const String &p_path, Error &r_erro
// Allowing lifting the lock might cause a script to be reloaded multiple times,
// which, as a last resort deadlock prevention strategy, is a good tradeoff.
uint32_t allowance_id = WorkerThreadPool::thread_enter_unlock_allowance_zone(&singleton->mutex);
uint32_t allowance_id = WorkerThreadPool::thread_enter_unlock_allowance_zone(singleton->mutex);
r_error = script->reload(true);
WorkerThreadPool::thread_exit_unlock_allowance_zone(allowance_id);
if (r_error) {

View file

@ -34,7 +34,7 @@
#include "gdscript.h"
#include "core/object/ref_counted.h"
#include "core/os/mutex.h"
#include "core/os/safe_binary_mutex.h"
#include "core/templates/hash_map.h"
#include "core/templates/hash_set.h"
@ -95,7 +95,12 @@ class GDScriptCache {
bool cleared = false;
Mutex mutex;
public:
static const int BINARY_MUTEX_TAG = 2;
private:
static SafeBinaryMutex<BINARY_MUTEX_TAG> mutex;
friend SafeBinaryMutex<BINARY_MUTEX_TAG> &_get_gdscript_cache_mutex();
public:
static void move_script(const String &p_from, const String &p_to);

View file

@ -142,6 +142,7 @@ public:
virtual void write_construct_array(const Address &p_target, const Vector<Address> &p_arguments) = 0;
virtual void write_construct_typed_array(const Address &p_target, const GDScriptDataType &p_element_type, const Vector<Address> &p_arguments) = 0;
virtual void write_construct_dictionary(const Address &p_target, const Vector<Address> &p_arguments) = 0;
virtual void write_construct_typed_dictionary(const Address &p_target, const GDScriptDataType &p_key_type, const GDScriptDataType &p_value_type, const Vector<Address> &p_arguments) = 0;
virtual void write_await(const Address &p_target, const Address &p_operand) = 0;
virtual void write_if(const Address &p_condition) = 0;
virtual void write_else() = 0;

View file

@ -120,8 +120,8 @@ GDScriptDataType GDScriptCompiler::_gdtype_from_datatype(const GDScriptParser::D
#ifdef DEBUG_ENABLED
if (unlikely(!GDScriptLanguage::get_singleton()->get_global_map().has(result.native_type))) {
ERR_PRINT(vformat(R"(GDScript bug: Native class "%s" not found.)", result.native_type));
result.native_type = Object::get_class_static();
_set_error(vformat(R"(GDScript bug (please report): Native class "%s" not found.)", result.native_type), nullptr);
return GDScriptDataType();
}
#endif
} break;
@ -161,6 +161,7 @@ GDScriptDataType GDScriptCompiler::_gdtype_from_datatype(const GDScriptParser::D
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);
return GDScriptDataType();
}
}
@ -193,7 +194,7 @@ GDScriptDataType GDScriptCompiler::_gdtype_from_datatype(const GDScriptParser::D
break;
case GDScriptParser::DataType::RESOLVING:
case GDScriptParser::DataType::UNRESOLVED: {
ERR_PRINT("Parser bug: converting unresolved type.");
_set_error("Parser bug (please report): converting unresolved type.", nullptr);
return GDScriptDataType();
}
}
@ -491,7 +492,7 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &code
case GDScriptParser::Node::SELF: {
//return constant
if (codegen.function_node && codegen.function_node->is_static) {
_set_error("'self' not present in static function!", p_expression);
_set_error("'self' not present in static function.", p_expression);
r_error = ERR_COMPILATION_FAILED;
return GDScriptCodeGenerator::Address();
}
@ -532,10 +533,7 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &code
Vector<GDScriptCodeGenerator::Address> elements;
// Create the result temporary first since it's the last to be killed.
GDScriptDataType dict_type;
dict_type.has_type = true;
dict_type.kind = GDScriptDataType::BUILTIN;
dict_type.builtin_type = Variant::DICTIONARY;
GDScriptDataType dict_type = _gdtype_from_datatype(dn->get_datatype(), codegen.script);
GDScriptCodeGenerator::Address result = codegen.add_temporary(dict_type);
for (int i = 0; i < dn->elements.size(); i++) {
@ -566,7 +564,11 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &code
elements.push_back(element);
}
gen->write_construct_dictionary(result, elements);
if (dict_type.has_container_element_types()) {
gen->write_construct_typed_dictionary(result, dict_type.get_container_element_type_or_variant(0), dict_type.get_container_element_type_or_variant(1), elements);
} else {
gen->write_construct_dictionary(result, elements);
}
for (int i = 0; i < elements.size(); i++) {
if (elements[i].mode == GDScriptCodeGenerator::Address::TEMPORARY) {
@ -725,6 +727,7 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &code
return GDScriptCodeGenerator::Address();
}
} else {
_set_error("Compiler bug (please report): incorrect callee type in call node.", call->callee);
r_error = ERR_COMPILATION_FAILED;
return GDScriptCodeGenerator::Address();
}
@ -1275,7 +1278,11 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &code
}
} else {
// Regular assignment.
ERR_FAIL_COND_V_MSG(assignment->assignee->type != GDScriptParser::Node::IDENTIFIER, GDScriptCodeGenerator::Address(), "Expected the assignee to be an identifier here.");
if (assignment->assignee->type != GDScriptParser::Node::IDENTIFIER) {
_set_error("Compiler bug (please report): Expected the assignee to be an identifier here.", assignment->assignee);
r_error = ERR_COMPILATION_FAILED;
return GDScriptCodeGenerator::Address();
}
GDScriptCodeGenerator::Address member;
bool is_member = false;
bool has_setter = false;
@ -1416,7 +1423,9 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &code
return result;
} break;
default: {
ERR_FAIL_V_MSG(GDScriptCodeGenerator::Address(), "Bug in bytecode compiler, unexpected node in parse tree while parsing expression."); // Unreachable code.
_set_error("Compiler bug (please report): Unexpected node in parse tree while parsing expression.", p_expression); // Unreachable code.
r_error = ERR_COMPILATION_FAILED;
return GDScriptCodeGenerator::Address();
} break;
}
}
@ -1853,7 +1862,10 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_match_pattern(CodeGen &c
}
return p_previous_test;
}
ERR_FAIL_V_MSG(p_previous_test, "Reaching the end of pattern compilation without matching a pattern.");
_set_error("Compiler bug (please report): Reaching the end of pattern compilation without matching a pattern.", p_pattern);
r_error = ERR_COMPILATION_FAILED;
return p_previous_test;
}
List<GDScriptCodeGenerator::Address> GDScriptCompiler::_add_block_locals(CodeGen &codegen, const GDScriptParser::SuiteNode *p_block) {
@ -2187,7 +2199,7 @@ Error GDScriptCompiler::_parse_block(CodeGen &codegen, const GDScriptParser::Sui
initialized = true;
} else if ((local_type.has_type && local_type.kind == GDScriptDataType::BUILTIN) || codegen.generator->is_local_dirty(local)) {
// Initialize with default for the type. Built-in types must always be cleared (they cannot be `null`).
// Objects and untyped variables are assigned to `null` only if the stack address has been re-used and not cleared.
// Objects and untyped variables are assigned to `null` only if the stack address has been reused and not cleared.
codegen.generator->clear_address(local);
initialized = true;
}
@ -2221,7 +2233,8 @@ Error GDScriptCompiler::_parse_block(CodeGen &codegen, const GDScriptParser::Sui
codegen.generator->pop_temporary();
}
} else {
ERR_FAIL_V_MSG(ERR_INVALID_DATA, "Bug in bytecode compiler, unexpected node in parse tree while parsing statement."); // Unreachable code.
_set_error("Compiler bug (please report): unexpected node in parse tree while parsing statement.", s); // Unreachable code.
return ERR_INVALID_DATA;
}
} break;
}
@ -2265,7 +2278,7 @@ GDScriptFunction *GDScriptCompiler::_parse_function(Error &r_error, GDScript *p_
return_type = _gdtype_from_datatype(p_func->get_datatype(), p_script);
} else {
if (p_for_ready) {
func_name = SceneStringName(_ready);
func_name = "@implicit_ready";
} else {
func_name = "@implicit_new";
}
@ -2325,8 +2338,11 @@ GDScriptFunction *GDScriptCompiler::_parse_function(Error &r_error, GDScript *p_
GDScriptCodeGenerator::Address dst_address(GDScriptCodeGenerator::Address::MEMBER, codegen.script->member_indices[field->identifier->name].index, field_type);
if (field_type.has_container_element_type(0)) {
if (field_type.builtin_type == Variant::ARRAY && field_type.has_container_element_type(0)) {
codegen.generator->write_construct_typed_array(dst_address, field_type.get_container_element_type(0), Vector<GDScriptCodeGenerator::Address>());
} else if (field_type.builtin_type == Variant::DICTIONARY && field_type.has_container_element_types()) {
codegen.generator->write_construct_typed_dictionary(dst_address, field_type.get_container_element_type_or_variant(0),
field_type.get_container_element_type_or_variant(1), Vector<GDScriptCodeGenerator::Address>());
} else if (field_type.kind == GDScriptDataType::BUILTIN) {
codegen.generator->write_construct(dst_address, field_type.builtin_type, Vector<GDScriptCodeGenerator::Address>());
}
@ -2347,7 +2363,7 @@ GDScriptFunction *GDScriptCompiler::_parse_function(Error &r_error, GDScript *p_
}
if (field->onready != is_implicit_ready) {
// Only initialize in @implicit_ready.
// Only initialize in `@implicit_ready()`.
continue;
}
@ -2515,11 +2531,17 @@ GDScriptFunction *GDScriptCompiler::_make_static_initializer(Error &r_error, GDS
if (field_type.has_type) {
codegen.generator->write_newline(field->start_line);
if (field_type.has_container_element_type(0)) {
if (field_type.builtin_type == Variant::ARRAY && field_type.has_container_element_type(0)) {
GDScriptCodeGenerator::Address temp = codegen.add_temporary(field_type);
codegen.generator->write_construct_typed_array(temp, field_type.get_container_element_type(0), Vector<GDScriptCodeGenerator::Address>());
codegen.generator->write_set_static_variable(temp, class_addr, p_script->static_variables_indices[field->identifier->name].index);
codegen.generator->pop_temporary();
} else if (field_type.builtin_type == Variant::DICTIONARY && field_type.has_container_element_types()) {
GDScriptCodeGenerator::Address temp = codegen.add_temporary(field_type);
codegen.generator->write_construct_typed_dictionary(temp, field_type.get_container_element_type_or_variant(0),
field_type.get_container_element_type_or_variant(1), Vector<GDScriptCodeGenerator::Address>());
codegen.generator->write_set_static_variable(temp, class_addr, p_script->static_variables_indices[field->identifier->name].index);
codegen.generator->pop_temporary();
} else if (field_type.kind == GDScriptDataType::BUILTIN) {
GDScriptCodeGenerator::Address temp = codegen.add_temporary(field_type);
codegen.generator->write_construct(temp, field_type.builtin_type, Vector<GDScriptCodeGenerator::Address>());
@ -2619,9 +2641,10 @@ Error GDScriptCompiler::_parse_setter_getter(GDScript *p_script, const GDScriptP
return err;
}
// Prepares given script, and inner class scripts, for compilation. It populates class members and initializes method
// RPC info for its base classes first, then for itself, then for inner classes.
// Warning: this function cannot initiate compilation of other classes, or it will result in cyclic dependency issues.
// Prepares given script, and inner class scripts, for compilation. It populates class members and
// initializes method RPC info for its base classes first, then for itself, then for inner classes.
// WARNING: This function cannot initiate compilation of other classes, or it will result in
// cyclic dependency issues.
Error GDScriptCompiler::_prepare_compilation(GDScript *p_script, const GDScriptParser::ClassNode *p_class, bool p_keep_state) {
if (parsed_classes.has(p_script)) {
return OK;
@ -2696,12 +2719,18 @@ Error GDScriptCompiler::_prepare_compilation(GDScript *p_script, const GDScriptP
GDScriptDataType base_type = _gdtype_from_datatype(p_class->base_type, p_script, false);
ERR_FAIL_COND_V_MSG(base_type.native_type == StringName(), ERR_BUG, vformat(R"(Failed to get base class for "%s")", p_script->path));
if (base_type.native_type == StringName()) {
_set_error(vformat(R"(Parser bug (please report): Empty native type in base class "%s")", p_script->path), p_class);
return ERR_BUG;
}
int native_idx = GDScriptLanguage::get_singleton()->get_global_map()[base_type.native_type];
p_script->native = GDScriptLanguage::get_singleton()->get_global_array()[native_idx];
ERR_FAIL_COND_V(p_script->native.is_null(), ERR_BUG);
if (p_script->native.is_null()) {
_set_error("Compiler bug (please report): script native type is null.", nullptr);
return ERR_BUG;
}
// Inheritance
switch (base_type.kind) {
@ -2711,7 +2740,8 @@ Error GDScriptCompiler::_prepare_compilation(GDScript *p_script, const GDScriptP
case GDScriptDataType::GDSCRIPT: {
Ref<GDScript> base = Ref<GDScript>(base_type.script_type);
if (base.is_null()) {
return ERR_COMPILATION_FAILED;
_set_error("Compiler bug (please report): base script type is null.", nullptr);
return ERR_BUG;
}
if (main_script->has_class(base.ptr())) {
@ -2746,7 +2776,7 @@ Error GDScriptCompiler::_prepare_compilation(GDScript *p_script, const GDScriptP
p_script->member_indices = base->member_indices;
} break;
default: {
_set_error("Parser bug: invalid inheritance.", nullptr);
_set_error("Parser bug (please report): invalid inheritance.", nullptr);
return ERR_BUG;
} break;
}
@ -2942,7 +2972,7 @@ Error GDScriptCompiler::_compile_class(GDScript *p_script, const GDScriptParser:
}
{
// Create an implicit constructor in any case.
// Create `@implicit_new()` special function in any case.
Error err = OK;
_parse_function(err, p_script, p_class, nullptr);
if (err) {
@ -2951,7 +2981,7 @@ Error GDScriptCompiler::_compile_class(GDScript *p_script, const GDScriptParser:
}
if (p_class->onready_used) {
// Create an implicit_ready constructor.
// Create `@implicit_ready()` special function.
Error err = OK;
_parse_function(err, p_script, p_class, nullptr, true);
if (err) {
@ -3048,9 +3078,9 @@ void GDScriptCompiler::convert_to_initializer_type(Variant &p_variant, const GDS
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;
const 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);
Variant::construct(member_t.builtin_type, p_variant, &v, 1, ce);
}
}
}
@ -3259,7 +3289,11 @@ Error GDScriptCompiler::compile(const GDScriptParser *p_parser, GDScript *p_scri
GDScriptCache::add_static_script(p_script);
}
return GDScriptCache::finish_compiling(main_script->path);
err = GDScriptCache::finish_compiling(main_script->path);
if (err) {
_set_error(R"(Failed to compile depended scripts.)", nullptr);
}
return err;
}
String GDScriptCompiler::get_error() const {

View file

@ -176,6 +176,47 @@ void GDScriptFunction::disassemble(const Vector<String> &p_code_lines) const {
incr += 6;
} break;
case OPCODE_TYPE_TEST_DICTIONARY: {
text += "type test ";
text += DADDR(1);
text += " = ";
text += DADDR(2);
text += " is Dictionary[";
Ref<Script> key_script_type = get_constant(_code_ptr[ip + 3] & ADDR_MASK);
Variant::Type key_builtin_type = (Variant::Type)_code_ptr[ip + 5];
StringName key_native_type = get_global_name(_code_ptr[ip + 6]);
if (key_script_type.is_valid() && key_script_type->is_valid()) {
text += "script(";
text += GDScript::debug_get_script_name(key_script_type);
text += ")";
} else if (key_native_type != StringName()) {
text += key_native_type;
} else {
text += Variant::get_type_name(key_builtin_type);
}
text += ", ";
Ref<Script> value_script_type = get_constant(_code_ptr[ip + 4] & ADDR_MASK);
Variant::Type value_builtin_type = (Variant::Type)_code_ptr[ip + 7];
StringName value_native_type = get_global_name(_code_ptr[ip + 8]);
if (value_script_type.is_valid() && value_script_type->is_valid()) {
text += "script(";
text += GDScript::debug_get_script_name(value_script_type);
text += ")";
} else if (value_native_type != StringName()) {
text += value_native_type;
} else {
text += Variant::get_type_name(value_builtin_type);
}
text += "]";
incr += 9;
} break;
case OPCODE_TYPE_TEST_NATIVE: {
text += "type test ";
text += DADDR(1);
@ -321,7 +362,12 @@ void GDScriptFunction::disassemble(const Vector<String> &p_code_lines) const {
incr += 3;
} break;
case OPCODE_SET_STATIC_VARIABLE: {
Ref<GDScript> gdscript = get_constant(_code_ptr[ip + 2] & ADDR_MASK);
Ref<GDScript> gdscript;
if (_code_ptr[ip + 2] == ADDR_CLASS) {
gdscript = Ref<GDScript>(_script);
} else {
gdscript = get_constant(_code_ptr[ip + 2] & ADDR_MASK);
}
text += "set_static_variable script(";
text += GDScript::debug_get_script_name(gdscript);
@ -337,7 +383,12 @@ void GDScriptFunction::disassemble(const Vector<String> &p_code_lines) const {
incr += 4;
} break;
case OPCODE_GET_STATIC_VARIABLE: {
Ref<GDScript> gdscript = get_constant(_code_ptr[ip + 2] & ADDR_MASK);
Ref<GDScript> gdscript;
if (_code_ptr[ip + 2] == ADDR_CLASS) {
gdscript = Ref<GDScript>(_script);
} else {
gdscript = get_constant(_code_ptr[ip + 2] & ADDR_MASK);
}
text += "get_static_variable ";
text += DADDR(1);
@ -399,6 +450,14 @@ void GDScriptFunction::disassemble(const Vector<String> &p_code_lines) const {
incr += 6;
} break;
case OPCODE_ASSIGN_TYPED_DICTIONARY: {
text += "assign typed dictionary ";
text += DADDR(1);
text += " = ";
text += DADDR(2);
incr += 9;
} break;
case OPCODE_ASSIGN_TYPED_NATIVE: {
text += "assign typed native (";
text += DADDR(3);
@ -564,6 +623,58 @@ void GDScriptFunction::disassemble(const Vector<String> &p_code_lines) const {
incr += 3 + argc * 2;
} break;
case OPCODE_CONSTRUCT_TYPED_DICTIONARY: {
int instr_var_args = _code_ptr[++ip];
int argc = _code_ptr[ip + 1 + instr_var_args];
Ref<Script> key_script_type = get_constant(_code_ptr[ip + argc * 2 + 2] & ADDR_MASK);
Variant::Type key_builtin_type = (Variant::Type)_code_ptr[ip + argc * 2 + 5];
StringName key_native_type = get_global_name(_code_ptr[ip + argc * 2 + 6]);
String key_type_name;
if (key_script_type.is_valid() && key_script_type->is_valid()) {
key_type_name = "script(" + GDScript::debug_get_script_name(key_script_type) + ")";
} else if (key_native_type != StringName()) {
key_type_name = key_native_type;
} else {
key_type_name = Variant::get_type_name(key_builtin_type);
}
Ref<Script> value_script_type = get_constant(_code_ptr[ip + argc * 2 + 3] & ADDR_MASK);
Variant::Type value_builtin_type = (Variant::Type)_code_ptr[ip + argc * 2 + 7];
StringName value_native_type = get_global_name(_code_ptr[ip + argc * 2 + 8]);
String value_type_name;
if (value_script_type.is_valid() && value_script_type->is_valid()) {
value_type_name = "script(" + GDScript::debug_get_script_name(value_script_type) + ")";
} else if (value_native_type != StringName()) {
value_type_name = value_native_type;
} else {
value_type_name = Variant::get_type_name(value_builtin_type);
}
text += "make_typed_dict (";
text += key_type_name;
text += ", ";
text += value_type_name;
text += ") ";
text += DADDR(1 + argc * 2);
text += " = {";
for (int i = 0; i < argc; i++) {
if (i > 0) {
text += ", ";
}
text += DADDR(1 + i * 2 + 0);
text += ": ";
text += DADDR(1 + i * 2 + 1);
}
text += "}";
incr += 9 + argc * 2;
} break;
case OPCODE_CALL:
case OPCODE_CALL_RETURN:
case OPCODE_CALL_ASYNC: {
@ -689,8 +800,9 @@ void GDScriptFunction::disassemble(const Vector<String> &p_code_lines) const {
text += method->get_name();
text += "(";
for (int i = 0; i < argc; i++) {
if (i > 0)
if (i > 0) {
text += ", ";
}
text += DADDR(1 + i);
}
text += ")";
@ -732,8 +844,9 @@ void GDScriptFunction::disassemble(const Vector<String> &p_code_lines) const {
text += method->get_name();
text += "(";
for (int i = 0; i < argc; i++) {
if (i > 0)
if (i > 0) {
text += ", ";
}
text += DADDR(1 + i);
}
text += ")";
@ -978,6 +1091,12 @@ void GDScriptFunction::disassemble(const Vector<String> &p_code_lines) const {
incr += 5;
} break;
case OPCODE_RETURN_TYPED_DICTIONARY: {
text += "return typed dictionary ";
text += DADDR(1);
incr += 8;
} break;
case OPCODE_RETURN_TYPED_NATIVE: {
text += "return typed native (";
text += DADDR(2);

File diff suppressed because it is too large Load diff

View file

@ -93,6 +93,41 @@ public:
} else {
valid = false;
}
} else if (valid && builtin_type == Variant::DICTIONARY && has_container_element_types()) {
Dictionary dictionary = p_variant;
if (dictionary.is_typed()) {
if (dictionary.is_typed_key()) {
GDScriptDataType key = get_container_element_type_or_variant(0);
Variant::Type key_builtin_type = (Variant::Type)dictionary.get_typed_key_builtin();
StringName key_native_type = dictionary.get_typed_key_class_name();
Ref<Script> key_script_type_ref = dictionary.get_typed_key_script();
if (key_script_type_ref.is_valid()) {
valid = (key.kind == SCRIPT || key.kind == GDSCRIPT) && key.script_type == key_script_type_ref.ptr();
} else if (key_native_type != StringName()) {
valid = key.kind == NATIVE && key.native_type == key_native_type;
} else {
valid = key.kind == BUILTIN && key.builtin_type == key_builtin_type;
}
}
if (valid && dictionary.is_typed_value()) {
GDScriptDataType value = get_container_element_type_or_variant(1);
Variant::Type value_builtin_type = (Variant::Type)dictionary.get_typed_value_builtin();
StringName value_native_type = dictionary.get_typed_value_class_name();
Ref<Script> value_script_type_ref = dictionary.get_typed_value_script();
if (value_script_type_ref.is_valid()) {
valid = (value.kind == SCRIPT || value.kind == GDSCRIPT) && value.script_type == value_script_type_ref.ptr();
} else if (value_native_type != StringName()) {
valid = value.kind == NATIVE && value.native_type == value_native_type;
} else {
valid = value.kind == BUILTIN && value.builtin_type == value_builtin_type;
}
}
} else {
valid = false;
}
} else if (!valid && p_allow_implicit_conversion) {
valid = Variant::can_convert_strict(var_type, builtin_type);
}
@ -156,6 +191,10 @@ public:
}
return true;
case Variant::DICTIONARY:
if (has_container_element_types()) {
return get_container_element_type_or_variant(0).can_contain_object() || get_container_element_type_or_variant(1).can_contain_object();
}
return true;
case Variant::NIL:
case Variant::OBJECT:
return true;
@ -220,6 +259,7 @@ public:
OPCODE_OPERATOR_VALIDATED,
OPCODE_TYPE_TEST_BUILTIN,
OPCODE_TYPE_TEST_ARRAY,
OPCODE_TYPE_TEST_DICTIONARY,
OPCODE_TYPE_TEST_NATIVE,
OPCODE_TYPE_TEST_SCRIPT,
OPCODE_SET_KEYED,
@ -242,6 +282,7 @@ public:
OPCODE_ASSIGN_FALSE,
OPCODE_ASSIGN_TYPED_BUILTIN,
OPCODE_ASSIGN_TYPED_ARRAY,
OPCODE_ASSIGN_TYPED_DICTIONARY,
OPCODE_ASSIGN_TYPED_NATIVE,
OPCODE_ASSIGN_TYPED_SCRIPT,
OPCODE_CAST_TO_BUILTIN,
@ -252,6 +293,7 @@ public:
OPCODE_CONSTRUCT_ARRAY,
OPCODE_CONSTRUCT_TYPED_ARRAY,
OPCODE_CONSTRUCT_DICTIONARY,
OPCODE_CONSTRUCT_TYPED_DICTIONARY,
OPCODE_CALL,
OPCODE_CALL_RETURN,
OPCODE_CALL_ASYNC,
@ -280,6 +322,7 @@ public:
OPCODE_RETURN,
OPCODE_RETURN_TYPED_BUILTIN,
OPCODE_RETURN_TYPED_ARRAY,
OPCODE_RETURN_TYPED_DICTIONARY,
OPCODE_RETURN_TYPED_NATIVE,
OPCODE_RETURN_TYPED_SCRIPT,
OPCODE_ITERATE_BEGIN,
@ -509,7 +552,7 @@ private:
} profile;
#endif
_FORCE_INLINE_ String _get_call_error(const Callable::CallError &p_err, const String &p_where, const Variant **argptrs) const;
_FORCE_INLINE_ String _get_call_error(const String &p_where, const Variant **p_argptrs, const Variant &p_ret, const Callable::CallError &p_err) const;
Variant _get_default_variant_for_data_type(const GDScriptDataType &p_data_type);
public:

View file

@ -97,25 +97,25 @@ void GDScriptLambdaCallable::call(const Variant **p_arguments, int p_argcount, V
}
if (captures_amount > 0) {
Vector<const Variant *> args;
args.resize(p_argcount + captures_amount);
const int total_argcount = p_argcount + captures_amount;
const Variant **args = (const Variant **)alloca(sizeof(Variant *) * total_argcount);
for (int i = 0; i < captures_amount; i++) {
args.write[i] = &captures[i];
args[i] = &captures[i];
if (captures[i].get_type() == Variant::OBJECT) {
bool was_freed = false;
captures[i].get_validated_object_with_check(was_freed);
if (was_freed) {
ERR_PRINT(vformat(R"(Lambda capture at index %d was freed. Passed "null" instead.)", i));
static Variant nil;
args.write[i] = &nil;
args[i] = &nil;
}
}
}
for (int i = 0; i < p_argcount; i++) {
args.write[i + captures_amount] = p_arguments[i];
args[i + captures_amount] = p_arguments[i];
}
r_return_value = function->call(nullptr, args.ptrw(), args.size(), r_call_error);
r_return_value = function->call(nullptr, args, total_argcount, r_call_error);
switch (r_call_error.error) {
case Callable::CallError::CALL_ERROR_INVALID_ARGUMENT:
r_call_error.argument -= captures_amount;
@ -150,7 +150,7 @@ void GDScriptLambdaCallable::call(const Variant **p_arguments, int p_argcount, V
GDScriptLambdaCallable::GDScriptLambdaCallable(Ref<GDScript> p_script, GDScriptFunction *p_function, const Vector<Variant> &p_captures) :
function(p_function) {
ERR_FAIL_NULL(p_script.ptr());
ERR_FAIL_COND(p_script.is_null());
ERR_FAIL_NULL(p_function);
script = p_script;
captures = p_captures;
@ -178,12 +178,12 @@ uint32_t GDScriptLambdaSelfCallable::hash() const {
String GDScriptLambdaSelfCallable::get_as_text() const {
if (function == nullptr) {
return "<invalid lambda>";
return "<invalid self lambda>";
}
if (function->get_name() != StringName()) {
return function->get_name().operator String() + "(lambda)";
return function->get_name().operator String() + "(self lambda)";
}
return "(anonymous lambda)";
return "(anonymous self lambda)";
}
CallableCustom::CompareEqualFunc GDScriptLambdaSelfCallable::get_compare_equal_func() const {
@ -229,25 +229,25 @@ void GDScriptLambdaSelfCallable::call(const Variant **p_arguments, int p_argcoun
}
if (captures_amount > 0) {
Vector<const Variant *> args;
args.resize(p_argcount + captures_amount);
const int total_argcount = p_argcount + captures_amount;
const Variant **args = (const Variant **)alloca(sizeof(Variant *) * total_argcount);
for (int i = 0; i < captures_amount; i++) {
args.write[i] = &captures[i];
args[i] = &captures[i];
if (captures[i].get_type() == Variant::OBJECT) {
bool was_freed = false;
captures[i].get_validated_object_with_check(was_freed);
if (was_freed) {
ERR_PRINT(vformat(R"(Lambda capture at index %d was freed. Passed "null" instead.)", i));
static Variant nil;
args.write[i] = &nil;
args[i] = &nil;
}
}
}
for (int i = 0; i < p_argcount; i++) {
args.write[i + captures_amount] = p_arguments[i];
args[i + captures_amount] = p_arguments[i];
}
r_return_value = function->call(static_cast<GDScriptInstance *>(object->get_script_instance()), args.ptrw(), args.size(), r_call_error);
r_return_value = function->call(static_cast<GDScriptInstance *>(object->get_script_instance()), args, total_argcount, r_call_error);
switch (r_call_error.error) {
case Callable::CallError::CALL_ERROR_INVALID_ARGUMENT:
r_call_error.argument -= captures_amount;
@ -282,7 +282,7 @@ void GDScriptLambdaSelfCallable::call(const Variant **p_arguments, int p_argcoun
GDScriptLambdaSelfCallable::GDScriptLambdaSelfCallable(Ref<RefCounted> p_self, GDScriptFunction *p_function, const Vector<Variant> &p_captures) :
function(p_function) {
ERR_FAIL_NULL(p_self.ptr());
ERR_FAIL_COND(p_self.is_null());
ERR_FAIL_NULL(p_function);
reference = p_self;
object = p_self.ptr();

File diff suppressed because it is too large Load diff

View file

@ -45,7 +45,6 @@
#include "core/string/ustring.h"
#include "core/templates/hash_map.h"
#include "core/templates/list.h"
#include "core/templates/rb_map.h"
#include "core/templates/vector.h"
#include "core/variant/variant.h"
@ -165,6 +164,10 @@ public:
container_element_types.write[p_index] = DataType(p_type);
}
_FORCE_INLINE_ int get_container_element_type_count() const {
return container_element_types.size();
}
_FORCE_INLINE_ DataType get_container_element_type(int p_index) const {
ERR_FAIL_INDEX_V(p_index, container_element_types.size(), get_variant_type());
return container_element_types[p_index];
@ -189,6 +192,8 @@ public:
GDScriptParser::DataType get_typed_container_type() const;
bool can_reference(const DataType &p_other) const;
bool operator==(const DataType &p_other) const {
if (type_source == UNDETECTED || p_other.type_source == UNDETECTED) {
return true; // Can be considered equal for parsing purposes.
@ -367,6 +372,7 @@ public:
Vector<ExpressionNode *> arguments;
Vector<Variant> resolved_arguments;
/** Information of the annotation. Might be null for unknown annotations. */
AnnotationInfo *info = nullptr;
PropertyInfo export_info;
bool is_resolved = false;
@ -856,6 +862,7 @@ public:
Vector<Variant> default_arg_values;
#ifdef TOOLS_ENABLED
MemberDocData doc_data;
int min_local_doc_line = 0;
#endif // TOOLS_ENABLED
bool resolved_signature = false;
@ -1352,6 +1359,7 @@ private:
List<GDScriptWarning> warnings;
List<PendingWarning> pending_warnings;
HashSet<int> warning_ignored_lines[GDScriptWarning::WARNING_MAX];
int warning_ignore_start_lines[GDScriptWarning::WARNING_MAX];
HashSet<int> unsafe_lines;
#endif
@ -1371,7 +1379,7 @@ private:
bool in_lambda = false;
bool lambda_ended = false; // Marker for when a lambda ends, to apply an end of statement if needed.
typedef bool (GDScriptParser::*AnnotationAction)(const AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class);
typedef bool (GDScriptParser::*AnnotationAction)(AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class);
struct AnnotationInfo {
enum TargetKind {
NONE = 0,
@ -1455,9 +1463,14 @@ private:
}
void apply_pending_warnings();
#endif
void make_completion_context(CompletionType p_type, Node *p_node, int p_argument = -1, bool p_force = false);
void make_completion_context(CompletionType p_type, Variant::Type p_builtin_type, bool p_force = false);
// Setting p_force to false will prevent the completion context from being update if a context was already set before.
// This should only be done when we push context before we consumed any tokens for the corresponding structure.
// See parse_precedence for an example.
void make_completion_context(CompletionType p_type, Node *p_node, int p_argument = -1, bool p_force = true);
void make_completion_context(CompletionType p_type, Variant::Type p_builtin_type, bool p_force = true);
// In some cases it might become necessary to alter the completion context after parsing a subexpression.
// For example to not override COMPLETE_CALL_ARGUMENTS with COMPLETION_NONE from string literals.
void override_completion_context(const Node *p_for_node, CompletionType p_type, Node *p_node, int p_argument = -1);
void push_completion_call(Node *p_call);
void pop_completion_call();
void set_last_completion_call_arg(int p_argument);
@ -1493,18 +1506,20 @@ private:
static bool register_annotation(const MethodInfo &p_info, uint32_t p_target_kinds, AnnotationAction p_apply, const Vector<Variant> &p_default_arguments = Vector<Variant>(), bool p_is_vararg = false);
bool validate_annotation_arguments(AnnotationNode *p_annotation);
void clear_unused_annotations();
bool tool_annotation(const AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class);
bool icon_annotation(const AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class);
bool onready_annotation(const AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class);
bool tool_annotation(AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class);
bool icon_annotation(AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class);
bool static_unload_annotation(AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class);
bool onready_annotation(AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class);
template <PropertyHint t_hint, Variant::Type t_type>
bool export_annotations(const AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class);
bool export_storage_annotation(const AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class);
bool export_custom_annotation(const AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class);
bool export_annotations(AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class);
bool export_storage_annotation(AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class);
bool export_custom_annotation(AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class);
bool export_tool_button_annotation(AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class);
template <PropertyUsageFlags t_usage>
bool export_group_annotations(const AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class);
bool warning_annotations(const AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class);
bool rpc_annotation(const AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class);
bool static_unload_annotation(const AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class);
bool export_group_annotations(AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class);
bool warning_ignore_annotation(AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class);
bool warning_ignore_region_annotations(AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class);
bool rpc_annotation(AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class);
// Statements.
Node *parse_statement();
VariableNode *parse_variable(bool p_is_static);
@ -1589,6 +1604,8 @@ public:
#ifdef TOOLS_ENABLED
static HashMap<String, String> theme_color_names;
HashMap<int, GDScriptTokenizer::CommentData> comment_data;
#endif // TOOLS_ENABLED
GDScriptParser();

View file

@ -49,7 +49,14 @@ uint32_t GDScriptRPCCallable::hash() const {
String GDScriptRPCCallable::get_as_text() const {
String class_name = object->get_class();
Ref<Script> script = object->get_script();
return class_name + "(" + script->get_path().get_file() + ")::" + String(method) + " (rpc)";
if (script.is_valid()) {
if (!script->get_global_name().is_empty()) {
class_name += "(" + script->get_global_name() + ")";
} else if (script->get_path().is_resource_file()) {
class_name += "(" + script->get_path().get_file() + ")";
}
}
return class_name + "::" + String(method) + " (rpc)";
}
CallableCustom::CompareEqualFunc GDScriptRPCCallable::get_compare_equal_func() const {

View file

@ -164,6 +164,15 @@ const char *GDScriptTokenizer::Token::get_name() const {
return token_names[type];
}
String GDScriptTokenizer::Token::get_debug_name() const {
switch (type) {
case IDENTIFIER:
return vformat(R"(identifier "%s")", source);
default:
return vformat(R"("%s")", get_name());
}
}
bool GDScriptTokenizer::Token::can_precede_bin_op() const {
switch (type) {
case IDENTIFIER:
@ -687,13 +696,13 @@ GDScriptTokenizer::Token GDScriptTokenizerText::number() {
if (_peek(-1) == '.') {
has_decimal = true;
} else if (_peek(-1) == '0') {
if (_peek() == 'x') {
if (_peek() == 'x' || _peek() == 'X') {
// Hexadecimal.
base = 16;
digit_check_func = is_hex_digit;
need_digits = true;
_advance();
} else if (_peek() == 'b') {
} else if (_peek() == 'b' || _peek() == 'B') {
// Binary.
base = 2;
digit_check_func = is_binary_digit;

View file

@ -32,7 +32,6 @@
#define GDSCRIPT_TOKENIZER_H
#include "core/templates/hash_map.h"
#include "core/templates/hash_set.h"
#include "core/templates/list.h"
#include "core/templates/vector.h"
#include "core/variant/variant.h"
@ -178,6 +177,7 @@ public:
String source;
const char *get_name() const;
String get_debug_name() const;
bool can_precede_bin_op() const;
bool is_identifier() const;
bool is_node_name() const;

View file

@ -296,6 +296,7 @@ Vector<uint8_t> GDScriptTokenizerBuffer::parse_code_string(const String &p_code,
encode_uint32(identifier_map.size(), &contents.write[0]);
encode_uint32(constant_map.size(), &contents.write[4]);
encode_uint32(token_lines.size(), &contents.write[8]);
encode_uint32(0, &contents.write[12]); // Unused, kept for compatibility. Please remove at next `TOKENIZER_VERSION` increment.
encode_uint32(token_counter, &contents.write[16]);
int buf_pos = 20;

View file

@ -79,7 +79,7 @@ public:
virtual bool is_past_cursor() const override;
virtual void push_expression_indented_block() override; // For lambdas, or blocks inside expressions.
virtual void pop_expression_indented_block() override; // For lambdas, or blocks inside expressions.
virtual bool is_text() override { return false; };
virtual bool is_text() override { return false; }
#ifdef TOOLS_ENABLED
virtual const HashMap<int, CommentData> &get_comments() const override {

View file

@ -30,8 +30,6 @@
#include "gdscript_utility_callable.h"
#include "core/templates/hashfuncs.h"
bool GDScriptUtilityCallable::compare_equal(const CallableCustom *p_a, const CallableCustom *p_b) {
return p_a->hash() == p_b->hash();
}

View file

@ -34,7 +34,6 @@
#include "core/io/resource_loader.h"
#include "core/object/class_db.h"
#include "core/object/method_bind.h"
#include "core/object/object.h"
#include "core/templates/oa_hash_map.h"
#include "core/templates/vector.h"
@ -42,101 +41,105 @@
#ifdef DEBUG_ENABLED
#define VALIDATE_ARG_COUNT(m_count) \
if (p_arg_count < m_count) { \
r_error.error = Callable::CallError::CALL_ERROR_TOO_FEW_ARGUMENTS; \
r_error.expected = m_count; \
#define DEBUG_VALIDATE_ARG_COUNT(m_min_count, m_max_count) \
if (unlikely(p_arg_count < m_min_count)) { \
*r_ret = Variant(); \
r_error.error = Callable::CallError::CALL_ERROR_TOO_FEW_ARGUMENTS; \
r_error.expected = m_min_count; \
return; \
} \
if (p_arg_count > m_count) { \
r_error.error = Callable::CallError::CALL_ERROR_TOO_MANY_ARGUMENTS; \
r_error.expected = m_count; \
if (unlikely(p_arg_count > m_max_count)) { \
*r_ret = Variant(); \
r_error.error = Callable::CallError::CALL_ERROR_TOO_MANY_ARGUMENTS; \
r_error.expected = m_max_count; \
return; \
}
#define VALIDATE_ARG_INT(m_arg) \
if (p_args[m_arg]->get_type() != Variant::INT) { \
#define DEBUG_VALIDATE_ARG_TYPE(m_arg, m_type) \
if (unlikely(!Variant::can_convert_strict(p_args[m_arg]->get_type(), m_type))) { \
*r_ret = Variant(); \
r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT; \
r_error.argument = m_arg; \
r_error.expected = m_type; \
return; \
}
#define DEBUG_VALIDATE_ARG_CUSTOM(m_arg, m_type, m_cond, m_msg) \
if (unlikely(m_cond)) { \
*r_ret = m_msg; \
r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT; \
r_error.argument = m_arg; \
r_error.expected = Variant::INT; \
*r_ret = Variant(); \
r_error.expected = m_type; \
return; \
}
#define VALIDATE_ARG_NUM(m_arg) \
if (!p_args[m_arg]->is_num()) { \
#else // !DEBUG_ENABLED
#define DEBUG_VALIDATE_ARG_COUNT(m_min_count, m_max_count)
#define DEBUG_VALIDATE_ARG_TYPE(m_arg, m_type)
#define DEBUG_VALIDATE_ARG_CUSTOM(m_arg, m_type, m_cond, m_msg)
#endif // DEBUG_ENABLED
#define VALIDATE_ARG_CUSTOM(m_arg, m_type, m_cond, m_msg) \
if (unlikely(m_cond)) { \
*r_ret = m_msg; \
r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT; \
r_error.argument = m_arg; \
r_error.expected = Variant::FLOAT; \
*r_ret = Variant(); \
r_error.expected = m_type; \
return; \
}
#else
#define VALIDATE_ARG_COUNT(m_count)
#define VALIDATE_ARG_INT(m_arg)
#define VALIDATE_ARG_NUM(m_arg)
#endif
#define GDFUNC_FAIL_COND_MSG(m_cond, m_msg) \
if (unlikely(m_cond)) { \
*r_ret = m_msg; \
r_error.error = Callable::CallError::CALL_ERROR_INVALID_METHOD; \
return; \
}
struct GDScriptUtilityFunctionsDefinitions {
#ifndef DISABLE_DEPRECATED
static inline void convert(Variant *r_ret, const Variant **p_args, int p_arg_count, Callable::CallError &r_error) {
VALIDATE_ARG_COUNT(2);
VALIDATE_ARG_INT(1);
int type = *p_args[1];
if (type < 0 || type >= Variant::VARIANT_MAX) {
*r_ret = RTR("Invalid type argument to convert(), use TYPE_* constants.");
r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT;
r_error.argument = 0;
r_error.expected = Variant::INT;
return;
DEBUG_VALIDATE_ARG_COUNT(2, 2);
DEBUG_VALIDATE_ARG_TYPE(1, Variant::INT);
} else {
Variant::construct(Variant::Type(type), *r_ret, p_args, 1, r_error);
if (r_error.error != Callable::CallError::CALL_OK) {
*r_ret = vformat(RTR(R"(Cannot convert "%s" to "%s".)"), Variant::get_type_name(p_args[0]->get_type()), Variant::get_type_name(Variant::Type(type)));
}
}
int type = *p_args[1];
DEBUG_VALIDATE_ARG_CUSTOM(1, Variant::INT, type < 0 || type >= Variant::VARIANT_MAX,
RTR("Invalid type argument to convert(), use TYPE_* constants."));
Variant::construct(Variant::Type(type), *r_ret, p_args, 1, r_error);
}
#endif // DISABLE_DEPRECATED
static inline void type_exists(Variant *r_ret, const Variant **p_args, int p_arg_count, Callable::CallError &r_error) {
VALIDATE_ARG_COUNT(1);
DEBUG_VALIDATE_ARG_COUNT(1, 1);
DEBUG_VALIDATE_ARG_TYPE(0, Variant::STRING_NAME);
*r_ret = ClassDB::class_exists(*p_args[0]);
}
static inline void _char(Variant *r_ret, const Variant **p_args, int p_arg_count, Callable::CallError &r_error) {
VALIDATE_ARG_COUNT(1);
VALIDATE_ARG_INT(0);
DEBUG_VALIDATE_ARG_COUNT(1, 1);
DEBUG_VALIDATE_ARG_TYPE(0, Variant::INT);
char32_t result[2] = { *p_args[0], 0 };
*r_ret = String(result);
}
static inline void range(Variant *r_ret, const Variant **p_args, int p_arg_count, Callable::CallError &r_error) {
DEBUG_VALIDATE_ARG_COUNT(1, 3);
switch (p_arg_count) {
case 0: {
r_error.error = Callable::CallError::CALL_ERROR_TOO_FEW_ARGUMENTS;
r_error.expected = 1;
*r_ret = Variant();
} break;
case 1: {
VALIDATE_ARG_NUM(0);
DEBUG_VALIDATE_ARG_TYPE(0, Variant::INT);
int count = *p_args[0];
Array arr;
if (count <= 0) {
*r_ret = arr;
return;
}
Error err = arr.resize(count);
if (err != OK) {
*r_ret = RTR("Cannot resize array.");
r_error.error = Callable::CallError::CALL_ERROR_INVALID_METHOD;
return;
}
GDFUNC_FAIL_COND_MSG(err != OK, RTR("Cannot resize array."));
for (int i = 0; i < count; i++) {
arr[i] = i;
@ -145,8 +148,8 @@ struct GDScriptUtilityFunctionsDefinitions {
*r_ret = arr;
} break;
case 2: {
VALIDATE_ARG_NUM(0);
VALIDATE_ARG_NUM(1);
DEBUG_VALIDATE_ARG_TYPE(0, Variant::INT);
DEBUG_VALIDATE_ARG_TYPE(1, Variant::INT);
int from = *p_args[0];
int to = *p_args[1];
@ -156,30 +159,26 @@ struct GDScriptUtilityFunctionsDefinitions {
*r_ret = arr;
return;
}
Error err = arr.resize(to - from);
if (err != OK) {
*r_ret = RTR("Cannot resize array.");
r_error.error = Callable::CallError::CALL_ERROR_INVALID_METHOD;
return;
}
GDFUNC_FAIL_COND_MSG(err != OK, RTR("Cannot resize array."));
for (int i = from; i < to; i++) {
arr[i - from] = i;
}
*r_ret = arr;
} break;
case 3: {
VALIDATE_ARG_NUM(0);
VALIDATE_ARG_NUM(1);
VALIDATE_ARG_NUM(2);
DEBUG_VALIDATE_ARG_TYPE(0, Variant::INT);
DEBUG_VALIDATE_ARG_TYPE(1, Variant::INT);
DEBUG_VALIDATE_ARG_TYPE(2, Variant::INT);
int from = *p_args[0];
int to = *p_args[1];
int incr = *p_args[2];
if (incr == 0) {
*r_ret = RTR("Step argument is zero!");
r_error.error = Callable::CallError::CALL_ERROR_INVALID_METHOD;
return;
}
VALIDATE_ARG_CUSTOM(2, Variant::INT, incr == 0, RTR("Step argument is zero!"));
Array arr;
if (from >= to && incr > 0) {
@ -200,12 +199,7 @@ struct GDScriptUtilityFunctionsDefinitions {
}
Error err = arr.resize(count);
if (err != OK) {
*r_ret = RTR("Cannot resize array.");
r_error.error = Callable::CallError::CALL_ERROR_INVALID_METHOD;
return;
}
GDFUNC_FAIL_COND_MSG(err != OK, RTR("Cannot resize array."));
if (incr > 0) {
int idx = 0;
@ -221,138 +215,81 @@ struct GDScriptUtilityFunctionsDefinitions {
*r_ret = arr;
} break;
default: {
r_error.error = Callable::CallError::CALL_ERROR_TOO_MANY_ARGUMENTS;
r_error.expected = 3;
*r_ret = Variant();
} break;
}
}
static inline void load(Variant *r_ret, const Variant **p_args, int p_arg_count, Callable::CallError &r_error) {
VALIDATE_ARG_COUNT(1);
if (p_args[0]->get_type() != Variant::STRING) {
r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT;
r_error.argument = 0;
r_error.expected = Variant::STRING;
*r_ret = Variant();
} else {
*r_ret = ResourceLoader::load(*p_args[0]);
}
DEBUG_VALIDATE_ARG_COUNT(1, 1);
DEBUG_VALIDATE_ARG_TYPE(0, Variant::STRING);
*r_ret = ResourceLoader::load(*p_args[0]);
}
#ifndef DISABLE_DEPRECATED
static inline void inst_to_dict(Variant *r_ret, const Variant **p_args, int p_arg_count, Callable::CallError &r_error) {
VALIDATE_ARG_COUNT(1);
DEBUG_VALIDATE_ARG_COUNT(1, 1);
DEBUG_VALIDATE_ARG_TYPE(0, Variant::OBJECT);
if (p_args[0]->get_type() == Variant::NIL) {
*r_ret = Variant();
} else if (p_args[0]->get_type() != Variant::OBJECT) {
r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT;
r_error.argument = 0;
r_error.expected = Variant::OBJECT;
return;
}
Object *obj = *p_args[0];
if (!obj) {
*r_ret = Variant();
} else {
Object *obj = *p_args[0];
if (!obj) {
*r_ret = Variant();
return;
}
} else if (!obj->get_script_instance() || obj->get_script_instance()->get_language() != GDScriptLanguage::get_singleton()) {
r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT;
r_error.argument = 0;
r_error.expected = Variant::DICTIONARY;
*r_ret = RTR("Not a script with an instance");
return;
} else {
GDScriptInstance *ins = static_cast<GDScriptInstance *>(obj->get_script_instance());
Ref<GDScript> base = ins->get_script();
if (base.is_null()) {
r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT;
r_error.argument = 0;
r_error.expected = Variant::DICTIONARY;
*r_ret = RTR("Not based on a script");
return;
}
VALIDATE_ARG_CUSTOM(0, Variant::OBJECT,
!obj->get_script_instance() || obj->get_script_instance()->get_language() != GDScriptLanguage::get_singleton(),
RTR("Not a script with an instance."));
GDScript *p = base.ptr();
String path = p->get_script_path();
Vector<StringName> sname;
GDScriptInstance *inst = static_cast<GDScriptInstance *>(obj->get_script_instance());
while (p->_owner) {
sname.push_back(p->local_name);
p = p->_owner;
}
sname.reverse();
Ref<GDScript> base = inst->get_script();
VALIDATE_ARG_CUSTOM(0, Variant::OBJECT, base.is_null(), RTR("Not based on a script."));
if (!path.is_resource_file()) {
r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT;
r_error.argument = 0;
r_error.expected = Variant::DICTIONARY;
*r_ret = Variant();
GDScript *p = base.ptr();
String path = p->get_script_path();
Vector<StringName> sname;
*r_ret = RTR("Not based on a resource file");
while (p->_owner) {
sname.push_back(p->local_name);
p = p->_owner;
}
sname.reverse();
return;
}
VALIDATE_ARG_CUSTOM(0, Variant::OBJECT, !path.is_resource_file(), RTR("Not based on a resource file."));
NodePath cp(sname, Vector<StringName>(), false);
NodePath cp(sname, Vector<StringName>(), false);
Dictionary d;
d["@subpath"] = cp;
d["@path"] = path;
Dictionary d;
d["@subpath"] = cp;
d["@path"] = path;
for (const KeyValue<StringName, GDScript::MemberInfo> &E : base->member_indices) {
if (!d.has(E.key)) {
d[E.key] = ins->members[E.value.index];
}
}
*r_ret = d;
for (const KeyValue<StringName, GDScript::MemberInfo> &E : base->member_indices) {
if (!d.has(E.key)) {
d[E.key] = inst->members[E.value.index];
}
}
*r_ret = d;
}
static inline void dict_to_inst(Variant *r_ret, const Variant **p_args, int p_arg_count, Callable::CallError &r_error) {
VALIDATE_ARG_COUNT(1);
if (p_args[0]->get_type() != Variant::DICTIONARY) {
r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT;
r_error.argument = 0;
r_error.expected = Variant::DICTIONARY;
*r_ret = Variant();
return;
}
DEBUG_VALIDATE_ARG_COUNT(1, 1);
DEBUG_VALIDATE_ARG_TYPE(0, Variant::DICTIONARY);
Dictionary d = *p_args[0];
if (!d.has("@path")) {
r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT;
r_error.argument = 0;
r_error.expected = Variant::OBJECT;
*r_ret = RTR("Invalid instance dictionary format (missing @path)");
return;
}
VALIDATE_ARG_CUSTOM(0, Variant::DICTIONARY, !d.has("@path"), RTR("Invalid instance dictionary format (missing @path)."));
Ref<Script> scr = ResourceLoader::load(d["@path"]);
if (!scr.is_valid()) {
r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT;
r_error.argument = 0;
r_error.expected = Variant::OBJECT;
*r_ret = RTR("Invalid instance dictionary format (can't load script at @path)");
return;
}
VALIDATE_ARG_CUSTOM(0, Variant::DICTIONARY, scr.is_null(), RTR("Invalid instance dictionary format (can't load script at @path)."));
Ref<GDScript> gdscr = scr;
if (!gdscr.is_valid()) {
r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT;
r_error.argument = 0;
r_error.expected = Variant::OBJECT;
*r_ret = Variant();
*r_ret = RTR("Invalid instance dictionary format (invalid script at @path)");
return;
}
VALIDATE_ARG_CUSTOM(0, Variant::DICTIONARY, gdscr.is_null(), RTR("Invalid instance dictionary format (invalid script at @path)."));
NodePath sub;
if (d.has("@subpath")) {
@ -361,60 +298,42 @@ struct GDScriptUtilityFunctionsDefinitions {
for (int i = 0; i < sub.get_name_count(); i++) {
gdscr = gdscr->subclasses[sub.get_name(i)];
if (!gdscr.is_valid()) {
r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT;
r_error.argument = 0;
r_error.expected = Variant::OBJECT;
*r_ret = Variant();
*r_ret = RTR("Invalid instance dictionary (invalid subclasses)");
return;
}
VALIDATE_ARG_CUSTOM(0, Variant::DICTIONARY, gdscr.is_null(), RTR("Invalid instance dictionary (invalid subclasses)."));
}
*r_ret = gdscr->_new(nullptr, -1 /*skip initializer*/, r_error);
*r_ret = gdscr->_new(nullptr, -1 /* skip initializer */, r_error);
if (r_error.error != Callable::CallError::CALL_OK) {
*r_ret = RTR("Cannot instantiate GDScript class.");
return;
}
GDScriptInstance *ins = static_cast<GDScriptInstance *>(static_cast<Object *>(*r_ret)->get_script_instance());
Ref<GDScript> gd_ref = ins->get_script();
GDScriptInstance *inst = static_cast<GDScriptInstance *>(static_cast<Object *>(*r_ret)->get_script_instance());
Ref<GDScript> gd_ref = inst->get_script();
for (KeyValue<StringName, GDScript::MemberInfo> &E : gd_ref->member_indices) {
if (d.has(E.key)) {
ins->members.write[E.value.index] = d[E.key];
inst->members.write[E.value.index] = d[E.key];
}
}
}
static inline void Color8(Variant *r_ret, const Variant **p_args, int p_arg_count, Callable::CallError &r_error) {
if (p_arg_count < 3) {
r_error.error = Callable::CallError::CALL_ERROR_TOO_FEW_ARGUMENTS;
r_error.expected = 3;
*r_ret = Variant();
return;
}
if (p_arg_count > 4) {
r_error.error = Callable::CallError::CALL_ERROR_TOO_MANY_ARGUMENTS;
r_error.expected = 4;
*r_ret = Variant();
return;
}
VALIDATE_ARG_INT(0);
VALIDATE_ARG_INT(1);
VALIDATE_ARG_INT(2);
Color color((int64_t)*p_args[0] / 255.0f, (int64_t)*p_args[1] / 255.0f, (int64_t)*p_args[2] / 255.0f);
DEBUG_VALIDATE_ARG_COUNT(3, 4);
DEBUG_VALIDATE_ARG_TYPE(0, Variant::INT);
DEBUG_VALIDATE_ARG_TYPE(1, Variant::INT);
DEBUG_VALIDATE_ARG_TYPE(2, Variant::INT);
int64_t alpha = 255;
if (p_arg_count == 4) {
VALIDATE_ARG_INT(3);
color.a = (int64_t)*p_args[3] / 255.0f;
DEBUG_VALIDATE_ARG_TYPE(3, Variant::INT);
alpha = *p_args[3];
}
*r_ret = color;
*r_ret = Color::from_rgba8(*p_args[0], *p_args[1], *p_args[2], alpha);
}
#endif // DISABLE_DEPRECATED
static inline void print_debug(Variant *r_ret, const Variant **p_args, int p_arg_count, Callable::CallError &r_error) {
String s;
for (int i = 0; i < p_arg_count; i++) {
@ -435,7 +354,8 @@ struct GDScriptUtilityFunctionsDefinitions {
}
static inline void print_stack(Variant *r_ret, const Variant **p_args, int p_arg_count, Callable::CallError &r_error) {
VALIDATE_ARG_COUNT(0);
DEBUG_VALIDATE_ARG_COUNT(0, 0);
if (Thread::get_caller_id() != Thread::get_main_id()) {
print_line("Cannot retrieve debug info outside the main thread. Thread ID: " + itos(Thread::get_caller_id()));
return;
@ -449,7 +369,8 @@ struct GDScriptUtilityFunctionsDefinitions {
}
static inline void get_stack(Variant *r_ret, const Variant **p_args, int p_arg_count, Callable::CallError &r_error) {
VALIDATE_ARG_COUNT(0);
DEBUG_VALIDATE_ARG_COUNT(0, 0);
if (Thread::get_caller_id() != Thread::get_main_id()) {
*r_ret = TypedArray<Dictionary>();
return;
@ -468,7 +389,7 @@ struct GDScriptUtilityFunctionsDefinitions {
}
static inline void len(Variant *r_ret, const Variant **p_args, int p_arg_count, Callable::CallError &r_error) {
VALIDATE_ARG_COUNT(1);
DEBUG_VALIDATE_ARG_COUNT(1, 1);
switch (p_args[0]->get_type()) {
case Variant::STRING:
case Variant::STRING_NAME: {
@ -524,56 +445,34 @@ struct GDScriptUtilityFunctionsDefinitions {
*r_ret = d.size();
} break;
default: {
*r_ret = vformat(RTR("Value of type '%s' can't provide a length."), Variant::get_type_name(p_args[0]->get_type()));
r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT;
r_error.argument = 0;
r_error.expected = Variant::NIL;
*r_ret = vformat(RTR("Value of type '%s' can't provide a length."), Variant::get_type_name(p_args[0]->get_type()));
}
} break;
}
}
static inline void is_instance_of(Variant *r_ret, const Variant **p_args, int p_arg_count, Callable::CallError &r_error) {
VALIDATE_ARG_COUNT(2);
DEBUG_VALIDATE_ARG_COUNT(2, 2);
if (p_args[1]->get_type() == Variant::INT) {
int builtin_type = *p_args[1];
if (builtin_type < 0 || builtin_type >= Variant::VARIANT_MAX) {
*r_ret = RTR("Invalid type argument for is_instance_of(), use TYPE_* constants for built-in types.");
r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT;
r_error.argument = 1;
r_error.expected = Variant::NIL;
return;
}
DEBUG_VALIDATE_ARG_CUSTOM(1, Variant::NIL, builtin_type < 0 || builtin_type >= Variant::VARIANT_MAX,
RTR("Invalid type argument for is_instance_of(), use TYPE_* constants for built-in types."));
*r_ret = p_args[0]->get_type() == builtin_type;
return;
}
bool was_type_freed = false;
Object *type_object = p_args[1]->get_validated_object_with_check(was_type_freed);
if (was_type_freed) {
*r_ret = RTR("Type argument is a previously freed instance.");
r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT;
r_error.argument = 1;
r_error.expected = Variant::NIL;
return;
}
if (!type_object) {
*r_ret = RTR("Invalid type argument for is_instance_of(), should be a TYPE_* constant, a class or a script.");
r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT;
r_error.argument = 1;
r_error.expected = Variant::NIL;
return;
}
VALIDATE_ARG_CUSTOM(1, Variant::NIL, was_type_freed, RTR("Type argument is a previously freed instance."));
VALIDATE_ARG_CUSTOM(1, Variant::NIL, !type_object,
RTR("Invalid type argument for is_instance_of(), should be a TYPE_* constant, a class or a script."));
bool was_value_freed = false;
Object *value_object = p_args[0]->get_validated_object_with_check(was_value_freed);
if (was_value_freed) {
*r_ret = RTR("Value argument is a previously freed instance.");
r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT;
r_error.argument = 0;
r_error.expected = Variant::NIL;
return;
}
VALIDATE_ARG_CUSTOM(0, Variant::NIL, was_value_freed, RTR("Value argument is a previously freed instance."));
if (!value_object) {
*r_ret = false;
return;
@ -618,113 +517,79 @@ struct GDScriptUtilityFunctionInfo {
static OAHashMap<StringName, GDScriptUtilityFunctionInfo> utility_function_table;
static List<StringName> utility_function_name_table;
static void _register_function(const String &p_name, const MethodInfo &p_method_info, GDScriptUtilityFunctions::FunctionPtr p_function, bool p_is_const) {
StringName sname(p_name);
ERR_FAIL_COND(utility_function_table.has(sname));
static void _register_function(const StringName &p_name, const MethodInfo &p_method_info, GDScriptUtilityFunctions::FunctionPtr p_function, bool p_is_const) {
ERR_FAIL_COND(utility_function_table.has(p_name));
GDScriptUtilityFunctionInfo function;
function.function = p_function;
function.info = p_method_info;
function.is_constant = p_is_const;
utility_function_table.insert(sname, function);
utility_function_name_table.push_back(sname);
utility_function_table.insert(p_name, function);
utility_function_name_table.push_back(p_name);
}
#define REGISTER_FUNC(m_func, m_is_const, m_return_type, ...) \
#define REGISTER_FUNC(m_func, m_is_const, m_return, m_args, m_is_vararg, m_default_args) \
{ \
String name(#m_func); \
if (name.begins_with("_")) { \
name = name.substr(1, name.length() - 1); \
name = name.substr(1); \
} \
MethodInfo info = m_args; \
info.name = name; \
info.return_val = m_return; \
info.default_arguments = m_default_args; \
if (m_is_vararg) { \
info.flags |= METHOD_FLAG_VARARG; \
} \
MethodInfo info = MethodInfo(name, __VA_ARGS__); \
info.return_val.type = m_return_type; \
_register_function(name, info, GDScriptUtilityFunctionsDefinitions::m_func, m_is_const); \
}
#define REGISTER_FUNC_NO_ARGS(m_func, m_is_const, m_return_type) \
{ \
String name(#m_func); \
if (name.begins_with("_")) { \
name = name.substr(1, name.length() - 1); \
} \
MethodInfo info = MethodInfo(name); \
info.return_val.type = m_return_type; \
_register_function(name, info, GDScriptUtilityFunctionsDefinitions::m_func, m_is_const); \
}
#define RET(m_type) \
PropertyInfo(Variant::m_type, "")
#define REGISTER_VARARG_FUNC(m_func, m_is_const, m_return_type) \
{ \
String name(#m_func); \
if (name.begins_with("_")) { \
name = name.substr(1, name.length() - 1); \
} \
MethodInfo info = MethodInfo(name); \
info.return_val.type = m_return_type; \
info.flags |= METHOD_FLAG_VARARG; \
_register_function(name, info, GDScriptUtilityFunctionsDefinitions::m_func, m_is_const); \
}
#define RETVAR \
PropertyInfo(Variant::NIL, "", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NIL_IS_VARIANT)
#define REGISTER_VARIANT_FUNC(m_func, m_is_const, ...) \
{ \
String name(#m_func); \
if (name.begins_with("_")) { \
name = name.substr(1, name.length() - 1); \
} \
MethodInfo info = MethodInfo(name, __VA_ARGS__); \
info.return_val.type = Variant::NIL; \
info.return_val.usage |= PROPERTY_USAGE_NIL_IS_VARIANT; \
_register_function(name, info, GDScriptUtilityFunctionsDefinitions::m_func, m_is_const); \
}
#define RETCLS(m_class) \
PropertyInfo(Variant::OBJECT, "", PROPERTY_HINT_RESOURCE_TYPE, m_class)
#define REGISTER_CLASS_FUNC(m_func, m_is_const, m_return_type, ...) \
{ \
String name(#m_func); \
if (name.begins_with("_")) { \
name = name.substr(1, name.length() - 1); \
} \
MethodInfo info = MethodInfo(name, __VA_ARGS__); \
info.return_val.type = Variant::OBJECT; \
info.return_val.hint = PROPERTY_HINT_RESOURCE_TYPE; \
info.return_val.class_name = m_return_type; \
_register_function(name, info, GDScriptUtilityFunctionsDefinitions::m_func, m_is_const); \
}
#define NOARGS \
MethodInfo()
#define REGISTER_FUNC_DEF(m_func, m_is_const, m_default, m_return_type, ...) \
{ \
String name(#m_func); \
if (name.begins_with("_")) { \
name = name.substr(1, name.length() - 1); \
} \
MethodInfo info = MethodInfo(name, __VA_ARGS__); \
info.return_val.type = m_return_type; \
info.default_arguments.push_back(m_default); \
_register_function(name, info, GDScriptUtilityFunctionsDefinitions::m_func, m_is_const); \
}
#define ARGS(...) \
MethodInfo("", __VA_ARGS__)
#define ARG(m_name, m_type) \
PropertyInfo(m_type, m_name)
PropertyInfo(Variant::m_type, m_name)
#define VARARG(m_name) \
PropertyInfo(Variant::NIL, m_name, PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_NIL_IS_VARIANT)
#define ARGVAR(m_name) \
PropertyInfo(Variant::NIL, m_name, PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NIL_IS_VARIANT)
#define ARGTYPE(m_name) \
PropertyInfo(Variant::INT, m_name, PROPERTY_HINT_NONE, "", PROPERTY_USAGE_CLASS_IS_ENUM, "Variant.Type")
void GDScriptUtilityFunctions::register_functions() {
/* clang-format off */
#ifndef DISABLE_DEPRECATED
REGISTER_VARIANT_FUNC(convert, true, VARARG("what"), ARG("type", Variant::INT));
REGISTER_FUNC( convert, true, RETVAR, ARGS( ARGVAR("what"), ARGTYPE("type") ), false, varray( ));
#endif // DISABLE_DEPRECATED
REGISTER_FUNC(type_exists, true, Variant::BOOL, ARG("type", Variant::STRING_NAME));
REGISTER_FUNC(_char, true, Variant::STRING, ARG("char", Variant::INT));
REGISTER_VARARG_FUNC(range, false, Variant::ARRAY);
REGISTER_CLASS_FUNC(load, false, "Resource", ARG("path", Variant::STRING));
REGISTER_FUNC(inst_to_dict, false, Variant::DICTIONARY, ARG("instance", Variant::OBJECT));
REGISTER_FUNC(dict_to_inst, false, Variant::OBJECT, ARG("dictionary", Variant::DICTIONARY));
REGISTER_FUNC_DEF(Color8, true, 255, Variant::COLOR, ARG("r8", Variant::INT), ARG("g8", Variant::INT), ARG("b8", Variant::INT), ARG("a8", Variant::INT));
REGISTER_VARARG_FUNC(print_debug, false, Variant::NIL);
REGISTER_FUNC_NO_ARGS(print_stack, false, Variant::NIL);
REGISTER_FUNC_NO_ARGS(get_stack, false, Variant::ARRAY);
REGISTER_FUNC(len, true, Variant::INT, VARARG("var"));
REGISTER_FUNC(is_instance_of, true, Variant::BOOL, VARARG("value"), VARARG("type"));
REGISTER_FUNC( type_exists, true, RET(BOOL), ARGS( ARG("type", STRING_NAME) ), false, varray( ));
REGISTER_FUNC( _char, true, RET(STRING), ARGS( ARG("char", INT) ), false, varray( ));
REGISTER_FUNC( range, false, RET(ARRAY), NOARGS, true, varray( ));
REGISTER_FUNC( load, false, RETCLS("Resource"), ARGS( ARG("path", STRING) ), false, varray( ));
#ifndef DISABLE_DEPRECATED
REGISTER_FUNC( inst_to_dict, false, RET(DICTIONARY), ARGS( ARG("instance", OBJECT) ), false, varray( ));
REGISTER_FUNC( dict_to_inst, false, RET(OBJECT), ARGS( ARG("dictionary", DICTIONARY) ), false, varray( ));
REGISTER_FUNC( Color8, true, RET(COLOR), ARGS( ARG("r8", INT), ARG("g8", INT),
ARG("b8", INT), ARG("a8", INT) ), false, varray( 255 ));
#endif // DISABLE_DEPRECATED
REGISTER_FUNC( print_debug, false, RET(NIL), NOARGS, true, varray( ));
REGISTER_FUNC( print_stack, false, RET(NIL), NOARGS, false, varray( ));
REGISTER_FUNC( get_stack, false, RET(ARRAY), NOARGS, false, varray( ));
REGISTER_FUNC( len, true, RET(INT), ARGS( ARGVAR("var") ), false, varray( ));
REGISTER_FUNC( is_instance_of, true, RET(BOOL), ARGS( ARGVAR("value"), ARGVAR("type") ), false, varray( ));
/* clang-format on */
}
void GDScriptUtilityFunctions::unregister_functions() {

View file

@ -84,9 +84,15 @@ static String _get_var_type(const Variant *p_var) {
if (p_var->get_type() == Variant::ARRAY) {
basestr = "Array";
const Array *p_array = VariantInternal::get_array(p_var);
Variant::Type builtin_type = (Variant::Type)p_array->get_typed_builtin();
if (builtin_type != Variant::NIL) {
basestr += "[" + _get_element_type(builtin_type, p_array->get_typed_class_name(), p_array->get_typed_script()) + "]";
if (p_array->is_typed()) {
basestr += "[" + _get_element_type((Variant::Type)p_array->get_typed_builtin(), p_array->get_typed_class_name(), p_array->get_typed_script()) + "]";
}
} else if (p_var->get_type() == Variant::DICTIONARY) {
basestr = "Dictionary";
const Dictionary *p_dictionary = VariantInternal::get_dictionary(p_var);
if (p_dictionary->is_typed()) {
basestr += "[" + _get_element_type((Variant::Type)p_dictionary->get_typed_key_builtin(), p_dictionary->get_typed_key_class_name(), p_dictionary->get_typed_key_script()) +
", " + _get_element_type((Variant::Type)p_dictionary->get_typed_value_builtin(), p_dictionary->get_typed_value_class_name(), p_dictionary->get_typed_value_script()) + "]";
}
} else {
basestr = Variant::get_type_name(p_var->get_type());
@ -120,6 +126,16 @@ Variant GDScriptFunction::_get_default_variant_for_data_type(const GDScriptDataT
}
return array;
} else if (p_data_type.builtin_type == Variant::DICTIONARY) {
Dictionary dict;
// Typed dictionary.
if (p_data_type.has_container_element_types()) {
const GDScriptDataType &key_type = p_data_type.get_container_element_type_or_variant(0);
const GDScriptDataType &value_type = p_data_type.get_container_element_type_or_variant(1);
dict.set_typed(key_type.builtin_type, key_type.native_type, key_type.script_type, value_type.builtin_type, value_type.native_type, value_type.script_type);
}
return dict;
} else {
Callable::CallError ce;
Variant variant;
@ -134,38 +150,39 @@ Variant GDScriptFunction::_get_default_variant_for_data_type(const GDScriptDataT
return Variant();
}
String GDScriptFunction::_get_call_error(const Callable::CallError &p_err, const String &p_where, const Variant **argptrs) const {
String err_text;
if (p_err.error == Callable::CallError::CALL_ERROR_INVALID_ARGUMENT) {
int errorarg = p_err.argument;
ERR_FAIL_COND_V_MSG(errorarg < 0 || argptrs[errorarg] == nullptr, "GDScript bug (please report): Invalid CallError argument index or null pointer.", "Invalid CallError argument index or null pointer.");
// Handle the Object to Object case separately as we don't have further class details.
String GDScriptFunction::_get_call_error(const String &p_where, const Variant **p_argptrs, const Variant &p_ret, const Callable::CallError &p_err) const {
switch (p_err.error) {
case Callable::CallError::CALL_OK:
return String();
case Callable::CallError::CALL_ERROR_INVALID_METHOD:
if (p_ret.get_type() == Variant::STRING && !p_ret.operator String().is_empty()) {
return "Invalid call " + p_where + ": " + p_ret.operator String();
}
return "Invalid call. Nonexistent " + p_where + ".";
case Callable::CallError::CALL_ERROR_INVALID_ARGUMENT:
ERR_FAIL_COND_V_MSG(p_err.argument < 0 || p_argptrs[p_err.argument] == nullptr, "Bug: Invalid CallError argument index or null pointer.", "Bug: Invalid CallError argument index or null pointer.");
// Handle the Object to Object case separately as we don't have further class details.
#ifdef DEBUG_ENABLED
if (p_err.expected == Variant::OBJECT && argptrs[errorarg]->get_type() == p_err.expected) {
err_text = "Invalid type in " + p_where + ". The Object-derived class of argument " + itos(errorarg + 1) + " (" + _get_var_type(argptrs[errorarg]) + ") is not a subclass of the expected argument class.";
} else if (p_err.expected == Variant::ARRAY && argptrs[errorarg]->get_type() == p_err.expected) {
err_text = "Invalid type in " + p_where + ". The array of argument " + itos(errorarg + 1) + " (" + _get_var_type(argptrs[errorarg]) + ") does not have the same element type as the expected typed array argument.";
} else
if (p_err.expected == Variant::OBJECT && p_argptrs[p_err.argument]->get_type() == p_err.expected) {
return "Invalid type in " + p_where + ". The Object-derived class of argument " + itos(p_err.argument + 1) + " (" + _get_var_type(p_argptrs[p_err.argument]) + ") is not a subclass of the expected argument class.";
}
if (p_err.expected == Variant::ARRAY && p_argptrs[p_err.argument]->get_type() == p_err.expected) {
return "Invalid type in " + p_where + ". The array of argument " + itos(p_err.argument + 1) + " (" + _get_var_type(p_argptrs[p_err.argument]) + ") does not have the same element type as the expected typed array argument.";
}
if (p_err.expected == Variant::DICTIONARY && p_argptrs[p_err.argument]->get_type() == p_err.expected) {
return "Invalid type in " + p_where + ". The dictionary of argument " + itos(p_err.argument + 1) + " (" + _get_var_type(p_argptrs[p_err.argument]) + ") does not have the same element type as the expected typed dictionary argument.";
}
#endif // DEBUG_ENABLED
{
err_text = "Invalid type in " + p_where + ". Cannot convert argument " + itos(errorarg + 1) + " from " + Variant::get_type_name(argptrs[errorarg]->get_type()) + " to " + Variant::get_type_name(Variant::Type(p_err.expected)) + ".";
}
} else if (p_err.error == Callable::CallError::CALL_ERROR_TOO_MANY_ARGUMENTS) {
err_text = "Invalid call to " + p_where + ". Expected " + itos(p_err.expected) + " arguments.";
} else if (p_err.error == Callable::CallError::CALL_ERROR_TOO_FEW_ARGUMENTS) {
err_text = "Invalid call to " + p_where + ". Expected " + itos(p_err.expected) + " arguments.";
} else if (p_err.error == Callable::CallError::CALL_ERROR_INVALID_METHOD) {
err_text = "Invalid call. Nonexistent " + p_where + ".";
} else if (p_err.error == Callable::CallError::CALL_ERROR_INSTANCE_IS_NULL) {
err_text = "Attempt to call " + p_where + " on a null instance.";
} else if (p_err.error == Callable::CallError::CALL_ERROR_METHOD_NOT_CONST) {
err_text = "Attempt to call " + p_where + " on a const instance.";
} else {
err_text = "Bug, call error: #" + itos(p_err.error);
return "Invalid type in " + p_where + ". Cannot convert argument " + itos(p_err.argument + 1) + " from " + Variant::get_type_name(p_argptrs[p_err.argument]->get_type()) + " to " + Variant::get_type_name(Variant::Type(p_err.expected)) + ".";
case Callable::CallError::CALL_ERROR_TOO_MANY_ARGUMENTS:
case Callable::CallError::CALL_ERROR_TOO_FEW_ARGUMENTS:
return "Invalid call to " + p_where + ". Expected " + itos(p_err.expected) + " arguments.";
case Callable::CallError::CALL_ERROR_INSTANCE_IS_NULL:
return "Attempt to call " + p_where + " on a null instance.";
case Callable::CallError::CALL_ERROR_METHOD_NOT_CONST:
return "Attempt to call " + p_where + " on a const instance.";
}
return err_text;
return "Bug: Invalid call error code " + itos(p_err.error) + ".";
}
void (*type_init_function_table[])(Variant *) = {
@ -210,13 +227,14 @@ void (*type_init_function_table[])(Variant *) = {
&VariantInitializer<PackedVector4Array>::init, // PACKED_VECTOR4_ARRAY.
};
#if defined(__GNUC__)
#if defined(__GNUC__) || defined(__clang__)
#define OPCODES_TABLE \
static const void *switch_table_ops[] = { \
&&OPCODE_OPERATOR, \
&&OPCODE_OPERATOR_VALIDATED, \
&&OPCODE_TYPE_TEST_BUILTIN, \
&&OPCODE_TYPE_TEST_ARRAY, \
&&OPCODE_TYPE_TEST_DICTIONARY, \
&&OPCODE_TYPE_TEST_NATIVE, \
&&OPCODE_TYPE_TEST_SCRIPT, \
&&OPCODE_SET_KEYED, \
@ -239,6 +257,7 @@ void (*type_init_function_table[])(Variant *) = {
&&OPCODE_ASSIGN_FALSE, \
&&OPCODE_ASSIGN_TYPED_BUILTIN, \
&&OPCODE_ASSIGN_TYPED_ARRAY, \
&&OPCODE_ASSIGN_TYPED_DICTIONARY, \
&&OPCODE_ASSIGN_TYPED_NATIVE, \
&&OPCODE_ASSIGN_TYPED_SCRIPT, \
&&OPCODE_CAST_TO_BUILTIN, \
@ -249,6 +268,7 @@ void (*type_init_function_table[])(Variant *) = {
&&OPCODE_CONSTRUCT_ARRAY, \
&&OPCODE_CONSTRUCT_TYPED_ARRAY, \
&&OPCODE_CONSTRUCT_DICTIONARY, \
&&OPCODE_CONSTRUCT_TYPED_DICTIONARY, \
&&OPCODE_CALL, \
&&OPCODE_CALL_RETURN, \
&&OPCODE_CALL_ASYNC, \
@ -277,6 +297,7 @@ void (*type_init_function_table[])(Variant *) = {
&&OPCODE_RETURN, \
&&OPCODE_RETURN_TYPED_BUILTIN, \
&&OPCODE_RETURN_TYPED_ARRAY, \
&&OPCODE_RETURN_TYPED_DICTIONARY, \
&&OPCODE_RETURN_TYPED_NATIVE, \
&&OPCODE_RETURN_TYPED_SCRIPT, \
&&OPCODE_ITERATE_BEGIN, \
@ -376,32 +397,36 @@ void (*type_init_function_table[])(Variant *) = {
#define OPCODES_OUT \
OPSOUT:
#define OPCODE_SWITCH(m_test) goto *switch_table_ops[m_test];
#ifdef DEBUG_ENABLED
#define DISPATCH_OPCODE \
last_opcode = _code_ptr[ip]; \
goto *switch_table_ops[last_opcode]
#else
#else // !DEBUG_ENABLED
#define DISPATCH_OPCODE goto *switch_table_ops[_code_ptr[ip]]
#endif
#endif // DEBUG_ENABLED
#define OPCODE_BREAK goto OPSEXIT
#define OPCODE_OUT goto OPSOUT
#else
#else // !(defined(__GNUC__) || defined(__clang__))
#define OPCODES_TABLE
#define OPCODE(m_op) case m_op:
#define OPCODE_WHILE(m_test) while (m_test)
#define OPCODES_END
#define OPCODES_OUT
#define DISPATCH_OPCODE continue
#ifdef _MSC_VER
#define OPCODE_SWITCH(m_test) \
__assume(m_test <= OPCODE_END); \
switch (m_test)
#else
#else // !_MSC_VER
#define OPCODE_SWITCH(m_test) switch (m_test)
#endif
#endif // _MSC_VER
#define OPCODE_BREAK break
#define OPCODE_OUT break
#endif
#endif // defined(__GNUC__) || defined(__clang__)
// Helpers for VariantInternal methods in macros.
#define OP_GET_BOOL get_bool
@ -486,12 +511,6 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a
Variant **instruction_args = nullptr;
int defarg = 0;
#ifdef DEBUG_ENABLED
//GDScriptLanguage::get_singleton()->calls++;
#endif
uint32_t alloca_size = 0;
GDScript *script;
int ip = 0;
@ -550,7 +569,12 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a
return _get_default_variant_for_data_type(return_type);
}
if (argument_types[i].kind == GDScriptDataType::BUILTIN) {
if (argument_types[i].builtin_type == Variant::ARRAY && argument_types[i].has_container_element_type(0)) {
if (argument_types[i].builtin_type == Variant::DICTIONARY && argument_types[i].has_container_element_types()) {
const GDScriptDataType &arg_key_type = argument_types[i].get_container_element_type_or_variant(0);
const GDScriptDataType &arg_value_type = argument_types[i].get_container_element_type_or_variant(1);
Dictionary dict(p_args[i]->operator Dictionary(), arg_key_type.builtin_type, arg_key_type.native_type, arg_key_type.script_type, arg_value_type.builtin_type, arg_value_type.native_type, arg_value_type.script_type);
memnew_placement(&stack[i + 3], Variant(dict));
} else if (argument_types[i].builtin_type == Variant::ARRAY && argument_types[i].has_container_element_type(0)) {
const GDScriptDataType &arg_type = argument_types[i].container_element_types[0];
Array array(p_args[i]->operator Array(), arg_type.builtin_type, arg_type.native_type, arg_type.script_type);
memnew_placement(&stack[i + 3], Variant(array));
@ -637,7 +661,7 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a
OPCODE_BREAK; \
}
#else
#else // !DEBUG_ENABLED
#define GD_ERR_BREAK(m_cond)
#define CHECK_SPACE(m_space)
@ -650,7 +674,7 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a
OPCODE_BREAK; \
}
#endif
#endif // DEBUG_ENABLED
#define LOAD_INSTRUCTION_ARGS \
int instr_arg_count = _code_ptr[ip + 1]; \
@ -829,6 +853,36 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a
}
DISPATCH_OPCODE;
OPCODE(OPCODE_TYPE_TEST_DICTIONARY) {
CHECK_SPACE(9);
GET_VARIANT_PTR(dst, 0);
GET_VARIANT_PTR(value, 1);
GET_VARIANT_PTR(key_script_type, 2);
Variant::Type key_builtin_type = (Variant::Type)_code_ptr[ip + 5];
int key_native_type_idx = _code_ptr[ip + 6];
GD_ERR_BREAK(key_native_type_idx < 0 || key_native_type_idx >= _global_names_count);
const StringName key_native_type = _global_names_ptr[key_native_type_idx];
GET_VARIANT_PTR(value_script_type, 3);
Variant::Type value_builtin_type = (Variant::Type)_code_ptr[ip + 7];
int value_native_type_idx = _code_ptr[ip + 8];
GD_ERR_BREAK(value_native_type_idx < 0 || value_native_type_idx >= _global_names_count);
const StringName value_native_type = _global_names_ptr[value_native_type_idx];
bool result = false;
if (value->get_type() == Variant::DICTIONARY) {
Dictionary *dictionary = VariantInternal::get_dictionary(value);
result = dictionary->get_typed_key_builtin() == ((uint32_t)key_builtin_type) && dictionary->get_typed_key_class_name() == key_native_type && dictionary->get_typed_key_script() == *key_script_type &&
dictionary->get_typed_value_builtin() == ((uint32_t)value_builtin_type) && dictionary->get_typed_value_class_name() == value_native_type && dictionary->get_typed_value_script() == *value_script_type;
}
*dst = result;
ip += 9;
}
DISPATCH_OPCODE;
OPCODE(OPCODE_TYPE_TEST_NATIVE) {
CHECK_SPACE(4);
@ -1386,6 +1440,50 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a
}
DISPATCH_OPCODE;
OPCODE(OPCODE_ASSIGN_TYPED_DICTIONARY) {
CHECK_SPACE(9);
GET_VARIANT_PTR(dst, 0);
GET_VARIANT_PTR(src, 1);
GET_VARIANT_PTR(key_script_type, 2);
Variant::Type key_builtin_type = (Variant::Type)_code_ptr[ip + 5];
int key_native_type_idx = _code_ptr[ip + 6];
GD_ERR_BREAK(key_native_type_idx < 0 || key_native_type_idx >= _global_names_count);
const StringName key_native_type = _global_names_ptr[key_native_type_idx];
GET_VARIANT_PTR(value_script_type, 3);
Variant::Type value_builtin_type = (Variant::Type)_code_ptr[ip + 7];
int value_native_type_idx = _code_ptr[ip + 8];
GD_ERR_BREAK(value_native_type_idx < 0 || value_native_type_idx >= _global_names_count);
const StringName value_native_type = _global_names_ptr[value_native_type_idx];
if (src->get_type() != Variant::DICTIONARY) {
#ifdef DEBUG_ENABLED
err_text = vformat(R"(Trying to assign a value of type "%s" to a variable of type "Dictionary[%s, %s]".)",
_get_var_type(src), _get_element_type(key_builtin_type, key_native_type, *key_script_type),
_get_element_type(value_builtin_type, value_native_type, *value_script_type));
#endif // DEBUG_ENABLED
OPCODE_BREAK;
}
Dictionary *dictionary = VariantInternal::get_dictionary(src);
if (dictionary->get_typed_key_builtin() != ((uint32_t)key_builtin_type) || dictionary->get_typed_key_class_name() != key_native_type || dictionary->get_typed_key_script() != *key_script_type ||
dictionary->get_typed_value_builtin() != ((uint32_t)value_builtin_type) || dictionary->get_typed_value_class_name() != value_native_type || dictionary->get_typed_value_script() != *value_script_type) {
#ifdef DEBUG_ENABLED
err_text = vformat(R"(Trying to assign a dictionary of type "%s" to a variable of type "Dictionary[%s, %s]".)",
_get_var_type(src), _get_element_type(key_builtin_type, key_native_type, *key_script_type),
_get_element_type(value_builtin_type, value_native_type, *value_script_type));
#endif // DEBUG_ENABLED
OPCODE_BREAK;
}
*dst = *src;
ip += 9;
}
DISPATCH_OPCODE;
OPCODE(OPCODE_ASSIGN_TYPED_NATIVE) {
CHECK_SPACE(4);
GET_VARIANT_PTR(dst, 0);
@ -1608,7 +1706,7 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a
#ifdef DEBUG_ENABLED
if (err.error != Callable::CallError::CALL_OK) {
err_text = _get_call_error(err, "'" + Variant::get_type_name(t) + "' constructor", (const Variant **)argptrs);
err_text = _get_call_error("'" + Variant::get_type_name(t) + "' constructor", (const Variant **)argptrs, *dst, err);
OPCODE_BREAK;
}
#endif
@ -1705,12 +1803,51 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a
GET_INSTRUCTION_ARG(dst, argc * 2);
*dst = Variant(); // Clear potential previous typed dictionary.
*dst = dict;
ip += 2;
}
DISPATCH_OPCODE;
OPCODE(OPCODE_CONSTRUCT_TYPED_DICTIONARY) {
LOAD_INSTRUCTION_ARGS
CHECK_SPACE(6 + instr_arg_count);
ip += instr_arg_count;
int argc = _code_ptr[ip + 1];
GET_INSTRUCTION_ARG(key_script_type, argc * 2 + 1);
Variant::Type key_builtin_type = (Variant::Type)_code_ptr[ip + 2];
int key_native_type_idx = _code_ptr[ip + 3];
GD_ERR_BREAK(key_native_type_idx < 0 || key_native_type_idx >= _global_names_count);
const StringName key_native_type = _global_names_ptr[key_native_type_idx];
GET_INSTRUCTION_ARG(value_script_type, argc * 2 + 2);
Variant::Type value_builtin_type = (Variant::Type)_code_ptr[ip + 4];
int value_native_type_idx = _code_ptr[ip + 5];
GD_ERR_BREAK(value_native_type_idx < 0 || value_native_type_idx >= _global_names_count);
const StringName value_native_type = _global_names_ptr[value_native_type_idx];
Dictionary dict;
for (int i = 0; i < argc; i++) {
GET_INSTRUCTION_ARG(k, i * 2 + 0);
GET_INSTRUCTION_ARG(v, i * 2 + 1);
dict[*k] = *v;
}
GET_INSTRUCTION_ARG(dst, argc * 2);
*dst = Variant(); // Clear potential previous typed dictionary.
*dst = Dictionary(dict, key_builtin_type, key_native_type, *key_script_type, value_builtin_type, value_native_type, *value_script_type);
ip += 6;
}
DISPATCH_OPCODE;
OPCODE(OPCODE_CALL_ASYNC)
OPCODE(OPCODE_CALL_RETURN)
OPCODE(OPCODE_CALL) {
@ -1744,10 +1881,12 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a
StringName base_class = base_obj ? base_obj->get_class_name() : StringName();
#endif
Variant temp_ret;
Callable::CallError err;
if (call_ret) {
GET_INSTRUCTION_ARG(ret, argc + 1);
base->callp(*methodname, (const Variant **)argptrs, argc, *ret, err);
base->callp(*methodname, (const Variant **)argptrs, argc, temp_ret, err);
*ret = temp_ret;
#ifdef DEBUG_ENABLED
if (ret->get_type() == Variant::NIL) {
if (base_type == Variant::OBJECT) {
@ -1776,8 +1915,7 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a
}
#endif
} else {
Variant ret;
base->callp(*methodname, (const Variant **)argptrs, argc, ret, err);
base->callp(*methodname, (const Variant **)argptrs, argc, temp_ret, err);
}
#ifdef DEBUG_ENABLED
@ -1822,10 +1960,10 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a
}
}
}
err_text = _get_call_error(err, "function '" + methodstr + (is_callable ? "" : "' in base '" + basestr) + "'", (const Variant **)argptrs);
err_text = _get_call_error("function '" + methodstr + (is_callable ? "" : "' in base '" + basestr) + "'", (const Variant **)argptrs, temp_ret, err);
OPCODE_BREAK;
}
#endif
#endif // DEBUG_ENABLED
ip += 3;
}
@ -1868,12 +2006,14 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a
}
#endif
Variant temp_ret;
Callable::CallError err;
if (call_ret) {
GET_INSTRUCTION_ARG(ret, argc + 1);
*ret = method->call(base_obj, (const Variant **)argptrs, argc, err);
temp_ret = method->call(base_obj, (const Variant **)argptrs, argc, err);
*ret = temp_ret;
} else {
method->call(base_obj, (const Variant **)argptrs, argc, err);
temp_ret = method->call(base_obj, (const Variant **)argptrs, argc, err);
}
#ifdef DEBUG_ENABLED
@ -1906,7 +2046,7 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a
}
}
}
err_text = _get_call_error(err, "function '" + methodstr + "' in base '" + basestr + "'", (const Variant **)argptrs);
err_text = _get_call_error("function '" + methodstr + "' in base '" + basestr + "'", (const Variant **)argptrs, temp_ret, err);
OPCODE_BREAK;
}
#endif
@ -1939,7 +2079,7 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a
#ifdef DEBUG_ENABLED
if (err.error != Callable::CallError::CALL_OK) {
err_text = _get_call_error(err, "static function '" + methodname->operator String() + "' in type '" + Variant::get_type_name(builtin_type) + "'", argptrs);
err_text = _get_call_error("static function '" + methodname->operator String() + "' in type '" + Variant::get_type_name(builtin_type) + "'", argptrs, *ret, err);
OPCODE_BREAK;
}
#endif
@ -1983,7 +2123,7 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a
#endif
if (err.error != Callable::CallError::CALL_OK) {
err_text = _get_call_error(err, "static function '" + method->get_name().operator String() + "' in type '" + method->get_instance_class().operator String() + "'", argptrs);
err_text = _get_call_error("static function '" + method->get_name().operator String() + "' in type '" + method->get_instance_class().operator String() + "'", argptrs, *ret, err);
OPCODE_BREAK;
}
@ -2214,7 +2354,7 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a
// Call provided error string.
err_text = vformat(R"*(Error calling utility function "%s()": %s)*", methodstr, *dst);
} else {
err_text = _get_call_error(err, vformat(R"*(utility function "%s()")*", methodstr), (const Variant **)argptrs);
err_text = _get_call_error(vformat(R"*(utility function "%s()")*", methodstr), (const Variant **)argptrs, *dst, err);
}
OPCODE_BREAK;
}
@ -2271,7 +2411,7 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a
// Call provided error string.
err_text = vformat(R"*(Error calling GDScript utility function "%s()": %s)*", methodstr, *dst);
} else {
err_text = _get_call_error(err, vformat(R"*(GDScript utility function "%s()")*", methodstr), (const Variant **)argptrs);
err_text = _get_call_error(vformat(R"*(GDScript utility function "%s()")*", methodstr), (const Variant **)argptrs, *dst, err);
}
OPCODE_BREAK;
}
@ -2338,7 +2478,7 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a
if (err.error != Callable::CallError::CALL_OK) {
String methodstr = *methodname;
err_text = _get_call_error(err, "function '" + methodstr + "'", (const Variant **)argptrs);
err_text = _get_call_error("function '" + methodstr + "'", (const Variant **)argptrs, *dst, err);
OPCODE_BREAK;
}
@ -2650,6 +2790,51 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a
retvalue = *array;
#ifdef DEBUG_ENABLED
exit_ok = true;
#endif // DEBUG_ENABLED
OPCODE_BREAK;
}
OPCODE(OPCODE_RETURN_TYPED_DICTIONARY) {
CHECK_SPACE(8);
GET_VARIANT_PTR(r, 0);
GET_VARIANT_PTR(key_script_type, 1);
Variant::Type key_builtin_type = (Variant::Type)_code_ptr[ip + 4];
int key_native_type_idx = _code_ptr[ip + 5];
GD_ERR_BREAK(key_native_type_idx < 0 || key_native_type_idx >= _global_names_count);
const StringName key_native_type = _global_names_ptr[key_native_type_idx];
GET_VARIANT_PTR(value_script_type, 2);
Variant::Type value_builtin_type = (Variant::Type)_code_ptr[ip + 6];
int value_native_type_idx = _code_ptr[ip + 7];
GD_ERR_BREAK(value_native_type_idx < 0 || value_native_type_idx >= _global_names_count);
const StringName value_native_type = _global_names_ptr[value_native_type_idx];
if (r->get_type() != Variant::DICTIONARY) {
#ifdef DEBUG_ENABLED
err_text = vformat(R"(Trying to return a value of type "%s" where expected return type is "Dictionary[%s, %s]".)",
_get_var_type(r), _get_element_type(key_builtin_type, key_native_type, *key_script_type),
_get_element_type(value_builtin_type, value_native_type, *value_script_type));
#endif // DEBUG_ENABLED
OPCODE_BREAK;
}
Dictionary *dictionary = VariantInternal::get_dictionary(r);
if (dictionary->get_typed_key_builtin() != ((uint32_t)key_builtin_type) || dictionary->get_typed_key_class_name() != key_native_type || dictionary->get_typed_key_script() != *key_script_type ||
dictionary->get_typed_value_builtin() != ((uint32_t)value_builtin_type) || dictionary->get_typed_value_class_name() != value_native_type || dictionary->get_typed_value_script() != *value_script_type) {
#ifdef DEBUG_ENABLED
err_text = vformat(R"(Trying to return a dictionary of type "%s" where expected return type is "Dictionary[%s, %s]".)",
_get_var_type(r), _get_element_type(key_builtin_type, key_native_type, *key_script_type),
_get_element_type(value_builtin_type, value_native_type, *value_script_type));
#endif // DEBUG_ENABLED
OPCODE_BREAK;
}
retvalue = *dictionary;
#ifdef DEBUG_ENABLED
exit_ok = true;
#endif // DEBUG_ENABLED

View file

@ -61,10 +61,13 @@ String GDScriptWarning::get_message() const {
return vformat(R"(The signal "%s" is declared but never explicitly used in the class.)", symbols[0]);
case SHADOWED_VARIABLE:
CHECK_SYMBOLS(4);
return vformat(R"(The local %s "%s" is shadowing an already-declared %s at line %s.)", symbols[0], symbols[1], symbols[2], symbols[3]);
return vformat(R"(The local %s "%s" is shadowing an already-declared %s at line %s in the current class.)", symbols[0], symbols[1], symbols[2], symbols[3]);
case SHADOWED_VARIABLE_BASE_CLASS:
CHECK_SYMBOLS(4);
return vformat(R"(The local %s "%s" is shadowing an already-declared %s at the base class "%s".)", symbols[0], symbols[1], symbols[2], symbols[3]);
if (symbols.size() > 4) {
return vformat(R"(The local %s "%s" is shadowing an already-declared %s at line %s in the base class "%s".)", symbols[0], symbols[1], symbols[2], symbols[3], symbols[4]);
}
return vformat(R"(The local %s "%s" is shadowing an already-declared %s in the base class "%s".)", symbols[0], symbols[1], symbols[2], symbols[3]);
case SHADOWED_GLOBAL_IDENTIFIER:
CHECK_SYMBOLS(3);
return vformat(R"(The %s "%s" has the same name as a %s.)", symbols[0], symbols[1], symbols[2]);
@ -109,6 +112,8 @@ String GDScriptWarning::get_message() const {
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 MISSING_TOOL:
return R"(The base class script has the "@tool" annotation, but this script does not have it.)";
case REDUNDANT_STATIC_UNLOAD:
return R"(The "@static_unload" annotation is redundant because the file does not have a class with static variables.)";
case REDUNDANT_AWAIT:
@ -134,8 +139,6 @@ String GDScriptWarning::get_message() const {
case DEPRECATED_KEYWORD:
CHECK_SYMBOLS(2);
return vformat(R"(The "%s" keyword is deprecated and will be removed in a future release, please replace its uses by "%s".)", symbols[0], symbols[1]);
case RENAMED_IN_GODOT_4_HINT:
break; // Renamed identifier hint is taken care of by the GDScriptAnalyzer. No message needed here.
case CONFUSABLE_IDENTIFIER:
CHECK_SYMBOLS(1);
return vformat(R"(The identifier "%s" has misleading characters and might be confused with something else.)", symbols[0]);
@ -180,10 +183,6 @@ int GDScriptWarning::get_default_value(Code p_code) {
}
PropertyInfo GDScriptWarning::get_property_info(Code p_code) {
// Making this a separate function in case a warning needs different PropertyInfo in the future.
if (p_code == Code::RENAMED_IN_GODOT_4_HINT) {
return PropertyInfo(Variant::BOOL, get_settings_path_from_code(p_code));
}
return PropertyInfo(Variant::INT, get_settings_path_from_code(p_code), PROPERTY_HINT_ENUM, "Ignore,Warn,Error");
}
@ -219,6 +218,7 @@ String GDScriptWarning::get_name_from_code(Code p_code) {
"UNSAFE_VOID_RETURN",
"RETURN_VALUE_DISCARDED",
"STATIC_CALLED_ON_INSTANCE",
"MISSING_TOOL",
"REDUNDANT_STATIC_UNLOAD",
"REDUNDANT_AWAIT",
"ASSERT_ALWAYS_TRUE",
@ -230,7 +230,6 @@ String GDScriptWarning::get_name_from_code(Code p_code) {
"ENUM_VARIABLE_WITHOUT_DEFAULT",
"EMPTY_FILE",
"DEPRECATED_KEYWORD",
"RENAMED_IN_GODOT_4_HINT",
"CONFUSABLE_IDENTIFIER",
"CONFUSABLE_LOCAL_DECLARATION",
"CONFUSABLE_LOCAL_USAGE",

View file

@ -53,8 +53,8 @@ public:
UNUSED_PRIVATE_CLASS_VARIABLE, // Class variable is declared private ("_" prefix) but never used in the class.
UNUSED_PARAMETER, // Function parameter is never used.
UNUSED_SIGNAL, // Signal is defined but never explicitly used in the class.
SHADOWED_VARIABLE, // Variable name shadowed by other variable in same class.
SHADOWED_VARIABLE_BASE_CLASS, // Variable name shadowed by other variable in some base class.
SHADOWED_VARIABLE, // A local variable/constant shadows a current class member.
SHADOWED_VARIABLE_BASE_CLASS, // A local variable/constant shadows a base class member.
SHADOWED_GLOBAL_IDENTIFIER, // A global class or function has the same name as variable.
UNREACHABLE_CODE, // Code after a return statement.
UNREACHABLE_PATTERN, // Pattern in a match statement after a catch all pattern (wildcard or bind).
@ -70,6 +70,7 @@ public:
UNSAFE_VOID_RETURN, // Function returns void but returned a call to a function that can't be type checked.
RETURN_VALUE_DISCARDED, // Function call returns something but the value isn't used.
STATIC_CALLED_ON_INSTANCE, // A static method was called on an instance of a class instead of on the class itself.
MISSING_TOOL, // The base class script has the "@tool" annotation, but this script does not have it.
REDUNDANT_STATIC_UNLOAD, // The `@static_unload` annotation is used but the class does not have static data.
REDUNDANT_AWAIT, // await is used but expression is synchronous (not a signal nor a coroutine).
ASSERT_ALWAYS_TRUE, // Expression for assert argument is always true.
@ -81,7 +82,6 @@ public:
ENUM_VARIABLE_WITHOUT_DEFAULT, // A variable with an enum type does not have a default value. The default will be set to `0` instead of the first enum value.
EMPTY_FILE, // A script file is empty.
DEPRECATED_KEYWORD, // The keyword is deprecated and should be replaced.
RENAMED_IN_GODOT_4_HINT, // A variable or function that could not be found has been renamed in Godot 4.
CONFUSABLE_IDENTIFIER, // The identifier contains misleading characters that can be confused. E.g. "usеr" (has Cyrillic "е" instead of Latin "e").
CONFUSABLE_LOCAL_DECLARATION, // The parent block declares an identifier with the same name below.
CONFUSABLE_LOCAL_USAGE, // The identifier will be shadowed below in the block.
@ -98,6 +98,10 @@ public:
WARNING_MAX,
};
#ifndef DISABLE_DEPRECATED
static constexpr int FIRST_DEPRECATED_WARNING = PROPERTY_USED_AS_FUNCTION;
#endif
constexpr static WarnLevel default_warning_levels[] = {
WARN, // UNASSIGNED_VARIABLE
WARN, // UNASSIGNED_VARIABLE_OP_ASSIGN
@ -123,6 +127,7 @@ public:
WARN, // UNSAFE_VOID_RETURN
IGNORE, // RETURN_VALUE_DISCARDED // Too spammy by default on common cases (connect, Tween, etc.).
WARN, // STATIC_CALLED_ON_INSTANCE
WARN, // MISSING_TOOL
WARN, // REDUNDANT_STATIC_UNLOAD
WARN, // REDUNDANT_AWAIT
WARN, // ASSERT_ALWAYS_TRUE
@ -134,7 +139,6 @@ public:
WARN, // ENUM_VARIABLE_WITHOUT_DEFAULT
WARN, // EMPTY_FILE
WARN, // DEPRECATED_KEYWORD
WARN, // RENAMED_IN_GODOT_4_HINT
WARN, // CONFUSABLE_IDENTIFIER
WARN, // CONFUSABLE_LOCAL_DECLARATION
WARN, // CONFUSABLE_LOCAL_USAGE

View file

@ -57,11 +57,17 @@ lsp::Position GodotPosition::to_lsp(const Vector<String> &p_lines) const {
return res;
}
res.line = line - 1;
// Special case: `column = 0` -> Starts at beginning of line.
if (column <= 0) {
return res;
}
// Note: character outside of `pos_line.length()-1` is valid.
res.character = column - 1;
String pos_line = p_lines[res.line];
if (pos_line.contains("\t")) {
if (pos_line.contains_char('\t')) {
int tab_size = get_indent_size();
int in_col = 1;
@ -238,9 +244,12 @@ void ExtendGDScriptParser::parse_class_symbol(const GDScriptParser::ClassNode *p
r_symbol.kind = lsp::SymbolKind::Class;
r_symbol.deprecated = false;
r_symbol.range = range_of_node(p_class);
r_symbol.range.start.line = MAX(r_symbol.range.start.line, 0);
if (p_class->identifier) {
r_symbol.selectionRange = range_of_node(p_class->identifier);
} else {
// No meaningful `selectionRange`, but we must ensure that it is inside of `range`.
r_symbol.selectionRange.start = r_symbol.range.start;
r_symbol.selectionRange.end = r_symbol.range.start;
}
r_symbol.detail = "class " + r_symbol.name;
{

View file

@ -37,18 +37,18 @@
#include "core/variant/variant.h"
#ifndef LINE_NUMBER_TO_INDEX
#define LINE_NUMBER_TO_INDEX(p_line) ((p_line)-1)
#define LINE_NUMBER_TO_INDEX(p_line) ((p_line) - 1)
#endif
#ifndef COLUMN_NUMBER_TO_INDEX
#define COLUMN_NUMBER_TO_INDEX(p_column) ((p_column)-1)
#define COLUMN_NUMBER_TO_INDEX(p_column) ((p_column) - 1)
#endif
#ifndef SYMBOL_SEPERATOR
#define SYMBOL_SEPERATOR "::"
#ifndef SYMBOL_SEPARATOR
#define SYMBOL_SEPARATOR "::"
#endif
#ifndef JOIN_SYMBOLS
#define JOIN_SYMBOLS(p_path, name) ((p_path) + SYMBOL_SEPERATOR + (name))
#define JOIN_SYMBOLS(p_path, name) ((p_path) + SYMBOL_SEPARATOR + (name))
#endif
typedef HashMap<String, const lsp::DocumentSymbol *> ClassMembers;

View file

@ -196,7 +196,7 @@ Dictionary GDScriptLanguageProtocol::initialize(const Dictionary &p_params) {
ERR_FAIL_COND_V_MSG(!clients.has(latest_client_id), ret.to_json(),
vformat("GDScriptLanguageProtocol: Can't initialize invalid peer '%d'.", latest_client_id));
Ref<LSPeer> peer = clients.get(latest_client_id);
if (peer != nullptr) {
if (peer.is_valid()) {
String msg = Variant(request).to_json_string();
msg = format_output(msg);
(*peer)->res_queue.push_back(msg.utf8());
@ -298,7 +298,7 @@ void GDScriptLanguageProtocol::notify_client(const String &p_method, const Varia
}
ERR_FAIL_COND(!clients.has(p_client_id));
Ref<LSPeer> peer = clients.get(p_client_id);
ERR_FAIL_NULL(peer);
ERR_FAIL_COND(peer.is_null());
Dictionary message = make_notification(p_method, p_params);
String msg = Variant(message).to_json_string();
@ -319,7 +319,7 @@ void GDScriptLanguageProtocol::request_client(const String &p_method, const Vari
}
ERR_FAIL_COND(!clients.has(p_client_id));
Ref<LSPeer> peer = clients.get(p_client_id);
ERR_FAIL_NULL(peer);
ERR_FAIL_COND(peer.is_null());
Dictionary message = make_request(p_method, p_params, next_server_id);
next_server_id++;

View file

@ -33,9 +33,7 @@
#include "gdscript_text_document.h"
#include "gdscript_workspace.h"
#include "godot_lsp.h"
#include "core/io/stream_peer.h"
#include "core/io/stream_peer_tcp.h"
#include "core/io/tcp_server.h"

View file

@ -30,7 +30,6 @@
#include "gdscript_language_server.h"
#include "core/io/file_access.h"
#include "core/os/os.h"
#include "editor/editor_log.h"
#include "editor/editor_node.h"
@ -39,6 +38,7 @@
int GDScriptLanguageServer::port_override = -1;
GDScriptLanguageServer::GDScriptLanguageServer() {
// TODO: Move to editor_settings.cpp
_EDITOR_DEF("network/language_server/remote_host", host);
_EDITOR_DEF("network/language_server/remote_port", port);
_EDITOR_DEF("network/language_server/enable_smart_resolve", true);

View file

@ -31,7 +31,6 @@
#ifndef GDSCRIPT_LANGUAGE_SERVER_H
#define GDSCRIPT_LANGUAGE_SERVER_H
#include "../gdscript_parser.h"
#include "gdscript_language_protocol.h"
#include "editor/plugins/editor_plugin.h"

View file

@ -34,7 +34,6 @@
#include "gdscript_extend_parser.h"
#include "gdscript_language_protocol.h"
#include "core/os/os.h"
#include "editor/editor_settings.h"
#include "editor/plugins/script_text_editor.h"
#include "servers/display_server.h"
@ -112,12 +111,21 @@ void GDScriptTextDocument::didSave(const Variant &p_param) {
}
scr->update_exports();
ScriptEditor::get_singleton()->reload_scripts(true);
ScriptEditor::get_singleton()->update_docs_from_script(scr);
ScriptEditor::get_singleton()->trigger_live_script_reload(scr->get_path());
if (!Thread::is_main_thread()) {
callable_mp(this, &GDScriptTextDocument::reload_script).call_deferred(scr);
} else {
reload_script(scr);
}
}
}
void GDScriptTextDocument::reload_script(Ref<GDScript> p_to_reload_script) {
ScriptEditor::get_singleton()->reload_scripts(true);
ScriptEditor::get_singleton()->update_docs_from_script(p_to_reload_script);
ScriptEditor::get_singleton()->trigger_live_script_reload(p_to_reload_script->get_path());
}
lsp::TextDocumentItem GDScriptTextDocument::load_document_item(const Variant &p_param) {
lsp::TextDocumentItem doc;
Dictionary params = p_param;
@ -229,19 +237,6 @@ Array GDScriptTextDocument::completion(const Dictionary &p_params) {
arr[i] = item.to_json();
i++;
}
} else if (GDScriptLanguageProtocol::get_singleton()->is_smart_resolve_enabled()) {
arr = native_member_completions.duplicate();
for (KeyValue<String, ExtendGDScriptParser *> &E : GDScriptLanguageProtocol::get_singleton()->get_workspace()->scripts) {
ExtendGDScriptParser *scr = E.value;
const Array &items = scr->get_member_completions();
const int start_size = arr.size();
arr.resize(start_size + items.size());
for (int i = start_size; i < arr.size(); i++) {
arr[i] = items[i - start_size];
}
}
}
return arr;
}
@ -309,10 +304,10 @@ Dictionary GDScriptTextDocument::resolve(const Dictionary &p_params) {
params.load(p_params["data"]);
symbol = GDScriptLanguageProtocol::get_singleton()->get_workspace()->resolve_symbol(params, item.label, item.kind == lsp::CompletionItemKind::Method || item.kind == lsp::CompletionItemKind::Function);
} else if (data.get_type() == Variant::STRING) {
} else if (data.is_string()) {
String query = data;
Vector<String> param_symbols = query.split(SYMBOL_SEPERATOR, false);
Vector<String> param_symbols = query.split(SYMBOL_SEPARATOR, false);
if (param_symbols.size() >= 2) {
StringName class_name = param_symbols[0];
@ -485,8 +480,6 @@ GDScriptTextDocument::GDScriptTextDocument() {
void GDScriptTextDocument::sync_script_content(const String &p_path, const String &p_content) {
String path = GDScriptLanguageProtocol::get_singleton()->get_workspace()->get_file_path(p_path);
GDScriptLanguageProtocol::get_singleton()->get_workspace()->parse_script(path, p_content);
EditorFileSystem::get_singleton()->update_file(path);
}
void GDScriptTextDocument::show_native_symbol_in_editor(const String &p_symbol_id) {

View file

@ -36,6 +36,8 @@
#include "core/io/file_access.h"
#include "core/object/ref_counted.h"
class GDScript;
class GDScriptTextDocument : public RefCounted {
GDCLASS(GDScriptTextDocument, RefCounted)
protected:
@ -49,6 +51,7 @@ protected:
void willSaveWaitUntil(const Variant &p_param);
void didSave(const Variant &p_param);
void reload_script(Ref<GDScript> p_to_reload_script);
void sync_script_content(const String &p_path, const String &p_content);
void show_native_symbol_in_editor(const String &p_symbol_id);

View file

@ -657,7 +657,7 @@ void GDScriptWorkspace::completion(const lsp::CompletionParams &p_params, List<S
}
Ref<GDScript> scr = current->get_script();
if (!scr.is_valid() || !GDScript::is_canonically_equal_paths(scr->get_path(), path)) {
if (scr.is_null() || !GDScript::is_canonically_equal_paths(scr->get_path(), path)) {
current = owner_scene_node;
}
}
@ -699,12 +699,12 @@ const lsp::DocumentSymbol *GDScriptWorkspace::resolve_symbol(const lsp::TextDocu
symbol_identifier = "_init";
}
if (OK == GDScriptLanguage::get_singleton()->lookup_code(parser->get_text_for_lookup_symbol(pos, symbol_identifier, p_func_required), symbol_identifier, path, nullptr, ret)) {
if (ret.type == ScriptLanguage::LOOKUP_RESULT_SCRIPT_LOCATION) {
if (ret.location >= 0) {
String target_script_path = path;
if (!ret.script.is_null()) {
if (ret.script.is_valid()) {
target_script_path = ret.script->get_path();
} else if (!ret.class_path.is_empty()) {
target_script_path = ret.class_path;
} else if (!ret.script_path.is_empty()) {
target_script_path = ret.script_path;
}
if (const ExtendGDScriptParser *target_parser = get_parse_result(target_script_path)) {
@ -720,7 +720,6 @@ const lsp::DocumentSymbol *GDScriptWorkspace::resolve_symbol(const lsp::TextDocu
}
}
}
} else {
String member = ret.class_member;
if (member.is_empty() && symbol_identifier != ret.class_name) {

View file

@ -958,28 +958,30 @@ struct CompletionItem {
/**
* A string that should be used when comparing this item
* with other items. When `falsy` the label is used.
* with other items. When omitted the label is used
* as the filter text for this item.
*/
String sortText;
/**
* A string that should be used when filtering a set of
* completion items. When `falsy` the label is used.
* completion items. When omitted the label is used as the
* filter text for this item.
*/
String filterText;
/**
* A string that should be inserted into a document when selecting
* this completion. When `falsy` the label is used.
* this completion. When omitted the label is used as the insert text
* for this item.
*
* The `insertText` is subject to interpretation by the client side.
* Some tools might not take the string literally. For example
* VS Code when code complete is requested in this example `con<cursor position>`
* and a completion item with an `insertText` of `console` is provided it
* will only insert `sole`. Therefore it is recommended to use `textEdit` instead
* since it avoids additional client side interpretation.
*
* @deprecated Use textEdit instead.
* VS Code when code complete is requested in this example
* `con<cursor position>` and a completion item with an `insertText` of
* `console` is provided it will only insert `sole`. Therefore it is
* recommended to use `textEdit` instead since it avoids additional client
* side interpretation.
*/
String insertText;
@ -1034,14 +1036,20 @@ struct CompletionItem {
dict["label"] = label;
dict["kind"] = kind;
dict["data"] = data;
dict["insertText"] = insertText;
if (!insertText.is_empty()) {
dict["insertText"] = insertText;
}
if (resolved) {
dict["detail"] = detail;
dict["documentation"] = documentation.to_json();
dict["deprecated"] = deprecated;
dict["preselect"] = preselect;
dict["sortText"] = sortText;
dict["filterText"] = filterText;
if (!sortText.is_empty()) {
dict["sortText"] = sortText;
}
if (!filterText.is_empty()) {
dict["filterText"] = filterText;
}
if (commitCharacters.size()) {
dict["commitCharacters"] = commitCharacters;
}
@ -1064,7 +1072,7 @@ struct CompletionItem {
}
if (p_dict.has("documentation")) {
Variant doc = p_dict["documentation"];
if (doc.get_type() == Variant::STRING) {
if (doc.is_string()) {
documentation.value = doc;
} else if (doc.get_type() == Variant::DICTIONARY) {
Dictionary v = doc;

View file

@ -31,9 +31,8 @@
#include "register_types.h"
#include "gdscript.h"
#include "gdscript_analyzer.h"
#include "gdscript_cache.h"
#include "gdscript_tokenizer.h"
#include "gdscript_parser.h"
#include "gdscript_tokenizer_buffer.h"
#include "gdscript_utility_functions.h"
@ -50,14 +49,11 @@
#include "tests/test_gdscript.h"
#endif
#include "core/io/dir_access.h"
#include "core/io/file_access.h"
#include "core/io/file_access_encrypted.h"
#include "core/io/resource_loader.h"
#ifdef TOOLS_ENABLED
#include "editor/editor_node.h"
#include "editor/editor_settings.h"
#include "editor/editor_translation_parser.h"
#include "editor/export/editor_export.h"
@ -165,6 +161,13 @@ void initialize_gdscript_module(ModuleInitializationLevel p_level) {
gdscript_translation_parser_plugin.instantiate();
EditorTranslationParser::get_singleton()->add_parser(gdscript_translation_parser_plugin, EditorTranslationParser::STANDARD);
} else if (p_level == MODULE_INITIALIZATION_LEVEL_EDITOR) {
ClassDB::APIType prev_api = ClassDB::get_current_api();
ClassDB::set_current_api(ClassDB::API_EDITOR);
GDREGISTER_CLASS(GDScriptSyntaxHighlighter);
ClassDB::set_current_api(prev_api);
}
#endif // TOOLS_ENABLED
}

View file

@ -78,7 +78,7 @@ void init_autoloads() {
scn.instantiate();
scn->set_path(info.path);
scn->reload_from_file();
ERR_CONTINUE_MSG(!scn.is_valid(), vformat("Failed to instantiate an autoload, can't load from path: %s.", info.path));
ERR_CONTINUE_MSG(scn.is_null(), vformat("Failed to instantiate an autoload, can't load from path: %s.", info.path));
if (scn.is_valid()) {
n = scn->instantiate();
@ -176,7 +176,7 @@ static String strip_warnings(const String &p_expected) {
// so it doesn't fail just because of difference in warnings.
String expected_no_warnings;
for (String line : p_expected.split("\n")) {
if (line.begins_with(">> ")) {
if (line.begins_with("~~ ")) {
continue;
}
expected_no_warnings += line + "\n";
@ -275,6 +275,7 @@ bool GDScriptTestRunner::make_tests_for_dir(const String &p_dir) {
return false;
}
} else {
// `*.notest.gd` files are skipped.
if (next.ends_with(".notest.gd")) {
next = dir->get_next();
continue;
@ -367,7 +368,9 @@ static bool generate_class_index_recursive(const String &p_dir) {
}
String base_type;
String source_file = current_dir.path_join(next);
String class_name = GDScriptLanguage::get_singleton()->get_global_class_name(source_file, &base_type);
bool is_abstract = false;
bool is_tool = false;
String class_name = GDScriptLanguage::get_singleton()->get_global_class_name(source_file, &base_type, nullptr, &is_abstract, &is_tool);
if (class_name.is_empty()) {
next = dir->get_next();
continue;
@ -375,7 +378,7 @@ static bool generate_class_index_recursive(const String &p_dir) {
ERR_FAIL_COND_V_MSG(ScriptServer::is_global_class(class_name), false,
"Class name '" + class_name + "' from " + source_file + " is already used in " + ScriptServer::get_global_class_path(class_name));
ScriptServer::add_global_class(class_name, base_type, gdscript_name, source_file);
ScriptServer::add_global_class(class_name, base_type, gdscript_name, source_file, is_abstract, is_tool);
}
next = dir->get_next();
@ -450,47 +453,43 @@ void GDScriptTest::error_handler(void *p_this, const char *p_function, const cha
result->status = GDTEST_RUNTIME_ERROR;
StringBuilder builder;
builder.append(">> ");
// Only include the function, file and line for script errors, otherwise the
// test outputs changes based on the platform/compiler.
// Only include the file, line, and function for script errors,
// otherwise the test outputs changes based on the platform/compiler.
String header;
bool include_source_info = false;
switch (p_type) {
case ERR_HANDLER_ERROR:
builder.append("ERROR");
header = "ERROR";
break;
case ERR_HANDLER_WARNING:
builder.append("WARNING");
header = "WARNING";
break;
case ERR_HANDLER_SCRIPT:
builder.append("SCRIPT ERROR");
header = "SCRIPT ERROR";
include_source_info = true;
break;
case ERR_HANDLER_SHADER:
builder.append("SHADER ERROR");
header = "SHADER ERROR";
break;
default:
builder.append("Unknown error type");
header = "UNKNOWN ERROR";
break;
}
if (include_source_info) {
builder.append("\n>> on function: ");
builder.append(String::utf8(p_function));
builder.append("()\n>> ");
builder.append(String::utf8(p_file).trim_prefix(self->base_dir).replace("\\", "/"));
builder.append("\n>> ");
builder.append(itos(p_line));
header += vformat(" at %s:%d on %s()",
String::utf8(p_file).trim_prefix(self->base_dir).replace("\\", "/"),
p_line,
String::utf8(p_function));
}
builder.append("\n>> ");
builder.append(String::utf8(p_error));
if (strlen(p_explanation) > 0) {
builder.append("\n>> ");
builder.append(String::utf8(p_explanation));
}
builder.append("\n");
result->output = builder.as_string();
StringBuilder error_string;
error_string.append(vformat(">> %s: %s\n", header, String::utf8(p_error)));
if (strlen(p_explanation) > 0) {
error_string.append(vformat(">> %s\n", String::utf8(p_explanation)));
}
result->output += error_string.as_string();
}
bool GDScriptTest::check_output(const String &p_output) const {
@ -588,11 +587,11 @@ GDScriptTest::TestResult GDScriptTest::execute_test_code(bool p_is_generating) {
result.status = GDTEST_ANALYZER_ERROR;
result.output = get_text_for_status(result.status) + "\n";
const List<GDScriptParser::ParserError> &errors = parser.get_errors();
if (!errors.is_empty()) {
// Only the first error since the following might be cascading.
result.output += errors.front()->get().message + "\n"; // TODO: line, column?
StringBuilder error_string;
for (const GDScriptParser::ParserError &error : parser.get_errors()) {
error_string.append(vformat(">> ERROR at line %d: %s\n", error.line, error.message));
}
result.output += error_string.as_string();
if (!p_is_generating) {
result.passed = check_output(result.output);
}
@ -601,16 +600,8 @@ GDScriptTest::TestResult GDScriptTest::execute_test_code(bool p_is_generating) {
#ifdef DEBUG_ENABLED
StringBuilder warning_string;
for (const GDScriptWarning &E : parser.get_warnings()) {
const GDScriptWarning warning = E;
warning_string.append(">> WARNING");
warning_string.append("\n>> Line: ");
warning_string.append(itos(warning.start_line));
warning_string.append("\n>> ");
warning_string.append(warning.get_name());
warning_string.append("\n>> ");
warning_string.append(warning.get_message());
warning_string.append("\n");
for (const GDScriptWarning &warning : parser.get_warnings()) {
warning_string.append(vformat("~~ WARNING at line %d: (%s) %s\n", warning.start_line, warning.get_name(), warning.get_message()));
}
result.output += warning_string.as_string();
#endif
@ -628,12 +619,18 @@ GDScriptTest::TestResult GDScriptTest::execute_test_code(bool p_is_generating) {
}
return result;
}
// Script files matching this pattern are allowed to not contain a test() function.
if (source_file.match("*.notest.gd")) {
// `*.norun.gd` files are allowed to not contain a `test()` function (no runtime testing).
if (source_file.ends_with(".norun.gd")) {
enable_stdout();
result.passed = check_output(result.output);
result.status = GDTEST_OK;
result.output = get_text_for_status(result.status) + "\n" + result.output;
if (!p_is_generating) {
result.passed = check_output(result.output);
}
return result;
}
// Test running.
const HashMap<StringName, GDScriptFunction *>::ConstIterator test_function_element = script->get_member_functions().find(GDScriptTestRunner::test_function_name);
if (!test_function_element) {

View file

@ -0,0 +1,11 @@
# Some tests handle invalid syntax deliberately; exclude relevant attributes.
# See also the `file-format` section in `.pre-commit-config.yaml`.
[parser/features/mixed_indentation_on_blank_lines.gd]
trim_trailing_whitespace = false
[parser/warnings/empty_file_newline.norun.gd]
insert_final_newline = false
[parser/warnings/empty_file_newline_comment.norun.gd]
insert_final_newline = false

View file

@ -1,2 +1,10 @@
class A extends InstancePlaceholder:
func _init():
print('no')
class B extends A:
pass
func test():
InstancePlaceholder.new()
B.new()

View file

@ -1,2 +1,5 @@
GDTEST_ANALYZER_ERROR
Native class "InstancePlaceholder" cannot be constructed as it is abstract.
>> ERROR at line 9: Native class "InstancePlaceholder" cannot be constructed as it is abstract.
>> ERROR at line 9: Name "new" is a Callable. You can call it with "new.call()" instead.
>> ERROR at line 10: Class "abstract_class_instantiate.gd::B" cannot be constructed as it is based on abstract native class "InstancePlaceholder".
>> ERROR at line 10: Name "new" is a Callable. You can call it with "new.call()" instead.

View file

@ -1,9 +0,0 @@
class A extends InstancePlaceholder:
func _init():
print('no')
class B extends A:
pass
func test():
B.new()

View file

@ -1,2 +0,0 @@
GDTEST_ANALYZER_ERROR
Class "abstract_script_instantiate.gd::B" cannot be constructed as it is based on abstract native class "InstancePlaceholder".

View file

@ -1,2 +1,2 @@
GDTEST_ANALYZER_ERROR
Argument 1 of annotation "@export_range" isn't a constant expression.
>> ERROR at line 3: Argument 1 of annotation "@export_range" isn't a constant expression.

View file

@ -1,2 +1,2 @@
GDTEST_ANALYZER_ERROR
Cannot assign a new value to a constant.
>> ERROR at line 3: Cannot assign a new value to a constant.

View file

@ -1,2 +1,2 @@
GDTEST_ANALYZER_ERROR
Cannot assign a new value to a constant.
>> ERROR at line 3: Cannot assign a new value to a constant.

View file

@ -1,2 +1,2 @@
GDTEST_ANALYZER_ERROR
Cannot assign a new value to a constant.
>> ERROR at line 4: Cannot assign a new value to a constant.

View file

@ -1,2 +1,2 @@
GDTEST_ANALYZER_ERROR
Cannot assign a new value to a read-only property.
>> ERROR at line 3: Cannot assign a new value to a read-only property.

View file

@ -1,2 +1,2 @@
GDTEST_ANALYZER_ERROR
Cannot assign a new value to a read-only property.
>> ERROR at line 3: Cannot assign a new value to a read-only property.

View file

@ -1,2 +1,3 @@
GDTEST_ANALYZER_ERROR
Cannot assign a value of type "Color" as "String".
>> ERROR at line 2: Cannot assign a value of type "Color" as "String".
>> ERROR at line 2: Cannot assign a value of type Color to variable "var_color" with specified type String.

View file

@ -1,2 +1,2 @@
GDTEST_ANALYZER_ERROR
Cannot infer the type of "_a" variable because the value doesn't have a set type.
>> ERROR at line 4: Cannot infer the type of "_a" variable because the value doesn't have a set type.

View file

@ -1,2 +1,2 @@
GDTEST_ANALYZER_ERROR
Invalid operands to operator <<, float and int.
>> ERROR at line 3: Invalid operands to operator <<, float and int.

View file

@ -1,2 +1,2 @@
GDTEST_ANALYZER_ERROR
Invalid operands to operator >>, int and float.
>> ERROR at line 3: Invalid operands to operator >>, int and float.

View file

@ -1,2 +1,2 @@
GDTEST_ANALYZER_ERROR
Static function "not_existing_method()" not found in base "MyClass".
>> ERROR at line 7: Static function "not_existing_method()" not found in base "MyClass".

View file

@ -1,2 +1,2 @@
GDTEST_ANALYZER_ERROR
Invalid cast. Cannot convert from "int" to "Array".
>> ERROR at line 3: Invalid cast. Cannot convert from "int" to "Array".

View file

@ -1,2 +1,2 @@
GDTEST_ANALYZER_ERROR
Invalid cast. Cannot convert from "int" to "Node".
>> ERROR at line 3: Invalid cast. Cannot convert from "int" to "Node".

View file

@ -1,2 +1,2 @@
GDTEST_ANALYZER_ERROR
Invalid cast. Cannot convert from "RefCounted" to "int".
>> ERROR at line 3: Invalid cast. Cannot convert from "RefCounted" to "int".

View file

@ -1,2 +1,2 @@
GDTEST_ANALYZER_ERROR
Class "Vector2" hides a built-in type.
>> ERROR at line 1: Class "Vector2" hides a built-in type.

View file

@ -1,2 +1,2 @@
GDTEST_ANALYZER_ERROR
Cannot assign a new value to a constant.
>> ERROR at line 5: Cannot assign a new value to a constant.

View file

@ -1,2 +1,2 @@
GDTEST_ANALYZER_ERROR
Cannot assign a new value to a constant.
>> ERROR at line 5: Cannot assign a new value to a constant.

View file

@ -1,2 +1,2 @@
GDTEST_ANALYZER_ERROR
The member "Vector2" cannot have the same name as a builtin type.
>> ERROR at line 1: The member "Vector2" cannot have the same name as a builtin type.

View file

@ -1,2 +1,2 @@
GDTEST_ANALYZER_ERROR
Expression is of type "int" so it can't be of type "String".
>> ERROR at line 5: Expression is of type "int" so it can't be of type "String".

View file

@ -1,2 +1,3 @@
GDTEST_ANALYZER_ERROR
Member "CONSTANT" is not a function.
>> ERROR at line 5: Member "CONSTANT" is not a function.
>> ERROR at line 5: Name "CONSTANT" called as a function but is a "int".

View file

@ -1,2 +1,2 @@
GDTEST_ANALYZER_ERROR
Expression is of type "B" so it can't be of type "C".
>> ERROR at line 10: Expression is of type "B" so it can't be of type "C".

View file

@ -1,2 +1,2 @@
GDTEST_ANALYZER_ERROR
Cyclic inheritance.
>> ERROR at line 4: Cyclic inheritance.

View file

@ -1,2 +1,3 @@
GDTEST_ANALYZER_ERROR
Could not resolve member "c1": Cyclic reference.
>> ERROR at line 5: Could not resolve member "c1": Cyclic reference.
>> ERROR at line 5: Could not resolve type for constant "c2".

View file

@ -1,2 +1,3 @@
GDTEST_ANALYZER_ERROR
Could not resolve member "E1": Cyclic reference.
>> ERROR at line 5: Could not resolve member "E1": Cyclic reference.
>> ERROR at line 5: Enum values must be constant.

View file

@ -1,2 +1,2 @@
GDTEST_ANALYZER_ERROR
Could not resolve member "EV1": Cyclic reference.
>> ERROR at line 5: Could not resolve member "EV1": Cyclic reference.

View file

@ -1,2 +1,3 @@
GDTEST_ANALYZER_ERROR
Could not resolve external class member "v".
>> ERROR at line 4: Could not resolve external class member "v".
>> ERROR at line 4: Cannot find member "v" in base "TestCyclicRefExternalA".

View file

@ -1,3 +1,5 @@
class_name TestCyclicRefExternalA
const B = preload("cyclic_ref_external.gd")
var v = B.v

View file

@ -1,2 +1,3 @@
GDTEST_ANALYZER_ERROR
Could not resolve member "f1": Cyclic reference.
>> ERROR at line 8: Could not resolve member "f1": Cyclic reference.
>> ERROR at line 8: Cannot infer the type of "p" parameter because the value doesn't have a set type.

View file

@ -1,2 +1,2 @@
GDTEST_ANALYZER_ERROR
Could not resolve member "f": Cyclic reference.
>> ERROR at line 11: Could not resolve member "f": Cyclic reference.

View file

@ -1,2 +1,3 @@
GDTEST_ANALYZER_ERROR
Could not resolve member "v1": Cyclic reference.
>> ERROR at line 5: Could not resolve member "v1": Cyclic reference.
>> ERROR at line 5: Cannot infer the type of "v2" variable because the value doesn't have a set type.

View file

@ -1,2 +1,3 @@
GDTEST_ANALYZER_ERROR
Could not resolve member "v1": Cyclic reference.
>> ERROR at line 1: Could not resolve member "v1": Cyclic reference.
>> ERROR at line 1: Could not resolve type for variable "v1".

View file

@ -1,2 +1,2 @@
GDTEST_ANALYZER_ERROR
Key "a" was already used in this dictionary (at line 3).
>> ERROR at line 5: Key "a" was already used in this dictionary (at line 3).

View file

@ -1,2 +1,2 @@
GDTEST_ANALYZER_ERROR
Key "a" was already used in this dictionary (at line 3).
>> ERROR at line 5: Key "a" was already used in this dictionary (at line 3).

View file

@ -1,2 +1,2 @@
GDTEST_ANALYZER_ERROR
Key "a" was already used in this dictionary (at line 3).
>> ERROR at line 5: Key "a" was already used in this dictionary (at line 3).

View file

@ -1,2 +1,2 @@
GDTEST_ANALYZER_ERROR
Key "key" was already used in this dictionary (at line 5).
>> ERROR at line 6: Key "key" was already used in this dictionary (at line 5).

View file

@ -1,2 +1,2 @@
GDTEST_ANALYZER_ERROR
Cannot construct native class "Time" because it is an engine singleton.
>> ERROR at line 2: Cannot construct native class "Time" because it is an engine singleton.

View file

@ -1,2 +1,2 @@
GDTEST_ANALYZER_ERROR
Cannot call non-const Dictionary function "clear()" on enum "Enum".
>> ERROR at line 4: Cannot call non-const Dictionary function "clear()" on enum "Enum".

View file

@ -1,2 +1,2 @@
GDTEST_ANALYZER_ERROR
Cannot find member "V3" in base "enum_bad_value.gd.Enum".
>> ERROR at line 4: Cannot find member "V3" in base "enum_bad_value.gd.Enum".

View file

@ -0,0 +1,2 @@
func test():
print(Vector3.Axis)

View file

@ -0,0 +1,2 @@
GDTEST_ANALYZER_ERROR
>> ERROR at line 2: Type "Axis" in base "Vector3" cannot be used on its own.

View file

@ -1,2 +1,3 @@
GDTEST_ANALYZER_ERROR
Cannot assign a value of type "enum_class_var_assign_with_wrong_enum_type.gd.MyOtherEnum" as "enum_class_var_assign_with_wrong_enum_type.gd.MyEnum".
>> ERROR at line 9: Cannot assign a value of type "enum_class_var_assign_with_wrong_enum_type.gd.MyOtherEnum" as "enum_class_var_assign_with_wrong_enum_type.gd.MyEnum".
>> ERROR at line 9: Value of type "enum_class_var_assign_with_wrong_enum_type.gd.MyOtherEnum" cannot be assigned to a variable of type "enum_class_var_assign_with_wrong_enum_type.gd.MyEnum".

View file

@ -1,2 +1,3 @@
GDTEST_ANALYZER_ERROR
Cannot assign a value of type "enum_class_var_init_with_wrong_enum_type.gd.MyOtherEnum" as "enum_class_var_init_with_wrong_enum_type.gd.MyEnum".
>> ERROR at line 5: Cannot assign a value of type "enum_class_var_init_with_wrong_enum_type.gd.MyOtherEnum" as "enum_class_var_init_with_wrong_enum_type.gd.MyEnum".
>> ERROR at line 5: Cannot assign a value of type enum_class_var_init_with_wrong_enum_type.gd.MyOtherEnum to variable "class_var" with specified type enum_class_var_init_with_wrong_enum_type.gd.MyEnum.

View file

@ -1,2 +1,2 @@
GDTEST_ANALYZER_ERROR
Cannot call non-const Dictionary function "clear()" on enum "Enum".
>> ERROR at line 5: Cannot call non-const Dictionary function "clear()" on enum "Enum".

View file

@ -1,2 +1,2 @@
GDTEST_ANALYZER_ERROR
Enum values must be integers.
>> ERROR at line 3: Enum values must be integers.

Some files were not shown because too many files have changed in this diff Show more