summaryrefslogtreecommitdiff
path: root/modules
diff options
context:
space:
mode:
Diffstat (limited to 'modules')
-rw-r--r--modules/assimp/editor_scene_importer_assimp.cpp3
-rw-r--r--modules/bmp/image_loader_bmp.cpp31
-rw-r--r--modules/bullet/rigid_body_bullet.cpp3
-rw-r--r--modules/bullet/space_bullet.cpp1
-rw-r--r--modules/csg/csg.cpp24
-rw-r--r--modules/csg/csg_shape.cpp8
-rw-r--r--modules/enet/doc_classes/NetworkedMultiplayerENet.xml4
-rw-r--r--modules/gdnative/doc_classes/GDNativeLibrary.xml4
-rw-r--r--modules/gdscript/doc_classes/@GDScript.xml4
-rw-r--r--modules/gdscript/doc_classes/GDScript.xml2
-rw-r--r--modules/gdscript/editor/gdscript_highlighter.cpp4
-rw-r--r--modules/gdscript/editor/gdscript_translation_parser_plugin.cpp376
-rw-r--r--modules/gdscript/editor/gdscript_translation_parser_plugin.h33
-rw-r--r--modules/gdscript/gdscript.cpp13
-rw-r--r--modules/gdscript/gdscript.h1
-rw-r--r--modules/gdscript/gdscript_analyzer.cpp270
-rw-r--r--modules/gdscript/gdscript_analyzer.h4
-rw-r--r--modules/gdscript/gdscript_cache.cpp29
-rw-r--r--modules/gdscript/gdscript_cache.h6
-rw-r--r--modules/gdscript/gdscript_compiler.cpp29
-rw-r--r--modules/gdscript/gdscript_editor.cpp47
-rw-r--r--modules/gdscript/gdscript_parser.cpp137
-rw-r--r--modules/gdscript/gdscript_parser.h14
-rw-r--r--modules/gdscript/gdscript_tokenizer.cpp58
-rw-r--r--modules/gdscript/gdscript_tokenizer.h6
-rw-r--r--modules/gdscript/language_server/gdscript_extend_parser.cpp2
-rw-r--r--modules/gdscript/register_types.cpp4
-rw-r--r--modules/gridmap/doc_classes/GridMap.xml2
-rw-r--r--modules/mobile_vr/register_types.cpp8
-rw-r--r--modules/mono/doc_classes/CSharpScript.xml2
-rw-r--r--modules/mono/editor/GodotTools/GodotTools.BuildLogger/GodotBuildLogger.cs10
-rw-r--r--modules/mono/editor/GodotTools/GodotTools.Core/StringExtensions.cs2
-rw-r--r--modules/mono/editor/GodotTools/GodotTools.ProjectEditor/GodotTools.ProjectEditor.csproj20
-rw-r--r--modules/mono/editor/GodotTools/GodotTools.ProjectEditor/ProjectGenerator.cs4
-rw-r--r--modules/mono/editor/GodotTools/GodotTools/Build/MsBuildFinder.cs17
-rw-r--r--modules/mono/editor/GodotTools/GodotTools/BuildManager.cs19
-rw-r--r--modules/mono/editor/GodotTools/GodotTools/Export/ExportPlugin.cs4
-rw-r--r--modules/mono/editor/GodotTools/GodotTools/GodotSharpEditor.cs3
-rw-r--r--modules/mono/editor/GodotTools/GodotTools/Ides/MessagingServer.cs23
-rw-r--r--modules/mono/editor/godotsharp_export.cpp41
-rw-r--r--modules/mono/mono_gd/gd_mono_utils.h21
-rw-r--r--modules/mono/utils/macros.h2
-rw-r--r--modules/visual_script/doc_classes/VisualScript.xml2
43 files changed, 957 insertions, 340 deletions
diff --git a/modules/assimp/editor_scene_importer_assimp.cpp b/modules/assimp/editor_scene_importer_assimp.cpp
index aedc4b690a..e5becfd559 100644
--- a/modules/assimp/editor_scene_importer_assimp.cpp
+++ b/modules/assimp/editor_scene_importer_assimp.cpp
@@ -441,7 +441,6 @@ EditorSceneImporterAssimp::_generate_scene(const String &p_path, aiScene *scene,
Transform pform = AssimpUtils::assimp_matrix_transform(bone->mNode->mTransformation);
skeleton->add_bone(bone_name);
skeleton->set_bone_rest(boneIdx, pform);
- skeleton->set_bone_pose(boneIdx, pform);
if (parent_node != nullptr) {
int parent_bone_id = skeleton->find_bone(AssimpUtils::get_anim_string_from_assimp(parent_node->mName));
@@ -612,7 +611,7 @@ void EditorSceneImporterAssimp::_insert_animation_track(ImportState &scene, cons
xform.basis.set_quat_scale(rot, scale);
xform.origin = pos;
- xform = skeleton->get_bone_pose(skeleton_bone).inverse() * xform;
+ xform = skeleton->get_bone_rest(skeleton_bone).inverse() * xform;
rot = xform.basis.get_rotation_quat();
rot.normalize();
diff --git a/modules/bmp/image_loader_bmp.cpp b/modules/bmp/image_loader_bmp.cpp
index ac4e534983..757afeb9e3 100644
--- a/modules/bmp/image_loader_bmp.cpp
+++ b/modules/bmp/image_loader_bmp.cpp
@@ -51,16 +51,20 @@ Error ImageLoaderBMP::convert_to_image(Ref<Image> p_image,
if (bits_per_pixel == 1) {
// Requires bit unpacking...
- ERR_FAIL_COND_V(width % 8 != 0, ERR_UNAVAILABLE);
- ERR_FAIL_COND_V(height % 8 != 0, ERR_UNAVAILABLE);
+ ERR_FAIL_COND_V_MSG(width % 8 != 0, ERR_UNAVAILABLE,
+ vformat("1-bpp BMP images must have a width that is a multiple of 8, but the imported BMP is %d pixels wide.", int(width)));
+ ERR_FAIL_COND_V_MSG(height % 8 != 0, ERR_UNAVAILABLE,
+ vformat("1-bpp BMP images must have a height that is a multiple of 8, but the imported BMP is %d pixels tall.", int(height)));
} else if (bits_per_pixel == 4) {
// Requires bit unpacking...
- ERR_FAIL_COND_V(width % 2 != 0, ERR_UNAVAILABLE);
- ERR_FAIL_COND_V(height % 2 != 0, ERR_UNAVAILABLE);
+ ERR_FAIL_COND_V_MSG(width % 2 != 0, ERR_UNAVAILABLE,
+ vformat("4-bpp BMP images must have a width that is a multiple of 2, but the imported BMP is %d pixels wide.", int(width)));
+ ERR_FAIL_COND_V_MSG(height % 2 != 0, ERR_UNAVAILABLE,
+ vformat("4-bpp BMP images must have a height that is a multiple of 2, but the imported BMP is %d pixels tall.", int(height)));
} else if (bits_per_pixel == 16) {
- ERR_FAIL_V(ERR_UNAVAILABLE);
+ ERR_FAIL_V_MSG(ERR_UNAVAILABLE, "16-bpp BMP images are not supported.");
}
// Image data (might be indexed)
@@ -72,7 +76,7 @@ Error ImageLoaderBMP::convert_to_image(Ref<Image> p_image,
} else { // color
data_len = width * height * 4;
}
- ERR_FAIL_COND_V(data_len == 0, ERR_BUG);
+ ERR_FAIL_COND_V_MSG(data_len == 0, ERR_BUG, "Couldn't parse the BMP image data.");
err = data.resize(data_len);
uint8_t *data_w = data.ptrw();
@@ -215,13 +219,15 @@ Error ImageLoaderBMP::load_image(Ref<Image> p_image, FileAccess *f,
// Info Header
bmp_header.bmp_info_header.bmp_header_size = f->get_32();
- ERR_FAIL_COND_V(bmp_header.bmp_info_header.bmp_header_size < BITMAP_INFO_HEADER_MIN_SIZE, ERR_FILE_CORRUPT);
+ ERR_FAIL_COND_V_MSG(bmp_header.bmp_info_header.bmp_header_size < BITMAP_INFO_HEADER_MIN_SIZE, ERR_FILE_CORRUPT,
+ vformat("Couldn't parse the BMP info header. The file is likely corrupt: %s", f->get_path()));
bmp_header.bmp_info_header.bmp_width = f->get_32();
bmp_header.bmp_info_header.bmp_height = f->get_32();
bmp_header.bmp_info_header.bmp_planes = f->get_16();
- ERR_FAIL_COND_V(bmp_header.bmp_info_header.bmp_planes != 1, ERR_FILE_CORRUPT);
+ ERR_FAIL_COND_V_MSG(bmp_header.bmp_info_header.bmp_planes != 1, ERR_FILE_CORRUPT,
+ vformat("Couldn't parse the BMP planes. The file is likely corrupt: %s", f->get_path()));
bmp_header.bmp_info_header.bmp_bit_count = f->get_16();
bmp_header.bmp_info_header.bmp_compression = f->get_32();
@@ -236,10 +242,10 @@ Error ImageLoaderBMP::load_image(Ref<Image> p_image, FileAccess *f,
case BI_RLE4:
case BI_CMYKRLE8:
case BI_CMYKRLE4: {
- // Stop parsing
- String bmp_path = f->get_path();
+ // Stop parsing.
f->close();
- ERR_FAIL_V_MSG(ERR_UNAVAILABLE, "Compressed BMP files are not supported: " + bmp_path + ".");
+ ERR_FAIL_V_MSG(ERR_UNAVAILABLE,
+ vformat("Compressed BMP files are not supported: %s", f->get_path()));
} break;
}
// Don't rely on sizeof(bmp_file_header) as structure padding
@@ -255,7 +261,8 @@ Error ImageLoaderBMP::load_image(Ref<Image> p_image, FileAccess *f,
if (bmp_header.bmp_info_header.bmp_bit_count <= 8) {
// Support 256 colors max
color_table_size = 1 << bmp_header.bmp_info_header.bmp_bit_count;
- ERR_FAIL_COND_V(color_table_size == 0, ERR_BUG);
+ ERR_FAIL_COND_V_MSG(color_table_size == 0, ERR_BUG,
+ vformat("Couldn't parse the BMP color table: %s", f->get_path()));
}
Vector<uint8_t> bmp_color_table;
diff --git a/modules/bullet/rigid_body_bullet.cpp b/modules/bullet/rigid_body_bullet.cpp
index 5c1144b875..32c3240a35 100644
--- a/modules/bullet/rigid_body_bullet.cpp
+++ b/modules/bullet/rigid_body_bullet.cpp
@@ -897,8 +897,7 @@ void RigidBodyBullet::on_exit_area(AreaBullet *p_area) {
}
void RigidBodyBullet::reload_space_override_modificator() {
- // Make sure that kinematic bodies have their total gravity calculated
- if (!is_active() && PhysicsServer3D::BODY_MODE_KINEMATIC != mode) {
+ if (mode == PhysicsServer3D::BODY_MODE_STATIC) {
return;
}
diff --git a/modules/bullet/space_bullet.cpp b/modules/bullet/space_bullet.cpp
index 2b60f8df36..d0515e7c97 100644
--- a/modules/bullet/space_bullet.cpp
+++ b/modules/bullet/space_bullet.cpp
@@ -177,6 +177,7 @@ bool BulletPhysicsDirectSpaceState::cast_motion(const RID &p_shape, const Transf
bt_xform_to.getOrigin() += bt_motion;
if ((bt_xform_to.getOrigin() - bt_xform_from.getOrigin()).fuzzyZero()) {
+ shape->destroy_bt_shape(btShape);
return false;
}
diff --git a/modules/csg/csg.cpp b/modules/csg/csg.cpp
index 6c0a3a4ca3..47982d519a 100644
--- a/modules/csg/csg.cpp
+++ b/modules/csg/csg.cpp
@@ -154,6 +154,14 @@ inline bool is_point_in_triangle(const Vector3 &p_point, const Vector3 p_vertice
return true;
}
+inline static bool is_triangle_degenerate(const Vector2 p_vertices[3], real_t p_vertex_snap2) {
+ real_t det = p_vertices[0].x * p_vertices[1].y - p_vertices[0].x * p_vertices[2].y +
+ p_vertices[0].y * p_vertices[2].x - p_vertices[0].y * p_vertices[1].x +
+ p_vertices[1].x * p_vertices[2].y - p_vertices[1].y * p_vertices[2].x;
+
+ return det < p_vertex_snap2;
+}
+
inline static bool are_segements_parallel(const Vector2 p_segment1_points[2], const Vector2 p_segment2_points[2], float p_vertex_snap2) {
Vector2 segment1 = p_segment1_points[1] - p_segment1_points[0];
Vector2 segment2 = p_segment2_points[1] - p_segment2_points[0];
@@ -583,8 +591,8 @@ bool CSGBrushOperation::MeshMerge::_bvh_inside(FaceBVH *facebvhptr, int p_max_de
// Check if faces are co-planar.
if ((current_normal - face_normal).length_squared() < CMP_EPSILON2 &&
is_point_in_triangle(face_center, current_points)) {
- // Only add an intersection if checking a B face.
- if (face.from_b) {
+ // Only add an intersection if not a B face.
+ if (!face.from_b) {
_add_distance(intersectionsA, intersectionsB, current_face.from_b, 0);
}
} else if (ray_intersects_triangle(face_center, face_normal, current_points, CMP_EPSILON, intersection_point)) {
@@ -1117,6 +1125,11 @@ int CSGBrushOperation::Build2DFaces::_insert_point(const Vector2 &p_point) {
face_vertices[2].uv
};
+ // Skip degenerate triangles.
+ if (is_triangle_degenerate(points, vertex_snap2)) {
+ continue;
+ }
+
// Check if point is existing face vertex.
for (int i = 0; i < 3; ++i) {
if ((p_point - face_vertices[i].point).length_squared() < vertex_snap2) {
@@ -1198,11 +1211,8 @@ int CSGBrushOperation::Build2DFaces::_insert_point(const Vector2 &p_point) {
// The new vertex is the last vertex.
for (int i = 0; i < 3; ++i) {
// Don't create degenerate triangles.
- Vector2 edge[2] = { points[i], points[(i + 1) % 3] };
- Vector2 new_edge1[2] = { vertices[new_vertex_idx].point, points[i] };
- Vector2 new_edge2[2] = { vertices[new_vertex_idx].point, points[(i + 1) % 3] };
- if (are_segements_parallel(edge, new_edge1, vertex_snap2) &&
- are_segements_parallel(edge, new_edge2, vertex_snap2)) {
+ Vector2 new_points[3] = { points[i], points[(i + 1) % 3], vertices[new_vertex_idx].point };
+ if (is_triangle_degenerate(new_points, vertex_snap2)) {
continue;
}
diff --git a/modules/csg/csg_shape.cpp b/modules/csg/csg_shape.cpp
index 82a47f594b..8f2ebc7232 100644
--- a/modules/csg/csg_shape.cpp
+++ b/modules/csg/csg_shape.cpp
@@ -506,6 +506,12 @@ void CSGShape3D::_notification(int p_what) {
_make_dirty();
}
+ if (p_what == NOTIFICATION_TRANSFORM_CHANGED) {
+ if (use_collision && is_root_shape() && root_collision_instance.is_valid()) {
+ PhysicsServer3D::get_singleton()->body_set_state(root_collision_instance, PhysicsServer3D::BODY_STATE_TRANSFORM, get_global_transform());
+ }
+ }
+
if (p_what == NOTIFICATION_LOCAL_TRANSFORM_CHANGED) {
if (parent) {
parent->_make_dirty();
@@ -641,7 +647,7 @@ CSGShape3D::~CSGShape3D() {
//////////////////////////////////
CSGBrush *CSGCombiner3D::_build_brush() {
- return nullptr; //does not build anything
+ return memnew(CSGBrush); //does not build anything
}
CSGCombiner3D::CSGCombiner3D() {
diff --git a/modules/enet/doc_classes/NetworkedMultiplayerENet.xml b/modules/enet/doc_classes/NetworkedMultiplayerENet.xml
index c908af7479..f46ef2d812 100644
--- a/modules/enet/doc_classes/NetworkedMultiplayerENet.xml
+++ b/modules/enet/doc_classes/NetworkedMultiplayerENet.xml
@@ -7,8 +7,8 @@
A PacketPeer implementation that should be passed to [member SceneTree.network_peer] after being initialized as either a client or server. Events can then be handled by connecting to [SceneTree] signals.
</description>
<tutorials>
- <link>https://docs.godotengine.org/en/latest/tutorials/networking/high_level_multiplayer.html</link>
- <link>http://enet.bespin.org/usergroup0.html</link>
+ <link title="High-level multiplayer">https://docs.godotengine.org/en/latest/tutorials/networking/high_level_multiplayer.html</link>
+ <link title="API documentation on the ENet website">http://enet.bespin.org/usergroup0.html</link>
</tutorials>
<methods>
<method name="close_connection">
diff --git a/modules/gdnative/doc_classes/GDNativeLibrary.xml b/modules/gdnative/doc_classes/GDNativeLibrary.xml
index 1aab864102..05cda05f9f 100644
--- a/modules/gdnative/doc_classes/GDNativeLibrary.xml
+++ b/modules/gdnative/doc_classes/GDNativeLibrary.xml
@@ -7,8 +7,8 @@
A GDNative library can implement [NativeScript]s, global functions to call with the [GDNative] class, or low-level engine extensions through interfaces such as [XRInterfaceGDNative]. The library must be compiled for each platform and architecture that the project will run on.
</description>
<tutorials>
- <link>https://docs.godotengine.org/en/latest/tutorials/plugins/gdnative/gdnative-c-example.html</link>
- <link>https://docs.godotengine.org/en/latest/tutorials/plugins/gdnative/gdnative-cpp-example.html</link>
+ <link title="GDNative C example">https://docs.godotengine.org/en/latest/tutorials/plugins/gdnative/gdnative-c-example.html</link>
+ <link title="GDNative C++ example">https://docs.godotengine.org/en/latest/tutorials/plugins/gdnative/gdnative-cpp-example.html</link>
</tutorials>
<methods>
<method name="get_current_dependencies" qualifiers="const">
diff --git a/modules/gdscript/doc_classes/@GDScript.xml b/modules/gdscript/doc_classes/@GDScript.xml
index 9e40a69712..e528fc6623 100644
--- a/modules/gdscript/doc_classes/@GDScript.xml
+++ b/modules/gdscript/doc_classes/@GDScript.xml
@@ -167,6 +167,7 @@
i = ceil(1.45) # i is 2
i = ceil(1.001) # i is 2
[/codeblock]
+ See also [method floor], [method round], and [method stepify].
</description>
</method>
<method name="char">
@@ -338,6 +339,7 @@
# a is -3.0
a = floor(-2.99)
[/codeblock]
+ See also [method ceil], [method round], and [method stepify].
[b]Note:[/b] This method returns a float. If you need an integer, you can use [code]int(s)[/code] directly.
</description>
</method>
@@ -1043,6 +1045,7 @@
[codeblock]
round(2.6) # Returns 3
[/codeblock]
+ See also [method floor], [method ceil], and [method stepify].
</description>
</method>
<method name="seed">
@@ -1161,6 +1164,7 @@
stepify(100, 32) # Returns 96
stepify(3.14159, 0.01) # Returns 3.14
[/codeblock]
+ See also [method ceil], [method floor], and [method round].
</description>
</method>
<method name="str" qualifiers="vararg">
diff --git a/modules/gdscript/doc_classes/GDScript.xml b/modules/gdscript/doc_classes/GDScript.xml
index 62ccb93901..631a102130 100644
--- a/modules/gdscript/doc_classes/GDScript.xml
+++ b/modules/gdscript/doc_classes/GDScript.xml
@@ -8,7 +8,7 @@
[method new] creates a new instance of the script. [method Object.set_script] extends an existing object, if that object's class matches one of the script's base classes.
</description>
<tutorials>
- <link>https://docs.godotengine.org/en/latest/getting_started/scripting/gdscript/index.html</link>
+ <link title="GDScript tutorial index">https://docs.godotengine.org/en/latest/getting_started/scripting/gdscript/index.html</link>
</tutorials>
<methods>
<method name="get_as_byte_code" qualifiers="const">
diff --git a/modules/gdscript/editor/gdscript_highlighter.cpp b/modules/gdscript/editor/gdscript_highlighter.cpp
index aba3e07134..ae1f2893f1 100644
--- a/modules/gdscript/editor/gdscript_highlighter.cpp
+++ b/modules/gdscript/editor/gdscript_highlighter.cpp
@@ -82,6 +82,10 @@ Dictionary GDScriptSyntaxHighlighter::_get_line_syntax_highlighting(int p_line)
const String &str = text_edit->get_line(p_line);
const int line_length = str.length();
Color prev_color;
+
+ if (in_region != -1 && str.length() == 0) {
+ color_region_cache[p_line] = in_region;
+ }
for (int j = 0; j < str.length(); j++) {
Dictionary highlighter_info;
diff --git a/modules/gdscript/editor/gdscript_translation_parser_plugin.cpp b/modules/gdscript/editor/gdscript_translation_parser_plugin.cpp
index 6d454e43f2..944ed859f5 100644
--- a/modules/gdscript/editor/gdscript_translation_parser_plugin.cpp
+++ b/modules/gdscript/editor/gdscript_translation_parser_plugin.cpp
@@ -37,9 +37,11 @@ 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_extracted_strings) {
- // Parse and match all GDScript function API that involves translation string.
- // E.g get_node("Label").text = "something", var test = tr("something"), "something" will be matched and collected.
+Error GDScriptEditorTranslationParserPlugin::parse_file(const String &p_path, Vector<String> *r_ids, Vector<Vector<String>> *r_ids_ctx_plural) {
+ // Extract all translatable strings using the parsed tree from GDSriptParser.
+ // 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.
+ // Search strings in AssignmentNode -> text = "__", hint_tooltip = "__" etc.
Error err;
RES loaded_res = ResourceLoader::load(p_path, "", false, &err);
@@ -48,108 +50,302 @@ Error GDScriptEditorTranslationParserPlugin::parse_file(const String &p_path, Ve
return err;
}
+ ids = r_ids;
+ ids_ctx_plural = r_ids_ctx_plural;
Ref<GDScript> gdscript = loaded_res;
String source_code = gdscript->get_source_code();
- Vector<String> parsed_strings;
-
- // Search translation strings with RegEx.
- regex.clear();
- regex.compile(String("|").join(patterns));
- Array results = regex.search_all(source_code);
- _get_captured_strings(results, &parsed_strings);
-
- // Special handling for FileDialog.
- Vector<String> temp;
- _parse_file_dialog(source_code, &temp);
- parsed_strings.append_array(temp);
-
- // Filter out / and +
- String filter = "(?:\\\\\\n|\"[\\s\\\\]*\\+\\s*\")";
- regex.clear();
- regex.compile(filter);
- for (int i = 0; i < parsed_strings.size(); i++) {
- parsed_strings.set(i, regex.sub(parsed_strings[i], "", true));
+
+ GDScriptParser parser;
+ err = parser.parse(source_code, p_path, false);
+ if (err != OK) {
+ ERR_PRINT("Failed to parse with GDScript with GDScriptParser.");
+ return err;
}
- r_extracted_strings->append_array(parsed_strings);
+ // Traverse through the parsed tree from GDScriptParser.
+ GDScriptParser::ClassNode *c = parser.get_tree();
+ _traverse_class(c);
return OK;
}
-void GDScriptEditorTranslationParserPlugin::_parse_file_dialog(const String &p_source_code, Vector<String> *r_output) {
- // FileDialog API has the form .filters = PackedStringArray(["*.png ; PNG Images","*.gd ; GDScript Files"]).
- // First filter: Get "*.png ; PNG Images", "*.gd ; GDScript Files" from PackedStringArray.
- regex.clear();
- regex.compile(String("|").join(file_dialog_patterns));
- Array results = regex.search_all(p_source_code);
-
- Vector<String> temp;
- _get_captured_strings(results, &temp);
- String captured_strings = String(",").join(temp);
-
- // Second filter: Get the texts after semicolon from "*.png ; PNG Images","*.gd ; GDScript Files".
- String second_filter = "\"[^;]+;" + text + "\"";
- regex.clear();
- regex.compile(second_filter);
- results = regex.search_all(captured_strings);
- _get_captured_strings(results, r_output);
- for (int i = 0; i < r_output->size(); i++) {
- r_output->set(i, r_output->get(i).strip_edges());
+void GDScriptEditorTranslationParserPlugin::_traverse_class(const GDScriptParser::ClassNode *p_class) {
+ for (int i = 0; i < p_class->members.size(); i++) {
+ const GDScriptParser::ClassNode::Member &m = p_class->members[i];
+ // There are 7 types of Member, but only class, function and variable can contain translatable strings.
+ switch (m.type) {
+ case GDScriptParser::ClassNode::Member::CLASS:
+ _traverse_class(m.m_class);
+ break;
+ case GDScriptParser::ClassNode::Member::FUNCTION:
+ _traverse_function(m.function);
+ break;
+ case GDScriptParser::ClassNode::Member::VARIABLE:
+ _read_variable(m.variable);
+ break;
+ default:
+ break;
+ }
+ }
+}
+
+void GDScriptEditorTranslationParserPlugin::_traverse_function(const GDScriptParser::FunctionNode *p_func) {
+ _traverse_block(p_func->body);
+}
+
+void GDScriptEditorTranslationParserPlugin::_read_variable(const GDScriptParser::VariableNode *p_var) {
+ _assess_expression(p_var->initializer);
+}
+
+void GDScriptEditorTranslationParserPlugin::_traverse_block(const GDScriptParser::SuiteNode *p_suite) {
+ if (!p_suite) {
+ return;
+ }
+
+ const Vector<GDScriptParser::Node *> &statements = p_suite->statements;
+ for (int i = 0; i < statements.size(); i++) {
+ GDScriptParser::Node *statement = statements[i];
+
+ // Statements with Node type constant, break, continue, pass, breakpoint are skipped because they can't contain translatable strings.
+ switch (statement->type) {
+ case GDScriptParser::Node::VARIABLE:
+ _assess_expression(static_cast<GDScriptParser::VariableNode *>(statement)->initializer);
+ break;
+ case GDScriptParser::Node::IF: {
+ GDScriptParser::IfNode *if_node = static_cast<GDScriptParser::IfNode *>(statement);
+ _assess_expression(if_node->condition);
+ //FIXME : if the elif logic is changed in GDScriptParser, then this probably will have to change as well. See GDScriptParser::TreePrinter::print_if().
+ _traverse_block(if_node->true_block);
+ _traverse_block(if_node->false_block);
+ break;
+ }
+ case GDScriptParser::Node::FOR: {
+ GDScriptParser::ForNode *for_node = static_cast<GDScriptParser::ForNode *>(statement);
+ _assess_expression(for_node->list);
+ _traverse_block(for_node->loop);
+ break;
+ }
+ case GDScriptParser::Node::WHILE: {
+ GDScriptParser::WhileNode *while_node = static_cast<GDScriptParser::WhileNode *>(statement);
+ _assess_expression(while_node->condition);
+ _traverse_block(while_node->loop);
+ break;
+ }
+ case GDScriptParser::Node::MATCH: {
+ GDScriptParser::MatchNode *match_node = static_cast<GDScriptParser::MatchNode *>(statement);
+ _assess_expression(match_node->test);
+ for (int j = 0; j < match_node->branches.size(); j++) {
+ _traverse_block(match_node->branches[j]->block);
+ }
+ break;
+ }
+ case GDScriptParser::Node::RETURN:
+ _assess_expression(static_cast<GDScriptParser::ReturnNode *>(statement)->return_value);
+ break;
+ case GDScriptParser::Node::ASSERT:
+ _assess_expression((static_cast<GDScriptParser::AssertNode *>(statement))->condition);
+ break;
+ case GDScriptParser::Node::ASSIGNMENT:
+ _assess_assignment(static_cast<GDScriptParser::AssignmentNode *>(statement));
+ break;
+ default:
+ if (statement->is_expression()) {
+ _assess_expression(static_cast<GDScriptParser::ExpressionNode *>(statement));
+ }
+ break;
+ }
+ }
+}
+
+void GDScriptEditorTranslationParserPlugin::_assess_expression(GDScriptParser::ExpressionNode *p_expression) {
+ // Explore all ExpressionNodes to find CallNodes which contain translation strings, such as tr(), set_text() etc.
+ // tr() can be embedded quite deep within multiple ExpressionNodes so need to dig down to search through all ExpressionNodes.
+ if (!p_expression) {
+ return;
+ }
+
+ // ExpressionNode of type await, cast, get_node, identifier, literal, preload, self, subscript, unary are ignored as they can't be CallNode
+ // containing translation strings.
+ switch (p_expression->type) {
+ case GDScriptParser::Node::ARRAY: {
+ GDScriptParser::ArrayNode *array_node = static_cast<GDScriptParser::ArrayNode *>(p_expression);
+ for (int i = 0; i < array_node->elements.size(); i++) {
+ _assess_expression(array_node->elements[i]);
+ }
+ break;
+ }
+ case GDScriptParser::Node::ASSIGNMENT:
+ _assess_assignment(static_cast<GDScriptParser::AssignmentNode *>(p_expression));
+ break;
+ case GDScriptParser::Node::BINARY_OPERATOR: {
+ GDScriptParser::BinaryOpNode *binary_op_node = static_cast<GDScriptParser::BinaryOpNode *>(p_expression);
+ _assess_expression(binary_op_node->left_operand);
+ _assess_expression(binary_op_node->right_operand);
+ break;
+ }
+ case GDScriptParser::Node::CALL: {
+ GDScriptParser::CallNode *call_node = static_cast<GDScriptParser::CallNode *>(p_expression);
+ _extract_from_call(call_node);
+ for (int i = 0; i < call_node->arguments.size(); i++) {
+ _assess_expression(call_node->arguments[i]);
+ }
+ } break;
+ case GDScriptParser::Node::DICTIONARY: {
+ GDScriptParser::DictionaryNode *dict_node = static_cast<GDScriptParser::DictionaryNode *>(p_expression);
+ for (int i = 0; i < dict_node->elements.size(); i++) {
+ _assess_expression(dict_node->elements[i].key);
+ _assess_expression(dict_node->elements[i].value);
+ }
+ break;
+ }
+ case GDScriptParser::Node::TERNARY_OPERATOR: {
+ GDScriptParser::TernaryOpNode *ternary_op_node = static_cast<GDScriptParser::TernaryOpNode *>(p_expression);
+ _assess_expression(ternary_op_node->condition);
+ _assess_expression(ternary_op_node->true_expr);
+ _assess_expression(ternary_op_node->false_expr);
+ break;
+ }
+ default:
+ break;
+ }
+}
+
+void GDScriptEditorTranslationParserPlugin::_assess_assignment(GDScriptParser::AssignmentNode *p_assignment) {
+ // Extract the translatable strings coming from assignments. For example, get_node("Label").text = "____"
+
+ StringName assignee_name;
+ if (p_assignment->assignee->type == GDScriptParser::Node::IDENTIFIER) {
+ assignee_name = static_cast<GDScriptParser::IdentifierNode *>(p_assignment->assignee)->name;
+ } else if (p_assignment->assignee->type == GDScriptParser::Node::SUBSCRIPT) {
+ assignee_name = static_cast<GDScriptParser::SubscriptNode *>(p_assignment->assignee)->attribute->name;
+ }
+
+ if (assignment_patterns.has(assignee_name) && p_assignment->assigned_value->type == GDScriptParser::Node::LITERAL) {
+ // If the assignment is towards one of the extract patterns (text, hint_tooltip etc.), and the value is a string literal, we collect the string.
+ ids->push_back(static_cast<GDScriptParser::LiteralNode *>(p_assignment->assigned_value)->value);
+ } else if (assignee_name == fd_filters && p_assignment->assigned_value->type == GDScriptParser::Node::CALL) {
+ // FileDialog.filters accepts assignment in the form of PackedStringArray. For example,
+ // get_node("FileDialog").filters = PackedStringArray(["*.png ; PNG Images","*.gd ; GDScript Files"]).
+
+ GDScriptParser::CallNode *call_node = static_cast<GDScriptParser::CallNode *>(p_assignment->assigned_value);
+ if (call_node->arguments[0]->type == GDScriptParser::Node::ARRAY) {
+ GDScriptParser::ArrayNode *array_node = static_cast<GDScriptParser::ArrayNode *>(call_node->arguments[0]);
+
+ // Extract the name in "extension ; name" of PackedStringArray.
+ for (int i = 0; i < array_node->elements.size(); i++) {
+ _extract_fd_literals(array_node->elements[i]);
+ }
+ }
+ } else {
+ // If the assignee is not in extract patterns or the assigned_value is not Literal type, try to see if the assigned_value contains tr().
+ _assess_expression(p_assignment->assigned_value);
}
}
-void GDScriptEditorTranslationParserPlugin::_get_captured_strings(const Array &p_results, Vector<String> *r_output) {
- Ref<RegExMatch> result;
- for (int i = 0; i < p_results.size(); i++) {
- result = p_results[i];
- for (int j = 0; j < result->get_group_count(); j++) {
- String s = result->get_string(j + 1);
- // Prevent reading text with only spaces.
- if (!s.strip_edges().empty()) {
- r_output->push_back(s);
+void GDScriptEditorTranslationParserPlugin::_extract_from_call(GDScriptParser::CallNode *p_call) {
+ // Extract the translatable strings coming from function calls. For example:
+ // tr("___"), get_node("Label").set_text("____"), get_node("LineEdit").set_placeholder("____").
+
+ StringName function_name = p_call->function_name;
+
+ // Variables for extracting tr() and tr_n().
+ Vector<String> id_ctx_plural;
+ id_ctx_plural.resize(3);
+ bool extract_id_ctx_plural = true;
+
+ if (function_name == tr_func) {
+ // Extract from tr(id, ctx).
+ for (int i = 0; i < p_call->arguments.size(); i++) {
+ if (p_call->arguments[i]->type == GDScriptParser::Node::LITERAL) {
+ id_ctx_plural.write[i] = static_cast<GDScriptParser::LiteralNode *>(p_call->arguments[i])->value;
+ } else {
+ // Avoid adding something like tr("Flying dragon", var_context_level_1). We want to extract both id and context together.
+ extract_id_ctx_plural = false;
+ }
+ }
+ if (extract_id_ctx_plural) {
+ ids_ctx_plural->push_back(id_ctx_plural);
+ }
+ } else if (function_name == trn_func) {
+ // Extract from tr_n(id, plural, n, ctx).
+ Vector<int> indices;
+ indices.push_back(0);
+ indices.push_back(3);
+ indices.push_back(1);
+ for (int i = 0; i < indices.size(); i++) {
+ if (indices[i] >= p_call->arguments.size()) {
+ continue;
+ }
+
+ if (p_call->arguments[indices[i]]->type == GDScriptParser::Node::LITERAL) {
+ id_ctx_plural.write[i] = static_cast<GDScriptParser::LiteralNode *>(p_call->arguments[indices[i]])->value;
+ } else {
+ extract_id_ctx_plural = false;
+ }
+ }
+ if (extract_id_ctx_plural) {
+ ids_ctx_plural->push_back(id_ctx_plural);
+ }
+ } else if (first_arg_patterns.has(function_name)) {
+ // Extracting argument with only string literals. In other words, not extracting something like set_text("hello " + some_var).
+ if (p_call->arguments[0]->type == GDScriptParser::Node::LITERAL) {
+ ids->push_back(static_cast<GDScriptParser::LiteralNode *>(p_call->arguments[0])->value);
+ }
+ } else if (second_arg_patterns.has(function_name)) {
+ if (p_call->arguments[1]->type == GDScriptParser::Node::LITERAL) {
+ ids->push_back(static_cast<GDScriptParser::LiteralNode *>(p_call->arguments[1])->value);
+ }
+ } else if (function_name == fd_add_filter) {
+ // Extract the 'JPE Images' in this example - get_node("FileDialog").add_filter("*.jpg; JPE Images").
+ _extract_fd_literals(p_call->arguments[0]);
+
+ } else if (function_name == fd_set_filter && p_call->arguments[0]->type == GDScriptParser::Node::CALL) {
+ // FileDialog.set_filters() accepts assignment in the form of PackedStringArray. For example,
+ // get_node("FileDialog").set_filters( PackedStringArray(["*.png ; PNG Images","*.gd ; GDScript Files"])).
+
+ GDScriptParser::CallNode *call_node = static_cast<GDScriptParser::CallNode *>(p_call->arguments[0]);
+ if (call_node->arguments[0]->type == GDScriptParser::Node::ARRAY) {
+ GDScriptParser::ArrayNode *array_node = static_cast<GDScriptParser::ArrayNode *>(call_node->arguments[0]);
+ for (int i = 0; i < array_node->elements.size(); i++) {
+ _extract_fd_literals(array_node->elements[i]);
}
}
}
}
+void GDScriptEditorTranslationParserPlugin::_extract_fd_literals(GDScriptParser::ExpressionNode *p_expression) {
+ // Extract the name in "extension ; name".
+
+ if (p_expression->type == GDScriptParser::Node::LITERAL) {
+ String arg_val = String(static_cast<GDScriptParser::LiteralNode *>(p_expression)->value);
+ PackedStringArray arr = arg_val.split(";", true);
+ if (arr.size() != 2) {
+ ERR_PRINT("Argument for setting FileDialog has bad format.");
+ return;
+ }
+ ids->push_back(arr[1].strip_edges());
+ }
+}
+
GDScriptEditorTranslationParserPlugin::GDScriptEditorTranslationParserPlugin() {
- // Regex search pattern templates.
- // The extra complication in the regex pattern is to ensure that the matching works when users write over multiple lines, use tabs etc.
- const String dot = "\\.[\\s\\\\]*";
- const String str_assign_template = "[\\s\\\\]*=[\\s\\\\]*\"" + text + "\"";
- const String first_arg_template = "[\\s\\\\]*\\([\\s\\\\]*\"" + text + "\"[\\s\\S]*?\\)";
- const String second_arg_template = "[\\s\\\\]*\\([\\s\\S]+?,[\\s\\\\]*\"" + text + "\"[\\s\\S]*?\\)";
-
- // Common patterns.
- patterns.push_back("tr" + first_arg_template);
- patterns.push_back(dot + "text" + str_assign_template);
- patterns.push_back(dot + "placeholder_text" + str_assign_template);
- patterns.push_back(dot + "hint_tooltip" + str_assign_template);
- patterns.push_back(dot + "set_text" + first_arg_template);
- patterns.push_back(dot + "set_tooltip" + first_arg_template);
- patterns.push_back(dot + "set_placeholder" + first_arg_template);
-
- // Tabs and TabContainer API.
- patterns.push_back(dot + "set_tab_title" + second_arg_template);
- patterns.push_back(dot + "add_tab" + first_arg_template);
-
- // PopupMenu API.
- patterns.push_back(dot + "add_check_item" + first_arg_template);
- patterns.push_back(dot + "add_icon_check_item" + second_arg_template);
- patterns.push_back(dot + "add_icon_item" + second_arg_template);
- patterns.push_back(dot + "add_icon_radio_check_item" + second_arg_template);
- patterns.push_back(dot + "add_item" + first_arg_template);
- patterns.push_back(dot + "add_multistate_item" + first_arg_template);
- patterns.push_back(dot + "add_radio_check_item" + first_arg_template);
- patterns.push_back(dot + "add_separator" + first_arg_template);
- patterns.push_back(dot + "add_submenu_item" + first_arg_template);
- patterns.push_back(dot + "set_item_text" + second_arg_template);
- //patterns.push_back(dot + "set_item_tooltip" + second_arg_template); //no tr() behind this function. might be bug.
-
- // FileDialog API - special case.
- const String fd_text = "((?:[\\s\\\\]*\"(?:[^\"\\\\]|\\\\[\\s\\S])*(?:\"[\\s\\\\]*\\+[\\s\\\\]*\"(?:[^\"\\\\]|\\\\[\\s\\S])*)*\"[\\s\\\\]*,?)*)";
- const String packed_string_array = "[\\s\\\\]*PackedStringArray[\\s\\\\]*\\([\\s\\\\]*\\[" + fd_text + "\\][\\s\\\\]*\\)";
- file_dialog_patterns.push_back(dot + "add_filter[\\s\\\\]*\\(" + fd_text + "[\\s\\\\]*\\)");
- file_dialog_patterns.push_back(dot + "filters[\\s\\\\]*=" + packed_string_array);
- file_dialog_patterns.push_back(dot + "set_filters[\\s\\\\]*\\(" + packed_string_array + "[\\s\\\\]*\\)");
+ assignment_patterns.insert("text");
+ assignment_patterns.insert("placeholder_text");
+ assignment_patterns.insert("hint_tooltip");
+
+ first_arg_patterns.insert("set_text");
+ first_arg_patterns.insert("set_tooltip");
+ first_arg_patterns.insert("set_placeholder");
+ first_arg_patterns.insert("add_tab");
+ first_arg_patterns.insert("add_check_item");
+ first_arg_patterns.insert("add_item");
+ first_arg_patterns.insert("add_multistate_item");
+ first_arg_patterns.insert("add_radio_check_item");
+ first_arg_patterns.insert("add_separator");
+ first_arg_patterns.insert("add_submenu_item");
+
+ second_arg_patterns.insert("set_tab_title");
+ second_arg_patterns.insert("add_icon_check_item");
+ second_arg_patterns.insert("add_icon_item");
+ second_arg_patterns.insert("add_icon_radio_check_item");
+ second_arg_patterns.insert("set_item_text");
}
diff --git a/modules/gdscript/editor/gdscript_translation_parser_plugin.h b/modules/gdscript/editor/gdscript_translation_parser_plugin.h
index 9fa4b69f01..5ea416d4cc 100644
--- a/modules/gdscript/editor/gdscript_translation_parser_plugin.h
+++ b/modules/gdscript/editor/gdscript_translation_parser_plugin.h
@@ -31,23 +31,40 @@
#ifndef GDSCRIPT_TRANSLATION_PARSER_PLUGIN_H
#define GDSCRIPT_TRANSLATION_PARSER_PLUGIN_H
+#include "core/set.h"
#include "editor/editor_translation_parser.h"
+#include "modules/gdscript/gdscript_parser.h"
#include "modules/regex/regex.h"
class GDScriptEditorTranslationParserPlugin : public EditorTranslationParserPlugin {
GDCLASS(GDScriptEditorTranslationParserPlugin, EditorTranslationParserPlugin);
- // Regex and search patterns that are used to match translation strings.
- const String text = "((?:[^\"\\\\]|\\\\[\\s\\S])*(?:\"[\\s\\\\]*\\+[\\s\\\\]*\"(?:[^\"\\\\]|\\\\[\\s\\S])*)*)";
- RegEx regex;
- Vector<String> patterns;
- Vector<String> file_dialog_patterns;
+ Vector<String> *ids;
+ Vector<Vector<String>> *ids_ctx_plural;
- void _parse_file_dialog(const String &p_source_code, Vector<String> *r_output);
- void _get_captured_strings(const Array &p_results, Vector<String> *r_output);
+ // List of patterns used for extracting translation strings.
+ StringName tr_func = "tr";
+ StringName trn_func = "tr_n";
+ Set<StringName> assignment_patterns;
+ Set<StringName> first_arg_patterns;
+ Set<StringName> second_arg_patterns;
+ // FileDialog patterns.
+ StringName fd_add_filter = "add_filter";
+ StringName fd_set_filter = "set_filters";
+ StringName fd_filters = "filters";
+
+ void _traverse_class(const GDScriptParser::ClassNode *p_class);
+ void _traverse_function(const GDScriptParser::FunctionNode *p_func);
+ void _traverse_block(const GDScriptParser::SuiteNode *p_suite);
+
+ void _read_variable(const GDScriptParser::VariableNode *p_var);
+ void _assess_expression(GDScriptParser::ExpressionNode *p_expression);
+ void _assess_assignment(GDScriptParser::AssignmentNode *p_assignment);
+ void _extract_from_call(GDScriptParser::CallNode *p_call);
+ void _extract_fd_literals(GDScriptParser::ExpressionNode *p_expression);
public:
- virtual Error parse_file(const String &p_path, Vector<String> *r_extracted_strings) override;
+ virtual Error parse_file(const String &p_path, Vector<String> *r_ids, Vector<Vector<String>> *r_ids_ctx_plural) override;
virtual void get_recognized_extensions(List<String> *r_extensions) const override;
GDScriptEditorTranslationParserPlugin();
diff --git a/modules/gdscript/gdscript.cpp b/modules/gdscript/gdscript.cpp
index 9170255c02..0263e32c5b 100644
--- a/modules/gdscript/gdscript.cpp
+++ b/modules/gdscript/gdscript.cpp
@@ -596,6 +596,19 @@ Error GDScript::reload(bool p_keep_state) {
return OK;
}
+ {
+ String source_path = path;
+ if (source_path.empty()) {
+ source_path = get_path();
+ }
+ if (!source_path.empty()) {
+ MutexLock lock(GDScriptCache::singleton->lock);
+ if (!GDScriptCache::singleton->shallow_gdscript_cache.has(source_path)) {
+ GDScriptCache::singleton->shallow_gdscript_cache[source_path] = this;
+ }
+ }
+ }
+
valid = false;
GDScriptParser parser;
Error err = parser.parse(source, path, false);
diff --git a/modules/gdscript/gdscript.h b/modules/gdscript/gdscript.h
index 9906b4014d..79317ff846 100644
--- a/modules/gdscript/gdscript.h
+++ b/modules/gdscript/gdscript.h
@@ -69,6 +69,7 @@ class GDScript : public Script {
friend class GDScriptInstance;
friend class GDScriptFunction;
+ friend class GDScriptAnalyzer;
friend class GDScriptCompiler;
friend class GDScriptFunctions;
friend class GDScriptLanguage;
diff --git a/modules/gdscript/gdscript_analyzer.cpp b/modules/gdscript/gdscript_analyzer.cpp
index 597a79a40c..cabe096579 100644
--- a/modules/gdscript/gdscript_analyzer.cpp
+++ b/modules/gdscript/gdscript_analyzer.cpp
@@ -33,6 +33,7 @@
#include "core/class_db.h"
#include "core/hash_map.h"
#include "core/io/resource_loader.h"
+#include "core/os/file_access.h"
#include "core/project_settings.h"
#include "core/script_language.h"
#include "gdscript.h"
@@ -71,6 +72,10 @@ static StringName get_real_class_name(const StringName &p_source) {
return p_source;
}
+void GDScriptAnalyzer::cleanup() {
+ underscore_map.clear();
+}
+
static GDScriptParser::DataType make_callable_type(const MethodInfo &p_info) {
GDScriptParser::DataType type;
type.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT;
@@ -177,7 +182,7 @@ Error GDScriptAnalyzer::resolve_inheritance(GDScriptParser::ClassNode *p_class,
return ERR_PARSE_ERROR;
}
- Error err = parser->raise_status(GDScriptParserRef::INHERITANCE_SOLVED);
+ Error err = parser->raise_status(GDScriptParserRef::INTERFACE_SOLVED);
if (err != OK) {
push_error(vformat(R"(Could not resolve super class inheritance from "%s".)", p_class->extends_path), p_class);
return err;
@@ -203,7 +208,7 @@ Error GDScriptAnalyzer::resolve_inheritance(GDScriptParser::ClassNode *p_class,
return ERR_PARSE_ERROR;
}
- Error err = parser->raise_status(GDScriptParserRef::INHERITANCE_SOLVED);
+ Error err = parser->raise_status(GDScriptParserRef::INTERFACE_SOLVED);
if (err != OK) {
push_error(vformat(R"(Could not resolve super class inheritance from "%s".)", name), p_class);
return err;
@@ -222,7 +227,7 @@ Error GDScriptAnalyzer::resolve_inheritance(GDScriptParser::ClassNode *p_class,
return ERR_PARSE_ERROR;
}
- Error err = parser->raise_status(GDScriptParserRef::INHERITANCE_SOLVED);
+ Error err = parser->raise_status(GDScriptParserRef::INTERFACE_SOLVED);
if (err != OK) {
push_error(vformat(R"(Could not resolve super class inheritance from "%s".)", name), p_class);
return err;
@@ -297,6 +302,16 @@ Error GDScriptAnalyzer::resolve_inheritance(GDScriptParser::ClassNode *p_class,
return ERR_PARSE_ERROR;
}
+ // Check for cyclic inheritance.
+ const GDScriptParser::ClassNode *base_class = result.class_type;
+ while (base_class) {
+ if (base_class->fqcn == p_class->fqcn) {
+ push_error("Cyclic inheritance.", p_class);
+ return ERR_PARSE_ERROR;
+ }
+ base_class = base_class->base_type.class_type;
+ }
+
p_class->base_type = result;
class_type.native_type = result.native_type;
p_class->set_datatype(class_type);
@@ -304,7 +319,10 @@ Error GDScriptAnalyzer::resolve_inheritance(GDScriptParser::ClassNode *p_class,
if (p_recursive) {
for (int i = 0; i < p_class->members.size(); i++) {
if (p_class->members[i].type == GDScriptParser::ClassNode::Member::CLASS) {
- resolve_inheritance(p_class->members[i].m_class, true);
+ Error err = resolve_inheritance(p_class->members[i].m_class, true);
+ if (err) {
+ return err;
+ }
}
}
}
@@ -494,7 +512,13 @@ void GDScriptAnalyzer::resolve_class_interface(GDScriptParser::ClassNode *p_clas
if (member.variable->initializer != nullptr) {
if (!is_type_compatible(datatype, member.variable->initializer->get_datatype(), true)) {
- push_error(vformat(R"(Value of type "%s" cannot be assigned to variable of type "%s".)", member.variable->initializer->get_datatype().to_string(), datatype.to_string()), member.variable->initializer);
+ // Try reverse test since it can be a masked subtype.
+ if (!is_type_compatible(member.variable->initializer->get_datatype(), datatype, true)) {
+ push_error(vformat(R"(Value of type "%s" cannot be assigned to a variable of type "%s".)", member.variable->initializer->get_datatype().to_string(), datatype.to_string()), member.variable->initializer);
+ } else {
+ // TODO: Add warning.
+ mark_node_unsafe(member.variable->initializer);
+ }
} else if (datatype.builtin_type == Variant::INT && member.variable->initializer->get_datatype().builtin_type == Variant::FLOAT) {
#ifdef DEBUG_ENABLED
parser->push_warning(member.variable->initializer, GDScriptWarning::NARROWING_CONVERSION);
@@ -529,6 +553,7 @@ void GDScriptAnalyzer::resolve_class_interface(GDScriptParser::ClassNode *p_clas
break;
case GDScriptParser::DataType::NATIVE:
if (ClassDB::is_parent_class(get_real_class_name(datatype.native_type), "Resource")) {
+ member.variable->export_info.hint = PROPERTY_HINT_RESOURCE_TYPE;
member.variable->export_info.hint_string = get_real_class_name(datatype.native_type);
} else {
push_error(R"(Export type can only be built-in or a resource.)", member.variable);
@@ -592,17 +617,67 @@ void GDScriptAnalyzer::resolve_class_interface(GDScriptParser::ClassNode *p_clas
enum_type.is_meta_type = true;
enum_type.is_constant = true;
+ // Enums can't be nested, so we can safely override this.
+ current_enum = member.m_enum;
+
for (int j = 0; j < member.m_enum->values.size(); j++) {
- enum_type.enum_values[member.m_enum->values[j].identifier->name] = member.m_enum->values[j].value;
+ GDScriptParser::EnumNode::Value &element = member.m_enum->values.write[j];
+
+ if (element.custom_value) {
+ reduce_expression(element.custom_value);
+ if (!element.custom_value->is_constant) {
+ push_error(R"(Enum values must be constant.)", element.custom_value);
+ } else if (element.custom_value->reduced_value.get_type() != Variant::INT) {
+ push_error(R"(Enum values must be integers.)", element.custom_value);
+ } else {
+ element.value = element.custom_value->reduced_value;
+ element.resolved = true;
+ }
+ } else {
+ if (element.index > 0) {
+ element.value = element.parent_enum->values[element.index - 1].value + 1;
+ } else {
+ element.value = 0;
+ }
+ element.resolved = true;
+ }
+
+ enum_type.enum_values[element.identifier->name] = element.value;
}
+ current_enum = nullptr;
+
member.m_enum->set_datatype(enum_type);
} break;
case GDScriptParser::ClassNode::Member::FUNCTION:
resolve_function_signature(member.function);
break;
- case GDScriptParser::ClassNode::Member::ENUM_VALUE:
- break; // Nothing to do, type and value set in parser.
+ case GDScriptParser::ClassNode::Member::ENUM_VALUE: {
+ if (member.enum_value.custom_value) {
+ current_enum = member.enum_value.parent_enum;
+ reduce_expression(member.enum_value.custom_value);
+ current_enum = nullptr;
+
+ if (!member.enum_value.custom_value->is_constant) {
+ push_error(R"(Enum values must be constant.)", member.enum_value.custom_value);
+ } else if (member.enum_value.custom_value->reduced_value.get_type() != Variant::INT) {
+ push_error(R"(Enum values must be integers.)", member.enum_value.custom_value);
+ } else {
+ member.enum_value.value = member.enum_value.custom_value->reduced_value;
+ member.enum_value.resolved = true;
+ }
+ } else {
+ if (member.enum_value.index > 0) {
+ member.enum_value.value = member.enum_value.parent_enum->values[member.enum_value.index - 1].value + 1;
+ } else {
+ member.enum_value.value = 0;
+ }
+ member.enum_value.resolved = true;
+ }
+ // Also update the original references.
+ member.enum_value.parent_enum->values.write[member.enum_value.index] = member.enum_value;
+ p_class->members.write[i].enum_value = member.enum_value;
+ } break;
case GDScriptParser::ClassNode::Member::CLASS:
break; // Done later.
case GDScriptParser::ClassNode::Member::UNDEFINED:
@@ -872,7 +947,8 @@ void GDScriptAnalyzer::resolve_for(GDScriptParser::ForNode *p_for) {
// Use int, Vector2, Vector3 instead, which also can be used as range iterators.
if (p_for->list && p_for->list->type == GDScriptParser::Node::CALL) {
GDScriptParser::CallNode *call = static_cast<GDScriptParser::CallNode *>(p_for->list);
- if (call->callee->type == GDScriptParser::Node::IDENTIFIER) {
+ GDScriptParser::Node::Type callee_type = call->get_callee_type();
+ if (callee_type == GDScriptParser::Node::IDENTIFIER) {
GDScriptParser::IdentifierNode *callee = static_cast<GDScriptParser::IdentifierNode *>(call->callee);
if (callee->name == "range") {
list_resolved = true;
@@ -989,7 +1065,13 @@ void GDScriptAnalyzer::resolve_variable(GDScriptParser::VariableNode *p_variable
if (p_variable->initializer != nullptr) {
if (!is_type_compatible(type, p_variable->initializer->get_datatype(), true)) {
- push_error(vformat(R"(Value of type "%s" cannot be assigned to variable of type "%s".)", p_variable->initializer->get_datatype().to_string(), type.to_string()), p_variable->initializer);
+ // Try reverse test since it can be a masked subtype.
+ if (!is_type_compatible(p_variable->initializer->get_datatype(), type, true)) {
+ push_error(vformat(R"(Value of type "%s" cannot be assigned to a variable of type "%s".)", p_variable->initializer->get_datatype().to_string(), type.to_string()), p_variable->initializer);
+ } else {
+ // TODO: Add warning.
+ mark_node_unsafe(p_variable->initializer);
+ }
#ifdef DEBUG_ENABLED
} else if (type.builtin_type == Variant::INT && p_variable->initializer->get_datatype().builtin_type == Variant::FLOAT) {
parser->push_warning(p_variable->initializer, GDScriptWarning::NARROWING_CONVERSION);
@@ -1369,11 +1451,31 @@ void GDScriptAnalyzer::reduce_assignment(GDScriptParser::AssignmentNode *p_assig
push_error("Cannot assign a new value to a constant.", p_assignment->assignee);
}
- if (!is_type_compatible(p_assignment->assignee->get_datatype(), p_assignment->assigned_value->get_datatype(), true)) {
- if (p_assignment->assignee->get_datatype().is_hard_type()) {
- push_error(vformat(R"(Cannot assign a value of type "%s" to a target of type "%s".)", p_assignment->assigned_value->get_datatype().to_string(), p_assignment->assignee->get_datatype().to_string()), p_assignment->assigned_value);
+ if (!p_assignment->assignee->get_datatype().is_variant() && !p_assignment->assigned_value->get_datatype().is_variant()) {
+ bool compatible = true;
+ GDScriptParser::DataType op_type = p_assignment->assigned_value->get_datatype();
+ if (p_assignment->operation != GDScriptParser::AssignmentNode::OP_NONE) {
+ op_type = get_operation_type(p_assignment->variant_op, p_assignment->assignee->get_datatype(), p_assignment->assigned_value->get_datatype(), compatible);
+ }
+
+ if (compatible) {
+ compatible = is_type_compatible(p_assignment->assignee->get_datatype(), op_type, true);
+ if (!compatible) {
+ if (p_assignment->assignee->get_datatype().is_hard_type()) {
+ // Try reverse test since it can be a masked subtype.
+ if (!is_type_compatible(op_type, p_assignment->assignee->get_datatype(), true)) {
+ push_error(vformat(R"(Cannot assign a value of type "%s" to a target of type "%s".)", p_assignment->assigned_value->get_datatype().to_string(), p_assignment->assignee->get_datatype().to_string()), p_assignment->assigned_value);
+ } else {
+ // TODO: Add warning.
+ mark_node_unsafe(p_assignment);
+ }
+ } else {
+ // TODO: Warning in this case.
+ mark_node_unsafe(p_assignment);
+ }
+ }
} else {
- // TODO: Warning in this case.
+ push_error(vformat(R"(Invalid operands "%s" and "%s" for assignment operator.)", p_assignment->assignee->get_datatype().to_string(), p_assignment->assigned_value->get_datatype().to_string()), p_assignment);
}
}
@@ -1488,7 +1590,19 @@ void GDScriptAnalyzer::reduce_binary_op(GDScriptParser::BinaryOpNode *p_binary_o
if (p_binary_op->left_operand->is_constant && p_binary_op->right_operand->is_constant) {
p_binary_op->is_constant = true;
if (p_binary_op->variant_op < Variant::OP_MAX) {
- p_binary_op->reduced_value = Variant::evaluate(p_binary_op->variant_op, p_binary_op->left_operand->reduced_value, p_binary_op->right_operand->reduced_value);
+ bool valid = false;
+ Variant::evaluate(p_binary_op->variant_op, p_binary_op->left_operand->reduced_value, p_binary_op->right_operand->reduced_value, p_binary_op->reduced_value, valid);
+ if (!valid) {
+ if (p_binary_op->reduced_value.get_type() == Variant::STRING) {
+ push_error(vformat(R"(%s in operator %s.)", p_binary_op->reduced_value, Variant::get_operator_name(p_binary_op->variant_op)), p_binary_op);
+ } else {
+ push_error(vformat(R"(Invalid operands to operator %s, %s and %s.".)",
+ Variant::get_operator_name(p_binary_op->variant_op),
+ Variant::get_type_name(p_binary_op->left_operand->reduced_value.get_type()),
+ Variant::get_type_name(p_binary_op->right_operand->reduced_value.get_type())),
+ p_binary_op);
+ }
+ }
} else {
if (p_binary_op->operation == GDScriptParser::BinaryOpNode::OP_TYPE_TEST) {
GDScriptParser::DataType test_type = right_type;
@@ -1560,9 +1674,10 @@ void GDScriptAnalyzer::reduce_call(GDScriptParser::CallNode *p_call, bool is_awa
all_is_constant = all_is_constant && p_call->arguments[i]->is_constant;
}
+ GDScriptParser::Node::Type callee_type = p_call->get_callee_type();
GDScriptParser::DataType call_type;
- if (!p_call->is_super && p_call->callee->type == GDScriptParser::Node::IDENTIFIER) {
+ if (!p_call->is_super && callee_type == GDScriptParser::Node::IDENTIFIER) {
// Call to name directly.
StringName function_name = p_call->function_name;
Variant::Type builtin_type = GDScriptParser::get_builtin_type(function_name);
@@ -1603,6 +1718,7 @@ void GDScriptAnalyzer::reduce_call(GDScriptParser::CallNode *p_call, bool is_awa
}
signature += p_call->arguments[i]->get_datatype().to_string();
}
+ signature += ")";
push_error(vformat(R"(No constructor of "%s" matches the signature "%s".)", Variant::get_type_name(builtin_type), signature), p_call->callee);
} break;
case Callable::CallError::CALL_ERROR_TOO_MANY_ARGUMENTS:
@@ -1653,7 +1769,7 @@ void GDScriptAnalyzer::reduce_call(GDScriptParser::CallNode *p_call, bool is_awa
for (int i = 0; i < p_call->arguments.size(); i++) {
GDScriptParser::DataType par_type = type_from_property(info.arguments[i]);
- if (!is_type_compatible(par_type, p_call->arguments[i]->get_datatype())) {
+ if (!is_type_compatible(par_type, p_call->arguments[i]->get_datatype(), true)) {
types_match = false;
break;
#ifdef DEBUG_ENABLED
@@ -1680,6 +1796,7 @@ void GDScriptAnalyzer::reduce_call(GDScriptParser::CallNode *p_call, bool is_awa
}
signature += p_call->arguments[i]->get_datatype().to_string();
}
+ signature += ")";
push_error(vformat(R"(No constructor of "%s" matches the signature "%s".)", Variant::get_type_name(builtin_type), signature), p_call);
}
}
@@ -1737,10 +1854,10 @@ void GDScriptAnalyzer::reduce_call(GDScriptParser::CallNode *p_call, bool is_awa
if (p_call->is_super) {
base_type = parser->current_class->base_type;
is_self = true;
- } else if (p_call->callee->type == GDScriptParser::Node::IDENTIFIER) {
+ } else if (callee_type == GDScriptParser::Node::IDENTIFIER) {
base_type = parser->current_class->get_datatype();
is_self = true;
- } else if (p_call->callee->type == GDScriptParser::Node::SUBSCRIPT) {
+ } else if (callee_type == GDScriptParser::Node::SUBSCRIPT) {
GDScriptParser::SubscriptNode *subscript = static_cast<GDScriptParser::SubscriptNode *>(p_call->callee);
if (!subscript->is_attribute) {
// Invalid call. Error already sent in parser.
@@ -1777,9 +1894,9 @@ void GDScriptAnalyzer::reduce_call(GDScriptParser::CallNode *p_call, bool is_awa
} else {
// Check if the name exists as something else.
bool found = false;
- if (!p_call->is_super) {
+ if (!p_call->is_super && callee_type != GDScriptParser::Node::NONE) {
GDScriptParser::IdentifierNode *callee_id;
- if (p_call->callee->type == GDScriptParser::Node::IDENTIFIER) {
+ if (callee_type == GDScriptParser::Node::IDENTIFIER) {
callee_id = static_cast<GDScriptParser::IdentifierNode *>(p_call->callee);
} else {
// Can only be attribute.
@@ -1787,13 +1904,13 @@ void GDScriptAnalyzer::reduce_call(GDScriptParser::CallNode *p_call, bool is_awa
}
if (callee_id) {
reduce_identifier_from_base(callee_id, &base_type);
- GDScriptParser::DataType callee_type = callee_id->get_datatype();
- if (callee_type.is_set() && !callee_type.is_variant()) {
+ GDScriptParser::DataType callee_datatype = callee_id->get_datatype();
+ if (callee_datatype.is_set() && !callee_datatype.is_variant()) {
found = true;
- if (callee_type.builtin_type == Variant::CALLABLE) {
+ if (callee_datatype.builtin_type == Variant::CALLABLE) {
push_error(vformat(R"*(Name "%s" is a Callable. You can call it with "%s.call()" instead.)*", p_call->function_name, p_call->function_name), p_call->callee);
} else {
- push_error(vformat(R"*(Name "%s" called as a function but is a "%s".)*", p_call->function_name, callee_type.to_string()), p_call->callee);
+ push_error(vformat(R"*(Name "%s" called as a function but is a "%s".)*", p_call->function_name, callee_datatype.to_string()), p_call->callee);
}
#ifdef DEBUG_ENABLED
} else if (!is_self) {
@@ -1858,6 +1975,8 @@ void GDScriptAnalyzer::reduce_cast(GDScriptParser::CastNode *p_cast) {
void GDScriptAnalyzer::reduce_dictionary(GDScriptParser::DictionaryNode *p_dictionary) {
bool all_is_constant = true;
+ HashMap<Variant, GDScriptParser::ExpressionNode *, VariantHasher, VariantComparator> elements;
+
for (int i = 0; i < p_dictionary->elements.size(); i++) {
const GDScriptParser::DictionaryNode::Pair &element = p_dictionary->elements[i];
if (p_dictionary->style == GDScriptParser::DictionaryNode::PYTHON_DICT) {
@@ -1865,6 +1984,14 @@ void GDScriptAnalyzer::reduce_dictionary(GDScriptParser::DictionaryNode *p_dicti
}
reduce_expression(element.value);
all_is_constant = all_is_constant && element.key->is_constant && element.value->is_constant;
+
+ if (element.key->is_constant) {
+ if (elements.has(element.key->reduced_value)) {
+ push_error(vformat(R"(Key "%s" was already used in this dictionary (at line %d).)", element.key->reduced_value, elements[element.key->reduced_value]->start_line), element.key);
+ } else {
+ elements[element.key->reduced_value] = element.value;
+ }
+ }
}
if (all_is_constant) {
@@ -2073,6 +2200,33 @@ void GDScriptAnalyzer::reduce_identifier_from_base(GDScriptParser::IdentifierNod
void GDScriptAnalyzer::reduce_identifier(GDScriptParser::IdentifierNode *p_identifier, bool can_be_builtin) {
// TODO: This is opportunity to further infer types.
+
+ // Check if we are inside and enum. This allows enum values to access other elements of the same enum.
+ if (current_enum) {
+ for (int i = 0; i < current_enum->values.size(); i++) {
+ const GDScriptParser::EnumNode::Value &element = current_enum->values[i];
+ if (element.identifier->name == p_identifier->name) {
+ GDScriptParser::DataType type;
+ type.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT;
+ type.kind = element.parent_enum->identifier ? GDScriptParser::DataType::ENUM_VALUE : GDScriptParser::DataType::BUILTIN;
+ type.builtin_type = Variant::INT;
+ type.is_constant = true;
+ if (element.parent_enum->identifier) {
+ type.enum_type = element.parent_enum->identifier->name;
+ }
+ p_identifier->set_datatype(type);
+
+ if (element.resolved) {
+ p_identifier->is_constant = true;
+ p_identifier->reduced_value = element.value;
+ } else {
+ push_error(R"(Cannot use another enum element before it was declared.)", p_identifier);
+ }
+ return; // Found anyway.
+ }
+ }
+ }
+
// Check if identifier is local.
// If that's the case, the declaration already was solved before.
switch (p_identifier->source) {
@@ -2172,6 +2326,37 @@ void GDScriptAnalyzer::reduce_literal(GDScriptParser::LiteralNode *p_literal) {
}
void GDScriptAnalyzer::reduce_preload(GDScriptParser::PreloadNode *p_preload) {
+ if (!p_preload->path) {
+ return;
+ }
+
+ reduce_expression(p_preload->path);
+
+ if (!p_preload->path->is_constant) {
+ push_error("Preloaded path must be a constant string.", p_preload->path);
+ return;
+ }
+
+ if (p_preload->path->reduced_value.get_type() != Variant::STRING) {
+ push_error("Preloaded path must be a constant string.", p_preload->path);
+ } else {
+ p_preload->resolved_path = p_preload->path->reduced_value;
+ // TODO: Save this as script dependency.
+ if (p_preload->resolved_path.is_rel_path()) {
+ p_preload->resolved_path = parser->script_path.get_base_dir().plus_file(p_preload->resolved_path);
+ }
+ p_preload->resolved_path = p_preload->resolved_path.simplify_path();
+ if (!FileAccess::exists(p_preload->resolved_path)) {
+ push_error(vformat(R"(Preload file "%s" does not exist.)", p_preload->resolved_path), p_preload->path);
+ } else {
+ // TODO: Don't load if validating: use completion cache.
+ p_preload->resource = ResourceLoader::load(p_preload->resolved_path);
+ if (p_preload->resource.is_null()) {
+ push_error(vformat(R"(Could not p_preload resource file "%s".)", p_preload->resolved_path), p_preload->path);
+ }
+ }
+ }
+
p_preload->is_constant = true;
p_preload->reduced_value = p_preload->resource;
p_preload->set_datatype(type_from_variant(p_preload->reduced_value));
@@ -2347,7 +2532,7 @@ void GDScriptAnalyzer::reduce_subscript(GDScriptParser::SubscriptNode *p_subscri
// Check resulting type if possible.
result_type.builtin_type = Variant::NIL;
result_type.kind = GDScriptParser::DataType::BUILTIN;
- result_type.type_source = GDScriptParser::DataType::INFERRED;
+ result_type.type_source = base_type.is_hard_type() ? GDScriptParser::DataType::ANNOTATED_INFERRED : GDScriptParser::DataType::INFERRED;
switch (base_type.builtin_type) {
// Can't index at all.
@@ -2478,6 +2663,12 @@ void GDScriptAnalyzer::reduce_unary_op(GDScriptParser::UnaryOpNode *p_unary_op)
GDScriptParser::DataType result;
+ if (p_unary_op->operand == nullptr) {
+ result.kind = GDScriptParser::DataType::VARIANT;
+ p_unary_op->set_datatype(result);
+ return;
+ }
+
if (p_unary_op->operand->is_constant) {
p_unary_op->is_constant = true;
p_unary_op->reduced_value = Variant::evaluate(p_unary_op->variant_op, p_unary_op->operand->reduced_value, Variant());
@@ -2524,9 +2715,27 @@ GDScriptParser::DataType GDScriptAnalyzer::type_from_variant(const Variant &p_va
Ref<GDScript> gds = scr;
if (gds.is_valid()) {
result.kind = GDScriptParser::DataType::CLASS;
- Ref<GDScriptParserRef> ref = get_parser_for(gds->get_path());
+ // This might be an inner class, so we want to get the parser for the root.
+ // But still get the inner class from that tree.
+ GDScript *current = gds.ptr();
+ List<StringName> class_chain;
+ while (current->_owner) {
+ // Push to front so it's in reverse.
+ class_chain.push_front(current->name);
+ current = current->_owner;
+ }
+
+ Ref<GDScriptParserRef> ref = get_parser_for(current->path);
ref->raise_status(GDScriptParserRef::INTERFACE_SOLVED);
- result.class_type = ref->get_parser()->head;
+
+ GDScriptParser::ClassNode *found = ref->get_parser()->head;
+
+ // It should be okay to assume this exists, since we have a complete script already.
+ for (const List<StringName>::Element *E = class_chain.front(); E; E = E->next()) {
+ found = found->get_member(E->get()).m_class;
+ }
+
+ result.class_type = found;
result.script_path = ref->get_parser()->script_path;
} else {
result.kind = GDScriptParser::DataType::SCRIPT;
@@ -2852,7 +3061,7 @@ GDScriptParser::DataType GDScriptAnalyzer::get_operation_type(Variant::Operator
}
// Avoid error in formatting operator (%) where it doesn't find a placeholder.
- if (a_type == Variant::STRING) {
+ if (a_type == Variant::STRING && b_type != Variant::ARRAY) {
a = String("%s");
}
@@ -3058,6 +3267,9 @@ Error GDScriptAnalyzer::resolve_program() {
List<String> parser_keys;
depended_parsers.get_key_list(&parser_keys);
for (const List<String>::Element *E = parser_keys.front(); E != nullptr; E = E->next()) {
+ if (depended_parsers[E->get()].is_null()) {
+ return ERR_PARSE_ERROR;
+ }
depended_parsers[E->get()]->raise_status(GDScriptParserRef::FULLY_SOLVED);
}
depended_parsers.clear();
diff --git a/modules/gdscript/gdscript_analyzer.h b/modules/gdscript/gdscript_analyzer.h
index 85183d3272..da767522ad 100644
--- a/modules/gdscript/gdscript_analyzer.h
+++ b/modules/gdscript/gdscript_analyzer.h
@@ -41,6 +41,8 @@ class GDScriptAnalyzer {
GDScriptParser *parser = nullptr;
HashMap<String, Ref<GDScriptParserRef>> depended_parsers;
+ const GDScriptParser::EnumNode *current_enum = nullptr;
+
Error resolve_inheritance(GDScriptParser::ClassNode *p_class, bool p_recursive = true);
GDScriptParser::DataType resolve_datatype(GDScriptParser::TypeNode *p_type);
@@ -113,6 +115,8 @@ public:
Error analyze();
GDScriptAnalyzer(GDScriptParser *p_parser);
+
+ static void cleanup();
};
#endif // GDSCRIPT_ANALYZER_H
diff --git a/modules/gdscript/gdscript_cache.cpp b/modules/gdscript/gdscript_cache.cpp
index 583283ff46..992f8f4b58 100644
--- a/modules/gdscript/gdscript_cache.cpp
+++ b/modules/gdscript/gdscript_cache.cpp
@@ -85,6 +85,17 @@ Error GDScriptParserRef::raise_status(Status p_new_status) {
return result;
}
}
+ if (result != OK) {
+ if (parser != nullptr) {
+ memdelete(parser);
+ parser = nullptr;
+ }
+ if (analyzer != nullptr) {
+ memdelete(analyzer);
+ analyzer = nullptr;
+ }
+ return result;
+ }
}
return result;
@@ -116,14 +127,17 @@ Ref<GDScriptParserRef> GDScriptCache::get_parser(const String &p_path, GDScriptP
singleton->dependencies[p_owner].insert(p_path);
}
if (singleton->parser_map.has(p_path)) {
- ref = singleton->parser_map[p_path];
+ ref = Ref<GDScriptParserRef>(singleton->parser_map[p_path]);
} else {
+ if (!FileAccess::exists(p_path)) {
+ r_error = ERR_FILE_NOT_FOUND;
+ return ref;
+ }
GDScriptParser *parser = memnew(GDScriptParser);
ref.instance();
ref->parser = parser;
ref->path = p_path;
- singleton->parser_map[p_path] = ref;
- ref->unreference();
+ singleton->parser_map[p_path] = ref.ptr();
}
r_error = ref->raise_status(p_status);
@@ -171,10 +185,7 @@ Ref<GDScript> GDScriptCache::get_shallow_script(const String &p_path, const Stri
script->set_script_path(p_path);
script->load_source_code(p_path);
- singleton->shallow_gdscript_cache[p_path] = script;
- // The one in cache is not a hard reference: if the script dies somewhere else it's fine.
- // Scripts remove themselves from cache when they die.
- script->unreference();
+ singleton->shallow_gdscript_cache[p_path] = script.ptr();
return script;
}
@@ -202,7 +213,7 @@ Ref<GDScript> GDScriptCache::get_full_script(const String &p_path, Error &r_erro
return script;
}
- singleton->full_gdscript_cache[p_path] = script;
+ singleton->full_gdscript_cache[p_path] = script.ptr();
singleton->shallow_gdscript_cache.erase(p_path);
return script;
@@ -211,7 +222,7 @@ Ref<GDScript> GDScriptCache::get_full_script(const String &p_path, Error &r_erro
Error GDScriptCache::finish_compiling(const String &p_owner) {
// Mark this as compiled.
Ref<GDScript> script = get_shallow_script(p_owner);
- singleton->full_gdscript_cache[p_owner] = script;
+ singleton->full_gdscript_cache[p_owner] = script.ptr();
singleton->shallow_gdscript_cache.erase(p_owner);
Set<String> depends = singleton->dependencies[p_owner];
diff --git a/modules/gdscript/gdscript_cache.h b/modules/gdscript/gdscript_cache.h
index 770704d6eb..865df34051 100644
--- a/modules/gdscript/gdscript_cache.h
+++ b/modules/gdscript/gdscript_cache.h
@@ -70,9 +70,9 @@ public:
class GDScriptCache {
// String key is full path.
- HashMap<String, Ref<GDScriptParserRef>> parser_map;
- HashMap<String, Ref<GDScript>> shallow_gdscript_cache;
- HashMap<String, Ref<GDScript>> full_gdscript_cache;
+ HashMap<String, GDScriptParserRef *> parser_map;
+ HashMap<String, GDScript *> shallow_gdscript_cache;
+ HashMap<String, GDScript *> full_gdscript_cache;
HashMap<String, Set<String>> dependencies;
friend class GDScript;
diff --git a/modules/gdscript/gdscript_compiler.cpp b/modules/gdscript/gdscript_compiler.cpp
index e34d87f5cc..67a894912f 100644
--- a/modules/gdscript/gdscript_compiler.cpp
+++ b/modules/gdscript/gdscript_compiler.cpp
@@ -2609,16 +2609,27 @@ Error GDScriptCompiler::_parse_class_level(GDScript *p_script, const GDScriptPar
p_script->_base = base.ptr();
if (p_class->base_type.kind == GDScriptParser::DataType::CLASS && p_class->base_type.class_type != nullptr) {
- if (!parsed_classes.has(p_script->_base)) {
- if (parsing_classes.has(p_script->_base)) {
- String class_name = p_class->identifier ? p_class->identifier->name : "<main>";
- _set_error("Cyclic class reference for '" + class_name + "'.", p_class);
- return ERR_PARSE_ERROR;
+ if (p_class->base_type.script_path == main_script->path) {
+ if (!parsed_classes.has(p_script->_base)) {
+ if (parsing_classes.has(p_script->_base)) {
+ String class_name = p_class->identifier ? p_class->identifier->name : "<main>";
+ _set_error("Cyclic class reference for '" + class_name + "'.", p_class);
+ return ERR_PARSE_ERROR;
+ }
+ Error err = _parse_class_level(p_script->_base, p_class->base_type.class_type, p_keep_state);
+ if (err) {
+ return err;
+ }
}
- Error err = _parse_class_level(p_script->_base, p_class->base_type.class_type, p_keep_state);
+ } else {
+ Error err = OK;
+ base = GDScriptCache::get_full_script(p_class->base_type.script_path, err, main_script->path);
if (err) {
return err;
}
+ if (base.is_null() && !base->is_valid()) {
+ return ERR_COMPILATION_FAILED;
+ }
}
}
@@ -2696,11 +2707,7 @@ Error GDScriptCompiler::_parse_class_level(GDScript *p_script, const GDScriptPar
const GDScriptParser::ConstantNode *constant = member.constant;
StringName name = constant->identifier->name;
- ERR_CONTINUE(constant->initializer->type != GDScriptParser::Node::LITERAL);
-
- const GDScriptParser::LiteralNode *literal = static_cast<const GDScriptParser::LiteralNode *>(constant->initializer);
-
- p_script->constants.insert(name, literal->value);
+ p_script->constants.insert(name, constant->initializer->reduced_value);
#ifdef TOOLS_ENABLED
p_script->member_lines[name] = constant->start_line;
diff --git a/modules/gdscript/gdscript_editor.cpp b/modules/gdscript/gdscript_editor.cpp
index 239015060e..2e372575da 100644
--- a/modules/gdscript/gdscript_editor.cpp
+++ b/modules/gdscript/gdscript_editor.cpp
@@ -483,6 +483,8 @@ String GDScriptLanguage::make_function(const String &p_class, const String &p_na
#ifdef TOOLS_ENABLED
+#define COMPLETION_RECURSION_LIMIT 200
+
struct GDScriptCompletionIdentifier {
GDScriptParser::DataType type;
String enumeration;
@@ -766,9 +768,11 @@ static void _find_identifiers_in_suite(const GDScriptParser::SuiteNode *p_suite,
}
}
-static void _find_identifiers_in_base(const GDScriptCompletionIdentifier &p_base, bool p_only_functions, Map<String, ScriptCodeCompletionOption> &r_result);
+static void _find_identifiers_in_base(const GDScriptCompletionIdentifier &p_base, bool p_only_functions, Map<String, ScriptCodeCompletionOption> &r_result, int p_recursion_depth);
+
+static void _find_identifiers_in_class(const GDScriptParser::ClassNode *p_class, bool p_only_functions, bool p_static, bool p_parent_only, Map<String, ScriptCodeCompletionOption> &r_result, int p_recursion_depth) {
+ ERR_FAIL_COND(p_recursion_depth > COMPLETION_RECURSION_LIMIT);
-static void _find_identifiers_in_class(const GDScriptParser::ClassNode *p_class, bool p_only_functions, bool p_static, bool p_parent_only, Map<String, ScriptCodeCompletionOption> &r_result) {
if (!p_parent_only) {
bool outer = false;
const GDScriptParser::ClassNode *clss = p_class;
@@ -820,7 +824,6 @@ static void _find_identifiers_in_class(const GDScriptParser::ClassNode *p_class,
break;
case GDScriptParser::ClassNode::Member::SIGNAL:
if (p_only_functions || outer) {
- clss = clss->outer;
continue;
}
option = ScriptCodeCompletionOption(member.signal->identifier->name, ScriptCodeCompletionOption::KIND_SIGNAL);
@@ -840,10 +843,12 @@ static void _find_identifiers_in_class(const GDScriptParser::ClassNode *p_class,
base_type.type = p_class->base_type;
base_type.type.is_meta_type = p_static;
- _find_identifiers_in_base(base_type, p_only_functions, r_result);
+ _find_identifiers_in_base(base_type, p_only_functions, r_result, p_recursion_depth + 1);
}
-static void _find_identifiers_in_base(const GDScriptCompletionIdentifier &p_base, bool p_only_functions, Map<String, ScriptCodeCompletionOption> &r_result) {
+static void _find_identifiers_in_base(const GDScriptCompletionIdentifier &p_base, bool p_only_functions, Map<String, ScriptCodeCompletionOption> &r_result, int p_recursion_depth) {
+ ERR_FAIL_COND(p_recursion_depth > COMPLETION_RECURSION_LIMIT);
+
GDScriptParser::DataType base_type = p_base.type;
bool _static = base_type.is_meta_type;
@@ -856,7 +861,7 @@ static void _find_identifiers_in_base(const GDScriptCompletionIdentifier &p_base
while (!base_type.has_no_type()) {
switch (base_type.kind) {
case GDScriptParser::DataType::CLASS: {
- _find_identifiers_in_class(base_type.class_type, p_only_functions, _static, false, r_result);
+ _find_identifiers_in_class(base_type.class_type, p_only_functions, _static, false, r_result, p_recursion_depth + 1);
// This already finds all parent identifiers, so we are done.
base_type = GDScriptParser::DataType();
} break;
@@ -1007,14 +1012,14 @@ static void _find_identifiers_in_base(const GDScriptCompletionIdentifier &p_base
}
}
-static void _find_identifiers(GDScriptParser::CompletionContext &p_context, bool p_only_functions, Map<String, ScriptCodeCompletionOption> &r_result) {
+static void _find_identifiers(GDScriptParser::CompletionContext &p_context, bool p_only_functions, Map<String, ScriptCodeCompletionOption> &r_result, int p_recursion_depth) {
if (!p_only_functions && p_context.current_suite) {
// This includes function parameters, since they are also locals.
_find_identifiers_in_suite(p_context.current_suite, r_result);
}
if (p_context.current_class) {
- _find_identifiers_in_class(p_context.current_class, p_only_functions, (!p_context.current_function || p_context.current_function->is_static), false, r_result);
+ _find_identifiers_in_class(p_context.current_class, p_only_functions, (!p_context.current_function || p_context.current_function->is_static), false, r_result, p_recursion_depth + 1);
}
for (int i = 0; i < GDScriptFunctions::FUNC_MAX; i++) {
@@ -1259,8 +1264,10 @@ static bool _guess_expression_type(GDScriptParser::CompletionContext &p_context,
GDScriptParser::CompletionContext c = p_context;
c.current_line = call->start_line;
+ GDScriptParser::Node::Type callee_type = call->get_callee_type();
+
GDScriptCompletionIdentifier base;
- if (call->callee->type == GDScriptParser::Node::IDENTIFIER || call->is_super) {
+ if (callee_type == GDScriptParser::Node::IDENTIFIER || call->is_super) {
// Simple call, so base is 'self'.
if (p_context.current_class) {
base.type.kind = GDScriptParser::DataType::CLASS;
@@ -1271,7 +1278,7 @@ static bool _guess_expression_type(GDScriptParser::CompletionContext &p_context,
} else {
break;
}
- } else if (call->callee->type == GDScriptParser::Node::SUBSCRIPT && static_cast<const GDScriptParser::SubscriptNode *>(call->callee)->is_attribute) {
+ } else if (callee_type == GDScriptParser::Node::SUBSCRIPT && static_cast<const GDScriptParser::SubscriptNode *>(call->callee)->is_attribute) {
if (!_guess_expression_type(c, static_cast<const GDScriptParser::SubscriptNode *>(call->callee)->base, base)) {
found = false;
break;
@@ -2290,6 +2297,7 @@ static void _find_call_arguments(GDScriptParser::CompletionContext &p_context, c
GDScriptParser::DataType base_type;
bool _static = false;
const GDScriptParser::CallNode *call = static_cast<const GDScriptParser::CallNode *>(p_call);
+ GDScriptParser::Node::Type callee_type = GDScriptParser::Node::NONE;
GDScriptCompletionIdentifier connect_base;
@@ -2319,14 +2327,14 @@ static void _find_call_arguments(GDScriptParser::CompletionContext &p_context, c
i++;
}
return;
- } else if (call->is_super || call->callee->type == GDScriptParser::Node::IDENTIFIER) {
+ } else if (call->is_super || callee_type == GDScriptParser::Node::IDENTIFIER) {
base = p_context.base;
if (p_context.current_class) {
base_type = p_context.current_class->get_datatype();
_static = !p_context.current_function || p_context.current_function->is_static;
}
- } else if (call->callee->type == GDScriptParser::Node::SUBSCRIPT) {
+ } else if (callee_type == GDScriptParser::Node::SUBSCRIPT) {
const GDScriptParser::SubscriptNode *subscript = static_cast<const GDScriptParser::SubscriptNode *>(call->callee);
if (subscript->is_attribute) {
@@ -2450,7 +2458,7 @@ Error GDScriptLanguage::complete_code(const String &p_code, const String &p_path
break;
}
if (!_guess_expression_type(completion_context, static_cast<const GDScriptParser::AssignmentNode *>(completion_context.node)->assignee, type)) {
- _find_identifiers(completion_context, false, options);
+ _find_identifiers(completion_context, false, options, 0);
r_forced = true;
break;
}
@@ -2459,7 +2467,7 @@ Error GDScriptLanguage::complete_code(const String &p_code, const String &p_path
_find_enumeration_candidates(completion_context, type.enumeration, options);
r_forced = options.size() > 0;
} else {
- _find_identifiers(completion_context, false, options);
+ _find_identifiers(completion_context, false, options, 0);
r_forced = true;
}
} break;
@@ -2467,7 +2475,7 @@ Error GDScriptLanguage::complete_code(const String &p_code, const String &p_path
is_function = true;
[[fallthrough]];
case GDScriptParser::COMPLETION_IDENTIFIER: {
- _find_identifiers(completion_context, is_function, options);
+ _find_identifiers(completion_context, is_function, options, 0);
} break;
case GDScriptParser::COMPLETION_ATTRIBUTE_METHOD:
is_function = true;
@@ -2481,7 +2489,7 @@ Error GDScriptLanguage::complete_code(const String &p_code, const String &p_path
break;
}
- _find_identifiers_in_base(base, is_function, options);
+ _find_identifiers_in_base(base, is_function, options, 0);
}
} break;
case GDScriptParser::COMPLETION_SUBSCRIPT: {
@@ -2501,7 +2509,7 @@ Error GDScriptLanguage::complete_code(const String &p_code, const String &p_path
c.current_class = nullptr;
}
- _find_identifiers_in_base(base, false, options);
+ _find_identifiers_in_base(base, false, options, 0);
} break;
case GDScriptParser::COMPLETION_TYPE_ATTRIBUTE: {
if (!completion_context.current_class) {
@@ -2527,7 +2535,7 @@ Error GDScriptLanguage::complete_code(const String &p_code, const String &p_path
// TODO: Improve this to only list types.
if (found) {
- _find_identifiers_in_base(base, false, options);
+ _find_identifiers_in_base(base, false, options, 0);
}
r_forced = true;
} break;
@@ -2648,7 +2656,7 @@ Error GDScriptLanguage::complete_code(const String &p_code, const String &p_path
if (!completion_context.current_class) {
break;
}
- _find_identifiers_in_class(completion_context.current_class, true, false, true, options);
+ _find_identifiers_in_class(completion_context.current_class, true, false, true, options, 0);
} break;
}
@@ -2762,6 +2770,7 @@ static Error _lookup_symbol_from_base(const GDScriptParser::DataType &p_base, co
if (base_type.class_type->has_member(p_symbol)) {
r_result.type = ScriptLanguage::LookupResult::RESULT_SCRIPT_LOCATION;
r_result.location = base_type.class_type->get_member(p_symbol).get_line();
+ return OK;
}
base_type = base_type.class_type->base_type;
}
diff --git a/modules/gdscript/gdscript_parser.cpp b/modules/gdscript/gdscript_parser.cpp
index af07457750..03da5e926b 100644
--- a/modules/gdscript/gdscript_parser.cpp
+++ b/modules/gdscript/gdscript_parser.cpp
@@ -94,6 +94,10 @@ Variant::Type GDScriptParser::get_builtin_type(const StringName &p_type) {
return Variant::VARIANT_MAX;
}
+void GDScriptParser::cleanup() {
+ builtin_types.clear();
+}
+
GDScriptFunctions::Function GDScriptParser::get_builtin_function(const StringName &p_name) {
for (int i = 0; i < GDScriptFunctions::FUNC_MAX; i++) {
if (p_name == GDScriptFunctions::get_func_name(GDScriptFunctions::Function(i))) {
@@ -462,44 +466,40 @@ void GDScriptParser::pop_multiline() {
}
bool GDScriptParser::is_statement_end() {
- return check(GDScriptTokenizer::Token::NEWLINE) || check(GDScriptTokenizer::Token::SEMICOLON);
+ return check(GDScriptTokenizer::Token::NEWLINE) || check(GDScriptTokenizer::Token::SEMICOLON) || check(GDScriptTokenizer::Token::TK_EOF);
}
void GDScriptParser::end_statement(const String &p_context) {
bool found = false;
- while (is_statement_end()) {
+ while (is_statement_end() && !is_at_end()) {
// Remove sequential newlines/semicolons.
found = true;
advance();
}
- if (!found) {
+ if (!found && !is_at_end()) {
push_error(vformat(R"(Expected end of statement after %s, found "%s" instead.)", p_context, current.get_name()));
}
}
void GDScriptParser::parse_program() {
- if (current.type == GDScriptTokenizer::Token::TK_EOF) {
- // Empty file.
- push_error("Source file is empty.");
- return;
- }
-
head = alloc_node<ClassNode>();
current_class = head;
if (match(GDScriptTokenizer::Token::ANNOTATION)) {
// Check for @tool annotation.
AnnotationNode *annotation = parse_annotation(AnnotationInfo::SCRIPT | AnnotationInfo::CLASS_LEVEL);
- if (annotation->name == "@tool") {
- // TODO: don't allow @tool anywhere else. (Should all script annotations be the first thing?).
- _is_tool = true;
- if (previous.type != GDScriptTokenizer::Token::NEWLINE) {
- push_error(R"(Expected newline after "@tool" annotation.)");
- }
- // @tool annotation has no specific target.
- annotation->apply(this, nullptr);
- } else {
- annotation_stack.push_back(annotation);
+ if (annotation != nullptr) {
+ if (annotation->name == "@tool") {
+ // TODO: don't allow @tool anywhere else. (Should all script annotations be the first thing?).
+ _is_tool = true;
+ if (previous.type != GDScriptTokenizer::Token::NEWLINE) {
+ push_error(R"(Expected newline after "@tool" annotation.)");
+ }
+ // @tool annotation has no specific target.
+ annotation->apply(this, nullptr);
+ } else {
+ annotation_stack.push_back(annotation);
+ }
}
}
@@ -631,7 +631,6 @@ void GDScriptParser::parse_extends() {
current_class->extends_path = previous.literal;
if (!match(GDScriptTokenizer::Token::PERIOD)) {
- end_statement("superclass path");
return;
}
}
@@ -986,9 +985,15 @@ GDScriptParser::SignalNode *GDScriptParser::parse_signal() {
signal->identifier = parse_identifier();
if (match(GDScriptTokenizer::Token::PARENTHESIS_OPEN)) {
- while (!check(GDScriptTokenizer::Token::PARENTHESIS_CLOSE) && !is_at_end()) {
+ do {
+ if (check(GDScriptTokenizer::Token::PARENTHESIS_CLOSE)) {
+ // Allow for trailing comma.
+ break;
+ }
+
ParameterNode *parameter = parse_parameter();
if (parameter == nullptr) {
+ push_error("Expected signal parameter name.");
break;
}
if (parameter->default_value != nullptr) {
@@ -1000,7 +1005,8 @@ GDScriptParser::SignalNode *GDScriptParser::parse_signal() {
signal->parameters_indices[parameter->identifier->name] = signal->parameters.size();
signal->parameters.push_back(parameter);
}
- }
+ } while (match(GDScriptTokenizer::Token::COMMA) && !is_at_end());
+
consume(GDScriptTokenizer::Token::PARENTHESIS_CLOSE, R"*(Expected closing ")" after signal parameters.)*");
}
@@ -1022,7 +1028,7 @@ GDScriptParser::EnumNode *GDScriptParser::parse_enum() {
push_multiline(true);
consume(GDScriptTokenizer::Token::BRACE_OPEN, vformat(R"(Expected "{" after %s.)", named ? "enum name" : R"("enum")"));
- int current_value = 0;
+ HashMap<StringName, int> elements;
do {
if (check(GDScriptTokenizer::Token::BRACE_CLOSE)) {
@@ -1031,8 +1037,13 @@ GDScriptParser::EnumNode *GDScriptParser::parse_enum() {
if (consume(GDScriptTokenizer::Token::IDENTIFIER, R"(Expected identifer for enum key.)")) {
EnumNode::Value item;
item.identifier = parse_identifier();
+ item.parent_enum = enum_node;
+ item.line = previous.start_line;
+ item.leftmost_column = previous.leftmost_column;
- if (!named) {
+ if (elements.has(item.identifier->name)) {
+ push_error(vformat(R"(Name "%s" was already in this enum (at line %d).)", item.identifier->name, elements[item.identifier->name]), item.identifier);
+ } else if (!named) {
// TODO: Abstract this recursive member check.
ClassNode *parent = current_class;
while (parent != nullptr) {
@@ -1044,19 +1055,18 @@ GDScriptParser::EnumNode *GDScriptParser::parse_enum() {
}
}
- if (match(GDScriptTokenizer::Token::EQUAL)) {
- if (consume(GDScriptTokenizer::Token::LITERAL, R"(Expected integer value after "=".)")) {
- item.custom_value = parse_literal();
+ elements[item.identifier->name] = item.line;
- if (item.custom_value->value.get_type() != Variant::INT) {
- push_error(R"(Expected integer value after "=".)");
- item.custom_value = nullptr;
- } else {
- current_value = item.custom_value->value;
- }
+ if (match(GDScriptTokenizer::Token::EQUAL)) {
+ ExpressionNode *value = parse_expression(false);
+ if (value == nullptr) {
+ push_error(R"(Expected expression value after "=".)");
}
+ item.custom_value = value;
}
- item.value = current_value++;
+ item.rightmost_column = previous.rightmost_column;
+
+ item.index = enum_node->values.size();
enum_node->values.push_back(item);
if (!named) {
// Add as member of current class.
@@ -1138,6 +1148,9 @@ GDScriptParser::FunctionNode *GDScriptParser::parse_function() {
if (match(GDScriptTokenizer::Token::FORWARD_ARROW)) {
make_completion_context(COMPLETION_TYPE_NAME_OR_VOID, function);
function->return_type = parse_type(true);
+ if (function->return_type == nullptr) {
+ push_error(R"(Expected return type or "void" after "->".)");
+ }
}
// TODO: Improve token consumption so it synchronizes to a statement boundary. This way we can get into the function body with unrecognized tokens.
@@ -1342,7 +1355,7 @@ GDScriptParser::Node *GDScriptParser::parse_statement() {
advance();
ReturnNode *n_return = alloc_node<ReturnNode>();
if (!is_statement_end()) {
- if (current_function->identifier->name == GDScriptLanguage::get_singleton()->strings._init) {
+ if (current_function && current_function->identifier->name == GDScriptLanguage::get_singleton()->strings._init) {
push_error(R"(Constructor cannot return a value.)");
}
n_return->return_value = parse_expression(false);
@@ -2005,7 +2018,6 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_binary_operator(Expression
push_error(vformat(R"(Expected expression after "%s" operator.")", op.get_name()));
}
- // TODO: Store the Variant operator here too (in the node).
// TODO: Also for unary, ternary, and assignment.
switch (op.type) {
case GDScriptTokenizer::Token::PLUS:
@@ -2167,39 +2179,50 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_assignment(ExpressionNode
switch (previous.type) {
case GDScriptTokenizer::Token::EQUAL:
assignment->operation = AssignmentNode::OP_NONE;
+ assignment->variant_op = Variant::OP_MAX;
#ifdef DEBUG_ENABLED
has_operator = false;
#endif
break;
case GDScriptTokenizer::Token::PLUS_EQUAL:
assignment->operation = AssignmentNode::OP_ADDITION;
+ assignment->variant_op = Variant::OP_ADD;
break;
case GDScriptTokenizer::Token::MINUS_EQUAL:
assignment->operation = AssignmentNode::OP_SUBTRACTION;
+ assignment->variant_op = Variant::OP_SUBTRACT;
break;
case GDScriptTokenizer::Token::STAR_EQUAL:
assignment->operation = AssignmentNode::OP_MULTIPLICATION;
+ assignment->variant_op = Variant::OP_MULTIPLY;
break;
case GDScriptTokenizer::Token::SLASH_EQUAL:
assignment->operation = AssignmentNode::OP_DIVISION;
+ assignment->variant_op = Variant::OP_DIVIDE;
break;
case GDScriptTokenizer::Token::PERCENT_EQUAL:
assignment->operation = AssignmentNode::OP_MODULO;
+ assignment->variant_op = Variant::OP_MODULE;
break;
case GDScriptTokenizer::Token::LESS_LESS_EQUAL:
assignment->operation = AssignmentNode::OP_BIT_SHIFT_LEFT;
+ assignment->variant_op = Variant::OP_SHIFT_LEFT;
break;
case GDScriptTokenizer::Token::GREATER_GREATER_EQUAL:
assignment->operation = AssignmentNode::OP_BIT_SHIFT_RIGHT;
+ assignment->variant_op = Variant::OP_SHIFT_RIGHT;
break;
case GDScriptTokenizer::Token::AMPERSAND_EQUAL:
assignment->operation = AssignmentNode::OP_BIT_AND;
+ assignment->variant_op = Variant::OP_BIT_AND;
break;
case GDScriptTokenizer::Token::PIPE_EQUAL:
assignment->operation = AssignmentNode::OP_BIT_OR;
+ assignment->variant_op = Variant::OP_BIT_OR;
break;
case GDScriptTokenizer::Token::CARET_EQUAL:
assignment->operation = AssignmentNode::OP_BIT_XOR;
+ assignment->variant_op = Variant::OP_BIT_XOR;
break;
default:
break; // Unreachable.
@@ -2328,7 +2351,11 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_dictionary(ExpressionNode
GDScriptParser::ExpressionNode *GDScriptParser::parse_grouping(ExpressionNode *p_previous_operand, bool p_can_assign) {
ExpressionNode *grouped = parse_expression(false);
pop_multiline();
- consume(GDScriptTokenizer::Token::PARENTHESIS_CLOSE, R"*(Expected closing ")" after grouping expression.)*");
+ if (grouped == nullptr) {
+ push_error(R"(Expected grouping expression.)");
+ } else {
+ consume(GDScriptTokenizer::Token::PARENTHESIS_CLOSE, R"*(Expected closing ")" after grouping expression.)*");
+ }
return grouped;
}
@@ -2419,7 +2446,9 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_call(ExpressionNode *p_pre
} else {
call->callee = p_previous_operand;
- if (call->callee->type == Node::IDENTIFIER) {
+ if (call->callee == nullptr) {
+ push_error(R"*(Cannot call on an expression. Use ".call()" if it's a Callable.)*");
+ } else if (call->callee->type == Node::IDENTIFIER) {
call->function_name = static_cast<IdentifierNode *>(call->callee)->name;
make_completion_context(COMPLETION_METHOD, call->callee);
} else if (call->callee->type == Node::SUBSCRIPT) {
@@ -2475,15 +2504,18 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_get_node(ExpressionNode *p
make_completion_context(COMPLETION_GET_NODE, get_node);
get_node->string = parse_literal();
return get_node;
- } else if (check(GDScriptTokenizer::Token::IDENTIFIER)) {
+ } else if (current.is_node_name()) {
GetNodeNode *get_node = alloc_node<GetNodeNode>();
int chain_position = 0;
do {
make_completion_context(COMPLETION_GET_NODE, get_node, chain_position++);
- if (!consume(GDScriptTokenizer::Token::IDENTIFIER, R"(Expect node identifer after "/".)")) {
+ if (!current.is_node_name()) {
+ push_error(R"(Expect node path after "/".)");
return nullptr;
}
- IdentifierNode *identifier = parse_identifier();
+ advance();
+ IdentifierNode *identifier = alloc_node<IdentifierNode>();
+ identifier->name = previous.get_identifier();
get_node->chain.push_back(identifier);
} while (match(GDScriptTokenizer::Token::SLASH));
return get_node;
@@ -2507,29 +2539,6 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_preload(ExpressionNode *p_
if (preload->path == nullptr) {
push_error(R"(Expected resource path after "(".)");
- } else if (preload->path->type != Node::LITERAL) {
- push_error("Preloaded path must be a constant string.");
- } else {
- LiteralNode *path = static_cast<LiteralNode *>(preload->path);
- if (path->value.get_type() != Variant::STRING) {
- push_error("Preloaded path must be a constant string.");
- } else {
- preload->resolved_path = path->value;
- // TODO: Save this as script dependency.
- if (preload->resolved_path.is_rel_path()) {
- preload->resolved_path = script_path.get_base_dir().plus_file(preload->resolved_path);
- }
- preload->resolved_path = preload->resolved_path.simplify_path();
- if (!FileAccess::exists(preload->resolved_path)) {
- push_error(vformat(R"(Preload file "%s" does not exist.)", preload->resolved_path));
- } else {
- // TODO: Don't load if validating: use completion cache.
- preload->resource = ResourceLoader::load(preload->resolved_path);
- if (preload->resource.is_null()) {
- push_error(vformat(R"(Could not preload resource file "%s".)", preload->resolved_path));
- }
- }
- }
}
pop_completion_call();
diff --git a/modules/gdscript/gdscript_parser.h b/modules/gdscript/gdscript_parser.h
index a741ae0cc7..7d8ae7fc55 100644
--- a/modules/gdscript/gdscript_parser.h
+++ b/modules/gdscript/gdscript_parser.h
@@ -383,6 +383,14 @@ public:
CallNode() {
type = CALL;
}
+
+ Type get_callee_type() const {
+ if (callee == nullptr) {
+ return Type::NONE;
+ } else {
+ return callee->type;
+ }
+ }
};
struct CastNode : public ExpressionNode {
@@ -397,7 +405,10 @@ public:
struct EnumNode : public Node {
struct Value {
IdentifierNode *identifier = nullptr;
- LiteralNode *custom_value = nullptr;
+ ExpressionNode *custom_value = nullptr;
+ EnumNode *parent_enum = nullptr;
+ int index = -1;
+ bool resolved = false;
int value = 0;
int line = 0;
int leftmost_column = 0;
@@ -1335,6 +1346,7 @@ public:
void print_tree(const GDScriptParser &p_parser);
};
#endif // DEBUG_ENABLED
+ static void cleanup();
};
#endif // GDSCRIPT_PARSER_H
diff --git a/modules/gdscript/gdscript_tokenizer.cpp b/modules/gdscript/gdscript_tokenizer.cpp
index 7a4bdd88ba..737e920693 100644
--- a/modules/gdscript/gdscript_tokenizer.cpp
+++ b/modules/gdscript/gdscript_tokenizer.cpp
@@ -156,6 +156,64 @@ const char *GDScriptTokenizer::Token::get_name() const {
return token_names[type];
}
+bool GDScriptTokenizer::Token::is_identifier() const {
+ // Note: Most keywords should not be recognized as identifiers.
+ // These are only exceptions for stuff that already is on the engine's API.
+ switch (type) {
+ case IDENTIFIER:
+ case MATCH: // Used in String.match().
+ return true;
+ default:
+ return false;
+ }
+}
+
+bool GDScriptTokenizer::Token::is_node_name() const {
+ // This is meant to allow keywords with the $ notation, but not as general identifiers.
+ switch (type) {
+ case IDENTIFIER:
+ case AND:
+ case AS:
+ case ASSERT:
+ case AWAIT:
+ case BREAK:
+ case BREAKPOINT:
+ case CLASS_NAME:
+ case CLASS:
+ case CONST:
+ case CONTINUE:
+ case ELIF:
+ case ELSE:
+ case ENUM:
+ case EXTENDS:
+ case FOR:
+ case FUNC:
+ case IF:
+ case IN:
+ case IS:
+ case MATCH:
+ case NAMESPACE:
+ case NOT:
+ case OR:
+ case PASS:
+ case PRELOAD:
+ case RETURN:
+ case SELF:
+ case SIGNAL:
+ case STATIC:
+ case SUPER:
+ case TRAIT:
+ case UNDERSCORE:
+ case VAR:
+ case VOID:
+ case WHILE:
+ case YIELD:
+ return true;
+ default:
+ return false;
+ }
+}
+
String GDScriptTokenizer::get_token_name(Token::Type p_token_type) {
ERR_FAIL_INDEX_V_MSG(p_token_type, Token::TK_MAX, "<error>", "Using token type out of the enum.");
return token_names[p_token_type];
diff --git a/modules/gdscript/gdscript_tokenizer.h b/modules/gdscript/gdscript_tokenizer.h
index 059a226924..100ed3f132 100644
--- a/modules/gdscript/gdscript_tokenizer.h
+++ b/modules/gdscript/gdscript_tokenizer.h
@@ -168,9 +168,9 @@ public:
String source;
const char *get_name() const;
- // TODO: Allow some keywords as identifiers?
- bool is_identifier() const { return type == IDENTIFIER; }
- StringName get_identifier() const { return literal; }
+ bool is_identifier() const;
+ bool is_node_name() const;
+ StringName get_identifier() const { return source; }
Token(Type p_type) {
type = p_type;
diff --git a/modules/gdscript/language_server/gdscript_extend_parser.cpp b/modules/gdscript/language_server/gdscript_extend_parser.cpp
index ae7898fdf2..4d79d9d395 100644
--- a/modules/gdscript/language_server/gdscript_extend_parser.cpp
+++ b/modules/gdscript/language_server/gdscript_extend_parser.cpp
@@ -237,7 +237,7 @@ void ExtendGDScriptParser::parse_class_symbol(const GDScriptParser::ClassNode *p
case ClassNode::Member::ENUM_VALUE: {
lsp::DocumentSymbol symbol;
- symbol.name = m.constant->identifier->name;
+ symbol.name = m.enum_value.identifier->name;
symbol.kind = lsp::SymbolKind::EnumMember;
symbol.deprecated = false;
symbol.range.start.line = LINE_NUMBER_TO_INDEX(m.enum_value.line);
diff --git a/modules/gdscript/register_types.cpp b/modules/gdscript/register_types.cpp
index 23c7f97b5a..c554cbac05 100644
--- a/modules/gdscript/register_types.cpp
+++ b/modules/gdscript/register_types.cpp
@@ -35,6 +35,7 @@
#include "core/os/dir_access.h"
#include "core/os/file_access.h"
#include "gdscript.h"
+#include "gdscript_analyzer.h"
#include "gdscript_cache.h"
#include "gdscript_tokenizer.h"
@@ -148,4 +149,7 @@ void unregister_gdscript_types() {
EditorTranslationParser::get_singleton()->remove_parser(gdscript_translation_parser_plugin, EditorTranslationParser::STANDARD);
gdscript_translation_parser_plugin.unref();
#endif // TOOLS_ENABLED
+
+ GDScriptParser::cleanup();
+ GDScriptAnalyzer::cleanup();
}
diff --git a/modules/gridmap/doc_classes/GridMap.xml b/modules/gridmap/doc_classes/GridMap.xml
index be2ec4a6eb..57fbc5bfc0 100644
--- a/modules/gridmap/doc_classes/GridMap.xml
+++ b/modules/gridmap/doc_classes/GridMap.xml
@@ -10,7 +10,7 @@
Internally, a GridMap is split into a sparse collection of octants for efficient rendering and physics processing. Every octant has the same dimensions and can contain several cells.
</description>
<tutorials>
- <link>https://docs.godotengine.org/en/latest/tutorials/3d/using_gridmaps.html</link>
+ <link title="Using gridmaps">https://docs.godotengine.org/en/latest/tutorials/3d/using_gridmaps.html</link>
</tutorials>
<methods>
<method name="clear">
diff --git a/modules/mobile_vr/register_types.cpp b/modules/mobile_vr/register_types.cpp
index 75638d47c4..0bb555e780 100644
--- a/modules/mobile_vr/register_types.cpp
+++ b/modules/mobile_vr/register_types.cpp
@@ -35,9 +35,11 @@
void register_mobile_vr_types() {
ClassDB::register_class<MobileVRInterface>();
- Ref<MobileVRInterface> mobile_vr;
- mobile_vr.instance();
- XRServer::get_singleton()->add_interface(mobile_vr);
+ if (XRServer::get_singleton()) {
+ Ref<MobileVRInterface> mobile_vr;
+ mobile_vr.instance();
+ XRServer::get_singleton()->add_interface(mobile_vr);
+ }
}
void unregister_mobile_vr_types() {
diff --git a/modules/mono/doc_classes/CSharpScript.xml b/modules/mono/doc_classes/CSharpScript.xml
index e1e9d1381f..45a6f991bf 100644
--- a/modules/mono/doc_classes/CSharpScript.xml
+++ b/modules/mono/doc_classes/CSharpScript.xml
@@ -8,7 +8,7 @@
See also [GodotSharp].
</description>
<tutorials>
- <link>https://docs.godotengine.org/en/latest/getting_started/scripting/c_sharp/index.html</link>
+ <link title="C# tutorial index">https://docs.godotengine.org/en/latest/getting_started/scripting/c_sharp/index.html</link>
</tutorials>
<methods>
<method name="new" qualifiers="vararg">
diff --git a/modules/mono/editor/GodotTools/GodotTools.BuildLogger/GodotBuildLogger.cs b/modules/mono/editor/GodotTools/GodotTools.BuildLogger/GodotBuildLogger.cs
index c2549b4ad5..5edf72d63e 100644
--- a/modules/mono/editor/GodotTools/GodotTools.BuildLogger/GodotBuildLogger.cs
+++ b/modules/mono/editor/GodotTools/GodotTools.BuildLogger/GodotBuildLogger.cs
@@ -70,13 +70,14 @@ namespace GodotTools.BuildLogger
{
string line = $"{e.File}({e.LineNumber},{e.ColumnNumber}): error {e.Code}: {e.Message}";
- if (e.ProjectFile.Length > 0)
+ if (!string.IsNullOrEmpty(e.ProjectFile))
line += $" [{e.ProjectFile}]";
WriteLine(line);
string errorLine = $@"error,{e.File.CsvEscape()},{e.LineNumber},{e.ColumnNumber}," +
- $@"{e.Code.CsvEscape()},{e.Message.CsvEscape()},{e.ProjectFile.CsvEscape()}";
+ $"{e.Code?.CsvEscape() ?? string.Empty},{e.Message.CsvEscape()}," +
+ $"{e.ProjectFile?.CsvEscape() ?? string.Empty}";
issuesStreamWriter.WriteLine(errorLine);
}
@@ -89,8 +90,9 @@ namespace GodotTools.BuildLogger
WriteLine(line);
- string warningLine = $@"warning,{e.File.CsvEscape()},{e.LineNumber},{e.ColumnNumber},{e.Code.CsvEscape()}," +
- $@"{e.Message.CsvEscape()},{(e.ProjectFile != null ? e.ProjectFile.CsvEscape() : string.Empty)}";
+ string warningLine = $@"warning,{e.File.CsvEscape()},{e.LineNumber},{e.ColumnNumber}," +
+ $"{e.Code?.CsvEscape() ?? string.Empty},{e.Message.CsvEscape()}," +
+ $"{e.ProjectFile?.CsvEscape() ?? string.Empty}";
issuesStreamWriter.WriteLine(warningLine);
}
diff --git a/modules/mono/editor/GodotTools/GodotTools.Core/StringExtensions.cs b/modules/mono/editor/GodotTools/GodotTools.Core/StringExtensions.cs
index 012b69032e..e6b0e8f1df 100644
--- a/modules/mono/editor/GodotTools/GodotTools.Core/StringExtensions.cs
+++ b/modules/mono/editor/GodotTools/GodotTools.Core/StringExtensions.cs
@@ -43,7 +43,7 @@ namespace GodotTools.Core
path.StartsWith(DriveRoot, StringComparison.Ordinal);
}
- public static string ToSafeDirName(this string dirName, bool allowDirSeparator)
+ public static string ToSafeDirName(this string dirName, bool allowDirSeparator = false)
{
var invalidChars = new List<string> { ":", "*", "?", "\"", "<", ">", "|" };
diff --git a/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/GodotTools.ProjectEditor.csproj b/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/GodotTools.ProjectEditor.csproj
index 9cb50014b0..41c94f19c8 100644
--- a/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/GodotTools.ProjectEditor.csproj
+++ b/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/GodotTools.ProjectEditor.csproj
@@ -11,13 +11,21 @@
<ItemGroup>
<ProjectReference Include="..\GodotTools.Core\GodotTools.Core.csproj" />
</ItemGroup>
+ <!--
+ The Microsoft.Build.Runtime package is too problematic so we create a MSBuild.exe stub. The workaround described
+ here doesn't work with Microsoft.NETFramework.ReferenceAssemblies: https://github.com/microsoft/msbuild/issues/3486
+ We need a MSBuild.exe file as there's an issue in Microsoft.Build where it executes platform dependent code when
+ searching for MSBuild.exe before the fallback to not using it. A stub is fine as it should never be executed.
+ -->
<ItemGroup>
- <!--
- The Microsoft.Build.Runtime package is too problematic so we create a MSBuild.exe stub. The workaround described
- here doesn't work with Microsoft.NETFramework.ReferenceAssemblies: https://github.com/microsoft/msbuild/issues/3486
- We need a MSBuild.exe file as there's an issue in Microsoft.Build where it executes platform dependent code when
- searching for MSBuild.exe before the fallback to not using it. A stub is fine as it should never be executed.
- -->
<None Include="MSBuild.exe" CopyToOutputDirectory="Always" />
</ItemGroup>
+ <Target Name="CopyMSBuildStubWindows" AfterTargets="Build" Condition="$([MSBuild]::IsOsPlatform(Windows))">
+ <PropertyGroup>
+ <GodotSourceRootPath>$(SolutionDir)/../../../../</GodotSourceRootPath>
+ <GodotOutputDataDir>$(GodotSourceRootPath)/bin/GodotSharp</GodotOutputDataDir>
+ </PropertyGroup>
+ <!-- Need to copy it here as well on Windows -->
+ <Copy SourceFiles="MSBuild.exe" DestinationFiles="$(GodotOutputDataDir)\Mono\lib\mono\v4.0\MSBuild.exe" />
+ </Target>
</Project>
diff --git a/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/ProjectGenerator.cs b/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/ProjectGenerator.cs
index 5541876f9e..01d7c99662 100644
--- a/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/ProjectGenerator.cs
+++ b/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/ProjectGenerator.cs
@@ -1,5 +1,6 @@
using System;
using System.IO;
+using System.Text;
using Microsoft.Build.Construction;
using Microsoft.Build.Evaluation;
@@ -41,7 +42,8 @@ namespace GodotTools.ProjectEditor
var root = GenGameProject(name);
- root.Save(path);
+ // Save (without BOM)
+ root.Save(path, new UTF8Encoding(encoderShouldEmitUTF8Identifier: false));
return Guid.NewGuid().ToString().ToUpper();
}
diff --git a/modules/mono/editor/GodotTools/GodotTools/Build/MsBuildFinder.cs b/modules/mono/editor/GodotTools/GodotTools/Build/MsBuildFinder.cs
index f36e581a5f..7bfba779fb 100644
--- a/modules/mono/editor/GodotTools/GodotTools/Build/MsBuildFinder.cs
+++ b/modules/mono/editor/GodotTools/GodotTools/Build/MsBuildFinder.cs
@@ -161,8 +161,21 @@ namespace GodotTools.Build
// Try to find 15.0 with vswhere
- string vsWherePath = Environment.GetEnvironmentVariable(Internal.GodotIs32Bits() ? "ProgramFiles" : "ProgramFiles(x86)");
- vsWherePath += "\\Microsoft Visual Studio\\Installer\\vswhere.exe";
+ var envNames = Internal.GodotIs32Bits() ? new[] { "ProgramFiles", "ProgramW6432" } : new[] { "ProgramFiles(x86)", "ProgramFiles" };
+
+ string vsWherePath = null;
+ foreach (var envName in envNames)
+ {
+ vsWherePath = Environment.GetEnvironmentVariable(envName);
+ if (!string.IsNullOrEmpty(vsWherePath))
+ {
+ vsWherePath += "\\Microsoft Visual Studio\\Installer\\vswhere.exe";
+ if (File.Exists(vsWherePath))
+ break;
+ }
+
+ vsWherePath = null;
+ }
var vsWhereArgs = new[] {"-latest", "-products", "*", "-requires", "Microsoft.Component.MSBuild"};
diff --git a/modules/mono/editor/GodotTools/GodotTools/BuildManager.cs b/modules/mono/editor/GodotTools/GodotTools/BuildManager.cs
index 6399991b84..ff7ce97c47 100644
--- a/modules/mono/editor/GodotTools/GodotTools/BuildManager.cs
+++ b/modules/mono/editor/GodotTools/GodotTools/BuildManager.cs
@@ -205,23 +205,8 @@ namespace GodotTools
if (File.Exists(editorScriptsMetadataPath))
File.Copy(editorScriptsMetadataPath, playerScriptsMetadataPath);
- var currentPlayRequest = GodotSharpEditor.Instance.CurrentPlaySettings;
-
- if (currentPlayRequest != null)
- {
- if (currentPlayRequest.Value.HasDebugger)
- {
- // Set the environment variable that will tell the player to connect to the IDE debugger
- // TODO: We should probably add a better way to do this
- Environment.SetEnvironmentVariable("GODOT_MONO_DEBUGGER_AGENT",
- "--debugger-agent=transport=dt_socket" +
- $",address={currentPlayRequest.Value.DebuggerHost}:{currentPlayRequest.Value.DebuggerPort}" +
- ",server=n");
- }
-
- if (!currentPlayRequest.Value.BuildBeforePlaying)
- return true; // Requested play from an external editor/IDE which already built the project
- }
+ if (GodotSharpEditor.Instance.SkipBuildBeforePlaying)
+ return true; // Requested play from an external editor/IDE which already built the project
return BuildProjectBlocking("Debug");
}
diff --git a/modules/mono/editor/GodotTools/GodotTools/Export/ExportPlugin.cs b/modules/mono/editor/GodotTools/GodotTools/Export/ExportPlugin.cs
index 554763eecb..599ca94699 100644
--- a/modules/mono/editor/GodotTools/GodotTools/Export/ExportPlugin.cs
+++ b/modules/mono/editor/GodotTools/GodotTools/Export/ExportPlugin.cs
@@ -19,7 +19,7 @@ namespace GodotTools.Export
public class ExportPlugin : EditorExportPlugin
{
[Flags]
- enum I18NCodesets
+ enum I18NCodesets : long
{
None = 0,
CJK = 1,
@@ -430,7 +430,7 @@ namespace GodotTools.Export
private static string DetermineDataDirNameForProject()
{
var appName = (string)ProjectSettings.GetSetting("application/config/name");
- string appNameSafe = appName.ToSafeDirName(allowDirSeparator: false);
+ string appNameSafe = appName.ToSafeDirName();
return $"data_{appNameSafe}";
}
diff --git a/modules/mono/editor/GodotTools/GodotTools/GodotSharpEditor.cs b/modules/mono/editor/GodotTools/GodotTools/GodotSharpEditor.cs
index a363ecc920..3148458d7e 100644
--- a/modules/mono/editor/GodotTools/GodotTools/GodotSharpEditor.cs
+++ b/modules/mono/editor/GodotTools/GodotTools/GodotSharpEditor.cs
@@ -38,13 +38,14 @@ namespace GodotTools
public BottomPanel BottomPanel { get; private set; }
- public PlaySettings? CurrentPlaySettings { get; set; }
+ public bool SkipBuildBeforePlaying { get; set; } = false;
public static string ProjectAssemblyName
{
get
{
var projectAssemblyName = (string)ProjectSettings.GetSetting("application/config/name");
+ projectAssemblyName = projectAssemblyName.ToSafeDirName();
if (string.IsNullOrEmpty(projectAssemblyName))
projectAssemblyName = "UnnamedProject";
return projectAssemblyName;
diff --git a/modules/mono/editor/GodotTools/GodotTools/Ides/MessagingServer.cs b/modules/mono/editor/GodotTools/GodotTools/Ides/MessagingServer.cs
index 17f3339560..eb34a2d0f7 100644
--- a/modules/mono/editor/GodotTools/GodotTools/Ides/MessagingServer.cs
+++ b/modules/mono/editor/GodotTools/GodotTools/Ides/MessagingServer.cs
@@ -330,9 +330,10 @@ namespace GodotTools.Ides
{
DispatchToMainThread(() =>
{
- GodotSharpEditor.Instance.CurrentPlaySettings = new PlaySettings();
+ // TODO: Add BuildBeforePlaying flag to PlayRequest
+
+ // Run the game
Internal.EditorRunPlay();
- GodotSharpEditor.Instance.CurrentPlaySettings = null;
});
return Task.FromResult<Response>(new PlayResponse());
}
@@ -341,10 +342,22 @@ namespace GodotTools.Ides
{
DispatchToMainThread(() =>
{
- GodotSharpEditor.Instance.CurrentPlaySettings =
- new PlaySettings(request.DebuggerHost, request.DebuggerPort, request.BuildBeforePlaying ?? true);
+ // Tell the build callback whether the editor already built the solution or not
+ GodotSharpEditor.Instance.SkipBuildBeforePlaying = !(request.BuildBeforePlaying ?? true);
+
+ // Pass the debugger agent settings to the player via an environment variables
+ // TODO: It would be better if this was an argument in EditorRunPlay instead
+ Environment.SetEnvironmentVariable("GODOT_MONO_DEBUGGER_AGENT",
+ "--debugger-agent=transport=dt_socket" +
+ $",address={request.DebuggerHost}:{request.DebuggerPort}" +
+ ",server=n");
+
+ // Run the game
Internal.EditorRunPlay();
- GodotSharpEditor.Instance.CurrentPlaySettings = null;
+
+ // Restore normal settings
+ Environment.SetEnvironmentVariable("GODOT_MONO_DEBUGGER_AGENT", "");
+ GodotSharpEditor.Instance.SkipBuildBeforePlaying = false;
});
return Task.FromResult<Response>(new DebugPlayResponse());
}
diff --git a/modules/mono/editor/godotsharp_export.cpp b/modules/mono/editor/godotsharp_export.cpp
index b15e9b060a..2edd8c87dc 100644
--- a/modules/mono/editor/godotsharp_export.cpp
+++ b/modules/mono/editor/godotsharp_export.cpp
@@ -43,6 +43,16 @@
namespace GodotSharpExport {
+MonoAssemblyName *new_mono_assembly_name() {
+ // Mono has no public API to create an empty MonoAssemblyName and the struct is private.
+ // As such the only way to create it is with a stub name and then clear it.
+
+ MonoAssemblyName *aname = mono_assembly_name_new("stub");
+ CRASH_COND(aname == nullptr);
+ mono_assembly_name_free(aname); // Frees the string fields, not the struct
+ return aname;
+}
+
struct AssemblyRefInfo {
String name;
uint16_t major;
@@ -67,7 +77,7 @@ AssemblyRefInfo get_assemblyref_name(MonoImage *p_image, int index) {
};
}
-Error get_assembly_dependencies(GDMonoAssembly *p_assembly, const Vector<String> &p_search_dirs, Dictionary &r_assembly_dependencies) {
+Error get_assembly_dependencies(GDMonoAssembly *p_assembly, MonoAssemblyName *reusable_aname, const Vector<String> &p_search_dirs, Dictionary &r_assembly_dependencies) {
MonoImage *image = p_assembly->get_image();
for (int i = 0; i < mono_image_get_table_rows(image, MONO_TABLE_ASSEMBLYREF); i++) {
@@ -79,26 +89,16 @@ Error get_assembly_dependencies(GDMonoAssembly *p_assembly, const Vector<String>
continue;
}
- GDMonoAssembly *ref_assembly = nullptr;
-
- {
- MonoAssemblyName *ref_aname = mono_assembly_name_new("A"); // We can't allocate an empty MonoAssemblyName, hence "A"
- CRASH_COND(ref_aname == nullptr);
- SCOPE_EXIT {
- mono_assembly_name_free(ref_aname);
- mono_free(ref_aname);
- };
-
- mono_assembly_get_assemblyref(image, i, ref_aname);
+ mono_assembly_get_assemblyref(image, i, reusable_aname);
- if (!GDMono::get_singleton()->load_assembly(ref_name, ref_aname, &ref_assembly, /* refonly: */ true, p_search_dirs)) {
- ERR_FAIL_V_MSG(ERR_CANT_RESOLVE, "Cannot load assembly (refonly): '" + ref_name + "'.");
- }
-
- r_assembly_dependencies[ref_name] = ref_assembly->get_path();
+ GDMonoAssembly *ref_assembly = NULL;
+ if (!GDMono::get_singleton()->load_assembly(ref_name, reusable_aname, &ref_assembly, /* refonly: */ true, p_search_dirs)) {
+ ERR_FAIL_V_MSG(ERR_CANT_RESOLVE, "Cannot load assembly (refonly): '" + ref_name + "'.");
}
- Error err = get_assembly_dependencies(ref_assembly, p_search_dirs, r_assembly_dependencies);
+ r_assembly_dependencies[ref_name] = ref_assembly->get_path();
+
+ Error err = get_assembly_dependencies(ref_assembly, reusable_aname, p_search_dirs, r_assembly_dependencies);
ERR_FAIL_COND_V_MSG(err != OK, err, "Cannot load one of the dependencies for the assembly: '" + ref_name + "'.");
}
@@ -130,7 +130,10 @@ Error get_exported_assembly_dependencies(const Dictionary &p_initial_assemblies,
ERR_FAIL_COND_V_MSG(!load_success, ERR_CANT_RESOLVE, "Cannot load assembly (refonly): '" + assembly_name + "'.");
- Error err = get_assembly_dependencies(assembly, search_dirs, r_assembly_dependencies);
+ MonoAssemblyName *reusable_aname = new_mono_assembly_name();
+ SCOPE_EXIT { mono_free(reusable_aname); };
+
+ Error err = get_assembly_dependencies(assembly, reusable_aname, search_dirs, r_assembly_dependencies);
if (err != OK) {
return err;
}
diff --git a/modules/mono/mono_gd/gd_mono_utils.h b/modules/mono/mono_gd/gd_mono_utils.h
index 9db4a5f3f0..5958bf3cc1 100644
--- a/modules/mono/mono_gd/gd_mono_utils.h
+++ b/modules/mono/mono_gd/gd_mono_utils.h
@@ -44,7 +44,8 @@
if (unlikely(m_exc != nullptr)) { \
GDMonoUtils::debug_unhandled_exception(m_exc); \
GD_UNREACHABLE(); \
- }
+ } else \
+ ((void)0)
namespace GDMonoUtils {
@@ -162,20 +163,24 @@ StringName get_native_godot_class_name(GDMonoClass *p_class);
#define GD_MONO_BEGIN_RUNTIME_INVOKE \
int &_runtime_invoke_count_ref = GDMonoUtils::get_runtime_invoke_count_ref(); \
- _runtime_invoke_count_ref += 1;
+ _runtime_invoke_count_ref += 1; \
+ ((void)0)
-#define GD_MONO_END_RUNTIME_INVOKE \
- _runtime_invoke_count_ref -= 1;
+#define GD_MONO_END_RUNTIME_INVOKE \
+ _runtime_invoke_count_ref -= 1; \
+ ((void)0)
#define GD_MONO_SCOPE_THREAD_ATTACH \
GDMonoUtils::ScopeThreadAttach __gdmono__scope__thread__attach__; \
- (void)__gdmono__scope__thread__attach__;
+ (void)__gdmono__scope__thread__attach__; \
+ ((void)0)
#ifdef DEBUG_ENABLED
-#define GD_MONO_ASSERT_THREAD_ATTACHED \
- { CRASH_COND(!GDMonoUtils::is_thread_attached()); }
+#define GD_MONO_ASSERT_THREAD_ATTACHED \
+ CRASH_COND(!GDMonoUtils::is_thread_attached()); \
+ ((void)0)
#else
-#define GD_MONO_ASSERT_THREAD_ATTACHED
+#define GD_MONO_ASSERT_THREAD_ATTACHED ((void)0)
#endif
#endif // GD_MONOUTILS_H
diff --git a/modules/mono/utils/macros.h b/modules/mono/utils/macros.h
index dc542477f5..c76619cca4 100644
--- a/modules/mono/utils/macros.h
+++ b/modules/mono/utils/macros.h
@@ -46,7 +46,7 @@
#define GD_UNREACHABLE() \
CRASH_NOW(); \
do { \
- } while (true);
+ } while (true)
#endif
namespace gdmono {
diff --git a/modules/visual_script/doc_classes/VisualScript.xml b/modules/visual_script/doc_classes/VisualScript.xml
index db1ef2adc6..088d84d2ec 100644
--- a/modules/visual_script/doc_classes/VisualScript.xml
+++ b/modules/visual_script/doc_classes/VisualScript.xml
@@ -9,7 +9,7 @@
You are most likely to use this class via the Visual Script editor or when writing plugins for it.
</description>
<tutorials>
- <link>https://docs.godotengine.org/en/latest/getting_started/scripting/visual_script/index.html</link>
+ <link title="VisualScript tutorial index">https://docs.godotengine.org/en/latest/getting_started/scripting/visual_script/index.html</link>
</tutorials>
<methods>
<method name="add_custom_signal">