summaryrefslogtreecommitdiff
path: root/modules
diff options
context:
space:
mode:
Diffstat (limited to 'modules')
-rw-r--r--modules/bullet/bullet_physics_server.cpp188
-rw-r--r--modules/bullet/bullet_physics_server.h46
-rw-r--r--modules/bullet/collision_object_bullet.cpp2
-rw-r--r--modules/bullet/register_types.cpp9
-rw-r--r--modules/bullet/rigid_body_bullet.cpp18
-rw-r--r--modules/bullet/rigid_body_bullet.h9
-rw-r--r--modules/bullet/soft_body_bullet.cpp474
-rw-r--r--modules/bullet/soft_body_bullet.h103
-rw-r--r--modules/bullet/space_bullet.cpp47
-rw-r--r--modules/bullet/space_bullet.h6
-rw-r--r--modules/gdnative/nativescript/api_generator.cpp2
-rw-r--r--modules/gdnative/nativescript/nativescript.cpp2
-rw-r--r--modules/gdnative/nativescript/nativescript.h2
-rw-r--r--modules/gdnative/pluginscript/pluginscript_language.cpp2
-rw-r--r--modules/gdnative/pluginscript/pluginscript_language.h2
-rw-r--r--modules/gdscript/editor/gdscript_highlighter.cpp30
-rw-r--r--modules/gdscript/editor/gdscript_highlighter.h4
-rw-r--r--modules/gdscript/gdscript.cpp64
-rw-r--r--modules/gdscript/gdscript.h8
-rw-r--r--modules/gdscript/gdscript_compiler.cpp626
-rw-r--r--modules/gdscript/gdscript_compiler.h11
-rw-r--r--modules/gdscript/gdscript_editor.cpp4098
-rw-r--r--modules/gdscript/gdscript_function.cpp232
-rw-r--r--modules/gdscript/gdscript_function.h99
-rw-r--r--modules/gdscript/gdscript_functions.cpp23
-rw-r--r--modules/gdscript/gdscript_parser.cpp3502
-rw-r--r--modules/gdscript/gdscript_parser.h252
-rw-r--r--modules/gdscript/gdscript_tokenizer.cpp11
-rw-r--r--modules/gdscript/gdscript_tokenizer.h3
-rw-r--r--modules/mono/csharp_script.cpp4
-rw-r--r--modules/mono/csharp_script.h2
-rw-r--r--modules/mono/editor/bindings_generator.cpp36
-rw-r--r--modules/mono/editor/godotsharp_editor.cpp10
-rw-r--r--modules/mono/glue/collections_glue.cpp240
-rw-r--r--modules/mono/glue/collections_glue.h100
-rw-r--r--modules/mono/glue/cs_files/Array.cs335
-rw-r--r--modules/mono/glue/cs_files/Dictionary.cs401
-rw-r--r--modules/mono/glue/cs_files/GD.cs35
-rw-r--r--modules/mono/glue/cs_files/MarshalUtils.cs29
-rwxr-xr-xmodules/mono/glue/cs_files/VERSION.txt2
-rw-r--r--modules/mono/glue/glue_header.h2
-rw-r--r--modules/mono/mono_gd/gd_mono_class.cpp32
-rw-r--r--modules/mono/mono_gd/gd_mono_class.h8
-rw-r--r--modules/mono/mono_gd/gd_mono_field.cpp45
-rw-r--r--modules/mono/mono_gd/gd_mono_header.h3
-rw-r--r--modules/mono/mono_gd/gd_mono_marshal.cpp173
-rw-r--r--modules/mono/mono_gd/gd_mono_marshal.h5
-rw-r--r--modules/mono/mono_gd/gd_mono_property.cpp19
-rw-r--r--modules/mono/mono_gd/gd_mono_utils.cpp129
-rw-r--r--modules/mono/mono_gd/gd_mono_utils.h31
-rw-r--r--modules/visual_script/visual_script.cpp15
-rw-r--r--modules/visual_script/visual_script.h3
-rw-r--r--modules/visual_script/visual_script_editor.cpp473
-rw-r--r--modules/visual_script/visual_script_editor.h43
-rw-r--r--modules/visual_script/visual_script_flow_control.cpp2
-rw-r--r--modules/visual_script/visual_script_func_nodes.cpp8
-rw-r--r--modules/visual_script/visual_script_nodes.cpp2
-rw-r--r--modules/visual_script/visual_script_property_selector.cpp705
-rw-r--r--modules/visual_script/visual_script_property_selector.h92
-rw-r--r--modules/websocket/emws_client.cpp1
60 files changed, 9747 insertions, 3113 deletions
diff --git a/modules/bullet/bullet_physics_server.cpp b/modules/bullet/bullet_physics_server.cpp
index 54431f93f1..2390c71b0a 100644
--- a/modules/bullet/bullet_physics_server.cpp
+++ b/modules/bullet/bullet_physics_server.cpp
@@ -169,7 +169,7 @@ real_t BulletPhysicsServer::shape_get_custom_solver_bias(RID p_shape) const {
}
RID BulletPhysicsServer::space_create() {
- SpaceBullet *space = bulletnew(SpaceBullet(false));
+ SpaceBullet *space = bulletnew(SpaceBullet);
CreateThenReturnRID(space_owner, space);
}
@@ -567,9 +567,6 @@ void BulletPhysicsServer::body_clear_shapes(RID p_body) {
void BulletPhysicsServer::body_attach_object_instance_id(RID p_body, uint32_t p_ID) {
CollisionObjectBullet *body = get_collisin_object(p_body);
- if (!body) {
- body = soft_body_owner.get(p_body);
- }
ERR_FAIL_COND(!body);
body->set_instance_id(p_ID);
@@ -647,6 +644,20 @@ float BulletPhysicsServer::body_get_param(RID p_body, BodyParameter p_param) con
return body->get_param(p_param);
}
+void BulletPhysicsServer::body_set_combine_mode(RID p_body, BodyParameter p_param, CombineMode p_mode) {
+ RigidBodyBullet *body = rigid_body_owner.get(p_body);
+ ERR_FAIL_COND(!body);
+
+ body->set_combine_mode(p_param, p_mode);
+}
+
+PhysicsServer::CombineMode BulletPhysicsServer::body_get_combine_mode(RID p_body, BodyParameter p_param) const {
+ RigidBodyBullet *body = rigid_body_owner.get(p_body);
+ ERR_FAIL_COND_V(!body, COMBINE_MODE_INHERIT);
+
+ return body->get_combine_mode(p_param);
+}
+
void BulletPhysicsServer::body_set_kinematic_safe_margin(RID p_body, real_t p_margin) {
RigidBodyBullet *body = rigid_body_owner.get(p_body);
ERR_FAIL_COND(!body);
@@ -853,6 +864,13 @@ RID BulletPhysicsServer::soft_body_create(bool p_init_sleeping) {
CreateThenReturnRID(soft_body_owner, body);
}
+void BulletPhysicsServer::soft_body_update_visual_server(RID p_body, class SoftBodyVisualServerHandler *p_visual_server_handler) {
+ SoftBodyBullet *body = soft_body_owner.get(p_body);
+ ERR_FAIL_COND(!body);
+
+ body->update_visual_server(p_visual_server_handler);
+}
+
void BulletPhysicsServer::soft_body_set_space(RID p_body, RID p_space) {
SoftBodyBullet *body = soft_body_owner.get(p_body);
ERR_FAIL_COND(!body);
@@ -879,11 +897,11 @@ RID BulletPhysicsServer::soft_body_get_space(RID p_body) const {
return space->get_self();
}
-void BulletPhysicsServer::soft_body_set_trimesh_body_shape(RID p_body, PoolVector<int> p_indices, PoolVector<Vector3> p_vertices, int p_triangles_num) {
+void BulletPhysicsServer::soft_body_set_mesh(RID p_body, const REF &p_mesh) {
SoftBodyBullet *body = soft_body_owner.get(p_body);
ERR_FAIL_COND(!body);
- body->set_trimesh_body_shape(p_indices, p_vertices, p_triangles_num);
+ body->set_soft_mesh(p_mesh);
}
void BulletPhysicsServer::soft_body_set_collision_layer(RID p_body, uint32_t p_layer) {
@@ -961,14 +979,16 @@ void BulletPhysicsServer::soft_body_set_transform(RID p_body, const Transform &p
SoftBodyBullet *body = soft_body_owner.get(p_body);
ERR_FAIL_COND(!body);
- body->set_transform(p_transform);
+ body->set_soft_transform(p_transform);
}
-Transform BulletPhysicsServer::soft_body_get_transform(RID p_body) const {
+Vector3 BulletPhysicsServer::soft_body_get_vertex_position(RID p_body, int vertex_index) const {
const SoftBodyBullet *body = soft_body_owner.get(p_body);
- ERR_FAIL_COND_V(!body, Transform());
+ Vector3 pos;
+ ERR_FAIL_COND_V(!body, pos);
- return body->get_transform();
+ body->get_node_position(vertex_index, pos);
+ return pos;
}
void BulletPhysicsServer::soft_body_set_ray_pickable(RID p_body, bool p_enable) {
@@ -983,6 +1003,154 @@ bool BulletPhysicsServer::soft_body_is_ray_pickable(RID p_body) const {
return body->is_ray_pickable();
}
+void BulletPhysicsServer::soft_body_set_simulation_precision(RID p_body, int p_simulation_precision) {
+ SoftBodyBullet *body = soft_body_owner.get(p_body);
+ ERR_FAIL_COND(!body);
+ body->set_simulation_precision(p_simulation_precision);
+}
+
+int BulletPhysicsServer::soft_body_get_simulation_precision(RID p_body) {
+ SoftBodyBullet *body = soft_body_owner.get(p_body);
+ ERR_FAIL_COND_V(!body, 0.f);
+ return body->get_simulation_precision();
+}
+
+void BulletPhysicsServer::soft_body_set_total_mass(RID p_body, real_t p_total_mass) {
+ SoftBodyBullet *body = soft_body_owner.get(p_body);
+ ERR_FAIL_COND(!body);
+ body->set_total_mass(p_total_mass);
+}
+
+real_t BulletPhysicsServer::soft_body_get_total_mass(RID p_body) {
+ SoftBodyBullet *body = soft_body_owner.get(p_body);
+ ERR_FAIL_COND_V(!body, 0.f);
+ return body->get_total_mass();
+}
+
+void BulletPhysicsServer::soft_body_set_linear_stiffness(RID p_body, real_t p_stiffness) {
+ SoftBodyBullet *body = soft_body_owner.get(p_body);
+ ERR_FAIL_COND(!body);
+ body->set_linear_stiffness(p_stiffness);
+}
+
+real_t BulletPhysicsServer::soft_body_get_linear_stiffness(RID p_body) {
+ SoftBodyBullet *body = soft_body_owner.get(p_body);
+ ERR_FAIL_COND_V(!body, 0.f);
+ return body->get_linear_stiffness();
+}
+
+void BulletPhysicsServer::soft_body_set_areaAngular_stiffness(RID p_body, real_t p_stiffness) {
+ SoftBodyBullet *body = soft_body_owner.get(p_body);
+ ERR_FAIL_COND(!body);
+ body->set_areaAngular_stiffness(p_stiffness);
+}
+
+real_t BulletPhysicsServer::soft_body_get_areaAngular_stiffness(RID p_body) {
+ SoftBodyBullet *body = soft_body_owner.get(p_body);
+ ERR_FAIL_COND_V(!body, 0.f);
+ return body->get_areaAngular_stiffness();
+}
+
+void BulletPhysicsServer::soft_body_set_volume_stiffness(RID p_body, real_t p_stiffness) {
+ SoftBodyBullet *body = soft_body_owner.get(p_body);
+ ERR_FAIL_COND(!body);
+ body->set_volume_stiffness(p_stiffness);
+}
+
+real_t BulletPhysicsServer::soft_body_get_volume_stiffness(RID p_body) {
+ SoftBodyBullet *body = soft_body_owner.get(p_body);
+ ERR_FAIL_COND_V(!body, 0.f);
+ return body->get_volume_stiffness();
+}
+
+void BulletPhysicsServer::soft_body_set_pressure_coefficient(RID p_body, real_t p_pressure_coefficient) {
+ SoftBodyBullet *body = soft_body_owner.get(p_body);
+ ERR_FAIL_COND(!body);
+ body->set_pressure_coefficient(p_pressure_coefficient);
+}
+
+real_t BulletPhysicsServer::soft_body_get_pressure_coefficient(RID p_body) {
+ SoftBodyBullet *body = soft_body_owner.get(p_body);
+ ERR_FAIL_COND_V(!body, 0.f);
+ return body->get_pressure_coefficient();
+}
+
+void BulletPhysicsServer::soft_body_set_pose_matching_coefficient(RID p_body, real_t p_pose_matching_coefficient) {
+ SoftBodyBullet *body = soft_body_owner.get(p_body);
+ ERR_FAIL_COND(!body);
+ return body->set_pose_matching_coefficient(p_pose_matching_coefficient);
+}
+
+real_t BulletPhysicsServer::soft_body_get_pose_matching_coefficient(RID p_body) {
+ SoftBodyBullet *body = soft_body_owner.get(p_body);
+ ERR_FAIL_COND_V(!body, 0.f);
+ return body->get_pose_matching_coefficient();
+}
+
+void BulletPhysicsServer::soft_body_set_damping_coefficient(RID p_body, real_t p_damping_coefficient) {
+ SoftBodyBullet *body = soft_body_owner.get(p_body);
+ ERR_FAIL_COND(!body);
+ body->set_damping_coefficient(p_damping_coefficient);
+}
+
+real_t BulletPhysicsServer::soft_body_get_damping_coefficient(RID p_body) {
+ SoftBodyBullet *body = soft_body_owner.get(p_body);
+ ERR_FAIL_COND_V(!body, 0.f);
+ return body->get_damping_coefficient();
+}
+
+void BulletPhysicsServer::soft_body_set_drag_coefficient(RID p_body, real_t p_drag_coefficient) {
+ SoftBodyBullet *body = soft_body_owner.get(p_body);
+ ERR_FAIL_COND(!body);
+ body->set_drag_coefficient(p_drag_coefficient);
+}
+
+real_t BulletPhysicsServer::soft_body_get_drag_coefficient(RID p_body) {
+ SoftBodyBullet *body = soft_body_owner.get(p_body);
+ ERR_FAIL_COND_V(!body, 0.f);
+ return body->get_drag_coefficient();
+}
+
+void BulletPhysicsServer::soft_body_move_point(RID p_body, int p_point_index, const Vector3 &p_global_position) {
+ SoftBodyBullet *body = soft_body_owner.get(p_body);
+ ERR_FAIL_COND(!body);
+ body->set_node_position(p_point_index, p_global_position);
+}
+
+Vector3 BulletPhysicsServer::soft_body_get_point_global_position(RID p_body, int p_point_index) {
+ SoftBodyBullet *body = soft_body_owner.get(p_body);
+ ERR_FAIL_COND_V(!body, Vector3(0., 0., 0.));
+ Vector3 pos;
+ body->get_node_position(p_point_index, pos);
+ return pos;
+}
+
+Vector3 BulletPhysicsServer::soft_body_get_point_offset(RID p_body, int p_point_index) const {
+ SoftBodyBullet *body = soft_body_owner.get(p_body);
+ ERR_FAIL_COND_V(!body, Vector3());
+ Vector3 res;
+ body->get_node_offset(p_point_index, res);
+ return res;
+}
+
+void BulletPhysicsServer::soft_body_remove_all_pinned_points(RID p_body) {
+ SoftBodyBullet *body = soft_body_owner.get(p_body);
+ ERR_FAIL_COND(!body);
+ body->reset_all_node_mass();
+}
+
+void BulletPhysicsServer::soft_body_pin_point(RID p_body, int p_point_index, bool p_pin) {
+ SoftBodyBullet *body = soft_body_owner.get(p_body);
+ ERR_FAIL_COND(!body);
+ body->set_node_mass(p_point_index, p_pin ? 0 : 1);
+}
+
+bool BulletPhysicsServer::soft_body_is_point_pinned(RID p_body, int p_point_index) {
+ SoftBodyBullet *body = soft_body_owner.get(p_body);
+ ERR_FAIL_COND_V(!body, 0.f);
+ return body->get_node_mass(p_point_index);
+}
+
PhysicsServer::JointType BulletPhysicsServer::joint_get_type(RID p_joint) const {
JointBullet *joint = joint_owner.get(p_joint);
ERR_FAIL_COND_V(!joint, JOINT_PIN);
diff --git a/modules/bullet/bullet_physics_server.h b/modules/bullet/bullet_physics_server.h
index e931915bba..2165845529 100644
--- a/modules/bullet/bullet_physics_server.h
+++ b/modules/bullet/bullet_physics_server.h
@@ -213,6 +213,9 @@ public:
virtual void body_set_param(RID p_body, BodyParameter p_param, float p_value);
virtual float body_get_param(RID p_body, BodyParameter p_param) const;
+ virtual void body_set_combine_mode(RID p_body, BodyParameter p_param, CombineMode p_mode);
+ virtual CombineMode body_get_combine_mode(RID p_body, BodyParameter p_param) const;
+
virtual void body_set_kinematic_safe_margin(RID p_body, real_t p_margin);
virtual real_t body_get_kinematic_safe_margin(RID p_body) const;
@@ -259,10 +262,12 @@ public:
virtual RID soft_body_create(bool p_init_sleeping = false);
+ virtual void soft_body_update_visual_server(RID p_body, class SoftBodyVisualServerHandler *p_visual_server_handler);
+
virtual void soft_body_set_space(RID p_body, RID p_space);
virtual RID soft_body_get_space(RID p_body) const;
- virtual void soft_body_set_trimesh_body_shape(RID p_body, PoolVector<int> p_indices, PoolVector<Vector3> p_vertices, int p_triangles_num);
+ virtual void soft_body_set_mesh(RID p_body, const REF &p_mesh);
virtual void soft_body_set_collision_layer(RID p_body, uint32_t p_layer);
virtual uint32_t soft_body_get_collision_layer(RID p_body) const;
@@ -277,12 +282,49 @@ public:
virtual void soft_body_set_state(RID p_body, BodyState p_state, const Variant &p_variant);
virtual Variant soft_body_get_state(RID p_body, BodyState p_state) const;
+ /// Special function. This function has bad performance
virtual void soft_body_set_transform(RID p_body, const Transform &p_transform);
- virtual Transform soft_body_get_transform(RID p_body) const;
+ virtual Vector3 soft_body_get_vertex_position(RID p_body, int vertex_index) const;
virtual void soft_body_set_ray_pickable(RID p_body, bool p_enable);
virtual bool soft_body_is_ray_pickable(RID p_body) const;
+ virtual void soft_body_set_simulation_precision(RID p_body, int p_simulation_precision);
+ virtual int soft_body_get_simulation_precision(RID p_body);
+
+ virtual void soft_body_set_total_mass(RID p_body, real_t p_total_mass);
+ virtual real_t soft_body_get_total_mass(RID p_body);
+
+ virtual void soft_body_set_linear_stiffness(RID p_body, real_t p_stiffness);
+ virtual real_t soft_body_get_linear_stiffness(RID p_body);
+
+ virtual void soft_body_set_areaAngular_stiffness(RID p_body, real_t p_stiffness);
+ virtual real_t soft_body_get_areaAngular_stiffness(RID p_body);
+
+ virtual void soft_body_set_volume_stiffness(RID p_body, real_t p_stiffness);
+ virtual real_t soft_body_get_volume_stiffness(RID p_body);
+
+ virtual void soft_body_set_pressure_coefficient(RID p_body, real_t p_pressure_coefficient);
+ virtual real_t soft_body_get_pressure_coefficient(RID p_body);
+
+ virtual void soft_body_set_pose_matching_coefficient(RID p_body, real_t p_pose_matching_coefficient);
+ virtual real_t soft_body_get_pose_matching_coefficient(RID p_body);
+
+ virtual void soft_body_set_damping_coefficient(RID p_body, real_t p_damping_coefficient);
+ virtual real_t soft_body_get_damping_coefficient(RID p_body);
+
+ virtual void soft_body_set_drag_coefficient(RID p_body, real_t p_drag_coefficient);
+ virtual real_t soft_body_get_drag_coefficient(RID p_body);
+
+ virtual void soft_body_move_point(RID p_body, int p_point_index, const Vector3 &p_global_position);
+ virtual Vector3 soft_body_get_point_global_position(RID p_body, int p_point_index);
+
+ virtual Vector3 soft_body_get_point_offset(RID p_body, int p_point_index) const;
+
+ virtual void soft_body_remove_all_pinned_points(RID p_body);
+ virtual void soft_body_pin_point(RID p_body, int p_point_index, bool p_pin);
+ virtual bool soft_body_is_point_pinned(RID p_body, int p_point_index);
+
/* JOINT API */
virtual JointType joint_get_type(RID p_joint) const;
diff --git a/modules/bullet/collision_object_bullet.cpp b/modules/bullet/collision_object_bullet.cpp
index 57e4db708e..1d63318fd7 100644
--- a/modules/bullet/collision_object_bullet.cpp
+++ b/modules/bullet/collision_object_bullet.cpp
@@ -111,6 +111,8 @@ void CollisionObjectBullet::setupBulletCollisionObject(btCollisionObject *p_coll
void CollisionObjectBullet::add_collision_exception(const CollisionObjectBullet *p_ignoreCollisionObject) {
exceptions.insert(p_ignoreCollisionObject->get_self());
+ if (!bt_collision_object)
+ return;
bt_collision_object->setIgnoreCollisionCheck(p_ignoreCollisionObject->bt_collision_object, true);
if (space)
space->get_broadphase()->getOverlappingPairCache()->cleanProxyFromPairs(bt_collision_object->getBroadphaseHandle(), space->get_dispatcher());
diff --git a/modules/bullet/register_types.cpp b/modules/bullet/register_types.cpp
index b119b7720f..a76b0438b4 100644
--- a/modules/bullet/register_types.cpp
+++ b/modules/bullet/register_types.cpp
@@ -32,19 +32,26 @@
#include "bullet_physics_server.h"
#include "class_db.h"
+#include "project_settings.h"
/**
@author AndreaCatania
*/
+#ifndef _3D_DISABLED
PhysicsServer *_createBulletPhysicsCallback() {
return memnew(BulletPhysicsServer);
}
+#endif
void register_bullet_types() {
-
+#ifndef _3D_DISABLED
PhysicsServerManager::register_server("Bullet", &_createBulletPhysicsCallback);
PhysicsServerManager::set_default_server("Bullet", 1);
+
+ GLOBAL_DEF("physics/3d/active_soft_world", true);
+ ProjectSettings::get_singleton()->set_custom_property_info("physics/3d/active_soft_world", PropertyInfo(Variant::BOOL, "physics/3d/active_soft_world"));
+#endif
}
void unregister_bullet_types() {
diff --git a/modules/bullet/rigid_body_bullet.cpp b/modules/bullet/rigid_body_bullet.cpp
index 2fc96a77b5..19fad283af 100644
--- a/modules/bullet/rigid_body_bullet.cpp
+++ b/modules/bullet/rigid_body_bullet.cpp
@@ -257,6 +257,8 @@ RigidBodyBullet::RigidBodyBullet() :
angularDamp(0),
can_sleep(true),
omit_forces_integration(false),
+ restitution_combine_mode(PhysicsServer::COMBINE_MODE_INHERIT),
+ friction_combine_mode(PhysicsServer::COMBINE_MODE_INHERIT),
force_integration_callback(NULL),
isTransformChanged(false),
previousActiveState(true),
@@ -750,6 +752,22 @@ Vector3 RigidBodyBullet::get_angular_velocity() const {
return gVec;
}
+void RigidBodyBullet::set_combine_mode(const PhysicsServer::BodyParameter p_param, const PhysicsServer::CombineMode p_mode) {
+ if (p_param == PhysicsServer::BODY_PARAM_BOUNCE) {
+ restitution_combine_mode = p_mode;
+ } else {
+ friction_combine_mode = p_mode;
+ }
+}
+
+PhysicsServer::CombineMode RigidBodyBullet::get_combine_mode(PhysicsServer::BodyParameter p_param) const {
+ if (p_param == PhysicsServer::BODY_PARAM_BOUNCE) {
+ return restitution_combine_mode;
+ } else {
+ return friction_combine_mode;
+ }
+}
+
void RigidBodyBullet::set_transform__bullet(const btTransform &p_global_transform) {
if (mode == PhysicsServer::BODY_MODE_KINEMATIC) {
// The kinematic use MotionState class
diff --git a/modules/bullet/rigid_body_bullet.h b/modules/bullet/rigid_body_bullet.h
index b9511243c7..911e93bfef 100644
--- a/modules/bullet/rigid_body_bullet.h
+++ b/modules/bullet/rigid_body_bullet.h
@@ -200,6 +200,9 @@ private:
bool can_sleep;
bool omit_forces_integration;
+ PhysicsServer::CombineMode restitution_combine_mode;
+ PhysicsServer::CombineMode friction_combine_mode;
+
Vector<CollisionData> collisions;
// these parameters are used to avoid vector resize
int maxCollisionsDetection;
@@ -295,6 +298,12 @@ public:
void set_angular_velocity(const Vector3 &p_velocity);
Vector3 get_angular_velocity() const;
+ void set_combine_mode(const PhysicsServer::BodyParameter p_param, const PhysicsServer::CombineMode p_mode);
+ PhysicsServer::CombineMode get_combine_mode(PhysicsServer::BodyParameter p_param) const;
+
+ _FORCE_INLINE_ PhysicsServer::CombineMode get_restitution_combine_mode() const { return restitution_combine_mode; }
+ _FORCE_INLINE_ PhysicsServer::CombineMode get_friction_combine_mode() const { return friction_combine_mode; }
+
virtual void set_transform__bullet(const btTransform &p_global_transform);
virtual const btTransform &get_transform__bullet() const;
diff --git a/modules/bullet/soft_body_bullet.cpp b/modules/bullet/soft_body_bullet.cpp
index 5c20eb73f1..b3680d58db 100644
--- a/modules/bullet/soft_body_bullet.cpp
+++ b/modules/bullet/soft_body_bullet.cpp
@@ -32,42 +32,24 @@
#include "bullet_types_converter.h"
#include "bullet_utilities.h"
-#include "scene/3d/immediate_geometry.h"
+#include "scene/3d/soft_body.h"
#include "space_bullet.h"
-/**
- @author AndreaCatania
-*/
-
SoftBodyBullet::SoftBodyBullet() :
CollisionObjectBullet(CollisionObjectBullet::TYPE_SOFT_BODY),
- mass(1),
+ total_mass(1),
simulation_precision(5),
- stiffness(0.5f),
- pressure_coefficient(50),
- damping_coefficient(0.005),
- drag_coefficient(0.005),
+ linear_stiffness(0.5),
+ areaAngular_stiffness(0.5),
+ volume_stiffness(0.5),
+ pressure_coefficient(0.),
+ pose_matching_coefficient(0.),
+ damping_coefficient(0.01),
+ drag_coefficient(0.),
bt_soft_body(NULL),
- soft_shape_type(SOFT_SHAPETYPE_NONE),
- isScratched(false),
- soft_body_shape_data(NULL) {
-
- test_geometry = memnew(ImmediateGeometry);
-
- red_mat = Ref<SpatialMaterial>(memnew(SpatialMaterial));
- red_mat->set_flag(SpatialMaterial::FLAG_UNSHADED, true);
- red_mat->set_line_width(20.0);
- red_mat->set_feature(SpatialMaterial::FEATURE_TRANSPARENT, true);
- red_mat->set_flag(SpatialMaterial::FLAG_ALBEDO_FROM_VERTEX_COLOR, true);
- red_mat->set_flag(SpatialMaterial::FLAG_SRGB_VERTEX_COLOR, true);
- red_mat->set_albedo(Color(1, 0, 0, 1));
- test_geometry->set_material_override(red_mat);
-
- test_is_in_scene = false;
-}
+ isScratched(false) {}
SoftBodyBullet::~SoftBodyBullet() {
- bulletdelete(soft_body_shape_data);
}
void SoftBodyBullet::reload_body() {
@@ -80,8 +62,6 @@ void SoftBodyBullet::reload_body() {
void SoftBodyBullet::set_space(SpaceBullet *p_space) {
if (space) {
isScratched = false;
-
- // Remove this object from the physics world
space->remove_soft_body(this);
}
@@ -90,86 +70,181 @@ void SoftBodyBullet::set_space(SpaceBullet *p_space) {
if (space) {
space->add_soft_body(this);
}
-
- reload_soft_body();
}
-void SoftBodyBullet::dispatch_callbacks() {
- if (!bt_soft_body) {
+void SoftBodyBullet::dispatch_callbacks() {}
+
+void SoftBodyBullet::on_collision_filters_change() {}
+
+void SoftBodyBullet::on_collision_checker_start() {}
+
+void SoftBodyBullet::on_enter_area(AreaBullet *p_area) {}
+
+void SoftBodyBullet::on_exit_area(AreaBullet *p_area) {}
+
+void SoftBodyBullet::update_visual_server(SoftBodyVisualServerHandler *p_visual_server_handler) {
+ if (!bt_soft_body)
return;
+
+ /// Update visual server vertices
+ const btSoftBody::tNodeArray &nodes(bt_soft_body->m_nodes);
+ const int nodes_count = nodes.size();
+
+ Vector<int> *vs_indices;
+ const void *vertex_position;
+ const void *vertex_normal;
+
+ for (int vertex_index = 0; vertex_index < nodes_count; ++vertex_index) {
+ vertex_position = reinterpret_cast<const void *>(&nodes[vertex_index].m_x);
+ vertex_normal = reinterpret_cast<const void *>(&nodes[vertex_index].m_n);
+
+ vs_indices = &indices_table[vertex_index];
+
+ const int vs_indices_size(vs_indices->size());
+ for (int x = 0; x < vs_indices_size; ++x) {
+ p_visual_server_handler->set_vertex((*vs_indices)[x], vertex_position);
+ p_visual_server_handler->set_normal((*vs_indices)[x], vertex_normal);
+ }
}
- if (!test_is_in_scene) {
- test_is_in_scene = true;
- SceneTree::get_singleton()->get_current_scene()->add_child(test_geometry);
+ /// Generate AABB
+ btVector3 aabb_min;
+ btVector3 aabb_max;
+ bt_soft_body->getAabb(aabb_min, aabb_max);
+
+ btVector3 size(aabb_max - aabb_min);
+
+ AABB aabb;
+ B_TO_G(aabb_min, aabb.position);
+ B_TO_G(size, aabb.size);
+
+ p_visual_server_handler->set_aabb(aabb);
+}
+
+void SoftBodyBullet::set_soft_mesh(const Ref<Mesh> &p_mesh) {
+
+ if (p_mesh.is_null() || !p_mesh->surface_is_softbody_friendly(0))
+ soft_mesh.unref();
+ else
+ soft_mesh = p_mesh;
+
+ if (soft_mesh.is_null()) {
+
+ destroy_soft_body();
+ return;
}
- test_geometry->clear();
- test_geometry->begin(Mesh::PRIMITIVE_LINES, NULL);
- bool first = true;
- Vector3 pos;
- for (int i = 0; i < bt_soft_body->m_nodes.size(); ++i) {
- const btSoftBody::Node &n = bt_soft_body->m_nodes[i];
- B_TO_G(n.m_x, pos);
- test_geometry->add_vertex(pos);
- if (!first) {
- test_geometry->add_vertex(pos);
- } else {
- first = false;
- }
+ Array arrays = soft_mesh->surface_get_arrays(0);
+ ERR_FAIL_COND(!(soft_mesh->surface_get_format(0) & VS::ARRAY_FORMAT_INDEX));
+ set_trimesh_body_shape(arrays[VS::ARRAY_INDEX], arrays[VS::ARRAY_VERTEX]);
+}
+
+void SoftBodyBullet::destroy_soft_body() {
+
+ if (!bt_soft_body)
+ return;
+
+ if (space) {
+ /// Remove from world before deletion
+ space->remove_soft_body(this);
}
- test_geometry->end();
+
+ destroyBulletCollisionObject();
+ bt_soft_body = NULL;
+}
+
+void SoftBodyBullet::set_soft_transform(const Transform &p_transform) {
+ reset_all_node_positions();
+ move_all_nodes(p_transform);
}
-void SoftBodyBullet::on_collision_filters_change() {
+void SoftBodyBullet::move_all_nodes(const Transform &p_transform) {
+ if (!bt_soft_body)
+ return;
+ btTransform bt_transf;
+ G_TO_B(p_transform, bt_transf);
+ bt_soft_body->transform(bt_transf);
}
-void SoftBodyBullet::on_collision_checker_start() {
+void SoftBodyBullet::set_node_position(int p_node_index, const Vector3 &p_global_position) {
+ btVector3 bt_pos;
+ G_TO_B(p_global_position, bt_pos);
+ set_node_position(p_node_index, bt_pos);
}
-void SoftBodyBullet::on_enter_area(AreaBullet *p_area) {
+void SoftBodyBullet::set_node_position(int p_node_index, const btVector3 &p_global_position) {
+ if (bt_soft_body) {
+ bt_soft_body->m_nodes[p_node_index].m_x = p_global_position;
+ }
}
-void SoftBodyBullet::on_exit_area(AreaBullet *p_area) {
+void SoftBodyBullet::get_node_position(int p_node_index, Vector3 &r_position) const {
+ if (bt_soft_body) {
+ B_TO_G(bt_soft_body->m_nodes[p_node_index].m_x, r_position);
+ }
}
-void SoftBodyBullet::set_trimesh_body_shape(PoolVector<int> p_indices, PoolVector<Vector3> p_vertices, int p_triangles_num) {
+void SoftBodyBullet::get_node_offset(int p_node_index, Vector3 &r_offset) const {
+ if (soft_mesh.is_null())
+ return;
+
+ Array arrays = soft_mesh->surface_get_arrays(0);
+ PoolVector<Vector3> vertices(arrays[VS::ARRAY_VERTEX]);
- TrimeshSoftShapeData *shape_data = bulletnew(TrimeshSoftShapeData);
- shape_data->m_triangles_indices = p_indices;
- shape_data->m_vertices = p_vertices;
- shape_data->m_triangles_num = p_triangles_num;
+ if (0 <= p_node_index && vertices.size() > p_node_index) {
+ r_offset = vertices[p_node_index];
+ }
+}
- set_body_shape_data(shape_data, SOFT_SHAPE_TYPE_TRIMESH);
- reload_soft_body();
+void SoftBodyBullet::get_node_offset(int p_node_index, btVector3 &r_offset) const {
+ Vector3 off;
+ get_node_offset(p_node_index, off);
+ G_TO_B(off, r_offset);
}
-void SoftBodyBullet::set_body_shape_data(SoftShapeData *p_soft_shape_data, SoftShapeType p_type) {
- bulletdelete(soft_body_shape_data);
- soft_body_shape_data = p_soft_shape_data;
- soft_shape_type = p_type;
+void SoftBodyBullet::set_node_mass(int node_index, btScalar p_mass) {
+ if (0 >= p_mass) {
+ pin_node(node_index);
+ } else {
+ unpin_node(node_index);
+ }
+ if (bt_soft_body) {
+ bt_soft_body->setMass(node_index, p_mass);
+ }
}
-void SoftBodyBullet::set_transform(const Transform &p_transform) {
- transform = p_transform;
+btScalar SoftBodyBullet::get_node_mass(int node_index) const {
if (bt_soft_body) {
- // TODO the softbody set new transform considering the current transform as center of world
- // like if it's local transform, so I must fix this by setting nwe transform considering the old
- btTransform bt_trans;
- G_TO_B(transform, bt_trans);
- //bt_soft_body->transform(bt_trans);
+ return bt_soft_body->getMass(node_index);
+ } else {
+ return -1 == search_node_pinned(node_index) ? 1 : 0;
}
}
-const Transform &SoftBodyBullet::get_transform() const {
- return transform;
+void SoftBodyBullet::reset_all_node_mass() {
+ if (bt_soft_body) {
+ for (int i = pinned_nodes.size() - 1; 0 <= i; --i) {
+ bt_soft_body->setMass(pinned_nodes[i], 1);
+ }
+ }
+ pinned_nodes.resize(0);
}
-void SoftBodyBullet::get_first_node_origin(btVector3 &p_out_origin) const {
- if (bt_soft_body && bt_soft_body->m_nodes.size()) {
- p_out_origin = bt_soft_body->m_nodes[0].m_x;
- } else {
- p_out_origin.setZero();
+void SoftBodyBullet::reset_all_node_positions() {
+ if (soft_mesh.is_null())
+ return;
+
+ Array arrays = soft_mesh->surface_get_arrays(0);
+ PoolVector<Vector3> vs_vertices(arrays[VS::ARRAY_VERTEX]);
+ PoolVector<Vector3>::Read vs_vertices_read = vs_vertices.read();
+
+ for (int vertex_index = bt_soft_body->m_nodes.size() - 1; 0 <= vertex_index; --vertex_index) {
+
+ G_TO_B(vs_vertices_read[indices_table[vertex_index][0]], bt_soft_body->m_nodes[vertex_index].m_x);
+
+ bt_soft_body->m_nodes[vertex_index].m_q = bt_soft_body->m_nodes[vertex_index].m_x;
+ bt_soft_body->m_nodes[vertex_index].m_v = btVector3(0, 0, 0);
+ bt_soft_body->m_nodes[vertex_index].m_f = btVector3(0, 0, 0);
}
}
@@ -181,22 +256,34 @@ void SoftBodyBullet::set_activation_state(bool p_active) {
}
}
-void SoftBodyBullet::set_mass(real_t p_val) {
+void SoftBodyBullet::set_total_mass(real_t p_val) {
if (0 >= p_val) {
p_val = 1;
}
- mass = p_val;
+ total_mass = p_val;
if (bt_soft_body) {
- bt_soft_body->setTotalMass(mass);
+ bt_soft_body->setTotalMass(total_mass);
}
}
-void SoftBodyBullet::set_stiffness(real_t p_val) {
- stiffness = p_val;
+void SoftBodyBullet::set_linear_stiffness(real_t p_val) {
+ linear_stiffness = p_val;
if (bt_soft_body) {
- mat0->m_kAST = stiffness;
- mat0->m_kLST = stiffness;
- mat0->m_kVST = stiffness;
+ mat0->m_kLST = linear_stiffness;
+ }
+}
+
+void SoftBodyBullet::set_areaAngular_stiffness(real_t p_val) {
+ areaAngular_stiffness = p_val;
+ if (bt_soft_body) {
+ mat0->m_kAST = areaAngular_stiffness;
+ }
+}
+
+void SoftBodyBullet::set_volume_stiffness(real_t p_val) {
+ volume_stiffness = p_val;
+ if (bt_soft_body) {
+ mat0->m_kVST = volume_stiffness;
}
}
@@ -204,6 +291,9 @@ void SoftBodyBullet::set_simulation_precision(int p_val) {
simulation_precision = p_val;
if (bt_soft_body) {
bt_soft_body->m_cfg.piterations = simulation_precision;
+ bt_soft_body->m_cfg.viterations = simulation_precision;
+ bt_soft_body->m_cfg.diterations = simulation_precision;
+ bt_soft_body->m_cfg.citerations = simulation_precision;
}
}
@@ -214,6 +304,13 @@ void SoftBodyBullet::set_pressure_coefficient(real_t p_val) {
}
}
+void SoftBodyBullet::set_pose_matching_coefficient(real_t p_val) {
+ pose_matching_coefficient = p_val;
+ if (bt_soft_body) {
+ bt_soft_body->m_cfg.kMT = pose_matching_coefficient;
+ }
+}
+
void SoftBodyBullet::set_damping_coefficient(real_t p_val) {
damping_coefficient = p_val;
if (bt_soft_body) {
@@ -228,89 +325,156 @@ void SoftBodyBullet::set_drag_coefficient(real_t p_val) {
}
}
-void SoftBodyBullet::reload_soft_body() {
-
+void SoftBodyBullet::set_trimesh_body_shape(PoolVector<int> p_indices, PoolVector<Vector3> p_vertices) {
+ /// Assert the current soft body is destroyed
destroy_soft_body();
- create_soft_body();
- if (bt_soft_body) {
+ /// Parse visual server indices to physical indices.
+ /// Merge all overlapping vertices and create a map of physical vertices to visual server
- // TODO the softbody set new transform considering the current transform as center of world
- // like if it's local transform, so I must fix this by setting nwe transform considering the old
- btTransform bt_trans;
- G_TO_B(transform, bt_trans);
- bt_soft_body->transform(bt_trans);
+ {
+ /// This is the map of visual server indices to physics indices (So it's the inverse of idices_map), Thanks to it I don't need make a heavy search in the indices_map
+ Vector<int> vs_indices_to_physics_table;
- bt_soft_body->generateBendingConstraints(2, mat0);
- mat0->m_kAST = stiffness;
- mat0->m_kLST = stiffness;
- mat0->m_kVST = stiffness;
+ { // Map vertices
+ indices_table.resize(0);
- bt_soft_body->m_cfg.piterations = simulation_precision;
- bt_soft_body->m_cfg.kDP = damping_coefficient;
- bt_soft_body->m_cfg.kDG = drag_coefficient;
- bt_soft_body->m_cfg.kPR = pressure_coefficient;
- bt_soft_body->setTotalMass(mass);
- }
- if (space) {
- // TODO remove this please
- space->add_soft_body(this);
- }
-}
+ int index = 0;
+ Map<Vector3, int> unique_vertices;
-void SoftBodyBullet::create_soft_body() {
- if (!space || !soft_body_shape_data) {
- return;
- }
- ERR_FAIL_COND(!space->is_using_soft_world());
- switch (soft_shape_type) {
- case SOFT_SHAPE_TYPE_TRIMESH: {
- TrimeshSoftShapeData *trimesh_data = static_cast<TrimeshSoftShapeData *>(soft_body_shape_data);
-
- Vector<int> indices;
- Vector<btScalar> vertices;
-
- int i;
- const int indices_size = trimesh_data->m_triangles_indices.size();
- const int vertices_size = trimesh_data->m_vertices.size();
- indices.resize(indices_size);
- vertices.resize(vertices_size * 3);
-
- PoolVector<int>::Read i_r = trimesh_data->m_triangles_indices.read();
- for (i = 0; i < indices_size; ++i) {
- indices[i] = i_r[i];
+ const int vs_vertices_size(p_vertices.size());
+
+ PoolVector<Vector3>::Read p_vertices_read = p_vertices.read();
+
+ for (int vs_vertex_index = 0; vs_vertex_index < vs_vertices_size; ++vs_vertex_index) {
+
+ Map<Vector3, int>::Element *e = unique_vertices.find(p_vertices_read[vs_vertex_index]);
+ int vertex_id;
+ if (e) {
+ // Already rxisting
+ vertex_id = e->value();
+ } else {
+ // Create new one
+ unique_vertices[p_vertices_read[vs_vertex_index]] = vertex_id = index++;
+ indices_table.push_back(Vector<int>());
+ }
+
+ indices_table[vertex_id].push_back(vs_vertex_index);
+ vs_indices_to_physics_table.push_back(vertex_id);
+ }
+ }
+
+ const int indices_map_size(indices_table.size());
+
+ Vector<btScalar> bt_vertices;
+
+ { // Parse vertices to bullet
+
+ bt_vertices.resize(indices_map_size * 3);
+ PoolVector<Vector3>::Read p_vertices_read = p_vertices.read();
+
+ for (int i = 0; i < indices_map_size; ++i) {
+ bt_vertices[3 * i + 0] = p_vertices_read[indices_table[i][0]].x;
+ bt_vertices[3 * i + 1] = p_vertices_read[indices_table[i][0]].y;
+ bt_vertices[3 * i + 2] = p_vertices_read[indices_table[i][0]].z;
}
- i_r = PoolVector<int>::Read();
+ }
+
+ Vector<int> bt_triangles;
+ const int triangles_size(p_indices.size() / 3);
+
+ { // Parse indices
+
+ bt_triangles.resize(triangles_size * 3);
+
+ PoolVector<int>::Read p_indices_read = p_indices.read();
- PoolVector<Vector3>::Read f_r = trimesh_data->m_vertices.read();
- for (int j = i = 0; i < vertices_size; ++i, j += 3) {
- vertices[j + 0] = f_r[i][0];
- vertices[j + 1] = f_r[i][1];
- vertices[j + 2] = f_r[i][2];
+ for (int i = 0; i < triangles_size; ++i) {
+ bt_triangles[3 * i + 0] = vs_indices_to_physics_table[p_indices_read[3 * i + 2]];
+ bt_triangles[3 * i + 1] = vs_indices_to_physics_table[p_indices_read[3 * i + 1]];
+ bt_triangles[3 * i + 2] = vs_indices_to_physics_table[p_indices_read[3 * i + 0]];
}
- f_r = PoolVector<Vector3>::Read();
+ }
- bt_soft_body = btSoftBodyHelpers::CreateFromTriMesh(*space->get_soft_body_world_info(), vertices.ptr(), indices.ptr(), trimesh_data->m_triangles_num);
- } break;
- default:
- ERR_PRINT("Shape type not supported");
- return;
+ btSoftBodyWorldInfo fake_world_info;
+ bt_soft_body = btSoftBodyHelpers::CreateFromTriMesh(fake_world_info, &bt_vertices[0], &bt_triangles[0], triangles_size, false);
+ setup_soft_body();
}
+}
+
+void SoftBodyBullet::setup_soft_body() {
+
+ if (!bt_soft_body)
+ return;
+ // Soft body setup
setupBulletCollisionObject(bt_soft_body);
- bt_soft_body->getCollisionShape()->setMargin(0.001f);
+ bt_soft_body->m_worldInfo = NULL; // Remove fake world info
+ bt_soft_body->getCollisionShape()->setMargin(0.01);
bt_soft_body->setCollisionFlags(bt_soft_body->getCollisionFlags() & (~(btCollisionObject::CF_KINEMATIC_OBJECT | btCollisionObject::CF_STATIC_OBJECT)));
+
+ // Space setup
+ if (space) {
+ space->add_soft_body(this);
+ }
+
mat0 = bt_soft_body->appendMaterial();
+
+ // Assign soft body data
+ bt_soft_body->generateBendingConstraints(2, mat0);
+
+ mat0->m_kLST = linear_stiffness;
+ mat0->m_kAST = areaAngular_stiffness;
+ mat0->m_kVST = volume_stiffness;
+
+ // Clusters allow to have Soft vs Soft collision but doesn't work well right now
+
+ //bt_soft_body->m_cfg.kSRHR_CL = 1;// Soft vs rigid hardness [0,1] (cluster only)
+ //bt_soft_body->m_cfg.kSKHR_CL = 1;// Soft vs kinematic hardness [0,1] (cluster only)
+ //bt_soft_body->m_cfg.kSSHR_CL = 1;// Soft vs soft hardness [0,1] (cluster only)
+ //bt_soft_body->m_cfg.kSR_SPLT_CL = 1; // Soft vs rigid impulse split [0,1] (cluster only)
+ //bt_soft_body->m_cfg.kSK_SPLT_CL = 1; // Soft vs kinematic impulse split [0,1] (cluster only)
+ //bt_soft_body->m_cfg.kSS_SPLT_CL = 1; // Soft vs Soft impulse split [0,1] (cluster only)
+ //bt_soft_body->m_cfg.collisions = btSoftBody::fCollision::CL_SS + btSoftBody::fCollision::CL_RS + btSoftBody::fCollision::VF_SS;
+ //bt_soft_body->generateClusters(64);
+
+ bt_soft_body->m_cfg.piterations = simulation_precision;
+ bt_soft_body->m_cfg.viterations = simulation_precision;
+ bt_soft_body->m_cfg.diterations = simulation_precision;
+ bt_soft_body->m_cfg.citerations = simulation_precision;
+ bt_soft_body->m_cfg.kDP = damping_coefficient;
+ bt_soft_body->m_cfg.kDG = drag_coefficient;
+ bt_soft_body->m_cfg.kPR = pressure_coefficient;
+ bt_soft_body->m_cfg.kMT = pose_matching_coefficient;
+ bt_soft_body->setTotalMass(total_mass);
+
+ btSoftBodyHelpers::ReoptimizeLinkOrder(bt_soft_body);
+ bt_soft_body->updateBounds();
+
+ // Set pinned nodes
+ for (int i = pinned_nodes.size() - 1; 0 <= i; --i) {
+ bt_soft_body->setMass(pinned_nodes[i], 0);
+ }
}
-void SoftBodyBullet::destroy_soft_body() {
- if (space) {
- /// This step is required to assert that the body is not into the world during deletion
- /// This step is required since to change the body shape the body must be re-created.
- /// Here is handled the case when the body is assigned into a world and the body
- /// shape is changed.
- space->remove_soft_body(this);
+void SoftBodyBullet::pin_node(int p_node_index) {
+ if (-1 == search_node_pinned(p_node_index)) {
+ pinned_nodes.push_back(p_node_index);
}
- destroyBulletCollisionObject();
- bt_soft_body = NULL;
+}
+
+void SoftBodyBullet::unpin_node(int p_node_index) {
+ const int id = search_node_pinned(p_node_index);
+ if (-1 != id) {
+ pinned_nodes.remove(id);
+ }
+}
+
+int SoftBodyBullet::search_node_pinned(int p_node_index) const {
+ for (int i = pinned_nodes.size() - 1; 0 <= i; --i) {
+ if (p_node_index == pinned_nodes[i]) {
+ return i;
+ }
+ }
+ return -1;
}
diff --git a/modules/bullet/soft_body_bullet.h b/modules/bullet/soft_body_bullet.h
index 9895643b84..c775193584 100644
--- a/modules/bullet/soft_body_bullet.h
+++ b/modules/bullet/soft_body_bullet.h
@@ -40,7 +40,10 @@
#define x11_None 0L
#endif
-#include <BulletSoftBody/btSoftBodyHelpers.h>
+#include "BulletSoftBody/btSoftBodyHelpers.h"
+#include "collision_object_bullet.h"
+#include "scene/resources/mesh.h"
+#include "servers/physics_server.h"
#ifdef x11_None
/// This is required to re add the macro None defined by x11 compiler
@@ -52,39 +55,34 @@
@author AndreaCatania
*/
-struct SoftShapeData {};
-struct TrimeshSoftShapeData : public SoftShapeData {
- PoolVector<int> m_triangles_indices;
- PoolVector<Vector3> m_vertices;
- int m_triangles_num;
-};
-
class SoftBodyBullet : public CollisionObjectBullet {
-public:
- enum SoftShapeType {
- SOFT_SHAPETYPE_NONE = 0,
- SOFT_SHAPE_TYPE_TRIMESH
- };
private:
btSoftBody *bt_soft_body;
+ Vector<Vector<int> > indices_table;
btSoftBody::Material *mat0; // This is just a copy of pointer managed by btSoftBody
- SoftShapeType soft_shape_type;
bool isScratched;
- SoftShapeData *soft_body_shape_data;
+ Ref<Mesh> soft_mesh;
- Transform transform;
int simulation_precision;
- real_t mass;
- real_t stiffness; // [0,1]
+ real_t total_mass;
+ real_t linear_stiffness; // [0,1]
+ real_t areaAngular_stiffness; // [0,1]
+ real_t volume_stiffness; // [0,1]
real_t pressure_coefficient; // [-inf,+inf]
+ real_t pose_matching_coefficient; // [0,1]
real_t damping_coefficient; // [0,1]
real_t drag_coefficient; // [0,1]
+ Vector<int> pinned_nodes;
- class ImmediateGeometry *test_geometry; // TODO remove this please
- Ref<SpatialMaterial> red_mat; // TODO remove this please
- bool test_is_in_scene; // TODO remove this please
+ // Other property to add
+ //btScalar kVC; // Volume conversation coefficient [0,+inf]
+ //btScalar kDF; // Dynamic friction coefficient [0,1]
+ //btScalar kMT; // Pose matching coefficient [0,1]
+ //btScalar kCHR; // Rigid contacts hardness [0,1]
+ //btScalar kKHR; // Kinetic contacts hardness [0,1]
+ //btScalar kSHR; // Soft contacts hardness [0,1]
public:
SoftBodyBullet();
@@ -101,39 +99,64 @@ public:
_FORCE_INLINE_ btSoftBody *get_bt_soft_body() const { return bt_soft_body; }
- void set_trimesh_body_shape(PoolVector<int> p_indices, PoolVector<Vector3> p_vertices, int p_triangles_num);
- void set_body_shape_data(SoftShapeData *p_soft_shape_data, SoftShapeType p_type);
+ void update_visual_server(class SoftBodyVisualServerHandler *p_visual_server_handler);
- void set_transform(const Transform &p_transform);
- /// This function doesn't return the exact COM transform.
- /// It returns the origin only of first node (vertice) of current soft body
- /// ---
- /// The soft body doesn't have a fixed center of mass, but is a group of nodes (vertices)
- /// that each has its own position in the world.
- /// For this reason return the correct COM is not so simple and must be calculate
- /// Check this to improve this function http://bulletphysics.org/Bullet/phpBB3/viewtopic.php?t=8803
- const Transform &get_transform() const;
- void get_first_node_origin(btVector3 &p_out_origin) const;
+ void set_soft_mesh(const Ref<Mesh> &p_mesh);
+ void destroy_soft_body();
+
+ // Special function. This function has bad performance
+ void set_soft_transform(const Transform &p_transform);
+
+ void move_all_nodes(const Transform &p_transform);
+ void set_node_position(int node_index, const Vector3 &p_global_position);
+ void set_node_position(int node_index, const btVector3 &p_global_position);
+ void get_node_position(int node_index, Vector3 &r_position) const;
+ // Heavy function, Please cache this info
+ void get_node_offset(int node_index, Vector3 &r_offset) const;
+ // Heavy function, Please cache this info
+ void get_node_offset(int node_index, btVector3 &r_offset) const;
+
+ void set_node_mass(int node_index, btScalar p_mass);
+ btScalar get_node_mass(int node_index) const;
+ void reset_all_node_mass();
+ void reset_all_node_positions();
void set_activation_state(bool p_active);
- void set_mass(real_t p_val);
- _FORCE_INLINE_ real_t get_mass() const { return mass; }
- void set_stiffness(real_t p_val);
- _FORCE_INLINE_ real_t get_stiffness() const { return stiffness; }
+ void set_total_mass(real_t p_val);
+ _FORCE_INLINE_ real_t get_total_mass() const { return total_mass; }
+
+ void set_linear_stiffness(real_t p_val);
+ _FORCE_INLINE_ real_t get_linear_stiffness() const { return linear_stiffness; }
+
+ void set_areaAngular_stiffness(real_t p_val);
+ _FORCE_INLINE_ real_t get_areaAngular_stiffness() const { return areaAngular_stiffness; }
+
+ void set_volume_stiffness(real_t p_val);
+ _FORCE_INLINE_ real_t get_volume_stiffness() const { return volume_stiffness; }
+
void set_simulation_precision(int p_val);
_FORCE_INLINE_ int get_simulation_precision() const { return simulation_precision; }
+
void set_pressure_coefficient(real_t p_val);
_FORCE_INLINE_ real_t get_pressure_coefficient() const { return pressure_coefficient; }
+
+ void set_pose_matching_coefficient(real_t p_val);
+ _FORCE_INLINE_ real_t get_pose_matching_coefficient() const { return pose_matching_coefficient; }
+
void set_damping_coefficient(real_t p_val);
_FORCE_INLINE_ real_t get_damping_coefficient() const { return damping_coefficient; }
+
void set_drag_coefficient(real_t p_val);
_FORCE_INLINE_ real_t get_drag_coefficient() const { return drag_coefficient; }
private:
- void reload_soft_body();
- void create_soft_body();
- void destroy_soft_body();
+ void set_trimesh_body_shape(PoolVector<int> p_indices, PoolVector<Vector3> p_vertices);
+ void setup_soft_body();
+
+ void pin_node(int p_node_index);
+ void unpin_node(int p_node_index);
+ int search_node_pinned(int p_node_index) const;
};
#endif // SOFT_BODY_BULLET_H
diff --git a/modules/bullet/space_bullet.cpp b/modules/bullet/space_bullet.cpp
index 971fd39509..132c3739d6 100644
--- a/modules/bullet/space_bullet.cpp
+++ b/modules/bullet/space_bullet.cpp
@@ -36,6 +36,7 @@
#include "constraint_bullet.h"
#include "godot_collision_configuration.h"
#include "godot_collision_dispatcher.h"
+#include "project_settings.h"
#include "rigid_body_bullet.h"
#include "servers/physics_server.h"
#include "soft_body_bullet.h"
@@ -325,7 +326,7 @@ Vector3 BulletPhysicsDirectSpaceState::get_closest_point_to_object_volume(RID p_
}
}
-SpaceBullet::SpaceBullet(bool p_create_soft_world) :
+SpaceBullet::SpaceBullet() :
broadphase(NULL),
dispatcher(NULL),
solver(NULL),
@@ -338,7 +339,7 @@ SpaceBullet::SpaceBullet(bool p_create_soft_world) :
gravityMagnitude(10),
contactDebugCount(0) {
- create_empty_world(p_create_soft_world);
+ create_empty_world(GLOBAL_DEF("physics/3d/active_soft_world", true));
direct_access = memnew(BulletPhysicsDirectSpaceState(this));
}
@@ -355,6 +356,7 @@ void SpaceBullet::flush_queries() {
}
void SpaceBullet::step(real_t p_delta_time) {
+ delta_time = p_delta_time;
dynamicsWorld->stepSimulation(p_delta_time, 0, 0);
}
@@ -483,6 +485,7 @@ void SpaceBullet::reload_collision_filters(RigidBodyBullet *p_body) {
void SpaceBullet::add_soft_body(SoftBodyBullet *p_body) {
if (is_using_soft_world()) {
if (p_body->get_bt_soft_body()) {
+ p_body->get_bt_soft_body()->m_worldInfo = get_soft_body_world_info();
static_cast<btSoftRigidDynamicsWorld *>(dynamicsWorld)->addSoftBody(p_body->get_bt_soft_body(), p_body->get_collision_layer(), p_body->get_collision_mask());
}
} else {
@@ -494,6 +497,7 @@ void SpaceBullet::remove_soft_body(SoftBodyBullet *p_body) {
if (is_using_soft_world()) {
if (p_body->get_bt_soft_body()) {
static_cast<btSoftRigidDynamicsWorld *>(dynamicsWorld)->removeSoftBody(p_body->get_bt_soft_body());
+ p_body->get_bt_soft_body()->m_worldInfo = NULL;
}
}
}
@@ -549,7 +553,43 @@ BulletPhysicsDirectSpaceState *SpaceBullet::get_direct_state() {
}
btScalar calculateGodotCombinedRestitution(const btCollisionObject *body0, const btCollisionObject *body1) {
- return MAX(body0->getRestitution(), body1->getRestitution());
+
+ const PhysicsServer::CombineMode cm = static_cast<RigidBodyBullet *>(body0->getUserPointer())->get_restitution_combine_mode();
+
+ switch (cm) {
+ case PhysicsServer::COMBINE_MODE_INHERIT:
+ if (static_cast<RigidBodyBullet *>(body1->getUserPointer())->get_restitution_combine_mode() != PhysicsServer::COMBINE_MODE_INHERIT)
+ return calculateGodotCombinedRestitution(body1, body0);
+ // else use MAX [This is used when the two bodies doesn't use physical material]
+ case PhysicsServer::COMBINE_MODE_MAX:
+ return MAX(body0->getRestitution(), body1->getRestitution());
+ case PhysicsServer::COMBINE_MODE_MIN:
+ return MIN(body0->getRestitution(), body1->getRestitution());
+ case PhysicsServer::COMBINE_MODE_MULTIPLY:
+ return body0->getRestitution() * body1->getRestitution();
+ default: // Is always PhysicsServer::COMBINE_MODE_AVERAGE:
+ return (body0->getRestitution() + body1->getRestitution()) / 2;
+ }
+}
+
+btScalar calculateGodotCombinedFriction(const btCollisionObject *body0, const btCollisionObject *body1) {
+
+ const PhysicsServer::CombineMode cm = static_cast<RigidBodyBullet *>(body0->getUserPointer())->get_friction_combine_mode();
+
+ switch (cm) {
+ case PhysicsServer::COMBINE_MODE_INHERIT:
+ if (static_cast<RigidBodyBullet *>(body1->getUserPointer())->get_friction_combine_mode() != PhysicsServer::COMBINE_MODE_INHERIT)
+ return calculateGodotCombinedFriction(body1, body0);
+ // else use MULTIPLY [This is used when the two bodies doesn't use physical material]
+ case PhysicsServer::COMBINE_MODE_MULTIPLY:
+ return body0->getFriction() * body1->getFriction();
+ case PhysicsServer::COMBINE_MODE_MAX:
+ return MAX(body0->getFriction(), body1->getFriction());
+ case PhysicsServer::COMBINE_MODE_MIN:
+ return MIN(body0->getFriction(), body1->getFriction());
+ default: // Is always PhysicsServer::COMBINE_MODE_AVERAGE:
+ return (body0->getFriction() * body1->getFriction()) / 2;
+ }
}
void SpaceBullet::create_empty_world(bool p_create_soft_world) {
@@ -585,6 +625,7 @@ void SpaceBullet::create_empty_world(bool p_create_soft_world) {
ghostPairCallback = bulletnew(btGhostPairCallback);
godotFilterCallback = bulletnew(GodotFilterCallback);
gCalculateCombinedRestitutionCallback = &calculateGodotCombinedRestitution;
+ gCalculateCombinedFrictionCallback = &calculateGodotCombinedFriction;
dynamicsWorld->setWorldUserInfo(this);
diff --git a/modules/bullet/space_bullet.h b/modules/bullet/space_bullet.h
index a6c2786878..006c6462cf 100644
--- a/modules/bullet/space_bullet.h
+++ b/modules/bullet/space_bullet.h
@@ -84,7 +84,7 @@ public:
};
class SpaceBullet : public RIDBullet {
-private:
+
friend class AreaBullet;
friend void onBulletTickCallback(btDynamicsWorld *world, btScalar timeStep);
friend class BulletPhysicsDirectSpaceState;
@@ -109,12 +109,14 @@ private:
Vector<Vector3> contactDebug;
int contactDebugCount;
+ real_t delta_time;
public:
- SpaceBullet(bool p_create_soft_world);
+ SpaceBullet();
virtual ~SpaceBullet();
void flush_queries();
+ real_t get_delta_time() { return delta_time; }
void step(real_t p_delta_time);
_FORCE_INLINE_ btBroadphaseInterface *get_broadphase() { return broadphase; }
diff --git a/modules/gdnative/nativescript/api_generator.cpp b/modules/gdnative/nativescript/api_generator.cpp
index 4012e821bb..70ca8d68b8 100644
--- a/modules/gdnative/nativescript/api_generator.cpp
+++ b/modules/gdnative/nativescript/api_generator.cpp
@@ -110,7 +110,6 @@ struct ClassAPI {
bool is_singleton;
bool is_instanciable;
// @Unclear
- bool is_creatable;
bool is_reference;
List<MethodAPI> methods;
@@ -385,7 +384,6 @@ static List<String> generate_c_api_json(const List<ClassAPI> &p_api) {
source.push_back(String("\t\t\"instanciable\": ") + (api.is_instanciable ? "true" : "false") + ",\n");
source.push_back(String("\t\t\"is_reference\": ") + (api.is_reference ? "true" : "false") + ",\n");
// @Unclear
- // source.push_back(String("\t\t\"createable\": ") + (api.is_creatable ? "true" : "false") + ",\n");
source.push_back("\t\t\"constants\": {\n");
for (List<ConstantAPI>::Element *e = api.constants.front(); e; e = e->next()) {
diff --git a/modules/gdnative/nativescript/nativescript.cpp b/modules/gdnative/nativescript/nativescript.cpp
index 7bab718b81..5e093109d5 100644
--- a/modules/gdnative/nativescript/nativescript.cpp
+++ b/modules/gdnative/nativescript/nativescript.cpp
@@ -1053,7 +1053,7 @@ Ref<Script> NativeScriptLanguage::get_template(const String &p_class_name, const
s->set_class_name(p_class_name);
return Ref<NativeScript>(s);
}
-bool NativeScriptLanguage::validate(const String &p_script, int &r_line_error, int &r_col_error, String &r_test_error, const String &p_path, List<String> *r_functions) const {
+bool NativeScriptLanguage::validate(const String &p_script, int &r_line_error, int &r_col_error, String &r_test_error, const String &p_path, List<String> *r_functions, Set<int> *r_safe_lines) const {
return true;
}
diff --git a/modules/gdnative/nativescript/nativescript.h b/modules/gdnative/nativescript/nativescript.h
index be093dde4b..1b39b63ad9 100644
--- a/modules/gdnative/nativescript/nativescript.h
+++ b/modules/gdnative/nativescript/nativescript.h
@@ -295,7 +295,7 @@ public:
virtual void get_comment_delimiters(List<String> *p_delimiters) const;
virtual void get_string_delimiters(List<String> *p_delimiters) const;
virtual Ref<Script> get_template(const String &p_class_name, const String &p_base_class_name) const;
- virtual bool validate(const String &p_script, int &r_line_error, int &r_col_error, String &r_test_error, const String &p_path, List<String> *r_functions) const;
+ virtual bool validate(const String &p_script, int &r_line_error, int &r_col_error, String &r_test_error, const String &p_path, List<String> *r_functions, Set<int> *r_safe_lines = NULL) const;
virtual Script *create_script() const;
virtual bool has_named_classes() const;
virtual bool supports_builtin_mode() const;
diff --git a/modules/gdnative/pluginscript/pluginscript_language.cpp b/modules/gdnative/pluginscript/pluginscript_language.cpp
index 8018178bd5..816b0f0cab 100644
--- a/modules/gdnative/pluginscript/pluginscript_language.cpp
+++ b/modules/gdnative/pluginscript/pluginscript_language.cpp
@@ -108,7 +108,7 @@ Ref<Script> PluginScriptLanguage::get_template(const String &p_class_name, const
return script;
}
-bool PluginScriptLanguage::validate(const String &p_script, int &r_line_error, int &r_col_error, String &r_test_error, const String &p_path, List<String> *r_functions) const {
+bool PluginScriptLanguage::validate(const String &p_script, int &r_line_error, int &r_col_error, String &r_test_error, const String &p_path, List<String> *r_functions, Set<int> *r_safe_lines) const {
PoolStringArray functions;
if (_desc.validate) {
bool ret = _desc.validate(
diff --git a/modules/gdnative/pluginscript/pluginscript_language.h b/modules/gdnative/pluginscript/pluginscript_language.h
index 709345885b..2443e31361 100644
--- a/modules/gdnative/pluginscript/pluginscript_language.h
+++ b/modules/gdnative/pluginscript/pluginscript_language.h
@@ -74,7 +74,7 @@ public:
virtual void get_comment_delimiters(List<String> *p_delimiters) const;
virtual void get_string_delimiters(List<String> *p_delimiters) const;
virtual Ref<Script> get_template(const String &p_class_name, const String &p_base_class_name) const;
- virtual bool validate(const String &p_script, int &r_line_error, int &r_col_error, String &r_test_error, const String &p_path = "", List<String> *r_functions = NULL) const;
+ virtual bool validate(const String &p_script, int &r_line_error, int &r_col_error, String &r_test_error, const String &p_path = "", List<String> *r_functions = NULL, Set<int> *r_safe_lines = NULL) const;
virtual Script *create_script() const;
virtual bool has_named_classes() const;
virtual bool supports_builtin_mode() const;
diff --git a/modules/gdscript/editor/gdscript_highlighter.cpp b/modules/gdscript/editor/gdscript_highlighter.cpp
index fedc510f01..f2a1a5b50c 100644
--- a/modules/gdscript/editor/gdscript_highlighter.cpp
+++ b/modules/gdscript/editor/gdscript_highlighter.cpp
@@ -75,9 +75,11 @@ Map<int, TextEdit::HighlighterInfo> GDScriptSyntaxHighlighter::_get_line_syntax_
bool in_keyword = false;
bool in_word = false;
bool in_function_name = false;
+ bool in_variable_declaration = false;
bool in_member_variable = false;
bool in_node_path = false;
bool is_hex_notation = false;
+ bool expect_type = false;
Color keyword_color;
Color color;
@@ -205,6 +207,8 @@ Map<int, TextEdit::HighlighterInfo> GDScriptSyntaxHighlighter::_get_line_syntax_
if (str[k] == '(') {
in_function_name = true;
+ } else if (previous_text == GDScriptTokenizer::get_token_name(GDScriptTokenizer::TK_PR_VAR)) {
+ in_variable_declaration = true;
}
}
@@ -222,6 +226,28 @@ Map<int, TextEdit::HighlighterInfo> GDScriptSyntaxHighlighter::_get_line_syntax_
if (is_symbol) {
in_function_name = false;
in_member_variable = false;
+
+ if (expect_type && str[j] != ' ' && str[j] != '\t' && str[j] != ':') {
+ expect_type = false;
+ }
+ if (j > 0 && str[j] == '>' && str[j - 1] == '-') {
+ expect_type = true;
+ }
+
+ if (in_variable_declaration || previous_text == "(" || previous_text == ",") {
+ int k = j;
+ // Skip space
+ while (k < str.length() && (str[k] == '\t' || str[k] == ' ')) {
+ k++;
+ }
+
+ if (str[k] == ':') {
+ // has type hint
+ expect_type = true;
+ }
+ }
+
+ in_variable_declaration = false;
}
if (!in_node_path && in_region == -1 && str[j] == '$') {
@@ -256,6 +282,9 @@ Map<int, TextEdit::HighlighterInfo> GDScriptSyntaxHighlighter::_get_line_syntax_
} else if (is_number) {
next_type = NUMBER;
color = number_color;
+ } else if (expect_type) {
+ next_type = TYPE;
+ color = type_color;
} else {
next_type = IDENTIFIER;
}
@@ -330,6 +359,7 @@ void GDScriptSyntaxHighlighter::_update_cache() {
function_definition_color = EDITOR_GET("text_editor/highlighting/gdscript/function_definition_color");
node_path_color = EDITOR_GET("text_editor/highlighting/gdscript/node_path_color");
+ type_color = EDITOR_GET("text_editor/highlighting/base_type_color");
}
SyntaxHighlighter *GDScriptSyntaxHighlighter::create() {
diff --git a/modules/gdscript/editor/gdscript_highlighter.h b/modules/gdscript/editor/gdscript_highlighter.h
index 0296ab7652..b8cb4a65e9 100644
--- a/modules/gdscript/editor/gdscript_highlighter.h
+++ b/modules/gdscript/editor/gdscript_highlighter.h
@@ -44,7 +44,8 @@ private:
FUNCTION,
KEYWORD,
MEMBER,
- IDENTIFIER
+ IDENTIFIER,
+ TYPE,
};
// colours
@@ -56,6 +57,7 @@ private:
Color number_color;
Color member_color;
Color node_path_color;
+ Color type_color;
public:
static SyntaxHighlighter *create();
diff --git a/modules/gdscript/gdscript.cpp b/modules/gdscript/gdscript.cpp
index b3ebd4fe4b..8bd29ffc55 100644
--- a/modules/gdscript/gdscript.cpp
+++ b/modules/gdscript/gdscript.cpp
@@ -220,16 +220,14 @@ void GDScript::_placeholder_erased(PlaceHolderScriptInstance *p_placeholder) {
void GDScript::get_script_method_list(List<MethodInfo> *p_list) const {
for (const Map<StringName, GDScriptFunction *>::Element *E = member_functions.front(); E; E = E->next()) {
+ GDScriptFunction *func = E->get();
MethodInfo mi;
mi.name = E->key();
- for (int i = 0; i < E->get()->get_argument_count(); i++) {
- PropertyInfo arg;
- arg.type = Variant::NIL; //variant
- arg.name = E->get()->get_argument_name(i);
- mi.arguments.push_back(arg);
+ for (int i = 0; i < func->get_argument_count(); i++) {
+ mi.arguments.push_back(func->get_argument_type(i));
}
- mi.return_val.name = "Variant";
+ mi.return_val = func->get_return_type();
p_list->push_back(mi);
}
}
@@ -277,16 +275,14 @@ MethodInfo GDScript::get_method_info(const StringName &p_method) const {
if (!E)
return MethodInfo();
+ GDScriptFunction *func = E->get();
MethodInfo mi;
mi.name = E->key();
- for (int i = 0; i < E->get()->get_argument_count(); i++) {
- PropertyInfo arg;
- arg.type = Variant::NIL; //variant
- arg.name = E->get()->get_argument_name(i);
- mi.arguments.push_back(arg);
+ for (int i = 0; i < func->get_argument_count(); i++) {
+ mi.arguments.push_back(func->get_argument_type(i));
}
- mi.return_val.name = "Variant";
+ mi.return_val = func->get_return_type();
return mi;
}
@@ -941,8 +937,12 @@ bool GDScriptInstance::set(const StringName &p_name, const Variant &p_value) {
if (err.error == Variant::CallError::CALL_OK) {
return true; //function exists, call was successful
}
- } else
+ } else {
+ if (!E->get().data_type.is_type(p_value)) {
+ return false; // Type mismatch
+ }
members[E->get().index] = p_value;
+ }
return true;
}
}
@@ -1735,7 +1735,9 @@ void GDScriptLanguage::get_reserved_words(List<String> *p_words) const {
"NAN",
"self",
"true",
+ "void",
// functions
+ "as",
"assert",
"breakpoint",
"class",
@@ -1824,8 +1826,40 @@ String GDScriptLanguage::get_global_class_name(const String &p_path, String *r_b
if (parser.get_parse_tree() && parser.get_parse_tree()->type == GDScriptParser::Node::TYPE_CLASS) {
const GDScriptParser::ClassNode *c = static_cast<const GDScriptParser::ClassNode *>(parser.get_parse_tree());
- if (r_base_type && c->extends_used && c->extends_class.size() == 1) {
- *r_base_type = c->extends_class[0]; //todo, should work much better
+ if (r_base_type) {
+ GDScriptParser::DataType base_type;
+ if (c->base_type.has_type) {
+ base_type = c->base_type;
+ while (base_type.has_type && base_type.kind != GDScriptParser::DataType::NATIVE) {
+ switch (base_type.kind) {
+ case GDScriptParser::DataType::CLASS: {
+ base_type = base_type.class_type->base_type;
+ } break;
+ case GDScriptParser::DataType::GDSCRIPT: {
+ Ref<GDScript> gds = base_type.script_type;
+ if (gds.is_valid()) {
+ base_type.kind = GDScriptParser::DataType::NATIVE;
+ base_type.native_type = gds->get_instance_base_type();
+ } else {
+ base_type = GDScriptParser::DataType();
+ }
+ } break;
+ default: {
+ base_type = GDScriptParser::DataType();
+ } break;
+ }
+ }
+ }
+ if (base_type.has_type) {
+ *r_base_type = base_type.native_type;
+ } else {
+ // Fallback
+ if (c->extends_used && c->extends_class.size() == 1) {
+ *r_base_type = c->extends_class[0];
+ } else if (!c->extends_used) {
+ *r_base_type = "Reference";
+ }
+ }
}
return c->name;
}
diff --git a/modules/gdscript/gdscript.h b/modules/gdscript/gdscript.h
index d1c57a0330..d5fe7a000b 100644
--- a/modules/gdscript/gdscript.h
+++ b/modules/gdscript/gdscript.h
@@ -64,6 +64,7 @@ class GDScript : public Script {
StringName setter;
StringName getter;
MultiplayerAPI::RPCMode rpc_mode;
+ GDScriptDataType data_type;
};
friend class GDScriptInstance;
@@ -145,8 +146,13 @@ public:
const Map<StringName, Ref<GDScript> > &get_subclasses() const { return subclasses; }
const Map<StringName, Variant> &get_constants() const { return constants; }
const Set<StringName> &get_members() const { return members; }
+ const GDScriptDataType &get_member_type(const StringName &p_member) const {
+ ERR_FAIL_COND_V(!member_indices.has(p_member), GDScriptDataType());
+ return member_indices[p_member].data_type;
+ }
const Map<StringName, GDScriptFunction *> &get_member_functions() const { return member_functions; }
const Ref<GDScriptNativeClass> &get_native() const { return native; }
+ const String &get_script_class_name() const { return name; }
virtual bool has_script_signal(const StringName &p_signal) const;
virtual void get_script_signal_list(List<MethodInfo> *r_signals) const;
@@ -391,7 +397,7 @@ public:
virtual Ref<Script> get_template(const String &p_class_name, const String &p_base_class_name) const;
virtual bool is_using_templates();
virtual void make_template(const String &p_class_name, const String &p_base_class_name, Ref<Script> &p_script);
- virtual bool validate(const String &p_script, int &r_line_error, int &r_col_error, String &r_test_error, const String &p_path = "", List<String> *r_functions = NULL) const;
+ virtual bool validate(const String &p_script, int &r_line_error, int &r_col_error, String &r_test_error, const String &p_path = "", List<String> *r_functions = NULL, Set<int> *r_safe_lines = NULL) const;
virtual Script *create_script() const;
virtual bool has_named_classes() const;
virtual bool supports_builtin_mode() const;
diff --git a/modules/gdscript/gdscript_compiler.cpp b/modules/gdscript/gdscript_compiler.cpp
index 70f3d704ae..a428ccd306 100644
--- a/modules/gdscript/gdscript_compiler.cpp
+++ b/modules/gdscript/gdscript_compiler.cpp
@@ -111,23 +111,50 @@ bool GDScriptCompiler::_create_binary_operator(CodeGen &codegen, const GDScriptP
return true;
}
-/*
-int GDScriptCompiler::_parse_subexpression(CodeGen& codegen,const GDScriptParser::Node *p_expression) {
-
+GDScriptDataType GDScriptCompiler::_gdtype_from_datatype(const GDScriptParser::DataType &p_datatype) const {
+ if (!p_datatype.has_type) {
+ return GDScriptDataType();
+ }
- int ret = _parse_expression(codegen,p_expression);
- if (ret<0)
- return ret;
+ GDScriptDataType result;
+ result.has_type = true;
- if (ret&(GDScriptFunction::ADDR_TYPE_STACK<<GDScriptFunction::ADDR_BITS)) {
- codegen.stack_level++;
- codegen.check_max_stack_level();
- //stack was used, keep value
+ switch (p_datatype.kind) {
+ case GDScriptParser::DataType::BUILTIN: {
+ result.kind = GDScriptDataType::BUILTIN;
+ result.builtin_type = p_datatype.builtin_type;
+ } break;
+ case GDScriptParser::DataType::NATIVE: {
+ result.kind = GDScriptDataType::NATIVE;
+ result.native_type = p_datatype.native_type;
+ } break;
+ case GDScriptParser::DataType::SCRIPT: {
+ result.kind = GDScriptDataType::SCRIPT;
+ result.script_type = p_datatype.script_type;
+ result.native_type = result.script_type->get_instance_base_type();
+ }
+ case GDScriptParser::DataType::GDSCRIPT: {
+ result.kind = GDScriptDataType::GDSCRIPT;
+ result.script_type = p_datatype.script_type;
+ result.native_type = result.script_type->get_instance_base_type();
+ } break;
+ case GDScriptParser::DataType::CLASS: {
+ result.kind = GDScriptDataType::GDSCRIPT;
+ if (p_datatype.class_type->name == StringName()) {
+ result.script_type = Ref<GDScript>(main_script);
+ } else {
+ result.script_type = class_map[p_datatype.class_type->name];
+ }
+ result.native_type = result.script_type->get_instance_base_type();
+ } break;
+ default: {
+ ERR_PRINT("Parser bug: converting unresolved type.");
+ result.has_type = false;
+ }
}
- return ret;
+ return result;
}
-*/
int GDScriptCompiler::_parse_assign_right_expression(CodeGen &codegen, const GDScriptParser::OperatorNode *p_expression, int p_stack_level) {
@@ -263,15 +290,6 @@ int GDScriptCompiler::_parse_expression(CodeGen &codegen, const GDScriptParser::
owner = owner->_owner;
}
- /*
- handled in constants now
- if (codegen.script->subclasses.has(identifier)) {
- //same with a subclass, make it a local constant.
- int idx = codegen.get_constant_pos(codegen.script->subclasses[identifier]);
- return idx|(GDScriptFunction::ADDR_TYPE_LOCAL_CONSTANT<<GDScriptFunction::ADDR_BITS); //make it a local constant (faster access)
-
- }*/
-
if (GDScriptLanguage::get_singleton()->get_global_map().has(identifier)) {
int idx = GDScriptLanguage::get_singleton()->get_global_map()[identifier];
@@ -430,6 +448,83 @@ int GDScriptCompiler::_parse_expression(CodeGen &codegen, const GDScriptParser::
return dst_addr;
} break;
+ case GDScriptParser::Node::TYPE_CAST: {
+ const GDScriptParser::CastNode *cn = static_cast<const GDScriptParser::CastNode *>(p_expression);
+
+ int slevel = p_stack_level;
+ int src_addr = _parse_expression(codegen, cn->source_node, slevel);
+ if (src_addr < 0)
+ return src_addr;
+ if (src_addr & GDScriptFunction::ADDR_TYPE_STACK << GDScriptFunction::ADDR_BITS) {
+ slevel++;
+ codegen.alloc_stack(slevel);
+ }
+
+ switch (cn->cast_type.kind) {
+ case GDScriptParser::DataType::BUILTIN: {
+ codegen.opcodes.push_back(GDScriptFunction::OPCODE_CAST_TO_BUILTIN);
+ codegen.opcodes.push_back(cn->cast_type.builtin_type);
+ } break;
+ case GDScriptParser::DataType::NATIVE: {
+ int class_idx;
+ if (GDScriptLanguage::get_singleton()->get_global_map().has(cn->cast_type.native_type)) {
+
+ class_idx = GDScriptLanguage::get_singleton()->get_global_map()[cn->cast_type.native_type];
+ class_idx |= (GDScriptFunction::ADDR_TYPE_GLOBAL << GDScriptFunction::ADDR_BITS); //argument (stack root)
+ } else {
+ _set_error("Invalid native class type '" + String(cn->cast_type.native_type) + "'.", cn);
+ return -1;
+ }
+ codegen.opcodes.push_back(GDScriptFunction::OPCODE_CAST_TO_NATIVE); // perform operator
+ codegen.opcodes.push_back(class_idx); // variable type
+ } break;
+ case GDScriptParser::DataType::CLASS: {
+
+ Variant script;
+ int idx = -1;
+ if (cn->cast_type.class_type->name == StringName()) {
+ script = codegen.script;
+ } else {
+ StringName name = cn->cast_type.class_type->name;
+ if (class_map[name] == codegen.script->subclasses[name]) {
+ idx = codegen.get_name_map_pos(name);
+ idx |= GDScriptFunction::ADDR_TYPE_CLASS_CONSTANT << GDScriptFunction::ADDR_BITS;
+ } else {
+ script = class_map[name];
+ }
+ }
+
+ if (idx < 0) {
+ idx = codegen.get_constant_pos(script);
+ idx |= GDScriptFunction::ADDR_TYPE_LOCAL_CONSTANT << GDScriptFunction::ADDR_BITS; //make it a local constant (faster access)
+ }
+
+ codegen.opcodes.push_back(GDScriptFunction::OPCODE_CAST_TO_SCRIPT); // perform operator
+ codegen.opcodes.push_back(idx); // variable type
+ } break;
+ case GDScriptParser::DataType::SCRIPT:
+ case GDScriptParser::DataType::GDSCRIPT: {
+
+ Variant script = cn->cast_type.script_type;
+ int idx = codegen.get_constant_pos(script);
+ idx |= GDScriptFunction::ADDR_TYPE_LOCAL_CONSTANT << GDScriptFunction::ADDR_BITS; //make it a local constant (faster access)
+
+ codegen.opcodes.push_back(GDScriptFunction::OPCODE_CAST_TO_SCRIPT); // perform operator
+ codegen.opcodes.push_back(idx); // variable type
+ } break;
+ default: {
+ _set_error("Parser bug: unresolved data type.", cn);
+ return -1;
+ }
+ }
+
+ codegen.opcodes.push_back(src_addr); // source adddress
+ int dst_addr = (p_stack_level) | (GDScriptFunction::ADDR_TYPE_STACK << GDScriptFunction::ADDR_BITS);
+ codegen.opcodes.push_back(dst_addr); // append the stack level as destination address of the opcode
+ codegen.alloc_stack(p_stack_level);
+ return dst_addr;
+
+ } break;
case GDScriptParser::Node::TYPE_OPERATOR: {
//hell breaks loose
@@ -782,14 +877,6 @@ int GDScriptCompiler::_parse_expression(CodeGen &codegen, const GDScriptParser::
case GDScriptParser::OperatorNode::OP_BIT_INVERT: {
if (!_create_unary_operator(codegen, on, Variant::OP_BIT_NEGATE, p_stack_level)) return -1;
} break;
- case GDScriptParser::OperatorNode::OP_PREINC: {
- } break; //?
- case GDScriptParser::OperatorNode::OP_PREDEC: {
- } break;
- case GDScriptParser::OperatorNode::OP_INC: {
- } break;
- case GDScriptParser::OperatorNode::OP_DEC: {
- } break;
//binary operators (in precedence order)
case GDScriptParser::OperatorNode::OP_IN: {
if (!_create_binary_operator(codegen, on, Variant::OP_IN, p_stack_level)) return -1;
@@ -1064,12 +1151,87 @@ int GDScriptCompiler::_parse_expression(CodeGen &codegen, const GDScriptParser::
if (src_address_b < 0)
return -1;
- codegen.opcodes.push_back(GDScriptFunction::OPCODE_ASSIGN); // perform operator
- codegen.opcodes.push_back(dst_address_a); // argument 1
- codegen.opcodes.push_back(src_address_b); // argument 2 (unary only takes one parameter)
+ GDScriptParser::DataType assign_type = on->arguments[0]->get_datatype();
+
+ if (assign_type.has_type && !on->arguments[1]->get_datatype().has_type) {
+ // Typed assignment
+ switch (assign_type.kind) {
+ case GDScriptParser::DataType::BUILTIN: {
+ codegen.opcodes.push_back(GDScriptFunction::OPCODE_ASSIGN_TYPED_BUILTIN); // perform operator
+ codegen.opcodes.push_back(assign_type.builtin_type); // variable type
+ codegen.opcodes.push_back(dst_address_a); // argument 1
+ codegen.opcodes.push_back(src_address_b); // argument 2
+ } break;
+ case GDScriptParser::DataType::NATIVE: {
+ int class_idx;
+ if (GDScriptLanguage::get_singleton()->get_global_map().has(assign_type.native_type)) {
+
+ class_idx = GDScriptLanguage::get_singleton()->get_global_map()[assign_type.native_type];
+ class_idx |= (GDScriptFunction::ADDR_TYPE_GLOBAL << GDScriptFunction::ADDR_BITS); //argument (stack root)
+ } else {
+ _set_error("Invalid native class type '" + String(assign_type.native_type) + "'.", on->arguments[0]);
+ return -1;
+ }
+ codegen.opcodes.push_back(GDScriptFunction::OPCODE_ASSIGN_TYPED_NATIVE); // perform operator
+ codegen.opcodes.push_back(class_idx); // variable type
+ codegen.opcodes.push_back(dst_address_a); // argument 1
+ codegen.opcodes.push_back(src_address_b); // argument 2
+ } break;
+ case GDScriptParser::DataType::CLASS: {
+
+ Variant script;
+ int idx = -1;
+ if (assign_type.class_type->name == StringName()) {
+ script = codegen.script;
+ } else {
+ StringName name = assign_type.class_type->name;
+ if (class_map[name] == codegen.script->subclasses[name]) {
+ idx = codegen.get_name_map_pos(name);
+ idx |= GDScriptFunction::ADDR_TYPE_CLASS_CONSTANT << GDScriptFunction::ADDR_BITS;
+ } else {
+ script = class_map[name];
+ }
+ }
+
+ if (idx < 0) {
+ idx = codegen.get_constant_pos(script);
+ idx |= GDScriptFunction::ADDR_TYPE_LOCAL_CONSTANT << GDScriptFunction::ADDR_BITS; //make it a local constant (faster access)
+ }
+
+ codegen.opcodes.push_back(GDScriptFunction::OPCODE_ASSIGN_TYPED_SCRIPT); // perform operator
+ codegen.opcodes.push_back(idx); // variable type
+ codegen.opcodes.push_back(dst_address_a); // argument 1
+ codegen.opcodes.push_back(src_address_b); // argument 2
+ } break;
+ case GDScriptParser::DataType::SCRIPT:
+ case GDScriptParser::DataType::GDSCRIPT: {
+
+ Variant script = assign_type.script_type;
+ int idx = codegen.get_constant_pos(script);
+ idx |= GDScriptFunction::ADDR_TYPE_LOCAL_CONSTANT << GDScriptFunction::ADDR_BITS; //make it a local constant (faster access)
+
+ codegen.opcodes.push_back(GDScriptFunction::OPCODE_ASSIGN_TYPED_SCRIPT); // perform operator
+ codegen.opcodes.push_back(idx); // variable type
+ codegen.opcodes.push_back(dst_address_a); // argument 1
+ codegen.opcodes.push_back(src_address_b); // argument 2
+ } break;
+ default: {
+ ERR_PRINT("Compiler bug: unresolved assign.");
+
+ // Shouldn't get here, but fail-safe to a regular assignment
+ codegen.opcodes.push_back(GDScriptFunction::OPCODE_ASSIGN); // perform operator
+ codegen.opcodes.push_back(dst_address_a); // argument 1
+ codegen.opcodes.push_back(src_address_b); // argument 2 (unary only takes one parameter)
+ }
+ }
+ } else {
+ // Either untyped assignment or already type-checked by the parser
+ codegen.opcodes.push_back(GDScriptFunction::OPCODE_ASSIGN); // perform operator
+ codegen.opcodes.push_back(dst_address_a); // argument 1
+ codegen.opcodes.push_back(src_address_b); // argument 2 (unary only takes one parameter)
+ }
return dst_address_a; //if anything, returns wathever was assigned or correct stack position
}
-
} break;
case GDScriptParser::OperatorNode::OP_IS: {
@@ -1525,6 +1687,18 @@ Error GDScriptCompiler::_parse_function(GDScript *p_script, const GDScriptParser
if (p_func) {
gdfunc->_static = p_func->_static;
gdfunc->rpc_mode = p_func->rpc_mode;
+ gdfunc->argument_types.resize(p_func->argument_types.size());
+ for (int i = 0; i < p_func->argument_types.size(); i++) {
+ gdfunc->argument_types[i] = _gdtype_from_datatype(p_func->argument_types[i]);
+ }
+ gdfunc->return_type = _gdtype_from_datatype(p_func->return_type);
+ } else {
+ gdfunc->_static = false;
+ gdfunc->rpc_mode = MultiplayerAPI::RPC_MODE_DISABLED;
+ gdfunc->return_type = GDScriptDataType();
+ gdfunc->return_type.has_type = true;
+ gdfunc->return_type.kind = GDScriptDataType::BUILTIN;
+ gdfunc->return_type.builtin_type = Variant::NIL;
}
#ifdef TOOLS_ENABLED
@@ -1653,12 +1827,23 @@ Error GDScriptCompiler::_parse_function(GDScript *p_script, const GDScriptParser
return OK;
}
-Error GDScriptCompiler::_parse_class(GDScript *p_script, GDScript *p_owner, const GDScriptParser::ClassNode *p_class, bool p_keep_state) {
-
- Map<StringName, Ref<GDScript> > old_subclasses;
+Error GDScriptCompiler::_parse_class_level(GDScript *p_script, GDScript *p_owner, const GDScriptParser::ClassNode *p_class, bool p_keep_state) {
- if (p_keep_state) {
- old_subclasses = p_script->subclasses;
+ if (p_class->owner && p_class->owner->owner) {
+ // Owner is not root
+ StringName owner_name = p_class->owner->name;
+ if (!parsed_classes.has(owner_name)) {
+ if (parsing_classes.has(owner_name)) {
+ _set_error("Cyclic class reference for '" + String(owner_name) + "'.", p_class);
+ return ERR_PARSE_ERROR;
+ }
+ parsing_classes.insert(owner_name);
+ Error err = _parse_class_level(class_map[owner_name].ptr(), class_map[owner_name]->_owner, p_class->owner, p_keep_state);
+ if (err) {
+ return err;
+ }
+ parsing_classes.erase(owner_name);
+ }
}
p_script->native = Ref<GDScriptNativeClass>();
@@ -1682,236 +1867,100 @@ Error GDScriptCompiler::_parse_class(GDScript *p_script, GDScript *p_owner, cons
Ref<GDScriptNativeClass> native;
- if (p_class->extends_used) {
- //do inheritance
- String path = p_class->extends_file;
-
- Ref<GDScript> script;
-
- if (path != "") {
- //path (and optionally subclasses)
-
- if (path.is_rel_path()) {
-
- String base;
-
- if (p_owner) {
- GDScript *current_class = p_owner;
- while (current_class != NULL) {
- base = current_class->get_path();
- if (base == "")
- current_class = current_class->_owner;
- else
- break;
- }
- } else {
- base = p_script->get_path();
- }
-
- if (base == "" || base.is_rel_path()) {
- _set_error("Could not resolve relative path for parent class: " + path, p_class);
- return ERR_FILE_NOT_FOUND;
- }
- path = base.get_base_dir().plus_file(path).simplify_path();
- }
- script = ResourceLoader::load(path);
- if (script.is_null()) {
- _set_error("Could not load base class: " + path, p_class);
- return ERR_FILE_NOT_FOUND;
- }
- if (!script->valid) {
-
- _set_error("Script not fully loaded (cyclic preload?): " + path, p_class);
- return ERR_BUSY;
- }
- //print_line("EXTENDS PATH: "+path+" script is "+itos(script.is_valid())+" indices is "+itos(script->member_indices.size())+" valid? "+itos(script->valid));
-
- if (p_class->extends_class.size()) {
-
- for (int i = 0; i < p_class->extends_class.size(); i++) {
-
- String sub = p_class->extends_class[i];
- if (script->subclasses.has(sub)) {
-
- Ref<Script> subclass = script->subclasses[sub]; //avoid reference from disappearing
- script = subclass;
- } else {
-
- _set_error("Could not find subclass: " + sub, p_class);
- return ERR_FILE_NOT_FOUND;
- }
- }
- }
-
- } else {
-
- ERR_FAIL_COND_V(p_class->extends_class.size() == 0, ERR_BUG);
- //look around for the subclasses
-
- String base = p_class->extends_class[0];
- GDScript *p = p_owner;
- Ref<GDScript> base_class;
-
- while (p) {
-
- if (p->subclasses.has(base)) {
-
- base_class = p->subclasses[base];
- break;
- }
-
- if (p->constants.has(base)) {
-
- base_class = p->constants[base];
- if (base_class.is_null()) {
- _set_error("Constant is not a class: " + base, p_class);
- return ERR_SCRIPT_FAILED;
- }
- break;
- }
-
- p = p->_owner;
- }
-
- if (base_class.is_valid()) {
-
- String ident = base;
-
- for (int i = 1; i < p_class->extends_class.size(); i++) {
-
- String subclass = p_class->extends_class[i];
-
- ident += ("." + subclass);
-
- if (base_class->subclasses.has(subclass)) {
-
- base_class = base_class->subclasses[subclass];
- } else if (base_class->constants.has(subclass)) {
-
- Ref<GDScript> new_base_class = base_class->constants[subclass];
- if (new_base_class.is_null()) {
- _set_error("Constant is not a class: " + ident, p_class);
- return ERR_SCRIPT_FAILED;
- }
- base_class = new_base_class;
- } else {
-
- _set_error("Could not find subclass: " + ident, p_class);
- return ERR_FILE_NOT_FOUND;
- }
- }
-
- script = base_class;
-
- } else {
-
- if (p_class->extends_class.size() > 1) {
-
- _set_error("Invalid inheritance (unknown class+subclasses)", p_class);
- return ERR_FILE_NOT_FOUND;
+ // Inheritance
+ switch (p_class->base_type.kind) {
+ case GDScriptParser::DataType::CLASS: {
+ StringName base_name = p_class->base_type.class_type->name;
+ // Make sure dependency is parsed first
+ if (!parsed_classes.has(base_name)) {
+ if (parsing_classes.has(base_name)) {
+ _set_error("Cyclic class reference for '" + String(base_name) + "'.", p_class);
+ return ERR_PARSE_ERROR;
}
- //if not found, try engine classes
- if (!GDScriptLanguage::get_singleton()->get_global_map().has(base)) {
-
- _set_error("Unknown class: '" + base + "'", p_class);
- return ERR_FILE_NOT_FOUND;
- }
-
- int base_idx = GDScriptLanguage::get_singleton()->get_global_map()[base];
- native = GDScriptLanguage::get_singleton()->get_global_array()[base_idx];
- if (!native.is_valid()) {
-
- _set_error("Global not a class: '" + base + "'", p_class);
-
- return ERR_FILE_NOT_FOUND;
+ parsing_classes.insert(base_name);
+ Error err = _parse_class_level(class_map[base_name].ptr(), class_map[base_name]->_owner, p_class->base_type.class_type, p_keep_state);
+ if (err) {
+ return err;
}
+ parsing_classes.erase(base_name);
}
- }
-
- if (script.is_valid()) {
-
- p_script->base = script;
+ Ref<GDScript> base = class_map[base_name];
+ p_script->base = base;
p_script->_base = p_script->base.ptr();
- p_script->member_indices = script->member_indices;
-
- } else if (native.is_valid()) {
-
+ p_script->member_indices = base->member_indices;
+ } break;
+ case GDScriptParser::DataType::GDSCRIPT: {
+ Ref<GDScript> base = p_class->base_type.script_type;
+ p_script->base = base;
+ p_script->_base = p_script->base.ptr();
+ p_script->member_indices = base->member_indices;
+ } break;
+ case GDScriptParser::DataType::NATIVE: {
+ int native_idx = GDScriptLanguage::get_singleton()->get_global_map()[p_class->base_type.native_type];
+ native = GDScriptLanguage::get_singleton()->get_global_array()[native_idx];
+ ERR_FAIL_COND_V(native.is_null(), ERR_BUG);
p_script->native = native;
- } else {
-
- _set_error("Could not determine inheritance", p_class);
- return ERR_FILE_NOT_FOUND;
- }
-
- } else {
- // without extends, implicitly extend Reference
- int native_idx = GDScriptLanguage::get_singleton()->get_global_map()["Reference"];
- native = GDScriptLanguage::get_singleton()->get_global_array()[native_idx];
- ERR_FAIL_COND_V(native.is_null(), ERR_BUG);
- p_script->native = native;
+ } break;
+ default: {
+ _set_error("Parser bug: invalid inheritance.", p_class);
+ return ERR_BUG;
+ } break;
}
- //print_line("Script: "+p_script->get_path()+" indices: "+itos(p_script->member_indices.size()));
-
for (int i = 0; i < p_class->variables.size(); i++) {
StringName name = p_class->variables[i].identifier;
- if (p_script->member_indices.has(name)) {
- _set_error("Member '" + name + "' already exists (in current or parent class)", p_class);
- return ERR_ALREADY_EXISTS;
- }
- if (_is_class_member_property(p_script, name)) {
- _set_error("Member '" + name + "' already exists as a class property.", p_class);
- return ERR_ALREADY_EXISTS;
- }
- if (p_class->variables[i]._export.type != Variant::NIL) {
+ GDScript::MemberInfo minfo;
+ minfo.index = p_script->member_indices.size();
+ minfo.setter = p_class->variables[i].setter;
+ minfo.getter = p_class->variables[i].getter;
+ minfo.rpc_mode = p_class->variables[i].rpc_mode;
+ minfo.data_type = _gdtype_from_datatype(p_class->variables[i].data_type);
- p_script->member_info[name] = p_class->variables[i]._export;
+ PropertyInfo prop_info = minfo.data_type;
+ prop_info.name = name;
+ PropertyInfo export_info = p_class->variables[i]._export;
+
+ if (export_info.type != Variant::NIL) {
+
+ if (!minfo.data_type.has_type) {
+ prop_info.type = export_info.type;
+ prop_info.class_name = export_info.class_name;
+ }
+ prop_info.hint = export_info.hint;
+ prop_info.hint_string = export_info.hint_string;
+ prop_info.usage = export_info.usage;
#ifdef TOOLS_ENABLED
if (p_class->variables[i].default_value.get_type() != Variant::NIL) {
-
p_script->member_default_values[name] = p_class->variables[i].default_value;
}
#endif
} else {
-
- p_script->member_info[name] = PropertyInfo(Variant::NIL, name, PROPERTY_HINT_NONE, "", PROPERTY_USAGE_SCRIPT_VARIABLE);
+ prop_info.usage = PROPERTY_USAGE_SCRIPT_VARIABLE;
}
- //int new_idx = p_script->member_indices.size();
- GDScript::MemberInfo minfo;
- minfo.index = p_script->member_indices.size();
- minfo.setter = p_class->variables[i].setter;
- minfo.getter = p_class->variables[i].getter;
- minfo.rpc_mode = p_class->variables[i].rpc_mode;
-
+ p_script->member_info[name] = prop_info;
p_script->member_indices[name] = minfo;
p_script->members.insert(name);
#ifdef TOOLS_ENABLED
-
p_script->member_lines[name] = p_class->variables[i].line;
#endif
}
- for (int i = 0; i < p_class->constant_expressions.size(); i++) {
+ for (Map<StringName, GDScriptParser::ClassNode::Constant>::Element *E = p_class->constant_expressions.front(); E; E = E->next()) {
- StringName name = p_class->constant_expressions[i].identifier;
- ERR_CONTINUE(p_class->constant_expressions[i].expression->type != GDScriptParser::Node::TYPE_CONSTANT);
+ StringName name = E->key();
- if (_is_class_member_property(p_script, name)) {
- _set_error("Member '" + name + "' already exists as a class property.", p_class);
- return ERR_ALREADY_EXISTS;
- }
+ ERR_CONTINUE(E->get().expression->type != GDScriptParser::Node::TYPE_CONSTANT);
- GDScriptParser::ConstantNode *constant = static_cast<GDScriptParser::ConstantNode *>(p_class->constant_expressions[i].expression);
+ GDScriptParser::ConstantNode *constant = static_cast<GDScriptParser::ConstantNode *>(E->get().expression);
p_script->constants.insert(name, constant->value);
-//p_script->constants[constant->value].make_const();
#ifdef TOOLS_ENABLED
- p_script->member_lines[name] = p_class->constant_expressions[i].expression->line;
+ p_script->member_lines[name] = E->get().expression->line;
#endif
}
@@ -1944,23 +1993,27 @@ Error GDScriptCompiler::_parse_class(GDScript *p_script, GDScript *p_owner, cons
p_script->_signals[name] = p_class->_signals[i].arguments;
}
+
+ if (p_class->name != StringName()) {
+ parsed_classes.insert(p_class->name);
+ }
+
//parse sub-classes
for (int i = 0; i < p_class->subclasses.size(); i++) {
StringName name = p_class->subclasses[i]->name;
- Ref<GDScript> subclass;
+ Ref<GDScript> subclass = class_map[name];
- if (old_subclasses.has(name)) {
- subclass = old_subclasses[name];
- } else {
- subclass.instance();
+ // Subclass might still be parsing, just skip it
+ if (!parsed_classes.has(name) && !parsing_classes.has(name)) {
+ parsing_classes.insert(name);
+ Error err = _parse_class_level(subclass.ptr(), p_script, p_class->subclasses[i], p_keep_state);
+ if (err)
+ return err;
+ parsing_classes.erase(name);
}
- Error err = _parse_class(subclass.ptr(), p_script, p_class->subclasses[i], p_keep_state);
- if (err)
- return err;
-
#ifdef TOOLS_ENABLED
p_script->member_lines[name] = p_class->subclasses[i]->line;
@@ -1970,6 +2023,10 @@ Error GDScriptCompiler::_parse_class(GDScript *p_script, GDScript *p_owner, cons
p_script->subclasses.insert(name, subclass);
}
+ return OK;
+}
+
+Error GDScriptCompiler::_parse_class_blocks(GDScript *p_script, const GDScriptParser::ClassNode *p_class, bool p_keep_state) {
//parse methods
bool has_initializer = false;
@@ -2010,44 +2067,6 @@ Error GDScriptCompiler::_parse_class(GDScript *p_script, GDScript *p_owner, cons
}
#ifdef DEBUG_ENABLED
- //validate setters/getters if debug is enabled
- for (int i = 0; i < p_class->variables.size(); i++) {
-
- if (p_class->variables[i].setter) {
- const Map<StringName, GDScriptFunction *>::Element *E = p_script->get_member_functions().find(p_class->variables[i].setter);
- if (!E) {
- _set_error("Setter function '" + String(p_class->variables[i].setter) + "' not found in class.", NULL);
- err_line = p_class->variables[i].line;
- err_column = 0;
- return ERR_PARSE_ERROR;
- }
-
- if (E->get()->is_static()) {
-
- _set_error("Setter function '" + String(p_class->variables[i].setter) + "' is static.", NULL);
- err_line = p_class->variables[i].line;
- err_column = 0;
- return ERR_PARSE_ERROR;
- }
- }
- if (p_class->variables[i].getter) {
- const Map<StringName, GDScriptFunction *>::Element *E = p_script->get_member_functions().find(p_class->variables[i].getter);
- if (!E) {
- _set_error("Getter function '" + String(p_class->variables[i].getter) + "' not found in class.", NULL);
- err_line = p_class->variables[i].line;
- err_column = 0;
- return ERR_PARSE_ERROR;
- }
-
- if (E->get()->is_static()) {
-
- _set_error("Getter function '" + String(p_class->variables[i].getter) + "' is static.", NULL);
- err_line = p_class->variables[i].line;
- err_column = 0;
- return ERR_PARSE_ERROR;
- }
- }
- }
//validate instances if keeping state
@@ -2100,22 +2119,67 @@ Error GDScriptCompiler::_parse_class(GDScript *p_script, GDScript *p_owner, cons
}
#endif
+ for (int i = 0; i < p_class->subclasses.size(); i++) {
+ StringName name = p_class->subclasses[i]->name;
+ Ref<GDScript> subclass = class_map[name];
+
+ Error err = _parse_class_blocks(subclass.ptr(), p_class->subclasses[i], p_keep_state);
+ if (err) {
+ return err;
+ }
+ }
+
p_script->valid = true;
return OK;
}
+void GDScriptCompiler::_make_scripts(const GDScript *p_script, const GDScriptParser::ClassNode *p_class, bool p_keep_state) {
+
+ Map<StringName, Ref<GDScript> > old_subclasses;
+
+ if (p_keep_state) {
+ old_subclasses = p_script->subclasses;
+ }
+
+ for (int i = 0; i < p_class->subclasses.size(); i++) {
+ StringName name = p_class->subclasses[i]->name;
+
+ Ref<GDScript> subclass;
+
+ if (old_subclasses.has(name)) {
+ subclass = old_subclasses[name];
+ } else {
+ subclass.instance();
+ }
+
+ subclass->_owner = const_cast<GDScript *>(p_script);
+ class_map.insert(name, subclass);
+
+ _make_scripts(subclass.ptr(), p_class->subclasses[i], p_keep_state);
+ }
+}
+
Error GDScriptCompiler::compile(const GDScriptParser *p_parser, GDScript *p_script, bool p_keep_state) {
err_line = -1;
err_column = -1;
error = "";
parser = p_parser;
+ main_script = p_script;
const GDScriptParser::Node *root = parser->get_parse_tree();
ERR_FAIL_COND_V(root->type != GDScriptParser::Node::TYPE_CLASS, ERR_INVALID_DATA);
source = p_script->get_path();
- Error err = _parse_class(p_script, NULL, static_cast<const GDScriptParser::ClassNode *>(root), p_keep_state);
+ // Create scripts for subclasses beforehand so they can be referenced
+ _make_scripts(p_script, static_cast<const GDScriptParser::ClassNode *>(root), p_keep_state);
+
+ Error err = _parse_class_level(p_script, NULL, static_cast<const GDScriptParser::ClassNode *>(root), p_keep_state);
+
+ if (err)
+ return err;
+
+ err = _parse_class_blocks(p_script, static_cast<const GDScriptParser::ClassNode *>(root), p_keep_state);
if (err)
return err;
diff --git a/modules/gdscript/gdscript_compiler.h b/modules/gdscript/gdscript_compiler.h
index 237b0de9e7..55f5e2b48e 100644
--- a/modules/gdscript/gdscript_compiler.h
+++ b/modules/gdscript/gdscript_compiler.h
@@ -31,12 +31,17 @@
#ifndef GDSCRIPT_COMPILER_H
#define GDSCRIPT_COMPILER_H
+#include "core/set.h"
#include "gdscript.h"
#include "gdscript_parser.h"
class GDScriptCompiler {
const GDScriptParser *parser;
+ Map<StringName, Ref<GDScript> > class_map;
+ Set<StringName> parsed_classes;
+ Set<StringName> parsing_classes;
+ GDScript *main_script;
struct CodeGen {
GDScript *script;
@@ -138,11 +143,15 @@ class GDScriptCompiler {
bool _create_unary_operator(CodeGen &codegen, const GDScriptParser::OperatorNode *on, Variant::Operator op, int p_stack_level);
bool _create_binary_operator(CodeGen &codegen, const GDScriptParser::OperatorNode *on, Variant::Operator op, int p_stack_level, bool p_initializer = false);
+ GDScriptDataType _gdtype_from_datatype(const GDScriptParser::DataType &p_datatype) const;
+
int _parse_assign_right_expression(CodeGen &codegen, const GDScriptParser::OperatorNode *p_expression, int p_stack_level);
int _parse_expression(CodeGen &codegen, const GDScriptParser::Node *p_expression, int p_stack_level, bool p_root = false, bool p_initializer = false);
Error _parse_block(CodeGen &codegen, const GDScriptParser::BlockNode *p_block, int p_stack_level = 0, int p_break_addr = -1, int p_continue_addr = -1);
Error _parse_function(GDScript *p_script, const GDScriptParser::ClassNode *p_class, const GDScriptParser::FunctionNode *p_func, bool p_for_ready = false);
- Error _parse_class(GDScript *p_script, GDScript *p_owner, const GDScriptParser::ClassNode *p_class, bool p_keep_state);
+ Error _parse_class_level(GDScript *p_script, GDScript *p_owner, const GDScriptParser::ClassNode *p_class, bool p_keep_state);
+ Error _parse_class_blocks(GDScript *p_script, const GDScriptParser::ClassNode *p_class, bool p_keep_state);
+ void _make_scripts(const GDScript *p_script, const GDScriptParser::ClassNode *p_class, bool p_keep_state);
int err_line;
int err_column;
StringName source;
diff --git a/modules/gdscript/gdscript_editor.cpp b/modules/gdscript/gdscript_editor.cpp
index c8d2d2e3e8..2e4a4c40dd 100644
--- a/modules/gdscript/gdscript_editor.cpp
+++ b/modules/gdscript/gdscript_editor.cpp
@@ -37,6 +37,7 @@
#include "os/file_access.h"
#ifdef TOOLS_ENABLED
+#include "core/reference.h"
#include "editor/editor_file_system.h"
#include "editor/editor_settings.h"
#include "engine.h"
@@ -53,21 +54,45 @@ void GDScriptLanguage::get_string_delimiters(List<String> *p_delimiters) const {
p_delimiters->push_back("' '");
}
Ref<Script> GDScriptLanguage::get_template(const String &p_class_name, const String &p_base_class_name) const {
+#ifdef TOOLS_ENABLED
+ bool th = EDITOR_DEF("text_editor/completion/add_type_hints", false);
+#else
+ bool th = false;
+#endif
String _template = "extends %BASE%\n"
"\n"
"# Declare member variables here. Examples:\n"
- "# var a = 2\n"
- "# var b = \"text\"\n"
+ "# var a %INT_TYPE%= 2\n"
+ "# var b %STRING_TYPE%= \"text\"\n"
"\n"
"# Called when the node enters the scene tree for the first time.\n"
- "func _ready():\n"
+ "func _ready()%VOID_RETURN%:\n"
"%TS%pass # Replace with function body.\n"
"\n"
"# Called every frame. 'delta' is the elapsed time since the previous frame.\n"
- "#func _process(delta):\n"
+ "#func _process(delta%FLOAT_TYPE%)%VOID_RETURN%:\n"
"#%TS%pass\n";
+#ifdef TOOLS_ENABLED
+ if (EDITOR_DEF("text_editor/completion/add_type_hints", false)) {
+ _template = _template.replace("%INT_TYPE%", ": int ");
+ _template = _template.replace("%STRING_TYPE%", ": String ");
+ _template = _template.replace("%FLOAT_TYPE%", " : float");
+ _template = _template.replace("%VOID_RETURN%", " -> void");
+ } else {
+ _template = _template.replace("%INT_TYPE%", "");
+ _template = _template.replace("%STRING_TYPE%", "");
+ _template = _template.replace("%FLOAT_TYPE%", "");
+ _template = _template.replace("%VOID_RETURN%", "");
+ }
+#else
+ _template = _template.replace("%INT_TYPE%", "");
+ _template = _template.replace("%STRING_TYPE%", "");
+ _template = _template.replace("%FLOAT_TYPE%", "");
+ _template = _template.replace("%VOID_RETURN%", "");
+#endif
+
_template = _template.replace("%BASE%", p_base_class_name);
_template = _template.replace("%TS%", _get_indentation());
@@ -91,11 +116,11 @@ void GDScriptLanguage::make_template(const String &p_class_name, const String &p
p_script->set_source_code(src);
}
-bool GDScriptLanguage::validate(const String &p_script, int &r_line_error, int &r_col_error, String &r_test_error, const String &p_path, List<String> *r_functions) const {
+bool GDScriptLanguage::validate(const String &p_script, int &r_line_error, int &r_col_error, String &r_test_error, const String &p_path, List<String> *r_functions, Set<int> *r_safe_lines) const {
GDScriptParser parser;
- Error err = parser.parse(p_script, p_path.get_base_dir(), true, p_path);
+ Error err = parser.parse(p_script, p_path.get_base_dir(), true, p_path, false, r_safe_lines);
if (err) {
r_line_error = parser.get_error_line();
r_col_error = parser.get_error_column();
@@ -415,63 +440,34 @@ void GDScriptLanguage::get_public_constants(List<Pair<String, Variant> > *p_cons
String GDScriptLanguage::make_function(const String &p_class, const String &p_name, const PoolStringArray &p_args) const {
+#ifdef TOOLS_ENABLED
+ bool th = EditorSettings::get_singleton()->get_setting("text_editor/completion/add_type_hints");
+#else
+ bool th = false;
+#endif
+
String s = "func " + p_name + "(";
if (p_args.size()) {
for (int i = 0; i < p_args.size(); i++) {
if (i > 0)
s += ", ";
s += p_args[i].get_slice(":", 0);
+ if (th) {
+ String type = p_args[i].get_slice(":", 1);
+ if (!type.empty() && type != "var") {
+ s += " : " + type;
+ }
+ }
}
}
- s += "):\n" + _get_indentation() + "pass # Replace with function body.\n";
+ s += String(")") + (th ? " -> void" : "") + ":\n" + _get_indentation() + "pass # Replace with function body.\n";
return s;
}
-#if defined(DEBUG_METHODS_ENABLED) && defined(TOOLS_ENABLED)
-
-struct GDScriptCompletionIdentifier {
-
- String enumeration;
- StringName obj_type;
- Ref<GDScript> script;
- Variant::Type type;
- Variant value; //im case there is a value, also return it
+//////// COMPLETION //////////
- GDScriptCompletionIdentifier() :
- type(Variant::NIL) {}
-};
-
-static GDScriptCompletionIdentifier _get_type_from_variant(const Variant &p_variant, bool p_allow_gdnative_class = false) {
-
- GDScriptCompletionIdentifier t;
- t.type = p_variant.get_type();
- t.value = p_variant;
- if (p_variant.get_type() == Variant::OBJECT) {
- Object *obj = p_variant;
- if (obj) {
-
- if (p_allow_gdnative_class && Object::cast_to<GDScriptNativeClass>(obj)) {
- t.obj_type = Object::cast_to<GDScriptNativeClass>(obj)->get_name();
- t.value = Variant();
- } else {
-
- t.obj_type = obj->get_class();
- }
- }
- }
- return t;
-}
-
-static GDScriptCompletionIdentifier _get_type_from_pinfo(const PropertyInfo &p_info) {
-
- GDScriptCompletionIdentifier t;
- t.type = p_info.type;
- if (p_info.hint == PROPERTY_HINT_RESOURCE_TYPE) {
- t.obj_type = p_info.hint_string;
- }
- return t;
-}
+#if defined(DEBUG_METHODS_ENABLED) && defined(TOOLS_ENABLED)
struct GDScriptCompletionContext {
@@ -480,1043 +476,1612 @@ struct GDScriptCompletionContext {
const GDScriptParser::BlockNode *block;
Object *base;
String base_path;
-};
-
-static Ref<Reference> _get_parent_class(GDScriptCompletionContext &context) {
-
- if (context._class->extends_used) {
- //do inheritance
- String path = context._class->extends_file;
-
- Ref<GDScript> script;
- Ref<GDScriptNativeClass> native;
+ int line;
- if (path != "") {
- //path (and optionally subclasses)
-
- if (path.is_rel_path()) {
-
- path = context.base_path.plus_file(path);
- }
-
- if (ScriptCodeCompletionCache::get_singleton())
- script = ScriptCodeCompletionCache::get_singleton()->get_cached_resource(path);
- else
- script = ResourceLoader::load(path);
+ GDScriptCompletionContext() :
+ _class(NULL),
+ function(NULL),
+ block(NULL),
+ base(NULL) {}
+};
- if (script.is_null()) {
- return REF();
- }
- if (!script->is_valid()) {
+struct GDScriptCompletionIdentifier {
+ GDScriptParser::DataType type;
+ String enumeration;
+ Variant value;
+ const GDScriptParser::Node *assigned_expression;
- return REF();
- }
- //print_line("EXTENDS PATH: "+path+" script is "+itos(script.is_valid())+" indices is "+itos(script->member_indices.size())+" valid? "+itos(script->valid));
+ GDScriptCompletionIdentifier() :
+ assigned_expression(NULL) {}
+};
- if (context._class->extends_class.size()) {
+static void _get_directory_contents(EditorFileSystemDirectory *p_dir, Set<String> &r_list) {
- for (int i = 0; i < context._class->extends_class.size(); i++) {
+ for (int i = 0; i < p_dir->get_file_count(); i++) {
+ r_list.insert("\"" + p_dir->get_file_path(i) + "\"");
+ }
- String sub = context._class->extends_class[i];
- if (script->get_subclasses().has(sub)) {
+ for (int i = 0; i < p_dir->get_subdir_count(); i++) {
+ _get_directory_contents(p_dir->get_subdir(i), r_list);
+ }
+}
- script = script->get_subclasses()[sub];
- } else {
+static String _get_visual_datatype(const PropertyInfo &p_info, bool p_isarg = true) {
- return REF();
- }
- }
- }
+ if (p_info.usage & PROPERTY_USAGE_CLASS_IS_ENUM) {
+ String enum_name = p_info.class_name;
+ if (enum_name.find(".") == -1) {
+ return enum_name;
+ }
+ return enum_name.get_slice(".", 1);
+ }
- if (script.is_valid())
- return script;
+ String n = p_info.name;
+ int idx = n.find(":");
+ if (idx != -1) {
+ return n.substr(idx + 1, n.length());
+ }
+ if (p_info.type == Variant::OBJECT) {
+ if (p_info.hint == PROPERTY_HINT_RESOURCE_TYPE) {
+ return p_info.hint_string;
} else {
+ return p_info.class_name.operator String();
+ }
+ }
+ if (p_info.type == Variant::NIL) {
+ if (p_isarg || (p_info.usage & PROPERTY_USAGE_NIL_IS_VARIANT)) {
+ return "var";
+ } else {
+ return "void";
+ }
+ }
- if (context._class->extends_class.size() == 0) {
- ERR_PRINT("BUG");
- return REF();
- }
-
- String base = context._class->extends_class[0];
-
- if (context._class->extends_class.size() > 1) {
-
- return REF();
- }
- //if not found, try engine classes
- if (!GDScriptLanguage::get_singleton()->get_global_map().has(base)) {
+ return Variant::get_type_name(p_info.type);
+}
- return REF();
+static GDScriptCompletionIdentifier _type_from_variant(const Variant &p_value) {
+ GDScriptCompletionIdentifier ci;
+ ci.value = p_value;
+ ci.type.is_constant = true;
+ ci.type.has_type = true;
+ ci.type.kind = GDScriptParser::DataType::BUILTIN;
+ ci.type.builtin_type = p_value.get_type();
+
+ if (ci.type.builtin_type == Variant::OBJECT) {
+ Object *obj = p_value.operator Object *();
+ if (!obj) {
+ return ci;
+ }
+ ci.type.native_type = obj->get_class_name();
+ Ref<Script> scr = p_value;
+ if (scr.is_valid()) {
+ ci.type.is_meta_type = true;
+ } else {
+ ci.type.is_meta_type = false;
+ scr = obj->get_script();
+ }
+ if (scr.is_valid()) {
+ ci.type.script_type = scr;
+ Ref<GDScript> gds = scr;
+ if (gds.is_valid()) {
+ ci.type.kind = GDScriptParser::DataType::GDSCRIPT;
+ } else {
+ ci.type.kind = GDScriptParser::DataType::SCRIPT;
}
-
- int base_idx = GDScriptLanguage::get_singleton()->get_global_map()[base];
- native = GDScriptLanguage::get_singleton()->get_global_array()[base_idx];
- return native;
+ ci.type.native_type = scr->get_instance_base_type();
+ } else {
+ ci.type.kind = GDScriptParser::DataType::NATIVE;
}
}
- return Ref<Reference>();
+ return ci;
}
-static GDScriptCompletionIdentifier _get_native_class(GDScriptCompletionContext &context) {
+static GDScriptCompletionIdentifier _type_from_property(const PropertyInfo &p_property) {
+ GDScriptCompletionIdentifier ci;
- GDScriptCompletionIdentifier id;
-
- REF pc = _get_parent_class(context);
- if (!pc.is_valid()) {
- return id;
+ if (p_property.type == Variant::NIL) {
+ // Variant
+ return ci;
}
- Ref<GDScriptNativeClass> nc = pc;
- Ref<GDScript> s = pc;
- if (s.is_null() && nc.is_null()) {
- return id;
- }
- while (!s.is_null()) {
- nc = s->get_native();
- s = s->get_base();
- }
- if (nc.is_null()) {
- return id;
+ if (p_property.usage & PROPERTY_USAGE_CLASS_IS_ENUM) {
+ ci.enumeration = p_property.class_name;
}
- id.type = Variant::OBJECT;
- if (context.base)
- id.value = context.base;
- id.obj_type = nc->get_name();
- return id;
+ ci.type.has_type = true;
+ ci.type.builtin_type = p_property.type;
+ if (p_property.type == Variant::OBJECT) {
+ ci.type.kind = GDScriptParser::DataType::NATIVE;
+ ci.type.native_type = p_property.class_name == StringName() ? "Object" : p_property.class_name;
+ } else {
+ ci.type.kind = GDScriptParser::DataType::BUILTIN;
+ }
+ return ci;
}
-static bool _guess_identifier_type(GDScriptCompletionContext &context, int p_line, const StringName &p_identifier, GDScriptCompletionIdentifier &r_type, bool p_for_indexing);
-
-static bool _guess_expression_type(GDScriptCompletionContext &context, const GDScriptParser::Node *p_node, int p_line, GDScriptCompletionIdentifier &r_type, bool p_for_indexing = false) {
-
- if (p_node->type == GDScriptParser::Node::TYPE_CONSTANT) {
-
- const GDScriptParser::ConstantNode *cn = static_cast<const GDScriptParser::ConstantNode *>(p_node);
+static GDScriptCompletionIdentifier _type_from_gdtype(const GDScriptDataType &p_gdtype) {
+ GDScriptCompletionIdentifier ci;
+ if (!p_gdtype.has_type) {
+ return ci;
+ }
- r_type = _get_type_from_variant(cn->value);
+ ci.type.has_type = true;
+ ci.type.builtin_type = p_gdtype.builtin_type;
+ ci.type.native_type = p_gdtype.native_type;
+ ci.type.script_type = p_gdtype.script_type;
- return true;
- } else if (p_node->type == GDScriptParser::Node::TYPE_DICTIONARY) {
-
- r_type.type = Variant::DICTIONARY;
+ switch (p_gdtype.kind) {
+ case GDScriptDataType::BUILTIN: {
+ ci.type.kind = GDScriptParser::DataType::BUILTIN;
+ } break;
+ case GDScriptDataType::NATIVE: {
+ ci.type.kind = GDScriptParser::DataType::NATIVE;
+ } break;
+ case GDScriptDataType::GDSCRIPT: {
+ ci.type.kind = GDScriptParser::DataType::GDSCRIPT;
+ } break;
+ case GDScriptDataType::SCRIPT: {
+ ci.type.kind = GDScriptParser::DataType::SCRIPT;
+ } break;
+ }
+ return ci;
+}
- //what the heck, fill it anyway
- const GDScriptParser::DictionaryNode *an = static_cast<const GDScriptParser::DictionaryNode *>(p_node);
- Dictionary d;
- for (int i = 0; i < an->elements.size(); i++) {
- GDScriptCompletionIdentifier k;
- if (_guess_expression_type(context, an->elements[i].key, p_line, k) && k.value.get_type() != Variant::NIL) {
- GDScriptCompletionIdentifier v;
- if (_guess_expression_type(context, an->elements[i].value, p_line, v)) {
- d[k.value] = v.value;
+static bool _guess_identifier_type(const GDScriptCompletionContext &p_context, const StringName &p_identifier, GDScriptCompletionIdentifier &r_type);
+static bool _guess_identifier_type_from_base(const GDScriptCompletionContext &p_context, const GDScriptCompletionIdentifier &p_base, const StringName &p_identifier, GDScriptCompletionIdentifier &r_type);
+static bool _guess_method_return_type_from_base(const GDScriptCompletionContext &p_context, const GDScriptCompletionIdentifier &p_base, const StringName &p_method, GDScriptCompletionIdentifier &r_type);
+
+static bool _guess_expression_type(const GDScriptCompletionContext &p_context, const GDScriptParser::Node *p_expression, GDScriptCompletionIdentifier &r_type) {
+ bool found = false;
+ switch (p_expression->type) {
+ case GDScriptParser::Node::TYPE_CONSTANT: {
+ const GDScriptParser::ConstantNode *cn = static_cast<const GDScriptParser::ConstantNode *>(p_expression);
+ r_type = _type_from_variant(cn->value);
+ found = true;
+ } break;
+ case GDScriptParser::Node::TYPE_SELF: {
+ if (p_context._class) {
+ r_type.type.has_type = true;
+ r_type.type.kind = GDScriptParser::DataType::CLASS;
+ r_type.type.class_type = const_cast<GDScriptParser::ClassNode *>(p_context._class);
+ r_type.type.is_constant = true;
+ r_type.value = p_context.base;
+ found = true;
+ }
+ } break;
+ case GDScriptParser::Node::TYPE_IDENTIFIER: {
+ const GDScriptParser::IdentifierNode *id = static_cast<const GDScriptParser::IdentifierNode *>(p_expression);
+ found = _guess_identifier_type(p_context, id->name, r_type);
+ } break;
+ case GDScriptParser::Node::TYPE_DICTIONARY: {
+ // Try to recreate the dictionary
+ const GDScriptParser::DictionaryNode *dn = static_cast<const GDScriptParser::DictionaryNode *>(p_expression);
+ Dictionary d;
+ bool full = true;
+ for (int i = 0; i < dn->elements.size(); i++) {
+ GDScriptCompletionIdentifier key;
+ if (_guess_expression_type(p_context, dn->elements[i].key, key)) {
+ GDScriptCompletionIdentifier value;
+ if (_guess_expression_type(p_context, dn->elements[i].value, value)) {
+ if (!value.type.is_constant) {
+ full = false;
+ break;
+ }
+ d[key.value] = value.value;
+ } else {
+ full = false;
+ break;
+ }
+ } else {
+ full = false;
+ break;
}
}
- }
- r_type.value = d;
- return true;
- } else if (p_node->type == GDScriptParser::Node::TYPE_ARRAY) {
-
- r_type.type = Variant::ARRAY;
- //what the heck, fill it anyway
- const GDScriptParser::ArrayNode *an = static_cast<const GDScriptParser::ArrayNode *>(p_node);
- Array arr;
- arr.resize(an->elements.size());
- for (int i = 0; i < an->elements.size(); i++) {
- GDScriptCompletionIdentifier ci;
- if (_guess_expression_type(context, an->elements[i], p_line, ci)) {
- arr[i] = ci.value;
+ if (full) {
+ // If not fully constant, setting this value is detrimental to the inference
+ r_type.value = d;
+ r_type.type.is_constant = true;
}
- }
- r_type.value = arr;
- return true;
-
- } else if (p_node->type == GDScriptParser::Node::TYPE_BUILT_IN_FUNCTION) {
-
- MethodInfo mi = GDScriptFunctions::get_info(static_cast<const GDScriptParser::BuiltInFunctionNode *>(p_node)->function);
- r_type = _get_type_from_pinfo(mi.return_val);
-
- return true;
- } else if (p_node->type == GDScriptParser::Node::TYPE_IDENTIFIER) {
-
- return _guess_identifier_type(context, p_line - 1, static_cast<const GDScriptParser::IdentifierNode *>(p_node)->name, r_type, p_for_indexing);
- } else if (p_node->type == GDScriptParser::Node::TYPE_SELF) {
- //eeh...
-
- r_type = _get_native_class(context);
- return r_type.type != Variant::NIL;
-
- } else if (p_node->type == GDScriptParser::Node::TYPE_OPERATOR) {
-
- const GDScriptParser::OperatorNode *op = static_cast<const GDScriptParser::OperatorNode *>(p_node);
- if (op->op == GDScriptParser::OperatorNode::OP_CALL) {
-
- if (op->arguments[0]->type == GDScriptParser::Node::TYPE_TYPE) {
-
- const GDScriptParser::TypeNode *tn = static_cast<const GDScriptParser::TypeNode *>(op->arguments[0]);
- r_type.type = tn->vtype;
- return true;
- } else if (op->arguments[0]->type == GDScriptParser::Node::TYPE_BUILT_IN_FUNCTION) {
-
- const GDScriptParser::BuiltInFunctionNode *bin = static_cast<const GDScriptParser::BuiltInFunctionNode *>(op->arguments[0]);
- return _guess_expression_type(context, bin, p_line, r_type);
-
- } else if (op->arguments.size() > 1 && op->arguments[1]->type == GDScriptParser::Node::TYPE_IDENTIFIER) {
-
- StringName id = static_cast<const GDScriptParser::IdentifierNode *>(op->arguments[1])->name;
-
- if (op->arguments[0]->type == GDScriptParser::Node::TYPE_IDENTIFIER && String(id) == "new") {
-
- //shortcut
- StringName identifier = static_cast<const GDScriptParser::IdentifierNode *>(op->arguments[0])->name;
-
- if (ClassDB::class_exists(identifier)) {
- r_type.type = Variant::OBJECT;
- r_type.value = Variant();
- r_type.obj_type = identifier;
- return true;
- }
+ r_type.type.has_type = true;
+ r_type.type.kind = GDScriptParser::DataType::BUILTIN;
+ r_type.type.builtin_type = Variant::DICTIONARY;
+ } break;
+ case GDScriptParser::Node::TYPE_ARRAY: {
+ // Try to recreate the array
+ const GDScriptParser::ArrayNode *an = static_cast<const GDScriptParser::ArrayNode *>(p_expression);
+ Array a;
+ bool full = true;
+ a.resize(an->elements.size());
+ for (int i = 0; i < an->elements.size(); i++) {
+ GDScriptCompletionIdentifier value;
+ if (_guess_expression_type(p_context, an->elements[i], value)) {
+ a[i] = value.value;
+ } else {
+ full = false;
+ break;
}
-
- GDScriptCompletionIdentifier base;
- if (!_guess_expression_type(context, op->arguments[0], p_line, base))
- return false;
-
- if (base.type == Variant::OBJECT) {
-
- if (id.operator String() == "new" && base.value.get_type() == Variant::OBJECT) {
-
- Object *obj = base.value;
- if (obj && Object::cast_to<GDScriptNativeClass>(obj)) {
- GDScriptNativeClass *gdnc = Object::cast_to<GDScriptNativeClass>(obj);
- r_type.type = Variant::OBJECT;
- r_type.value = Variant();
- r_type.obj_type = gdnc->get_name();
- return true;
- } else {
-
- if (base.obj_type != StringName()) {
-
- r_type.type = Variant::OBJECT;
- r_type.value = Variant();
- r_type.obj_type = base.obj_type;
- return true;
- }
+ }
+ if (full) {
+ // If not fully constant, setting this value is detrimental to the inference
+ r_type.value = a;
+ }
+ r_type.type.has_type = true;
+ r_type.type.kind = GDScriptParser::DataType::BUILTIN;
+ r_type.type.builtin_type = Variant::ARRAY;
+ } break;
+ case GDScriptParser::Node::TYPE_OPERATOR: {
+ const GDScriptParser::OperatorNode *op = static_cast<const GDScriptParser::OperatorNode *>(p_expression);
+ switch (op->op) {
+ case GDScriptParser::OperatorNode::OP_CALL: {
+ if (op->arguments[0]->type == GDScriptParser::Node::TYPE_TYPE) {
+ const GDScriptParser::TypeNode *tn = static_cast<const GDScriptParser::TypeNode *>(op->arguments[0]);
+ r_type.type.has_type = true;
+ r_type.type.kind = GDScriptParser::DataType::BUILTIN;
+ r_type.type.builtin_type = tn->vtype;
+ found = true;
+ break;
+ } else if (op->arguments[0]->type == GDScriptParser::Node::TYPE_BUILT_IN_FUNCTION) {
+ const GDScriptParser::BuiltInFunctionNode *bin = static_cast<const GDScriptParser::BuiltInFunctionNode *>(op->arguments[0]);
+ MethodInfo mi = GDScriptFunctions::get_info(bin->function);
+ r_type = _type_from_property(mi.return_val);
+ found = true;
+ break;
+ } else if (op->arguments.size() >= 2 && op->arguments[1]->type == GDScriptParser::Node::TYPE_IDENTIFIER) {
+ StringName id = static_cast<const GDScriptParser::IdentifierNode *>(op->arguments[1])->name;
+
+ GDScriptCompletionContext c = p_context;
+ c.line = op->line;
+
+ GDScriptCompletionIdentifier base;
+ if (!_guess_expression_type(c, op->arguments[0], base)) {
+ found = false;
+ break;
}
- }
- if (ClassDB::has_method(base.obj_type, id)) {
-
-#ifdef TOOLS_ENABLED
- MethodBind *mb = ClassDB::get_method(base.obj_type, id);
- PropertyInfo pi = mb->get_return_info();
+ // Try call if constant methods with constant arguments
+ if (base.type.is_constant && base.value.get_type() == Variant::OBJECT) {
+ GDScriptParser::DataType native_type = base.type;
- //try calling the function if constant and all args are constant, should not crash..
- Object *baseptr = base.value;
-
- if (mb->is_const() && pi.type == Variant::OBJECT) {
-
- bool all_valid = true;
- Vector<Variant> args;
- for (int i = 2; i < op->arguments.size(); i++) {
- GDScriptCompletionIdentifier arg;
+ while (native_type.kind == GDScriptParser::DataType::CLASS) {
+ native_type = native_type.class_type->base_type;
+ }
- if (_guess_expression_type(context, op->arguments[i], p_line, arg)) {
- if (arg.value.get_type() != Variant::NIL && arg.value.get_type() != Variant::OBJECT) { // calling with object seems dangerous, i don' t know
- args.push_back(arg.value);
+ while (native_type.kind == GDScriptParser::DataType::GDSCRIPT || native_type.kind == GDScriptParser::DataType::SCRIPT) {
+ if (native_type.script_type.is_valid()) {
+ Ref<Script> parent = native_type.script_type->get_base_script();
+ if (parent.is_valid()) {
+ native_type.script_type = parent;
} else {
- all_valid = false;
- break;
+ native_type.kind = GDScriptParser::DataType::NATIVE;
+ native_type.native_type = native_type.script_type->get_instance_base_type();
+ if (!ClassDB::class_exists(native_type.native_type)) {
+ native_type.native_type = String("_") + native_type.native_type;
+ if (!ClassDB::class_exists(native_type.native_type)) {
+ native_type.has_type = false;
+ }
+ }
}
- } else {
- all_valid = false;
}
}
- if (all_valid && String(id) == "get_node" && ClassDB::is_parent_class(base.obj_type, "Node") && args.size()) {
-
- String arg1 = args[0];
- if (arg1.begins_with("/root/")) {
- String which = arg1.get_slice("/", 2);
- if (which != "") {
- List<PropertyInfo> props;
- ProjectSettings::get_singleton()->get_property_list(&props);
- //print_line("find singleton");
-
- for (List<PropertyInfo>::Element *E = props.front(); E; E = E->next()) {
-
- String s = E->get().name;
- if (!s.begins_with("autoload/"))
- continue;
- //print_line("found "+s);
- String name = s.get_slice("/", 1);
- //print_line("name: "+name+", which: "+which);
- if (name == which) {
- String script = ProjectSettings::get_singleton()->get(s);
-
- if (!script.begins_with("res://")) {
- script = "res://" + script;
- }
-
- if (!script.ends_with(".gd")) {
- //not a script, try find the script anyway,
- //may have some success
- script = script.get_basename() + ".gd";
- }
-
- if (FileAccess::exists(script)) {
-
- //print_line("is a script");
-
- Ref<Script> scr;
- if (ScriptCodeCompletionCache::get_singleton())
- scr = ScriptCodeCompletionCache::get_singleton()->get_cached_resource(script);
- else
- scr = ResourceLoader::load(script);
-
- r_type.obj_type = "Node";
- r_type.type = Variant::OBJECT;
- r_type.script = scr;
- r_type.value = Variant();
-
- return true;
- }
+ if (native_type.has_type && native_type.kind == GDScriptParser::DataType::NATIVE) {
+ MethodBind *mb = ClassDB::get_method(native_type.native_type, id);
+ if (mb && mb->is_const()) {
+ bool all_is_const = true;
+ Vector<Variant> args;
+ GDScriptCompletionContext c = p_context;
+ c.line = op->line;
+ for (int i = 2; all_is_const && i < op->arguments.size(); i++) {
+ GDScriptCompletionIdentifier arg;
+
+ if (_guess_expression_type(c, op->arguments[i], arg)) {
+ if (arg.type.has_type && arg.type.is_constant && arg.value.get_type() != Variant::OBJECT) {
+ args.push_back(arg.value);
+ } else {
+ all_is_const = false;
}
+ } else {
+ all_is_const = false;
}
}
- }
- }
- if (baseptr) {
-
- if (all_valid) {
- Vector<const Variant *> argptr;
- for (int i = 0; i < args.size(); i++) {
- argptr.push_back(&args[i]);
+ Object *baseptr = base.value;
+
+ if (all_is_const && String(id) == "get_node" && ClassDB::is_parent_class(native_type.native_type, "Node") && args.size()) {
+
+ String arg1 = args[0];
+ if (arg1.begins_with("/root/")) {
+ String which = arg1.get_slice("/", 2);
+ if (which != "") {
+ // Try singletons first
+ if (GDScriptLanguage::get_singleton()->get_named_globals_map().has(which)) {
+ r_type = _type_from_variant(GDScriptLanguage::get_singleton()->get_named_globals_map()[which]);
+ found = true;
+ } else {
+ List<PropertyInfo> props;
+ ProjectSettings::get_singleton()->get_property_list(&props);
+
+ for (List<PropertyInfo>::Element *E = props.front(); E; E = E->next()) {
+
+ String s = E->get().name;
+ if (!s.begins_with("autoload/")) {
+ continue;
+ }
+ String name = s.get_slice("/", 1);
+ if (name == which) {
+ String script = ProjectSettings::get_singleton()->get(s);
+
+ if (script.begins_with("*")) {
+ script = script.right(1);
+ }
+
+ if (!script.begins_with("res://")) {
+ script = "res://" + script;
+ }
+
+ if (!script.ends_with(".gd")) {
+ //not a script, try find the script anyway,
+ //may have some success
+ script = script.get_basename() + ".gd";
+ }
+
+ if (FileAccess::exists(script)) {
+ Ref<Script> scr;
+ if (ScriptCodeCompletionCache::get_singleton()) {
+ scr = ScriptCodeCompletionCache::get_singleton()->get_cached_resource(script);
+ } else {
+ scr = ResourceLoader::load(script);
+ }
+ if (scr.is_valid()) {
+ r_type.type.has_type = true;
+ r_type.type.script_type = scr;
+ r_type.type.is_constant = false;
+ Ref<GDScript> gds = scr;
+ if (gds.is_valid()) {
+ r_type.type.kind = GDScriptParser::DataType::GDSCRIPT;
+ } else {
+ r_type.type.kind = GDScriptParser::DataType::SCRIPT;
+ }
+ r_type.value = Variant();
+ found = true;
+ }
+ }
+ break;
+ }
+ }
+ }
+ }
+ }
}
- Variant::CallError ce;
- Variant ret = mb->call(baseptr, (const Variant **)argptr.ptr(), argptr.size(), ce);
-
- if (ce.error == Variant::CallError::CALL_OK && ret.get_type() != Variant::NIL) {
+ if (!found && all_is_const && baseptr) {
+ Vector<const Variant *> argptr;
+ for (int i = 0; i < args.size(); i++) {
+ argptr.push_back(&args[i]);
+ }
- if (ret.get_type() != Variant::OBJECT || ret.operator Object *() != NULL) {
+ Variant::CallError ce;
+ Variant ret = mb->call(baseptr, (const Variant **)argptr.ptr(), argptr.size(), ce);
- r_type = _get_type_from_variant(ret);
- return true;
+ if (ce.error == Variant::CallError::CALL_OK && ret.get_type() != Variant::NIL) {
+ if (ret.get_type() != Variant::OBJECT || ret.operator Object *() != NULL) {
+ r_type = _type_from_variant(ret);
+ found = true;
+ }
}
}
}
}
}
- r_type.type = pi.type;
- if (pi.hint == PROPERTY_HINT_RESOURCE_TYPE) {
- r_type.obj_type = pi.hint_string;
+ if (!found) {
+ found = _guess_method_return_type_from_base(c, base, id, r_type);
}
-
- return true;
-#else
- return false;
-#endif
- } else {
- return false;
}
- } else {
- //method for some variant..
- Variant::CallError ce;
- Variant v = Variant::construct(base.type, NULL, 0, ce);
- List<MethodInfo> mi;
- v.get_method_list(&mi);
- for (List<MethodInfo>::Element *E = mi.front(); E; E = E->next()) {
-
- if (!E->get().name.begins_with("_") && E->get().name == id.operator String()) {
-
- MethodInfo mi = E->get();
- r_type.type = mi.return_val.type;
- if (mi.return_val.hint == PROPERTY_HINT_RESOURCE_TYPE) {
- r_type.obj_type = mi.return_val.hint_string;
- }
- return true;
- }
+ } break;
+ case GDScriptParser::OperatorNode::OP_PARENT_CALL: {
+ if (!p_context._class || !op->arguments.size() || op->arguments[0]->type != GDScriptParser::Node::TYPE_IDENTIFIER) {
+ break;
}
- }
- }
- } else if (op->op == GDScriptParser::OperatorNode::OP_INDEX || op->op == GDScriptParser::OperatorNode::OP_INDEX_NAMED) {
- GDScriptCompletionIdentifier p1;
- GDScriptCompletionIdentifier p2;
+ StringName id = static_cast<const GDScriptParser::IdentifierNode *>(op->arguments[0])->name;
- if (op->op == GDScriptParser::OperatorNode::OP_INDEX_NAMED) {
+ GDScriptCompletionIdentifier base;
+ base.value = p_context.base;
+ base.type = p_context._class->base_type;
- if (op->arguments[1]->type == GDScriptParser::Node::TYPE_IDENTIFIER) {
- String id = static_cast<const GDScriptParser::IdentifierNode *>(op->arguments[1])->name;
- p2.type = Variant::STRING;
- p2.value = id;
- }
+ GDScriptCompletionContext c = p_context;
+ c.line = op->line;
- } else {
- if (op->arguments[1]) {
- if (!_guess_expression_type(context, op->arguments[1], p_line, p2)) {
-
- return false;
+ found = _guess_method_return_type_from_base(c, base, id, r_type);
+ } break;
+ case GDScriptParser::OperatorNode::OP_INDEX_NAMED: {
+ if (op->arguments.size() < 2 || op->arguments[1]->type != GDScriptParser::Node::TYPE_IDENTIFIER) {
+ found = false;
+ break;
}
- }
- }
-
- if (op->arguments[0]->type == GDScriptParser::Node::TYPE_ARRAY) {
-
- const GDScriptParser::ArrayNode *an = static_cast<const GDScriptParser::ArrayNode *>(op->arguments[0]);
- if (p2.value.is_num()) {
- int index = p2.value;
- if (index < 0 || index >= an->elements.size())
- return false;
- return _guess_expression_type(context, an->elements[index], p_line, r_type);
- }
-
- } else if (op->arguments[0]->type == GDScriptParser::Node::TYPE_DICTIONARY) {
-
- const GDScriptParser::DictionaryNode *dn = static_cast<const GDScriptParser::DictionaryNode *>(op->arguments[0]);
+ const GDScriptParser::IdentifierNode *id = static_cast<const GDScriptParser::IdentifierNode *>(op->arguments[1]);
- if (p2.value.get_type() == Variant::NIL)
- return false;
-
- for (int i = 0; i < dn->elements.size(); i++) {
-
- GDScriptCompletionIdentifier k;
+ GDScriptCompletionContext c = p_context;
+ c.line = op->line;
- if (!_guess_expression_type(context, dn->elements[i].key, p_line, k)) {
-
- return false;
+ GDScriptCompletionIdentifier base;
+ if (!_guess_expression_type(c, op->arguments[0], base)) {
+ found = false;
+ break;
}
- if (k.value.get_type() == Variant::NIL)
- return false;
+ if (base.value.get_type() == Variant::DICTIONARY && base.value.operator Dictionary().has(String(id->name))) {
+ Variant value = base.value.operator Dictionary()[String(id->name)];
+ r_type = _type_from_variant(value);
+ found = true;
+ break;
+ }
- if (k.value == p2.value) {
+ const GDScriptParser::DictionaryNode *dn = NULL;
+ if (op->arguments[0]->type == GDScriptParser::Node::TYPE_DICTIONARY) {
+ dn = static_cast<const GDScriptParser::DictionaryNode *>(op->arguments[0]);
+ } else if (base.assigned_expression && base.assigned_expression->type == GDScriptParser::Node::TYPE_DICTIONARY) {
+ dn = static_cast<const GDScriptParser::DictionaryNode *>(base.assigned_expression);
+ }
- return _guess_expression_type(context, dn->elements[i].value, p_line, r_type);
+ if (dn) {
+ for (int i = 0; i < dn->elements.size(); i++) {
+ GDScriptCompletionIdentifier key;
+ if (!_guess_expression_type(c, dn->elements[i].key, key)) {
+ continue;
+ }
+ if (key.value == String(id->name)) {
+ r_type.assigned_expression = dn->elements[i].value;
+ found = _guess_expression_type(c, dn->elements[i].value, r_type);
+ break;
+ }
+ }
}
- }
- } else {
+ if (!found) {
+ found = _guess_identifier_type_from_base(c, base, id->name, r_type);
+ }
+ } break;
+ case GDScriptParser::OperatorNode::OP_INDEX: {
+ if (op->arguments.size() < 2) {
+ found = false;
+ break;
+ }
- if (op->arguments[0]) {
- if (!_guess_expression_type(context, op->arguments[0], p_line, p1)) {
+ GDScriptCompletionContext c = p_context;
+ c.line = op->line;
- return false;
+ GDScriptCompletionIdentifier base;
+ if (!_guess_expression_type(c, op->arguments[0], base)) {
+ found = false;
+ break;
}
- }
- if (p1.value.get_type() == Variant::OBJECT) {
- //??
-
- if (p1.obj_type != StringName() && p2.type == Variant::STRING) {
+ GDScriptCompletionIdentifier index;
+ if (!_guess_expression_type(c, op->arguments[1], index)) {
+ found = false;
+ break;
+ }
- StringName base_type = p1.obj_type;
+ if (base.value.in(index.value)) {
+ Variant value = base.value.get(index.value);
+ r_type = _type_from_variant(value);
+ found = true;
+ break;
+ }
- if (p1.obj_type == "GDScriptNativeClass") {
- //native enum
- Ref<GDScriptNativeClass> gdn = p1.value;
- if (gdn.is_valid()) {
+ // Look if it is a dictionary node
+ const GDScriptParser::DictionaryNode *dn = NULL;
+ if (op->arguments[0]->type == GDScriptParser::Node::TYPE_DICTIONARY) {
+ dn = static_cast<const GDScriptParser::DictionaryNode *>(op->arguments[0]);
+ } else if (base.assigned_expression && base.assigned_expression->type == GDScriptParser::Node::TYPE_DICTIONARY) {
+ dn = static_cast<const GDScriptParser::DictionaryNode *>(base.assigned_expression);
+ }
- base_type = gdn->get_name();
+ if (dn) {
+ for (int i = 0; i < dn->elements.size(); i++) {
+ GDScriptCompletionIdentifier key;
+ if (!_guess_expression_type(c, dn->elements[i].key, key)) {
+ continue;
}
- }
- StringName index = p2.value;
- bool valid;
- Variant::Type t = ClassDB::get_property_type(base_type, index, &valid);
- if (t != Variant::NIL && valid) {
- r_type.type = t;
- if (t == Variant::INT || t == Variant::OBJECT) {
-//check for enum!
-#if defined(DEBUG_METHODS_ENABLED) && defined(TOOLS_ENABLED)
-
- StringName getter = ClassDB::get_property_getter(base_type, index);
- if (getter != StringName()) {
- MethodBind *mb = ClassDB::get_method(base_type, getter);
- if (mb) {
- PropertyInfo rt = mb->get_return_info();
- if ((rt.usage & PROPERTY_USAGE_CLASS_IS_ENUM) && t == Variant::INT) {
- r_type.enumeration = rt.class_name;
- } else if (t == Variant::OBJECT) {
-
- r_type.obj_type = rt.class_name;
- }
- }
- }
-#endif
+ if (key.value == index.value) {
+ r_type.assigned_expression = dn->elements[i].value;
+ found = _guess_expression_type(p_context, dn->elements[i].value, r_type);
+ break;
}
-
- return true;
}
}
- } else if (p1.value.get_type() != Variant::NIL) {
- bool valid;
- Variant ret = p1.value.get(p2.value, &valid);
- if (valid) {
- r_type = _get_type_from_variant(ret);
- return true;
+ // Look if it is an array node
+ if (!found && index.value.is_num()) {
+ int idx = index.value;
+ const GDScriptParser::ArrayNode *an = NULL;
+ if (op->arguments[0]->type == GDScriptParser::Node::TYPE_ARRAY) {
+ an = static_cast<const GDScriptParser::ArrayNode *>(op->arguments[0]);
+ } else if (base.assigned_expression && base.assigned_expression->type == GDScriptParser::Node::TYPE_ARRAY) {
+ an = static_cast<const GDScriptParser::ArrayNode *>(base.assigned_expression);
+ }
+
+ if (an && idx >= 0 && an->elements.size() > idx) {
+ r_type.assigned_expression = an->elements[idx];
+ found = _guess_expression_type(c, an->elements[idx], r_type);
+ break;
+ }
}
- } else {
- if (p1.type != Variant::NIL) {
- Variant::CallError ce;
- Variant base = Variant::construct(p1.type, NULL, 0, ce);
- bool valid;
- Variant ret = base.get(p2.value, &valid);
+ // Look for valid indexing in other types
+ if (!found && (index.value.get_type() == Variant::STRING || index.value.get_type() == Variant::NODE_PATH)) {
+ StringName id = index.value;
+ found = _guess_identifier_type_from_base(c, base, id, r_type);
+ } else if (!found && index.type.kind == GDScriptParser::DataType::BUILTIN) {
+ Variant::CallError err;
+ Variant base_val = Variant::construct(base.type.builtin_type, NULL, 0, err);
+ bool valid = false;
+ Variant res = base_val.get(index.value, &valid);
if (valid) {
- r_type = _get_type_from_variant(ret);
- return true;
+ r_type = _type_from_variant(res);
+ r_type.value = Variant();
+ r_type.type.is_constant = false;
+ found = true;
}
}
- }
- }
+ } break;
+ default: {
+ if (op->arguments.size() < 2) {
+ found = false;
+ break;
+ }
- } else {
+ Variant::Operator vop = Variant::OP_MAX;
+ switch (op->op) {
+ case GDScriptParser::OperatorNode::OP_ADD: vop = Variant::OP_ADD; break;
+ case GDScriptParser::OperatorNode::OP_SUB: vop = Variant::OP_SUBTRACT; break;
+ case GDScriptParser::OperatorNode::OP_MUL: vop = Variant::OP_MULTIPLY; break;
+ case GDScriptParser::OperatorNode::OP_DIV: vop = Variant::OP_DIVIDE; break;
+ case GDScriptParser::OperatorNode::OP_MOD: vop = Variant::OP_MODULE; break;
+ case GDScriptParser::OperatorNode::OP_SHIFT_LEFT: vop = Variant::OP_SHIFT_LEFT; break;
+ case GDScriptParser::OperatorNode::OP_SHIFT_RIGHT: vop = Variant::OP_SHIFT_RIGHT; break;
+ case GDScriptParser::OperatorNode::OP_BIT_AND: vop = Variant::OP_BIT_AND; break;
+ case GDScriptParser::OperatorNode::OP_BIT_OR: vop = Variant::OP_BIT_OR; break;
+ case GDScriptParser::OperatorNode::OP_BIT_XOR: vop = Variant::OP_BIT_XOR; break;
+ default: {}
+ }
- Variant::Operator vop = Variant::OP_MAX;
- switch (op->op) {
- case GDScriptParser::OperatorNode::OP_ADD: vop = Variant::OP_ADD; break;
- case GDScriptParser::OperatorNode::OP_SUB: vop = Variant::OP_SUBTRACT; break;
- case GDScriptParser::OperatorNode::OP_MUL: vop = Variant::OP_MULTIPLY; break;
- case GDScriptParser::OperatorNode::OP_DIV: vop = Variant::OP_DIVIDE; break;
- case GDScriptParser::OperatorNode::OP_MOD: vop = Variant::OP_MODULE; break;
- case GDScriptParser::OperatorNode::OP_SHIFT_LEFT: vop = Variant::OP_SHIFT_LEFT; break;
- case GDScriptParser::OperatorNode::OP_SHIFT_RIGHT: vop = Variant::OP_SHIFT_RIGHT; break;
- case GDScriptParser::OperatorNode::OP_BIT_AND: vop = Variant::OP_BIT_AND; break;
- case GDScriptParser::OperatorNode::OP_BIT_OR: vop = Variant::OP_BIT_OR; break;
- case GDScriptParser::OperatorNode::OP_BIT_XOR: vop = Variant::OP_BIT_XOR; break;
- default: {}
- }
-
- if (vop == Variant::OP_MAX)
- return false;
+ if (vop == Variant::OP_MAX) {
+ break;
+ }
- GDScriptCompletionIdentifier p1;
- GDScriptCompletionIdentifier p2;
+ GDScriptCompletionContext context = p_context;
+ context.line = op->line;
- if (op->arguments[0]) {
- if (!_guess_expression_type(context, op->arguments[0], p_line, p1)) {
+ GDScriptCompletionIdentifier p1;
+ GDScriptCompletionIdentifier p2;
- return false;
- }
- }
+ if (!_guess_expression_type(context, op->arguments[0], p1)) {
+ found = false;
+ break;
+ }
- if (op->arguments.size() > 1) {
- if (!_guess_expression_type(context, op->arguments[1], p_line, p2)) {
+ if (!_guess_expression_type(context, op->arguments[1], p2)) {
+ found = false;
+ break;
+ }
- return false;
- }
- }
+ Variant::CallError ce;
+ bool v1_use_value = p1.value.get_type() != Variant::NIL && p1.value.get_type() != Variant::OBJECT;
+ Variant v1 = (v1_use_value) ? p1.value : Variant::construct(p1.type.builtin_type, NULL, 0, ce);
+ bool v2_use_value = p2.value.get_type() != Variant::NIL && p2.value.get_type() != Variant::OBJECT;
+ Variant v2 = (v2_use_value) ? p2.value : Variant::construct(p2.type.builtin_type, NULL, 0, ce);
+ // avoid potential invalid ops
+ if ((vop == Variant::OP_DIVIDE || vop == Variant::OP_MODULE) && v2.get_type() == Variant::INT) {
+ v2 = 1;
+ v2_use_value = false;
+ }
+ if (vop == Variant::OP_DIVIDE && v2.get_type() == Variant::REAL) {
+ v2 = 1.0;
+ v2_use_value = false;
+ }
- Variant::CallError ce;
- bool v1_use_value = p1.value.get_type() != Variant::NIL && p1.value.get_type() != Variant::OBJECT;
- Variant v1 = (v1_use_value) ? p1.value : Variant::construct(p1.type, NULL, 0, ce);
- bool v2_use_value = p2.value.get_type() != Variant::NIL && p2.value.get_type() != Variant::OBJECT;
- Variant v2 = (v2_use_value) ? p2.value : Variant::construct(p2.type, NULL, 0, ce);
- // avoid potential invalid ops
- if ((vop == Variant::OP_DIVIDE || vop == Variant::OP_MODULE) && v2.get_type() == Variant::INT) {
- v2 = 1;
- v2_use_value = false;
- }
- if (vop == Variant::OP_DIVIDE && v2.get_type() == Variant::REAL) {
- v2 = 1.0;
- v2_use_value = false;
+ Variant res;
+ bool valid;
+ Variant::evaluate(vop, v1, v2, res, valid);
+ if (!valid) {
+ found = false;
+ break;
+ }
+ r_type = _type_from_variant(res);
+ if (!v1_use_value || !v2_use_value) {
+ r_type.value = Variant();
+ r_type.type.is_constant = false;
+ }
+
+ found = true;
+ } break;
}
+ } break;
+ }
- Variant r;
- bool valid;
- Variant::evaluate(vop, v1, v2, r, valid);
- if (!valid)
- return false;
- r_type.type = r.get_type();
- if (v1_use_value && v2_use_value)
- r_type.value = r;
+ // It may have found a null, but that's never useful
+ if (found && r_type.type.has_type && r_type.type.kind == GDScriptParser::DataType::BUILTIN && r_type.type.builtin_type == Variant::NIL) {
+ found = false;
+ }
- return true;
+ // Check type hint last. For collections we want chance to get the actual value first
+ // This way we can detect types from the content of dictionaries and arrays
+ if (!found && p_expression->get_datatype().has_type) {
+ r_type.type = p_expression->get_datatype();
+ if (!r_type.assigned_expression) {
+ r_type.assigned_expression = p_expression;
}
+ found = true;
}
- return false;
+ return found;
}
-static bool _guess_identifier_type_in_block(GDScriptCompletionContext &context, int p_line, const StringName &p_identifier, GDScriptCompletionIdentifier &r_type) {
+static bool _guess_identifier_type(const GDScriptCompletionContext &p_context, const StringName &p_identifier, GDScriptCompletionIdentifier &r_type) {
- if (context.block->if_condition && context.block->if_condition->type == GDScriptParser::Node::TYPE_OPERATOR && static_cast<const GDScriptParser::OperatorNode *>(context.block->if_condition)->op == GDScriptParser::OperatorNode::OP_IS) {
- //is used, check if identifier is in there! this helps resolve in blocks that are (if (identifier is value)): which are very common..
- //super dirty hack, but very useful
- //credit: Zylann
- //TODO: this could be hacked to detect ANDed conditions too..
- const GDScriptParser::OperatorNode *op = static_cast<const GDScriptParser::OperatorNode *>(context.block->if_condition);
- if (op->arguments[0]->type == GDScriptParser::Node::TYPE_IDENTIFIER && static_cast<const GDScriptParser::IdentifierNode *>(op->arguments[0])->name == p_identifier) {
- //bingo
- if (_guess_expression_type(context, op->arguments[1], op->line, r_type)) {
- return true;
+ // Look in blocks first
+ const GDScriptParser::BlockNode *blk = p_context.block;
+ int last_assign_line = -1;
+ const GDScriptParser::Node *last_assigned_expression = NULL;
+ GDScriptParser::DataType var_type;
+ while (blk) {
+ if (blk->variables.has(p_identifier)) {
+ if (blk->variables[p_identifier]->line > p_context.line) {
+ return false;
}
- }
- }
- GDScriptCompletionIdentifier gdi = _get_native_class(context);
- if (gdi.obj_type != StringName()) {
- bool valid;
- Variant::Type t = ClassDB::get_property_type(gdi.obj_type, p_identifier, &valid);
- if (t != Variant::NIL && valid) {
- r_type.type = t;
- if (t == Variant::INT) {
-//check for enum!
-#if defined(DEBUG_METHODS_ENABLED) && defined(TOOLS_ENABLED)
+ var_type = blk->variables[p_identifier]->datatype;
- StringName getter = ClassDB::get_property_getter(gdi.obj_type, p_identifier);
- if (getter != StringName()) {
- MethodBind *mb = ClassDB::get_method(gdi.obj_type, getter);
- if (mb) {
- PropertyInfo rt = mb->get_return_info();
- if (rt.usage & PROPERTY_USAGE_CLASS_IS_ENUM) {
- r_type.enumeration = rt.class_name;
- }
- }
+ if (!last_assigned_expression && blk->variables[p_identifier]->assign && blk->variables[p_identifier]->assign->type == GDScriptParser::Node::TYPE_OPERATOR) {
+ const GDScriptParser::OperatorNode *op = static_cast<const GDScriptParser::OperatorNode *>(blk->variables[p_identifier]->assign);
+ if (op->op == GDScriptParser::OperatorNode::OP_ASSIGN && op->arguments.size() >= 2) {
+ last_assign_line = op->line;
+ last_assigned_expression = op->arguments[1];
}
-#endif
}
- return true;
}
- }
-
- const GDScriptParser::Node *last_assign = NULL;
- int last_assign_line = -1;
-
- for (int i = 0; i < context.block->statements.size(); i++) {
-
- if (context.block->statements[i]->line > p_line)
- continue;
-
- if (context.block->statements[i]->type == GDScriptParser::BlockNode::TYPE_LOCAL_VAR) {
- const GDScriptParser::LocalVarNode *lv = static_cast<const GDScriptParser::LocalVarNode *>(context.block->statements[i]);
+ for (const List<GDScriptParser::Node *>::Element *E = blk->statements.front(); E; E = E->next()) {
+ const GDScriptParser::Node *expr = E->get();
+ if (expr->line > p_context.line || expr->type != GDScriptParser::Node::TYPE_OPERATOR) {
+ continue;
+ }
- if (lv->assign && lv->name == p_identifier) {
+ const GDScriptParser::OperatorNode *op = static_cast<const GDScriptParser::OperatorNode *>(expr);
+ if (op->op != GDScriptParser::OperatorNode::OP_ASSIGN || op->line < last_assign_line) {
+ continue;
+ }
- last_assign = lv->assign;
- last_assign_line = context.block->statements[i]->line;
+ if (op->arguments.size() >= 2 && op->arguments[0]->type == GDScriptParser::Node::TYPE_IDENTIFIER) {
+ const GDScriptParser::IdentifierNode *id = static_cast<const GDScriptParser::IdentifierNode *>(op->arguments[0]);
+ if (id->name == p_identifier) {
+ last_assign_line = op->line;
+ last_assigned_expression = op->arguments[1];
+ }
}
}
- if (context.block->statements[i]->type == GDScriptParser::BlockNode::TYPE_OPERATOR) {
- const GDScriptParser::OperatorNode *op = static_cast<const GDScriptParser::OperatorNode *>(context.block->statements[i]);
- if (op->op == GDScriptParser::OperatorNode::OP_ASSIGN) {
-
- if (op->arguments.size() && op->arguments[0]->type == GDScriptParser::Node::TYPE_IDENTIFIER) {
-
- const GDScriptParser::IdentifierNode *id = static_cast<const GDScriptParser::IdentifierNode *>(op->arguments[0]);
-
- if (id->name == p_identifier) {
-
- last_assign = op->arguments[1];
- last_assign_line = context.block->statements[i]->line;
- }
+ if (blk->if_condition && blk->if_condition->type == GDScriptParser::Node::TYPE_OPERATOR && static_cast<const GDScriptParser::OperatorNode *>(blk->if_condition)->op == GDScriptParser::OperatorNode::OP_IS) {
+ //is used, check if identifier is in there! this helps resolve in blocks that are (if (identifier is value)): which are very common..
+ //super dirty hack, but very useful
+ //credit: Zylann
+ //TODO: this could be hacked to detect ANDed conditions too..
+ const GDScriptParser::OperatorNode *op = static_cast<const GDScriptParser::OperatorNode *>(blk->if_condition);
+ if (op->arguments[0]->type == GDScriptParser::Node::TYPE_IDENTIFIER && static_cast<const GDScriptParser::IdentifierNode *>(op->arguments[0])->name == p_identifier) {
+ //bingo
+ GDScriptCompletionContext c = p_context;
+ c.line = op->line;
+ c.block = blk;
+ if (_guess_expression_type(p_context, op->arguments[1], r_type)) {
+ return true;
}
}
}
- }
-
- //use the last assignment, (then backwards?)
- if (last_assign && last_assign_line != p_line) {
- return _guess_expression_type(context, last_assign, last_assign_line, r_type);
+ blk = blk->parent_block;
}
- return false;
-}
-
-static bool _guess_identifier_from_assignment_in_function(GDScriptCompletionContext &context, int p_src_line, const StringName &p_identifier, const StringName &p_function, GDScriptCompletionIdentifier &r_type) {
-
- const GDScriptParser::FunctionNode *func = NULL;
- for (int i = 0; i < context._class->functions.size(); i++) {
- if (context._class->functions[i]->name == p_function) {
- func = context._class->functions[i];
- break;
+ if (last_assigned_expression && last_assign_line != p_context.line) {
+ GDScriptCompletionContext c = p_context;
+ c.line = last_assign_line;
+ r_type.assigned_expression = last_assigned_expression;
+ if (_guess_expression_type(c, last_assigned_expression, r_type)) {
+ return true;
}
}
- if (!func)
- return false;
+ if (var_type.has_type) {
+ r_type.type = var_type;
+ return true;
+ }
- for (int i = 0; i < func->body->statements.size(); i++) {
+ if (p_context.function) {
+ for (int i = 0; i < p_context.function->arguments.size(); i++) {
+ if (p_context.function->arguments[i] == p_identifier) {
+ if (p_context.function->argument_types[i].has_type) {
+ r_type.type = p_context.function->argument_types[i];
+ return true;
+ }
- if (func->body->statements[i]->line == p_src_line) {
- break;
+ int def_from = p_context.function->arguments.size() - p_context.function->default_values.size();
+ if (i >= def_from) {
+ int def_idx = def_from - i;
+ if (p_context.function->default_values[def_idx]->type == GDScriptParser::Node::TYPE_OPERATOR) {
+ const GDScriptParser::OperatorNode *op = static_cast<const GDScriptParser::OperatorNode *>(p_context.function->default_values[def_idx]);
+ if (op->arguments.size() < 2) {
+ return false;
+ }
+ GDScriptCompletionContext c = p_context;
+ c.function = NULL;
+ c.block = NULL;
+ return _guess_expression_type(c, op->arguments[1], r_type);
+ }
+ }
+ break;
+ }
}
- if (func->body->statements[i]->type == GDScriptParser::BlockNode::TYPE_OPERATOR) {
- const GDScriptParser::OperatorNode *op = static_cast<const GDScriptParser::OperatorNode *>(func->body->statements[i]);
- if (op->op == GDScriptParser::OperatorNode::OP_ASSIGN) {
-
- if (op->arguments.size() && op->arguments[0]->type == GDScriptParser::Node::TYPE_IDENTIFIER) {
-
- const GDScriptParser::IdentifierNode *id = static_cast<const GDScriptParser::IdentifierNode *>(op->arguments[0]);
-
- if (id->name == p_identifier) {
-
- return _guess_expression_type(context, op->arguments[1], func->body->statements[i]->line, r_type);
+ GDScriptParser::DataType base_type = p_context._class->base_type;
+ while (base_type.has_type) {
+ switch (base_type.kind) {
+ case GDScriptParser::DataType::GDSCRIPT: {
+ Ref<GDScript> gds = base_type.script_type;
+ if (gds.is_valid() && gds->has_method(p_context.function->name)) {
+ GDScriptFunction *func = gds->get_member_functions()[p_context.function->name];
+ if (func) {
+ for (int i = 0; i < func->get_argument_count(); i++) {
+ if (func->get_argument_name(i) == p_identifier) {
+ r_type = _type_from_gdtype(func->get_argument_type(i));
+ return true;
+ }
+ }
+ }
+ Ref<GDScript> base_gds = gds->get_base_script();
+ if (base_gds.is_valid()) {
+ base_type.kind = GDScriptParser::DataType::GDSCRIPT;
+ base_type.script_type = base_gds;
+ } else {
+ base_type.kind = GDScriptParser::DataType::NATIVE;
+ base_type.native_type = gds->get_instance_base_type();
+ }
+ } else {
+ base_type.kind = GDScriptParser::DataType::NATIVE;
+ base_type.native_type = gds->get_instance_base_type();
}
- }
+ } break;
+ case GDScriptParser::DataType::NATIVE: {
+ List<MethodInfo> methods;
+ ClassDB::get_method_list(base_type.native_type, &methods);
+ ClassDB::get_virtual_methods(base_type.native_type, &methods);
+
+ for (List<MethodInfo>::Element *E = methods.front(); E; E = E->next()) {
+ if (E->get().name == p_context.function->name) {
+ MethodInfo &mi = E->get();
+ for (List<PropertyInfo>::Element *E = mi.arguments.front(); E; E = E->next()) {
+ if (E->get().name == p_identifier) {
+ r_type = _type_from_property(E->get());
+ return true;
+ }
+ }
+ }
+ }
+ base_type.has_type = false;
+ } break;
+ default: {
+ base_type.has_type = false;
+ } break;
}
}
}
- return false;
-}
-
-static bool _guess_identifier_type(GDScriptCompletionContext &context, int p_line, const StringName &p_identifier, GDScriptCompletionIdentifier &r_type, bool p_for_indexing) {
-
- //go to block first
+ // Check current class (including inheritance)
+ if (p_context._class) {
+ GDScriptCompletionIdentifier context_base;
+ context_base.value = p_context.base;
+ context_base.type.has_type = true;
+ context_base.type.kind = GDScriptParser::DataType::CLASS;
+ context_base.type.class_type = const_cast<GDScriptParser::ClassNode *>(p_context._class);
+ context_base.type.is_meta_type = p_context.function && p_context.function->_static;
- const GDScriptParser::BlockNode *block = context.block;
-
- while (block) {
-
- GDScriptCompletionContext c = context;
- c.block = block;
-
- if (_guess_identifier_type_in_block(c, p_line, p_identifier, r_type)) {
+ if (_guess_identifier_type_from_base(p_context, context_base, p_identifier, r_type)) {
return true;
}
-
- block = block->parent_block;
}
- //guess from argument if virtual
- if (context.function && context.function->name != StringName()) {
-
- int argindex = -1;
+ // Check named scripts
+ if (ScriptServer::is_global_class(p_identifier)) {
+ Ref<Script> scr = ResourceLoader::load(ScriptServer::get_global_class_path(p_identifier));
+ if (scr.is_valid()) {
+ r_type = _type_from_variant(scr);
+ r_type.type.is_meta_type = true;
+ return true;
+ }
+ return false;
+ }
- for (int i = 0; i < context.function->arguments.size(); i++) {
+ // Check ClassDB
+ if (ClassDB::class_exists(p_identifier)) {
+ r_type.type.has_type = true;
+ r_type.type.kind = GDScriptParser::DataType::NATIVE;
+ r_type.type.native_type = p_identifier;
+ if (Engine::get_singleton()->has_singleton(p_identifier)) {
+ r_type.type.is_meta_type = false;
+ r_type.value = Engine::get_singleton()->get_singleton_object(p_identifier);
+ } else {
+ r_type.type.is_meta_type = true;
+ int idx = GDScriptLanguage::get_singleton()->get_global_map()[p_identifier];
+ r_type.value = GDScriptLanguage::get_singleton()->get_global_array()[idx];
+ }
+ return true;
+ }
- if (context.function->arguments[i] == p_identifier) {
- argindex = i;
- break;
- }
+ // ClassDB again for underscore-prefixed classes
+ StringName under_id = String("_") + p_identifier;
+ if (ClassDB::class_exists(under_id)) {
+ r_type.type.has_type = true;
+ r_type.type.kind = GDScriptParser::DataType::NATIVE;
+ r_type.type.native_type = p_identifier;
+ if (Engine::get_singleton()->has_singleton(p_identifier)) {
+ r_type.type.is_meta_type = false;
+ r_type.value = Engine::get_singleton()->get_singleton_object(p_identifier);
+ } else {
+ r_type.type.is_meta_type = true;
+ int idx = GDScriptLanguage::get_singleton()->get_global_map()[p_identifier];
+ r_type.value = GDScriptLanguage::get_singleton()->get_global_array()[idx];
}
+ return true;
+ }
- if (argindex != -1) {
- GDScriptCompletionIdentifier id = _get_native_class(context);
- if (id.type == Variant::OBJECT && id.obj_type != StringName()) {
- //this kinda sucks but meh
+ // Check autoload singletons
+ if (GDScriptLanguage::get_singleton()->get_named_globals_map().has(p_identifier)) {
+ r_type = _type_from_variant(GDScriptLanguage::get_singleton()->get_named_globals_map()[p_identifier]);
+ return true;
+ }
- List<MethodInfo> vmethods;
- ClassDB::get_virtual_methods(id.obj_type, &vmethods);
- for (List<MethodInfo>::Element *E = vmethods.front(); E; E = E->next()) {
+ return false;
+}
- if (E->get().name == context.function->name && argindex < E->get().arguments.size()) {
+static bool _guess_identifier_type_from_base(const GDScriptCompletionContext &p_context, const GDScriptCompletionIdentifier &p_base, const StringName &p_identifier, GDScriptCompletionIdentifier &r_type) {
+ GDScriptParser::DataType base_type = p_base.type;
+ bool _static = base_type.is_meta_type;
+ while (base_type.has_type) {
+ switch (base_type.kind) {
+ case GDScriptParser::DataType::CLASS: {
+ if (base_type.class_type->constant_expressions.has(p_identifier)) {
+ GDScriptParser::ClassNode::Constant c = base_type.class_type->constant_expressions[p_identifier];
+ r_type.type = c.type;
+ if (c.expression->type == GDScriptParser::Node::TYPE_CONSTANT) {
+ r_type.value = static_cast<const GDScriptParser::ConstantNode *>(c.expression)->value;
+ }
+ return true;
+ }
- PropertyInfo arg = E->get().arguments[argindex];
+ if (!_static) {
+ for (int i = 0; i < base_type.class_type->variables.size(); i++) {
+ GDScriptParser::ClassNode::Member m = base_type.class_type->variables[i];
+ if (m.identifier == p_identifier) {
+ if (m.data_type.has_type) {
+ r_type.type = m.data_type;
+ return true;
+ }
+ if (m.expression) {
+ if (_guess_expression_type(p_context, m.expression, r_type)) {
+ return true;
+ }
+ if (m.expression->get_datatype().has_type) {
+ r_type.type = m.expression->get_datatype();
+ return true;
+ }
+ }
+ return false;
+ }
+ }
+ }
+ base_type = base_type.class_type->base_type;
+ } break;
+ case GDScriptParser::DataType::GDSCRIPT: {
+ Ref<GDScript> gds = base_type.script_type;
+ if (gds.is_valid()) {
+ if (gds->get_constants().has(p_identifier)) {
+ r_type = _type_from_variant(gds->get_constants()[p_identifier]);
+ return true;
+ }
+ if (!_static) {
+ const Set<StringName>::Element *m = gds->get_members().find(p_identifier);
+ if (m) {
+ r_type = _type_from_gdtype(gds->get_member_type(p_identifier));
+ return true;
+ }
+ }
+ Ref<GDScript> parent = gds->get_base_script();
+ if (parent.is_valid()) {
+ base_type.script_type = parent;
+ } else {
+ base_type.kind = GDScriptParser::DataType::NATIVE;
+ base_type.native_type = gds->get_instance_base_type();
+ }
+ } else {
+ return false;
+ }
+ } break;
+ case GDScriptParser::DataType::SCRIPT: {
+ Ref<Script> scr = base_type.script_type;
+ if (scr.is_valid()) {
+ Map<StringName, Variant> constants;
+ scr->get_constants(&constants);
+ if (constants.has(p_identifier)) {
+ r_type = _type_from_variant(constants[p_identifier]);
+ return true;
+ }
- int scp = String(arg.name).find(":");
- if (scp != -1) {
+ if (!_static) {
+ List<PropertyInfo> members;
+ scr->get_script_property_list(&members);
+ for (const List<PropertyInfo>::Element *E = members.front(); E; E = E->next()) {
+ const PropertyInfo &prop = E->get();
+ if (prop.name == p_identifier) {
+ r_type = _type_from_property(prop);
+ return true;
+ }
+ }
+ }
+ Ref<Script> parent = scr->get_base_script();
+ if (parent.is_valid()) {
+ base_type.script_type = parent;
+ } else {
+ base_type.kind = GDScriptParser::DataType::NATIVE;
+ base_type.native_type = scr->get_instance_base_type();
+ }
+ } else {
+ return false;
+ }
+ } break;
+ case GDScriptParser::DataType::NATIVE: {
+ StringName class_name = base_type.native_type;
+ if (!ClassDB::class_exists(class_name)) {
+ class_name = String("_") + class_name;
+ if (!ClassDB::class_exists(class_name)) {
+ return false;
+ }
+ }
- r_type.type = Variant::OBJECT;
- r_type.obj_type = String(arg.name).substr(scp + 1, String(arg.name).length());
- return true;
+ // Skip constants since they're all integers. Type does not matter because int has no members
+ List<PropertyInfo> props;
+ ClassDB::get_property_list(class_name, &props);
+ for (const List<PropertyInfo>::Element *E = props.front(); E; E = E->next()) {
+ const PropertyInfo &prop = E->get();
+ if (prop.name == p_identifier) {
+ StringName getter = ClassDB::get_property_getter(class_name, p_identifier);
+ if (getter != StringName()) {
+ MethodBind *g = ClassDB::get_method(class_name, getter);
+ if (g) {
+ r_type = _type_from_property(g->get_return_info());
+ return true;
+ }
} else {
-
- r_type.type = arg.type;
- if (arg.hint == PROPERTY_HINT_RESOURCE_TYPE)
- r_type.obj_type = arg.hint_string;
+ r_type = _type_from_property(prop);
return true;
}
+ break;
}
}
- }
+ return false;
+ } break;
+ case GDScriptParser::DataType::BUILTIN: {
+ Variant::CallError err;
+ Variant tmp = Variant::construct(base_type.builtin_type, NULL, 0, err);
+
+ if (err.error != Variant::CallError::CALL_OK) {
+ return false;
+ }
+ bool valid = false;
+ Variant res = tmp.get(p_identifier, &valid);
+ if (valid) {
+ r_type = _type_from_variant(res);
+ r_type.value = Variant();
+ r_type.type.is_constant = false;
+ return true;
+ }
+ return false;
+ } break;
+ default: {
+ return false;
+ } break;
}
}
- //guess type in constant
+ return false;
+}
- for (int i = 0; i < context._class->constant_expressions.size(); i++) {
+static bool _find_last_return_in_block(const GDScriptCompletionContext &p_context, int &r_last_return_line, const GDScriptParser::Node **r_last_returned_value) {
+ if (!p_context.block) {
+ return false;
+ }
- if (context._class->constant_expressions[i].identifier == p_identifier) {
+ for (int i = 0; i < p_context.block->statements.size(); i++) {
+ if (p_context.block->statements[i]->line < r_last_return_line) {
+ continue;
+ }
+ if (p_context.block->statements[i]->type != GDScriptParser::Node::TYPE_CONTROL_FLOW) {
+ continue;
+ }
- ERR_FAIL_COND_V(context._class->constant_expressions[i].expression->type != GDScriptParser::Node::TYPE_CONSTANT, false);
- r_type = _get_type_from_variant(static_cast<const GDScriptParser::ConstantNode *>(context._class->constant_expressions[i].expression)->value);
- return true;
+ const GDScriptParser::ControlFlowNode *cf = static_cast<const GDScriptParser::ControlFlowNode *>(p_context.block->statements[i]);
+ if (cf->cf_type == GDScriptParser::ControlFlowNode::CF_RETURN && cf->arguments.size() > 0) {
+ if (cf->line > r_last_return_line) {
+ r_last_return_line = cf->line;
+ *r_last_returned_value = cf->arguments[0];
+ }
}
}
- if (!(context.function && context.function->_static)) {
+ // Recurse into subblocks
+ for (int i = 0; i < p_context.block->sub_blocks.size(); i++) {
+ GDScriptCompletionContext c = p_context;
+ c.block = p_context.block->sub_blocks[i];
+ _find_last_return_in_block(c, r_last_return_line, r_last_returned_value);
+ }
- for (int i = 0; i < context._class->variables.size(); i++) {
+ return false;
+}
- if (context._class->variables[i].identifier == p_identifier) {
+static bool _guess_method_return_type_from_base(const GDScriptCompletionContext &p_context, const GDScriptCompletionIdentifier &p_base, const StringName &p_method, GDScriptCompletionIdentifier &r_type) {
+ GDScriptParser::DataType base_type = p_base.type;
+ bool _static = base_type.is_meta_type;
- if (context._class->variables[i]._export.type != Variant::NIL) {
+ if (_static && p_method == "new") {
+ r_type.type = base_type;
+ r_type.type.is_meta_type = false;
+ r_type.type.is_constant = false;
+ return true;
+ }
- r_type = _get_type_from_pinfo(context._class->variables[i]._export);
- return true;
- } else if (context._class->variables[i].expression) {
- if (p_line <= context._class->variables[i].line)
- return false;
+ while (base_type.has_type) {
+ switch (base_type.kind) {
+ case GDScriptParser::DataType::CLASS: {
+ if (!base_type.class_type) {
+ base_type.has_type = false;
+ break;
+ }
- bool rtype = _guess_expression_type(context, context._class->variables[i].expression, context._class->variables[i].line, r_type);
- if (rtype && r_type.type != Variant::NIL)
- return true;
- //return _guess_expression_type(context,context._class->variables[i].expression,context._class->variables[i].line,r_type);
+ for (int i = 0; i < base_type.class_type->static_functions.size(); i++) {
+ if (base_type.class_type->static_functions[i]->name == p_method) {
+ int last_return_line = -1;
+ const GDScriptParser::Node *last_returned_value = NULL;
+ GDScriptCompletionContext c = p_context;
+ c._class = base_type.class_type;
+ c.function = base_type.class_type->static_functions[i];
+ c.block = c.function->body;
+
+ _find_last_return_in_block(c, last_return_line, &last_returned_value);
+ if (last_returned_value) {
+ c.line = c.block->end_line;
+ return _guess_expression_type(c, last_returned_value, r_type);
+ }
+ }
+ }
+ if (!_static) {
+ for (int i = 0; i < base_type.class_type->functions.size(); i++) {
+ if (base_type.class_type->functions[i]->name == p_method) {
+ int last_return_line = -1;
+ const GDScriptParser::Node *last_returned_value = NULL;
+ GDScriptCompletionContext c = p_context;
+ c._class = base_type.class_type;
+ c.function = base_type.class_type->functions[i];
+ c.block = c.function->body;
+
+ _find_last_return_in_block(c, last_return_line, &last_returned_value);
+ if (last_returned_value) {
+ c.line = c.block->end_line;
+ return _guess_expression_type(c, last_returned_value, r_type);
+ }
+ }
+ }
}
- //try to guess from assignment in constructor or _ready
- if (_guess_identifier_from_assignment_in_function(context, p_line + 1, p_identifier, "_ready", r_type))
- return true;
- if (_guess_identifier_from_assignment_in_function(context, p_line + 1, p_identifier, "_enter_tree", r_type))
- return true;
- if (_guess_identifier_from_assignment_in_function(context, p_line + 1, p_identifier, "_init", r_type))
+ base_type = base_type.class_type->base_type;
+ } break;
+ case GDScriptParser::DataType::GDSCRIPT: {
+ Ref<GDScript> gds = base_type.script_type;
+ if (gds.is_valid()) {
+ if (gds->get_member_functions().has(p_method)) {
+ r_type = _type_from_gdtype(gds->get_member_functions()[p_method]->get_return_type());
+ return true;
+ }
+ Ref<GDScript> base_script = gds->get_base_script();
+ if (base_script.is_valid()) {
+ base_type.script_type = base_script;
+ } else {
+ base_type.kind = GDScriptParser::DataType::NATIVE;
+ base_type.native_type = gds->get_instance_base_type();
+ }
+ } else {
+ return false;
+ }
+ } break;
+ case GDScriptParser::DataType::SCRIPT: {
+ Ref<Script> scr = base_type.script_type;
+ if (scr.is_valid()) {
+ List<MethodInfo> methods;
+ scr->get_script_method_list(&methods);
+ for (List<MethodInfo>::Element *E = methods.front(); E; E = E->next()) {
+ MethodInfo &mi = E->get();
+ if (mi.name == p_method) {
+ r_type = _type_from_property(mi.return_val);
+ return true;
+ }
+ }
+ Ref<Script> base_script = scr->get_base_script();
+ if (base_script.is_valid()) {
+ base_type.script_type = base_script;
+ } else {
+ base_type.kind = GDScriptParser::DataType::NATIVE;
+ base_type.native_type = scr->get_instance_base_type();
+ }
+ } else {
+ return false;
+ }
+ } break;
+ case GDScriptParser::DataType::NATIVE: {
+ StringName native = base_type.native_type;
+ if (!ClassDB::class_exists(native)) {
+ native = String("_") + native;
+ if (!ClassDB::class_exists(native)) {
+ return false;
+ }
+ }
+ MethodBind *mb = ClassDB::get_method(native, p_method);
+ if (mb) {
+ r_type = _type_from_property(mb->get_return_info());
return true;
+ }
+ return false;
+ } break;
+ case GDScriptParser::DataType::BUILTIN: {
+ Variant::CallError err;
+ Variant tmp = Variant::construct(base_type.builtin_type, NULL, 0, err);
+ if (err.error != Variant::CallError::CALL_OK) {
+ return false;
+ }
+
+ List<MethodInfo> methods;
+ tmp.get_method_list(&methods);
+ for (List<MethodInfo>::Element *E = methods.front(); E; E = E->next()) {
+ MethodInfo &mi = E->get();
+ if (mi.name == p_method) {
+ r_type = _type_from_property(mi.return_val);
+ return true;
+ }
+ }
+ return false;
+ } break;
+ default: {
return false;
}
}
}
+ return false;
+}
- //autoloads as singletons
- List<PropertyInfo> props;
- ProjectSettings::get_singleton()->get_property_list(&props);
-
- for (List<PropertyInfo>::Element *E = props.front(); E; E = E->next()) {
-
- String s = E->get().name;
- if (!s.begins_with("autoload/"))
- continue;
- String name = s.get_slice("/", 1);
- if (name == String(p_identifier)) {
+static String _make_arguments_hint(const MethodInfo &p_info, int p_arg_idx) {
- String path = ProjectSettings::get_singleton()->get(s);
- if (path.begins_with("*")) {
- String script = path.substr(1, path.length());
+ String arghint = _get_visual_datatype(p_info.return_val, false) + " " + p_info.name + "(";
- if (!script.ends_with(".gd")) {
- //not a script, try find the script anyway,
- //may have some success
- script = script.get_basename() + ".gd";
- }
+ int def_args = p_info.arguments.size() - p_info.default_arguments.size();
+ int i = 0;
+ for (const List<PropertyInfo>::Element *E = p_info.arguments.front(); E; E = E->next()) {
+ if (i > 0) {
+ arghint += ", ";
+ } else {
+ arghint += " ";
+ }
- if (FileAccess::exists(script)) {
+ if (i == p_arg_idx) {
+ arghint += String::chr(0xFFFF);
+ }
+ arghint += _get_visual_datatype(E->get(), true) + " " + E->get().name;
- //print_line("is a script");
+ if (i - def_args >= 0) {
+ arghint += String(" = ") + p_info.default_arguments[i - def_args].get_construct_string();
+ }
- Ref<Script> scr;
- if (ScriptCodeCompletionCache::get_singleton())
- scr = ScriptCodeCompletionCache::get_singleton()->get_cached_resource(script);
- else
- scr = ResourceLoader::load(script);
+ if (i == p_arg_idx) {
+ arghint += String::chr(0xFFFF);
+ }
- r_type.obj_type = "Node";
- r_type.type = Variant::OBJECT;
- r_type.script = scr;
- r_type.value = Variant();
+ i++;
+ }
- return true;
- }
- }
+ if (p_info.flags & METHOD_FLAG_VARARG) {
+ if (p_info.arguments.size() > 0) {
+ arghint += ", ";
+ } else {
+ arghint += " ";
}
+ if (p_arg_idx >= p_info.arguments.size()) {
+ arghint += String::chr(0xFFFF);
+ }
+ arghint += "...";
+ if (p_arg_idx >= p_info.arguments.size()) {
+ arghint += String::chr(0xFFFF);
+ }
+ }
+ if (p_info.arguments.size() > 0 || (p_info.flags & METHOD_FLAG_VARARG)) {
+ arghint += " ";
}
- //global
- for (Map<StringName, int>::Element *E = GDScriptLanguage::get_singleton()->get_global_map().front(); E; E = E->next()) {
- if (E->key() == p_identifier) {
+ arghint += ")";
- r_type = _get_type_from_variant(GDScriptLanguage::get_singleton()->get_global_array()[E->get()], !p_for_indexing);
- return true;
- }
- }
- return false;
+ return arghint;
}
-static void _find_identifiers_in_block(GDScriptCompletionContext &context, int p_line, bool p_only_functions, Set<String> &result) {
+static String _make_arguments_hint(const GDScriptParser::FunctionNode *p_function, int p_arg_idx) {
- if (p_only_functions)
- return;
+ String arghint = p_function->return_type.to_string() + " " + p_function->name.operator String() + "(";
- for (int i = 0; i < context.block->statements.size(); i++) {
+ int def_args = p_function->arguments.size() - p_function->default_values.size();
+ for (int i = 0; i < p_function->arguments.size(); i++) {
+ if (i > 0) {
+ arghint += ", ";
+ } else {
+ arghint += " ";
+ }
- GDScriptParser::Node *statement = context.block->statements[i];
- if (statement->line > p_line)
- continue;
+ if (i == p_arg_idx) {
+ arghint += String::chr(0xFFFF);
+ }
+ arghint += p_function->argument_types[i].to_string() + " " + p_function->arguments[i].operator String();
+
+ if (i - def_args >= 0) {
+ String def_val = "<unknown>";
+ if (p_function->default_values[i - def_args] && p_function->default_values[i - def_args]->type == GDScriptParser::Node::TYPE_OPERATOR) {
+ const GDScriptParser::OperatorNode *assign = static_cast<const GDScriptParser::OperatorNode *>(p_function->default_values[i - def_args]);
+
+ if (assign->arguments.size() >= 2) {
+ if (assign->arguments[1]->type == GDScriptParser::Node::TYPE_CONSTANT) {
+ const GDScriptParser::ConstantNode *cn = static_cast<const GDScriptParser::ConstantNode *>(assign->arguments[1]);
+ def_val = cn->value.get_construct_string();
+ } else if (assign->arguments[1]->type == GDScriptParser::Node::TYPE_IDENTIFIER) {
+ const GDScriptParser::IdentifierNode *id = static_cast<const GDScriptParser::IdentifierNode *>(assign->arguments[1]);
+ def_val = id->name.operator String();
+ }
+ }
+ }
+ arghint += " = " + def_val;
+ }
+ if (i == p_arg_idx) {
+ arghint += String::chr(0xFFFF);
+ }
+ }
- GDScriptParser::BlockNode::Type statementType = statement->type;
- if (statementType == GDScriptParser::BlockNode::TYPE_LOCAL_VAR) {
+ if (p_function->arguments.size() > 0) {
+ arghint += " ";
+ }
+ arghint += ")";
- const GDScriptParser::LocalVarNode *lv = static_cast<const GDScriptParser::LocalVarNode *>(statement);
- result.insert(lv->name.operator String());
- } else if (statementType == GDScriptParser::BlockNode::TYPE_CONTROL_FLOW) {
+ return arghint;
+}
- const GDScriptParser::ControlFlowNode *cf = static_cast<const GDScriptParser::ControlFlowNode *>(statement);
- if (cf->cf_type == GDScriptParser::ControlFlowNode::CF_FOR) {
+static void _find_enumeration_candidates(const String p_enum_hint, Set<String> &r_result) {
- const GDScriptParser::IdentifierNode *id = static_cast<const GDScriptParser::IdentifierNode *>(cf->arguments[0]);
- result.insert(id->name.operator String());
+ if (p_enum_hint.find(".") == -1) {
+ // Global constant
+ StringName current_enum = p_enum_hint;
+ for (int i = 0; i < GlobalConstants::get_global_constant_count(); i++) {
+ if (GlobalConstants::get_global_constant_enum(i) == current_enum) {
+ r_result.insert(GlobalConstants::get_global_constant_name(i));
}
}
- }
-}
-
-static void _find_identifiers_in_class(GDScriptCompletionContext &context, bool p_static, bool p_only_functions, Set<String> &result) {
+ } else {
+ String class_name = p_enum_hint.get_slice(".", 0);
+ String enum_name = p_enum_hint.get_slice(".", 1);
- if (!p_static && !p_only_functions) {
+ if (!ClassDB::class_exists(class_name)) {
+ return;
+ }
- for (int i = 0; i < context._class->variables.size(); i++) {
- result.insert(context._class->variables[i].identifier);
+ List<StringName> enum_constants;
+ ClassDB::get_enum_constants(class_name, enum_name, &enum_constants);
+ for (List<StringName>::Element *E = enum_constants.front(); E; E = E->next()) {
+ String candidate = class_name + "." + E->get();
+ r_result.insert(candidate);
}
}
- if (!p_only_functions) {
+}
- for (int i = 0; i < context._class->constant_expressions.size(); i++) {
- result.insert(context._class->constant_expressions[i].identifier);
+static void _find_identifiers_in_block(const GDScriptCompletionContext &p_context, Set<String> &r_result) {
+ for (Map<StringName, GDScriptParser::LocalVarNode *>::Element *E = p_context.block->variables.front(); E; E = E->next()) {
+ if (E->get()->line < p_context.line) {
+ r_result.insert(E->key().operator String());
}
+ }
+ if (p_context.block->parent_block) {
+ GDScriptCompletionContext c = p_context;
+ c.block = p_context.block->parent_block;
+ _find_identifiers_in_block(c, r_result);
+ }
+}
+
+static void _find_identifiers_in_base(const GDScriptCompletionContext &p_context, const GDScriptCompletionIdentifier &p_base, bool p_only_functions, Set<String> &r_result);
- for (int i = 0; i < context._class->subclasses.size(); i++) {
- result.insert(context._class->subclasses[i]->name);
+static void _find_identifiers_in_class(const GDScriptCompletionContext &p_context, bool p_static, bool p_only_functions, bool p_parent_only, Set<String> &r_result) {
+ if (!p_parent_only) {
+ if (!p_static && !p_only_functions) {
+ for (int i = 0; i < p_context._class->variables.size(); i++) {
+ r_result.insert(p_context._class->variables[i].identifier);
+ }
}
- }
- for (int i = 0; i < context._class->static_functions.size(); i++) {
- if (context._class->static_functions[i]->arguments.size())
- result.insert(context._class->static_functions[i]->name.operator String() + "(");
- else
- result.insert(context._class->static_functions[i]->name.operator String() + "()");
- }
+ if (!p_only_functions) {
+ for (Map<StringName, GDScriptParser::ClassNode::Constant>::Element *E = p_context._class->constant_expressions.front(); E; E = E->next()) {
+ r_result.insert(E->key());
+ }
+ for (int i = 0; i < p_context._class->subclasses.size(); i++) {
+ r_result.insert(p_context._class->subclasses[i]->name);
+ }
+ }
- if (!p_static) {
+ for (int i = 0; i < p_context._class->static_functions.size(); i++) {
+ if (p_context._class->static_functions[i]->arguments.size()) {
+ r_result.insert(p_context._class->static_functions[i]->name.operator String() + "(");
+ } else {
+ r_result.insert(p_context._class->static_functions[i]->name.operator String() + "()");
+ }
+ }
- for (int i = 0; i < context._class->functions.size(); i++) {
- if (context._class->functions[i]->arguments.size())
- result.insert(context._class->functions[i]->name.operator String() + "(");
- else
- result.insert(context._class->functions[i]->name.operator String() + "()");
+ if (!p_static) {
+ for (int i = 0; i < p_context._class->functions.size(); i++) {
+ if (p_context._class->functions[i]->arguments.size()) {
+ r_result.insert(p_context._class->functions[i]->name.operator String() + "(");
+ } else {
+ r_result.insert(p_context._class->functions[i]->name.operator String() + "()");
+ }
+ }
}
}
- //globals
-
- Ref<Reference> base = _get_parent_class(context);
+ // Parents
+ GDScriptCompletionIdentifier base_type;
+ base_type.type = p_context._class->base_type;
+ base_type.type.is_meta_type = p_static;
+ base_type.value = p_context.base;
- while (true) {
+ GDScriptCompletionContext c = p_context;
+ c.block = NULL;
+ c.function = NULL;
- Ref<GDScript> script = base;
- Ref<GDScriptNativeClass> nc = base;
- if (script.is_valid()) {
+ _find_identifiers_in_base(c, base_type, p_only_functions, r_result);
+}
- if (!p_static && !p_only_functions) {
- for (const Set<StringName>::Element *E = script->get_members().front(); E; E = E->next()) {
- result.insert(E->get().operator String());
+static void _find_identifiers_in_base(const GDScriptCompletionContext &p_context, const GDScriptCompletionIdentifier &p_base, bool p_only_functions, Set<String> &r_result) {
+ GDScriptParser::DataType base_type = p_base.type;
+ bool _static = base_type.is_meta_type;
+
+ if (_static && base_type.kind != GDScriptParser::DataType::BUILTIN) {
+ r_result.insert("new(");
+ }
+
+ while (base_type.has_type) {
+ switch (base_type.kind) {
+ case GDScriptParser::DataType::CLASS: {
+ GDScriptCompletionContext c = p_context;
+ c._class = base_type.class_type;
+ c.block = NULL;
+ c.function = NULL;
+ _find_identifiers_in_class(c, _static, p_only_functions, false, r_result);
+ base_type = base_type.class_type->base_type;
+ } break;
+ case GDScriptParser::DataType::GDSCRIPT: {
+ Ref<GDScript> script = base_type.script_type;
+ if (script.is_valid()) {
+ if (!_static && !p_only_functions) {
+ for (const Set<StringName>::Element *E = script->get_members().front(); E; E = E->next()) {
+ r_result.insert(E->get().operator String());
+ }
+ }
+ if (!p_only_functions) {
+ for (const Map<StringName, Variant>::Element *E = script->get_constants().front(); E; E = E->next()) {
+ r_result.insert(E->key().operator String());
+ }
+ }
+ for (const Map<StringName, GDScriptFunction *>::Element *E = script->get_member_functions().front(); E; E = E->next()) {
+ if (!_static || E->get()->is_static()) {
+ if (E->get()->get_argument_count()) {
+ r_result.insert(E->key().operator String() + "(");
+ } else {
+ r_result.insert(E->key().operator String() + "()");
+ }
+ }
+ }
+ if (!p_only_functions) {
+ for (const Map<StringName, Ref<GDScript> >::Element *E = script->get_subclasses().front(); E; E = E->next()) {
+ r_result.insert(E->key().operator String());
+ }
+ }
+ base_type = GDScriptParser::DataType();
+ if (script->get_base().is_valid()) {
+ base_type.has_type = true;
+ base_type.kind = GDScriptParser::DataType::GDSCRIPT;
+ base_type.script_type = script->get_base();
+ } else {
+ base_type.has_type = script->get_instance_base_type() != StringName();
+ base_type.kind = GDScriptParser::DataType::NATIVE;
+ base_type.script_type = script->get_instance_base_type();
+ }
+ } else {
+ return;
}
- }
+ } break;
+ case GDScriptParser::DataType::SCRIPT: {
+ Ref<Script> scr = base_type.script_type;
+ if (scr.is_valid()) {
+ if (!_static && !p_only_functions) {
+ List<PropertyInfo> members;
+ scr->get_script_property_list(&members);
+ for (List<PropertyInfo>::Element *E = members.front(); E; E = E->next()) {
+ r_result.insert(E->get().name);
+ }
+ }
+ if (!p_only_functions) {
+ Map<StringName, Variant> constants;
+ scr->get_constants(&constants);
+ for (Map<StringName, Variant>::Element *E = constants.front(); E; E = E->next()) {
+ r_result.insert(E->key().operator String());
+ }
+ }
- if (!p_only_functions) {
- for (const Map<StringName, Variant>::Element *E = script->get_constants().front(); E; E = E->next()) {
- result.insert(E->key().operator String());
- }
- }
+ List<MethodInfo> methods;
+ scr->get_script_method_list(&methods);
+ for (List<MethodInfo>::Element *E = methods.front(); E; E = E->next()) {
+ if (E->get().arguments.size()) {
+ r_result.insert(E->get().name + "(");
+ } else {
+ r_result.insert(E->get().name + "()");
+ }
+ }
- for (const Map<StringName, GDScriptFunction *>::Element *E = script->get_member_functions().front(); E; E = E->next()) {
- if (!p_static || E->get()->is_static()) {
- if (E->get()->get_argument_count())
- result.insert(E->key().operator String() + "(");
- else
- result.insert(E->key().operator String() + "()");
+ Ref<Script> base_script = scr->get_base_script();
+ if (base_script.is_valid()) {
+ base_type.script_type = base_script;
+ } else {
+ base_type.kind = GDScriptParser::DataType::NATIVE;
+ base_type.native_type = scr->get_instance_base_type();
+ }
+ } else {
+ return;
}
- }
-
- if (!p_only_functions) {
- for (const Map<StringName, Ref<GDScript> >::Element *E = script->get_subclasses().front(); E; E = E->next()) {
- result.insert(E->key().operator String());
+ } break;
+ case GDScriptParser::DataType::NATIVE: {
+ StringName type = base_type.native_type;
+ if (!ClassDB::class_exists(type)) {
+ type = String("_") + type;
+ if (!ClassDB::class_exists(type)) {
+ return;
+ }
}
- }
- base = script->get_base();
- if (base.is_null())
- base = script->get_native();
- } else if (nc.is_valid()) {
+ if (!p_only_functions) {
+ List<String> constants;
+ ClassDB::get_integer_constant_list(type, &constants);
+ for (List<String>::Element *E = constants.front(); E; E = E->next()) {
+ r_result.insert(E->get());
+ }
- StringName type = nc->get_name();
+ if (!_static) {
+ List<PropertyInfo> pinfo;
+ ClassDB::get_property_list(type, &pinfo);
+ for (List<PropertyInfo>::Element *E = pinfo.front(); E; E = E->next()) {
+ if (E->get().usage & (PROPERTY_USAGE_GROUP | PROPERTY_USAGE_CATEGORY)) {
+ continue;
+ }
+ if (E->get().name.find("/") != -1) {
+ continue;
+ }
+ r_result.insert(E->get().name);
+ }
+ }
+ }
- if (!p_only_functions) {
+ if (!_static) {
+ List<MethodInfo> methods;
+ ClassDB::get_method_list(type, &methods, false, true);
+ for (List<MethodInfo>::Element *E = methods.front(); E; E = E->next()) {
+ if (E->get().name.begins_with("_")) {
+ continue;
+ }
+ if (E->get().arguments.size()) {
+ r_result.insert(E->get().name + "(");
+ } else {
+ r_result.insert(E->get().name + "()");
+ }
+ }
+ }
- List<String> constants;
- ClassDB::get_integer_constant_list(type, &constants);
- for (List<String>::Element *E = constants.front(); E; E = E->next()) {
- result.insert(E->get());
+ return;
+ } break;
+ case GDScriptParser::DataType::BUILTIN: {
+ Variant::CallError err;
+ Variant tmp = Variant::construct(base_type.builtin_type, NULL, 0, err);
+ if (err.error != Variant::CallError::CALL_OK) {
+ return;
}
- List<PropertyInfo> pinfo;
+ if (!p_only_functions) {
+ List<PropertyInfo> members;
+ tmp.get_property_list(&members);
- ClassDB::get_property_list(type, &pinfo);
+ for (List<PropertyInfo>::Element *E = members.front(); E; E = E->next()) {
+ if (String(E->get().name).find("/") == -1) {
+ r_result.insert(E->get().name);
+ }
+ }
+ }
- for (List<PropertyInfo>::Element *E = pinfo.front(); E; E = E->next()) {
- if (E->get().usage & (PROPERTY_USAGE_GROUP | PROPERTY_USAGE_CATEGORY))
- continue;
- if (String(E->get().name).find("/") != -1)
- continue;
- result.insert(E->get().name);
+ List<MethodInfo> methods;
+ tmp.get_method_list(&methods);
+ for (List<MethodInfo>::Element *E = methods.front(); E; E = E->next()) {
+ if (E->get().arguments.size()) {
+ r_result.insert(E->get().name + "(");
+ } else {
+ r_result.insert(E->get().name + "()");
+ }
}
- }
- List<MethodInfo> methods;
- ClassDB::get_method_list(type, &methods, false, true);
- for (List<MethodInfo>::Element *E = methods.front(); E; E = E->next()) {
- if (E->get().name.begins_with("_"))
- continue;
- if (E->get().arguments.size())
- result.insert(E->get().name + "(");
- else
- result.insert(E->get().name + "()");
- }
- break;
- } else
- break;
+ return;
+ } break;
+ default: {
+ return;
+ } break;
+ }
}
}
-static void _find_identifiers(GDScriptCompletionContext &context, int p_line, bool p_only_functions, Set<String> &result) {
+static void _find_identifiers(const GDScriptCompletionContext &p_context, bool p_only_functions, Set<String> &r_result) {
- const GDScriptParser::BlockNode *block = context.block;
+ const GDScriptParser::BlockNode *block = p_context.block;
- if (context.function) {
+ if (p_context.function) {
- const GDScriptParser::FunctionNode *f = context.function;
+ const GDScriptParser::FunctionNode *f = p_context.function;
for (int i = 0; i < f->arguments.size(); i++) {
- result.insert(f->arguments[i].operator String());
+ r_result.insert(f->arguments[i].operator String());
}
}
- while (block) {
-
- GDScriptCompletionContext c = context;
+ if (!p_only_functions && block) {
+ GDScriptCompletionContext c = p_context;
c.block = block;
-
- _find_identifiers_in_block(c, p_line, p_only_functions, result);
- block = block->parent_block;
+ _find_identifiers_in_block(c, r_result);
}
- const GDScriptParser::ClassNode *clss = context._class;
-
- bool _static = context.function && context.function->_static;
+ const GDScriptParser::ClassNode *clss = p_context._class;
+ bool _static = !p_context.function || p_context.function->_static;
while (clss) {
- GDScriptCompletionContext c = context;
+ GDScriptCompletionContext c = p_context;
c._class = clss;
c.block = NULL;
c.function = NULL;
- _find_identifiers_in_class(c, _static, p_only_functions, result);
+ _find_identifiers_in_class(c, _static, p_only_functions, false, r_result);
+ _static = true;
clss = clss->owner;
}
for (int i = 0; i < GDScriptFunctions::FUNC_MAX; i++) {
-
- result.insert(GDScriptFunctions::get_func_name(GDScriptFunctions::Function(i)));
+ MethodInfo mi = GDScriptFunctions::get_info(GDScriptFunctions::Function(i));
+ if (mi.arguments.size() || (mi.flags & METHOD_FLAG_VARARG)) {
+ r_result.insert(String(GDScriptFunctions::get_func_name(GDScriptFunctions::Function(i))) + "(");
+ } else {
+ r_result.insert(String(GDScriptFunctions::get_func_name(GDScriptFunctions::Function(i))) + "()");
+ }
}
static const char *_type_names[Variant::VARIANT_MAX] = {
@@ -1526,667 +2091,372 @@ static void _find_identifiers(GDScriptCompletionContext &context, int p_line, bo
};
for (int i = 0; i < Variant::VARIANT_MAX; i++) {
- result.insert(_type_names[i]);
+ r_result.insert(_type_names[i]);
}
- List<String> reserved_words;
- GDScriptLanguage::get_singleton()->get_reserved_words(&reserved_words);
+ static const char *_keywords[] = {
+ "and", "in", "not", "or", "false", "PI", "TAU", "INF", "NAN", "self", "true", "as", "assert",
+ "breakpoint", "class", "extends", "is", "func", "preload", "setget", "signal", "tool", "yield",
+ "const", "enum", "export", "onready", "static", "var", "break", "continue", "if", "elif",
+ "else", "for", "pass", "return", "match", "while", "remote", "sync", "master", "slave",
+ "remotesync", "mastersync", "slavesync",
+ 0
+ };
- for (List<String>::Element *E = reserved_words.front(); E; E = E->next()) {
- result.insert(E->get());
+ const char **kw = _keywords;
+ while (*kw) {
+ r_result.insert(*kw);
+ kw++;
}
- //autoload singletons
+ // Autoload singletons
List<PropertyInfo> props;
ProjectSettings::get_singleton()->get_property_list(&props);
-
for (List<PropertyInfo>::Element *E = props.front(); E; E = E->next()) {
-
String s = E->get().name;
- if (!s.begins_with("autoload/"))
+ if (!s.begins_with("autoload/")) {
continue;
- String name = s.get_slice("/", 1);
+ }
String path = ProjectSettings::get_singleton()->get(s);
if (path.begins_with("*")) {
- result.insert(name);
+ r_result.insert(s.get_slice("/", 1));
}
}
- for (const Map<StringName, int>::Element *E = GDScriptLanguage::get_singleton()->get_global_map().front(); E; E = E->next()) {
- result.insert(E->key().operator String());
- }
-}
-
-static String _get_visual_datatype(const PropertyInfo &p_info, bool p_isarg = true) {
-
- String n = p_info.name;
- int idx = n.find(":");
- if (idx != -1) {
- return n.substr(idx + 1, n.length());
+ // Named scripts
+ List<StringName> named_scripts;
+ ScriptServer::get_global_class_list(&named_scripts);
+ for (List<StringName>::Element *E = named_scripts.front(); E; E = E->next()) {
+ r_result.insert(E->get().operator String());
}
- if (p_info.type == Variant::OBJECT && p_info.hint == PROPERTY_HINT_RESOURCE_TYPE)
- return p_info.hint_string;
- if (p_info.type == Variant::NIL) {
- if (p_isarg)
- return "var";
- else
- return "void";
+ // Native classes
+ for (const Map<StringName, int>::Element *E = GDScriptLanguage::get_singleton()->get_global_map().front(); E; E = E->next()) {
+ r_result.insert(E->key().operator String());
}
-
- return Variant::get_type_name(p_info.type);
}
-static void _make_function_hint(const GDScriptParser::FunctionNode *p_func, int p_argidx, String &arghint) {
-
- arghint = "func " + p_func->name + "(";
- for (int i = 0; i < p_func->arguments.size(); i++) {
- if (i > 0)
- arghint += ", ";
- else
- arghint += " ";
-
- if (i == p_argidx) {
- arghint += String::chr(0xFFFF);
- }
- arghint += p_func->arguments[i].operator String();
- int deffrom = p_func->arguments.size() - p_func->default_values.size();
-
- if (i >= deffrom) {
- int defidx = deffrom - i;
-
- if (defidx >= 0 && defidx < p_func->default_values.size()) {
-
- if (p_func->default_values[defidx]->type == GDScriptParser::Node::TYPE_OPERATOR) {
-
- const GDScriptParser::OperatorNode *op = static_cast<const GDScriptParser::OperatorNode *>(p_func->default_values[defidx]);
- if (op->op == GDScriptParser::OperatorNode::OP_ASSIGN) {
- const GDScriptParser::ConstantNode *cn = static_cast<const GDScriptParser::ConstantNode *>(op->arguments[1]);
- arghint += "=" + cn->value.get_construct_string();
+static void _find_call_arguments(const GDScriptCompletionContext &p_context, const GDScriptCompletionIdentifier &p_base, const StringName &p_method, int p_argidx, bool p_static, Set<String> &r_result, String &r_arghint) {
+ Variant base = p_base.value;
+ GDScriptParser::DataType base_type = p_base.type;
+ bool _static = false;
+
+ while (base_type.has_type) {
+ switch (base_type.kind) {
+ case GDScriptParser::DataType::CLASS: {
+ for (int i = 0; i < base_type.class_type->static_functions.size(); i++) {
+ if (base_type.class_type->static_functions[i]->name == p_method) {
+ r_arghint = _make_arguments_hint(base_type.class_type->static_functions[i], p_argidx);
+ return;
+ }
+ }
+ if (!_static) {
+ for (int i = 0; i < base_type.class_type->functions.size(); i++) {
+ if (base_type.class_type->functions[i]->name == p_method) {
+ r_arghint = _make_arguments_hint(base_type.class_type->functions[i], p_argidx);
+ return;
+ }
}
- } else {
}
- }
- }
-
- if (i == p_argidx) {
- arghint += String::chr(0xFFFF);
- }
- }
- if (p_func->arguments.size() > 0)
- arghint += " ";
- arghint += ")";
-}
-
-void get_directory_contents(EditorFileSystemDirectory *p_dir, Set<String> &r_list) {
-
- for (int i = 0; i < p_dir->get_subdir_count(); i++) {
- get_directory_contents(p_dir->get_subdir(i), r_list);
- }
-
- for (int i = 0; i < p_dir->get_file_count(); i++) {
- r_list.insert("\"" + p_dir->get_file_path(i) + "\"");
- }
-}
-
-static void _find_type_arguments(GDScriptCompletionContext &context, const GDScriptParser::Node *p_node, int p_line, const StringName &p_method, const GDScriptCompletionIdentifier &id, int p_argidx, Set<String> &result, bool &r_forced, String &arghint) {
-
- //print_line("find type arguments?");
- if (id.type == Variant::OBJECT && id.obj_type != StringName()) {
-
- MethodBind *m = ClassDB::get_method(id.obj_type, p_method);
- if (!m) {
- //not in static method, see script
-
- //print_line("not in static: "+String(p_method));
- Ref<GDScript> on_script;
-
- if (id.value.get_type()) {
- Object *obj = id.value;
- GDScript *scr = Object::cast_to<GDScript>(obj);
- if (scr) {
- while (scr) {
+ if ((p_method == "connect" || p_method == "emit_signal") && p_argidx == 0) {
+ for (int i = 0; i < base_type.class_type->_signals.size(); i++) {
+ r_result.insert("\"" + base_type.class_type->_signals[i].name.operator String() + "\"");
+ }
+ }
- for (const Map<StringName, GDScriptFunction *>::Element *E = scr->get_member_functions().front(); E; E = E->next()) {
- if (E->get()->is_static() && p_method == E->get()->get_name()) {
- arghint = "static func " + String(p_method) + "(";
- for (int i = 0; i < E->get()->get_argument_count(); i++) {
- if (i > 0)
- arghint += ", ";
- else
- arghint += " ";
- if (i == p_argidx) {
- arghint += String::chr(0xFFFF);
- }
- arghint += "var " + E->get()->get_argument_name(i);
- int deffrom = E->get()->get_argument_count() - E->get()->get_default_argument_count();
- if (i >= deffrom) {
- int defidx = deffrom - i;
- if (defidx >= 0 && defidx < E->get()->get_default_argument_count()) {
- arghint += "=" + E->get()->get_default_argument(defidx).get_construct_string();
- }
- }
- if (i == p_argidx) {
- arghint += String::chr(0xFFFF);
- }
- }
- arghint += ")";
- return; //found
- }
+ base_type = base_type.class_type->base_type;
+ } break;
+ case GDScriptParser::DataType::GDSCRIPT: {
+ Ref<GDScript> gds = base_type.script_type;
+ if (gds.is_valid()) {
+ if ((p_method == "connect" || p_method == "emit_signal") && p_argidx == 0) {
+ List<MethodInfo> signals;
+ gds->get_script_signal_list(&signals);
+ for (List<MethodInfo>::Element *E = signals.front(); E; E = E->next()) {
+ r_result.insert("\"" + E->get().name + "\"");
}
-
- if (scr->get_base().is_valid())
- scr = scr->get_base().ptr();
- else
- scr = NULL;
+ }
+ Ref<GDScript> base_script = gds->get_base_script();
+ if (base_script.is_valid()) {
+ base_type.script_type = base_script;
+ } else {
+ base_type.kind = GDScriptParser::DataType::NATIVE;
+ base_type.native_type = gds->get_instance_base_type();
}
} else {
- if (obj) {
- on_script = obj->get_script();
+ return;
+ }
+ } break;
+ case GDScriptParser::DataType::NATIVE: {
+ StringName class_name = base_type.native_type;
+ if (!ClassDB::class_exists(class_name)) {
+ class_name = String("_") + class_name;
+ if (!ClassDB::class_exists(class_name)) {
+ base_type.has_type = false;
+ break;
}
}
- }
-
- //print_line("but it has a script?");
- if (!on_script.is_valid() && id.script.is_valid()) {
- //print_line("yes");
- on_script = id.script;
- }
-
- if (on_script.is_valid()) {
-
- GDScript *scr = on_script.ptr();
- if (scr) {
- while (scr) {
-
- String code = scr->get_source_code();
- //print_line("has source code!");
-
- if (code != "") {
- //if there is code, parse it. This way is slower but updates in real-time
- GDScriptParser p;
- //Error parse(const String& p_code, const String& p_base_path="", bool p_just_validate=false,const String& p_self_path="",bool p_for_completion=false);
-
- Error err = p.parse(scr->get_source_code(), scr->get_path().get_base_dir(), true, "", false);
-
- if (err == OK) {
- //print_line("checking the functions...");
- //only if ok, otherwise use what is cached on the script
- //GDScriptParser::ClassNode *base = p.
- const GDScriptParser::Node *root = p.get_parse_tree();
- ERR_FAIL_COND(root->type != GDScriptParser::Node::TYPE_CLASS);
-
- const GDScriptParser::ClassNode *cl = static_cast<const GDScriptParser::ClassNode *>(root);
-
- const GDScriptParser::FunctionNode *func = NULL;
- bool st = false;
-
- for (int i = 0; i < cl->functions.size(); i++) {
- //print_line(String(cl->functions[i]->name)+" vs "+String(p_method));
- if (cl->functions[i]->name == p_method) {
- func = cl->functions[i];
- }
- }
-
- for (int i = 0; i < cl->static_functions.size(); i++) {
-
- //print_line(String(cl->static_functions[i]->name)+" vs "+String(p_method));
- if (cl->static_functions[i]->name == p_method) {
- func = cl->static_functions[i];
- st = true;
- }
- }
-
- if (func) {
-
- arghint = "func " + String(p_method) + "(";
- if (st)
- arghint = "static " + arghint;
- for (int i = 0; i < func->arguments.size(); i++) {
- if (i > 0)
- arghint += ", ";
- else
- arghint += " ";
- if (i == p_argidx) {
- arghint += String::chr(0xFFFF);
- }
- arghint += "var " + String(func->arguments[i]);
- int deffrom = func->arguments.size() - func->default_values.size();
- if (i >= deffrom) {
- int defidx = deffrom - i;
+ List<MethodInfo> methods;
+ ClassDB::get_method_list(class_name, &methods);
+ ClassDB::get_virtual_methods(class_name, &methods);
+ int method_args = 0;
- if (defidx >= 0 && defidx < func->default_values.size() && func->default_values[defidx]->type == GDScriptParser::Node::TYPE_OPERATOR) {
- const GDScriptParser::OperatorNode *op = static_cast<const GDScriptParser::OperatorNode *>(func->default_values[defidx]);
- if (op->op == GDScriptParser::OperatorNode::OP_ASSIGN) {
- const GDScriptParser::ConstantNode *cn = static_cast<const GDScriptParser::ConstantNode *>(op->arguments[1]);
- arghint += "=" + cn->value.get_construct_string();
- }
- }
- }
- if (i == p_argidx) {
- arghint += String::chr(0xFFFF);
- }
- }
-
- arghint += " )";
- return;
+ for (List<MethodInfo>::Element *E = methods.front(); E; E = E->next()) {
+ if (E->get().name == p_method) {
+ method_args = E->get().arguments.size();
+ if (base.get_type() == Variant::OBJECT) {
+ Object *obj = base.operator Object *();
+ if (obj) {
+ List<String> options;
+ obj->get_argument_options(p_method, p_argidx, &options);
+ for (List<String>::Element *E = options.front(); E; E = E->next()) {
+ r_result.insert(E->get());
}
- } else {
- //print_line("failed parsing?");
- code = "";
}
}
- if (code == "") {
-
- for (const Map<StringName, GDScriptFunction *>::Element *E = scr->get_member_functions().front(); E; E = E->next()) {
- if (p_method == E->get()->get_name()) {
- arghint = "func " + String(p_method) + "(";
- for (int i = 0; i < E->get()->get_argument_count(); i++) {
- if (i > 0)
- arghint += ", ";
- else
- arghint += " ";
- if (i == p_argidx) {
- arghint += String::chr(0xFFFF);
- }
- arghint += "var " + E->get()->get_argument_name(i);
- int deffrom = E->get()->get_argument_count() - E->get()->get_default_argument_count();
- if (i >= deffrom) {
- int defidx = deffrom - i;
- if (defidx >= 0 && defidx < E->get()->get_default_argument_count()) {
- arghint += "=" + E->get()->get_default_argument(defidx).get_construct_string();
- }
- }
- if (i == p_argidx) {
- arghint += String::chr(0xFFFF);
- }
- }
- arghint += ")";
- return; //found
- }
+ if (p_argidx < method_args) {
+ PropertyInfo arg_info = E->get().arguments[p_argidx];
+ if (arg_info.usage & PROPERTY_USAGE_CLASS_IS_ENUM) {
+ _find_enumeration_candidates(arg_info.class_name, r_result);
}
}
- if (scr->get_base().is_valid())
- scr = scr->get_base().ptr();
- else
- scr = NULL;
+ r_arghint = _make_arguments_hint(E->get(), p_argidx);
+ break;
}
}
- }
-
- } else {
-
- //regular method
-#if defined(DEBUG_METHODS_ENABLED) && defined(TOOLS_ENABLED)
- if (p_argidx < m->get_argument_count()) {
- PropertyInfo pi = m->get_argument_info(p_argidx);
-
- if (pi.usage & PROPERTY_USAGE_CLASS_IS_ENUM) {
- String enumeration = pi.class_name;
- if (enumeration.find(".") != -1) {
- //class constant
- List<StringName> constants;
- String cls = enumeration.get_slice(".", 0);
- String enm = enumeration.get_slice(".", 1);
-
- ClassDB::get_enum_constants(cls, enm, &constants);
- //constants.sort_custom<StringName::AlphCompare>();
- for (List<StringName>::Element *E = constants.front(); E; E = E->next()) {
- String add = cls + "." + E->get();
- result.insert(add);
- r_forced = true;
- }
- } else {
- //global constant
- StringName current_enum = enumeration;
-
- for (int i = 0; i < GlobalConstants::get_global_constant_count(); i++) {
- if (GlobalConstants::get_global_constant_enum(i) == current_enum) {
- result.insert(GlobalConstants::get_global_constant_name(i));
- r_forced = true;
- }
- }
- //global
+ if ((p_method == "connect" || p_method == "emit_signal") && p_argidx == 0) {
+ List<MethodInfo> signals;
+ ClassDB::get_signal_list(class_name, &signals);
+ for (List<MethodInfo>::Element *E = signals.front(); E; E = E->next()) {
+ r_result.insert("\"" + E->get().name + "\"");
}
}
- }
-#endif
- if (p_method.operator String() == "connect" || (p_method.operator String() == "emit_signal" && p_argidx == 0)) {
-
- if (p_argidx == 0) {
- List<MethodInfo> sigs;
- ClassDB::get_signal_list(id.obj_type, &sigs);
-
- if (id.script.is_valid()) {
- id.script->get_script_signal_list(&sigs);
- } else if (id.value.get_type() == Variant::OBJECT) {
- Object *obj = id.value;
- if (obj && !obj->get_script().is_null()) {
- Ref<Script> scr = obj->get_script();
- if (scr.is_valid()) {
- scr->get_script_signal_list(&sigs);
- }
- }
- }
-
- for (List<MethodInfo>::Element *E = sigs.front(); E; E = E->next()) {
- result.insert("\"" + E->get().name + "\"");
- r_forced = true;
- }
- } else if (p_argidx == 2) {
+ if (ClassDB::is_parent_class(class_name, "Node") && (p_method == "get_node" || p_method == "has_node") && p_argidx == 0) {
+ // Get autoloads
+ List<PropertyInfo> props;
+ ProjectSettings::get_singleton()->get_property_list(&props);
- if (context._class) {
- for (int i = 0; i < context._class->functions.size(); i++) {
- result.insert("\"" + context._class->functions[i]->name + "\"");
- r_forced = true;
+ for (List<PropertyInfo>::Element *E = props.front(); E; E = E->next()) {
+ String s = E->get().name;
+ if (!s.begins_with("autoload/")) {
+ continue;
}
+ String name = s.get_slice("/", 1);
+ r_result.insert("\"/root/" + name + "\"");
}
}
- /*if (p_argidx==2) {
-
- ERR_FAIL_COND(p_node->type!=GDScriptParser::Node::TYPE_OPERATOR);
- const GDScriptParser::OperatorNode *op=static_cast<const GDScriptParser::OperatorNode *>(p_node);
- if (op->arguments.size()>)
-
- }*/
- } else {
-
- if (p_argidx == 0 && (String(p_method) == "get_node" || String(p_method) == "has_node") && ClassDB::is_parent_class(id.obj_type, "Node")) {
+ if (p_argidx == 0 && method_args > 0 && ClassDB::is_parent_class(class_name, "InputEvent") && p_method.operator String().find("action") != -1) {
+ // Get input actions
List<PropertyInfo> props;
ProjectSettings::get_singleton()->get_property_list(&props);
-
for (List<PropertyInfo>::Element *E = props.front(); E; E = E->next()) {
-
String s = E->get().name;
- if (!s.begins_with("autoload/"))
+ if (!s.begins_with("input/")) {
continue;
- //print_line("found "+s);
+ }
String name = s.get_slice("/", 1);
- result.insert("\"/root/" + name + "\"");
- r_forced = true;
+ r_result.insert("\"" + name + "\"");
}
}
- Object *obj = id.value;
- if (obj) {
- List<String> options;
- obj->get_argument_options(p_method, p_argidx, &options);
-
- for (List<String>::Element *E = options.front(); E; E = E->next()) {
-
- result.insert(E->get());
- r_forced = true;
+ base_type.has_type = false;
+ } break;
+ case GDScriptParser::DataType::BUILTIN: {
+ if (base.get_type() == Variant::NIL) {
+ Variant::CallError err;
+ base = Variant::construct(base_type.builtin_type, NULL, 0, err);
+ if (err.error != Variant::CallError::CALL_OK) {
+ return;
}
}
- }
-
- arghint = _get_visual_datatype(m->get_return_info(), false) + " " + p_method.operator String() + String("(");
-
- for (int i = 0; i < m->get_argument_count(); i++) {
- if (i > 0)
- arghint += ", ";
- else
- arghint += " ";
-
- if (i == p_argidx) {
- arghint += String::chr(0xFFFF);
- }
- String n = m->get_argument_info(i).name;
- int dp = n.find(":");
- if (dp != -1)
- n = n.substr(0, dp);
- arghint += _get_visual_datatype(m->get_argument_info(i)) + " " + n;
- int deffrom = m->get_argument_count() - m->get_default_argument_count();
-
- if (i >= deffrom) {
- int defidx = i - deffrom;
- if (defidx >= 0 && defidx < m->get_default_argument_count()) {
- Variant v = m->get_default_argument(i);
- arghint += "=" + v.get_construct_string();
+ List<MethodInfo> methods;
+ base.get_method_list(&methods);
+ for (List<MethodInfo>::Element *E = methods.front(); E; E = E->next()) {
+ if (E->get().name == p_method) {
+ r_arghint = _make_arguments_hint(E->get(), p_argidx);
+ return;
}
}
- if (i == p_argidx) {
- arghint += String::chr(0xFFFF);
- }
- }
- if (m->get_argument_count() > 0)
- arghint += " ";
-
- arghint += ")";
+ base_type.has_type = false;
+ } break;
+ default: {
+ base_type.has_type = false;
+ } break;
}
}
}
-static void _find_call_arguments(GDScriptCompletionContext &context, const GDScriptParser::Node *p_node, int p_line, int p_argidx, Set<String> &result, bool &r_forced, String &arghint) {
+static void _find_call_arguments(const GDScriptCompletionContext &p_context, const GDScriptParser::Node *p_node, int p_argidx, Set<String> &r_result, bool &r_forced, String &r_arghint) {
if (!p_node || p_node->type != GDScriptParser::Node::TYPE_OPERATOR) {
-
return;
}
+ Variant base;
+ GDScriptParser::DataType base_type;
+ StringName function;
+ bool _static = false;
const GDScriptParser::OperatorNode *op = static_cast<const GDScriptParser::OperatorNode *>(p_node);
- if (op->op != GDScriptParser::OperatorNode::OP_CALL) {
+ GDScriptCompletionIdentifier connect_base;
+ if (op->op != GDScriptParser::OperatorNode::OP_CALL && op->op != GDScriptParser::OperatorNode::OP_PARENT_CALL) {
return;
}
- if (op->arguments[0]->type == GDScriptParser::Node::TYPE_BUILT_IN_FUNCTION) {
- //complete built-in function
- const GDScriptParser::BuiltInFunctionNode *fn = static_cast<const GDScriptParser::BuiltInFunctionNode *>(op->arguments[0]);
- MethodInfo mi = GDScriptFunctions::get_info(fn->function);
+ if (!op->arguments.size()) {
+ return;
+ }
- if (mi.name == "load" && bool(EditorSettings::get_singleton()->get("text_editor/completion/complete_file_paths"))) {
- get_directory_contents(EditorFileSystem::get_singleton()->get_filesystem(), result);
- }
+ if (op->op == GDScriptParser::OperatorNode::OP_CALL) {
+ if (op->arguments[0]->type == GDScriptParser::Node::TYPE_BUILT_IN_FUNCTION) {
+ // Complete built-in function
+ const GDScriptParser::BuiltInFunctionNode *fn = static_cast<const GDScriptParser::BuiltInFunctionNode *>(op->arguments[0]);
+ MethodInfo mi = GDScriptFunctions::get_info(fn->function);
- arghint = _get_visual_datatype(mi.return_val, false) + " " + GDScriptFunctions::get_func_name(fn->function) + String("(");
- for (int i = 0; i < mi.arguments.size(); i++) {
- if (i > 0)
- arghint += ", ";
- else
- arghint += " ";
- if (i == p_argidx || ((mi.flags & METHOD_FLAG_VARARG) && i > p_argidx)) {
- arghint += String::chr(0xFFFF);
- }
- arghint += _get_visual_datatype(mi.arguments[i]) + " " + mi.arguments[i].name;
- if (i == p_argidx || ((mi.flags & METHOD_FLAG_VARARG) && i > p_argidx)) {
- arghint += String::chr(0xFFFF);
+ if ((mi.name == "load" || mi.name == "preload") && bool(EditorSettings::get_singleton()->get("text_editor/completion/complete_file_paths"))) {
+ _get_directory_contents(EditorFileSystem::get_singleton()->get_filesystem(), r_result);
}
- }
- if (mi.arguments.size() > 0)
- arghint += " ";
- arghint += ")";
- } else if (op->arguments[0]->type == GDScriptParser::Node::TYPE_TYPE) {
- //complete constructor
- const GDScriptParser::TypeNode *tn = static_cast<const GDScriptParser::TypeNode *>(op->arguments[0]);
+ r_arghint = _make_arguments_hint(mi, p_argidx);
+ return;
- List<MethodInfo> mil;
- Variant::get_constructor_list(tn->vtype, &mil);
+ } else if (op->arguments[0]->type == GDScriptParser::Node::TYPE_TYPE) {
+ // Complete constructor
+ const GDScriptParser::TypeNode *tn = static_cast<const GDScriptParser::TypeNode *>(op->arguments[0]);
- for (List<MethodInfo>::Element *E = mil.front(); E; E = E->next()) {
+ List<MethodInfo> constructors;
+ Variant::get_constructor_list(tn->vtype, &constructors);
- MethodInfo mi = E->get();
- if (mi.arguments.size() == 0)
- continue;
- if (E->prev())
- arghint += "\n";
- arghint += Variant::get_type_name(tn->vtype) + " " + Variant::get_type_name(tn->vtype) + String("(");
- for (int i = 0; i < mi.arguments.size(); i++) {
- if (i > 0)
- arghint += ", ";
- else
- arghint += " ";
- if (i == p_argidx) {
- arghint += String::chr(0xFFFF);
- }
- arghint += _get_visual_datatype(mi.arguments[i]) + " " + mi.arguments[i].name;
- if (i == p_argidx) {
- arghint += String::chr(0xFFFF);
+ int i = 0;
+ for (List<MethodInfo>::Element *E = constructors.front(); E; E = E->next()) {
+ if (p_argidx >= E->get().arguments.size()) {
+ continue;
}
- }
- if (mi.arguments.size() > 0)
- arghint += " ";
- arghint += ")";
- }
-
- } else if (op->arguments.size() >= 2 && op->arguments[1]->type == GDScriptParser::Node::TYPE_IDENTIFIER) {
- //make sure identifier exists...
-
- const GDScriptParser::IdentifierNode *id = static_cast<const GDScriptParser::IdentifierNode *>(op->arguments[1]);
- if (op->arguments[0]->type == GDScriptParser::Node::TYPE_SELF) {
- //self, look up
-
- for (int i = 0; i < context._class->static_functions.size(); i++) {
- if (context._class->static_functions[i]->name == id->name) {
- _make_function_hint(context._class->static_functions[i], p_argidx, arghint);
- return;
+ if (i > 0) {
+ r_arghint += "\n";
}
+ r_arghint += _make_arguments_hint(E->get(), p_argidx);
+ i++;
}
+ return;
+ } else if (op->arguments[0]->type == GDScriptParser::Node::TYPE_SELF) {
- if (context.function && !context.function->_static) {
-
- for (int i = 0; i < context._class->functions.size(); i++) {
- if (context._class->functions[i]->name == id->name) {
- _make_function_hint(context._class->functions[i], p_argidx, arghint);
- return;
- }
- }
+ if (op->arguments.size() < 2 || op->arguments[1]->type != GDScriptParser::Node::TYPE_IDENTIFIER) {
+ return;
}
- Ref<Reference> base = _get_parent_class(context);
-
- while (true) {
-
- Ref<GDScript> script = base;
- Ref<GDScriptNativeClass> nc = base;
- if (script.is_valid()) {
-
- for (const Map<StringName, GDScriptFunction *>::Element *E = script->get_member_functions().front(); E; E = E->next()) {
+ base = p_context.base;
- if (E->key() == id->name) {
+ const GDScriptParser::IdentifierNode *id = static_cast<const GDScriptParser::IdentifierNode *>(op->arguments[1]);
+ function = id->name;
+ base_type.has_type = true;
+ base_type.kind = GDScriptParser::DataType::CLASS;
+ base_type.class_type = const_cast<GDScriptParser::ClassNode *>(p_context._class);
+ _static = p_context.function && p_context.function->_static;
- if (context.function && context.function->_static && !E->get()->is_static())
- continue;
+ if (function == "connect" && op->arguments.size() >= 4) {
+ _guess_expression_type(p_context, op->arguments[3], connect_base);
+ }
- arghint = "func " + id->name.operator String() + String("(");
- for (int i = 0; i < E->get()->get_argument_count(); i++) {
- if (i > 0)
- arghint += ", ";
- else
- arghint += " ";
- if (i == p_argidx) {
- arghint += String::chr(0xFFFF);
- }
- arghint += E->get()->get_argument_name(i);
- int deffrom = E->get()->get_argument_count() - E->get()->get_default_argument_count();
- if (i >= deffrom) {
- int defidx = deffrom - i;
- if (defidx >= 0 && defidx < E->get()->get_default_argument_count()) {
- arghint += "=" + E->get()->get_default_argument(defidx).get_construct_string();
- }
- }
- if (i == p_argidx) {
- arghint += String::chr(0xFFFF);
- }
- }
- if (E->get()->get_argument_count() > 0)
- arghint += " ";
- arghint += ")";
- return;
- }
- }
+ } else {
+ if (op->arguments.size() < 2 || op->arguments[1]->type != GDScriptParser::Node::TYPE_IDENTIFIER) {
+ return;
+ }
+ const GDScriptParser::IdentifierNode *id = static_cast<const GDScriptParser::IdentifierNode *>(op->arguments[1]);
+ function = id->name;
- base = script->get_base();
- if (base.is_null())
- base = script->get_native();
- } else if (nc.is_valid()) {
+ GDScriptCompletionIdentifier ci;
+ if (_guess_expression_type(p_context, op->arguments[0], ci)) {
+ base_type = ci.type;
+ base = ci.value;
+ } else {
+ return;
+ }
+ _static = ci.type.is_meta_type;
- if (!(context.function && context.function->_static)) {
+ if (function == "connect" && op->arguments.size() >= 4) {
+ _guess_expression_type(p_context, op->arguments[3], connect_base);
+ }
+ }
+ } else {
+ if (!p_context._class || op->arguments.size() < 1 || op->arguments[0]->type != GDScriptParser::Node::TYPE_IDENTIFIER) {
+ return;
+ }
+ base_type.has_type = true;
+ base_type.kind = GDScriptParser::DataType::CLASS;
+ base_type.class_type = const_cast<GDScriptParser::ClassNode *>(p_context._class);
+ base_type.is_meta_type = p_context.function && p_context.function->_static;
+ base = p_context.base;
- GDScriptCompletionIdentifier ci;
- ci.type = Variant::OBJECT;
- ci.obj_type = nc->get_name();
- if (!context._class->owner)
- ci.value = context.base;
+ function = static_cast<const GDScriptParser::IdentifierNode *>(op->arguments[0])->name;
- _find_type_arguments(context, p_node, p_line, id->name, ci, p_argidx, result, r_forced, arghint);
- //guess type..
- /*
- List<MethodInfo> methods;
- ClassDB::get_method_list(type,&methods);
- for(List<MethodInfo>::Element *E=methods.front();E;E=E->next()) {
- if (E->get().arguments.size())
- result.insert(E->get().name+"(");
- else
- result.insert(E->get().name+"()");
- }*/
- }
- break;
- } else
- break;
- }
- } else {
- //indexed lookup
+ if (function == "connect" && op->arguments.size() >= 4) {
+ _guess_expression_type(p_context, op->arguments[3], connect_base);
+ }
+ }
- GDScriptCompletionIdentifier ci;
- if (_guess_expression_type(context, op->arguments[0], p_line, ci)) {
+ GDScriptCompletionIdentifier ci;
+ ci.type = base_type;
+ ci.value = base;
+ _find_call_arguments(p_context, ci, function, p_argidx, _static, r_result, r_arghint);
- _find_type_arguments(context, p_node, p_line, id->name, ci, p_argidx, result, r_forced, arghint);
- return;
- }
+ if (function == "connect" && p_argidx == 2) {
+ Set<String> methods;
+ _find_identifiers_in_base(p_context, connect_base, true, methods);
+ for (Set<String>::Element *E = methods.front(); E; E = E->next()) {
+ r_result.insert("\"" + E->get().replace("(", "").replace(")", "") + "\"");
}
}
+
+ r_forced = r_result.size() > 0;
}
Error GDScriptLanguage::complete_code(const String &p_code, const String &p_base_path, Object *p_owner, List<String> *r_options, bool &r_forced, String &r_call_hint) {
- GDScriptParser p;
+ GDScriptParser parser;
- p.parse(p_code, p_base_path, false, "", true);
- bool isfunction = false;
- Set<String> options;
+ parser.parse(p_code, p_base_path, false, "", true);
r_forced = false;
+ Set<String> options;
GDScriptCompletionContext context;
- context._class = p.get_completion_class();
- context.block = p.get_completion_block();
- context.function = p.get_completion_function();
+ context._class = parser.get_completion_class();
+ context.block = parser.get_completion_block();
+ context.function = parser.get_completion_function();
context.base = p_owner;
context.base_path = p_base_path;
+ context.line = parser.get_completion_line();
+ bool is_function = false;
- switch (p.get_completion_type()) {
-
+ switch (parser.get_completion_type()) {
case GDScriptParser::COMPLETION_NONE: {
} break;
case GDScriptParser::COMPLETION_BUILT_IN_TYPE_CONSTANT: {
List<StringName> constants;
- Variant::get_numeric_constants_for_type(p.get_completion_built_in_constant(), &constants);
+ Variant::get_numeric_constants_for_type(parser.get_completion_built_in_constant(), &constants);
for (List<StringName>::Element *E = constants.front(); E; E = E->next()) {
options.insert(E->get().operator String());
}
-
- } break;
- case GDScriptParser::COMPLETION_FUNCTION:
- isfunction = true;
- case GDScriptParser::COMPLETION_IDENTIFIER: {
-
- _find_identifiers(context, p.get_completion_line(), isfunction, options);
} break;
case GDScriptParser::COMPLETION_PARENT_FUNCTION: {
-
+ _find_identifiers_in_class(context, !context.function || context.function->_static, true, true, options);
+ } break;
+ case GDScriptParser::COMPLETION_FUNCTION: {
+ is_function = true;
+ } // fallthrough
+ case GDScriptParser::COMPLETION_IDENTIFIER: {
+ _find_identifiers(context, is_function, options);
} break;
case GDScriptParser::COMPLETION_GET_NODE: {
-
if (p_owner) {
List<String> opts;
p_owner->get_argument_options("get_node", 0, &opts);
@@ -2204,315 +2474,358 @@ Error GDScriptLanguage::complete_code(const String &p_code, const String &p_base
}
}
}
+
+ // Get autoloads
+ List<PropertyInfo> props;
+ ProjectSettings::get_singleton()->get_property_list(&props);
+
+ for (List<PropertyInfo>::Element *E = props.front(); E; E = E->next()) {
+ String s = E->get().name;
+ if (!s.begins_with("autoload/")) {
+ continue;
+ }
+ String name = s.get_slice("/", 1);
+ options.insert("\"/root/" + name + "\"");
+ }
}
} break;
- case GDScriptParser::COMPLETION_METHOD:
- isfunction = true;
+ case GDScriptParser::COMPLETION_METHOD: {
+ is_function = true;
+ } // fallthrough
case GDScriptParser::COMPLETION_INDEX: {
-
- const GDScriptParser::Node *node = p.get_completion_node();
- if (node->type != GDScriptParser::Node::TYPE_OPERATOR)
+ const GDScriptParser::Node *node = parser.get_completion_node();
+ if (node->type != GDScriptParser::Node::TYPE_OPERATOR) {
break;
+ }
+ const GDScriptParser::OperatorNode *op = static_cast<const GDScriptParser::OperatorNode *>(node);
+ if (op->arguments.size() < 1) {
+ break;
+ }
- GDScriptCompletionIdentifier t;
- if (_guess_expression_type(context, static_cast<const GDScriptParser::OperatorNode *>(node)->arguments[0], p.get_completion_line(), t, true)) {
-
- if (t.type == Variant::OBJECT && t.obj_type == "GDScriptNativeClass") {
- //native enum
- Ref<GDScriptNativeClass> gdn = t.value;
- if (gdn.is_valid()) {
- StringName cn = gdn->get_name();
- List<String> cnames;
- ClassDB::get_integer_constant_list(cn, &cnames);
- for (List<String>::Element *E = cnames.front(); E; E = E->next()) {
- options.insert(E->get());
- }
-
- List<PropertyInfo> pinfo;
- ClassDB::get_property_list(cn, &pinfo);
-
- for (List<PropertyInfo>::Element *E = pinfo.front(); E; E = E->next()) {
- if (E->get().usage & (PROPERTY_USAGE_GROUP | PROPERTY_USAGE_CATEGORY))
- continue;
- if (String(E->get().name).find("/") != -1)
- continue;
- options.insert(E->get().name);
- }
- }
- } else if (t.type == Variant::OBJECT && t.obj_type != StringName()) {
-
- Ref<GDScript> on_script;
-
- if (t.value.get_type()) {
- Object *obj = t.value;
-
- GDScript *scr = Object::cast_to<GDScript>(obj);
- if (scr) {
- while (scr) {
+ GDScriptCompletionIdentifier base;
+ if (!_guess_expression_type(context, op->arguments[0], base)) {
+ break;
+ }
- if (!isfunction) {
- for (const Map<StringName, Variant>::Element *E = scr->get_constants().front(); E; E = E->next()) {
- options.insert(E->key());
- }
- }
- for (const Map<StringName, GDScriptFunction *>::Element *E = scr->get_member_functions().front(); E; E = E->next()) {
- if (E->get()->is_static())
- options.insert(E->key());
- }
+ GDScriptCompletionContext c = context;
+ c.function = NULL;
+ c.block = NULL;
+ c.base = base.value.get_type() == Variant::OBJECT ? base.value.operator Object *() : NULL;
+ if (base.type.kind == GDScriptParser::DataType::CLASS) {
+ c._class = base.type.class_type;
+ } else {
+ c._class = NULL;
+ }
- if (scr->get_base().is_valid())
- scr = scr->get_base().ptr();
- else
- scr = NULL;
+ _find_identifiers_in_base(c, base, is_function, options);
+ } break;
+ case GDScriptParser::COMPLETION_CALL_ARGUMENTS: {
+ _find_call_arguments(context, parser.get_completion_node(), parser.get_completion_argument_index(), options, r_forced, r_call_hint);
+ } break;
+ case GDScriptParser::COMPLETION_VIRTUAL_FUNC: {
+ GDScriptParser::DataType native_type = context._class->base_type;
+ while (native_type.has_type && native_type.kind != GDScriptParser::DataType::NATIVE) {
+ switch (native_type.kind) {
+ case GDScriptParser::DataType::CLASS: {
+ native_type = native_type.class_type->base_type;
+ } break;
+ case GDScriptParser::DataType::GDSCRIPT: {
+ Ref<GDScript> gds = native_type.script_type;
+ if (gds.is_valid()) {
+ Ref<GDScript> base = gds->get_base_script();
+ if (base.is_valid()) {
+ native_type.script_type = base;
+ } else {
+ native_type.native_type = gds->get_instance_base_type();
+ native_type.kind = GDScriptParser::DataType::NATIVE;
}
} else {
- if (obj) {
- on_script = obj->get_script();
- }
+ native_type.has_type = false;
}
- }
-
- if (!on_script.is_valid() && t.script.is_valid()) {
- on_script = t.script;
- }
-
- if (on_script.is_valid()) {
-
- GDScript *scr = on_script.ptr();
- if (scr) {
- while (scr) {
-
- String code = scr->get_source_code();
-
- if (code != "") {
- //if there is code, parse it. This way is slower but updates in real-time
- GDScriptParser p;
-
- Error err = p.parse(scr->get_source_code(), scr->get_path().get_base_dir(), true, "", false);
-
- if (err == OK) {
- //only if ok, otherwise use what is cached on the script
- //GDScriptParser::ClassNode *base = p.
- const GDScriptParser::Node *root = p.get_parse_tree();
- ERR_FAIL_COND_V(root->type != GDScriptParser::Node::TYPE_CLASS, ERR_PARSE_ERROR);
-
- const GDScriptParser::ClassNode *cl = static_cast<const GDScriptParser::ClassNode *>(root);
-
- for (int i = 0; i < cl->functions.size(); i++) {
-
- if (cl->functions[i]->arguments.size())
- options.insert(String(cl->functions[i]->name) + "(");
- else
- options.insert(String(cl->functions[i]->name) + "()");
- }
-
- for (int i = 0; i < cl->static_functions.size(); i++) {
-
- if (cl->static_functions[i]->arguments.size())
- options.insert(String(cl->static_functions[i]->name) + "(");
- else
- options.insert(String(cl->static_functions[i]->name) + "()");
- }
+ } break;
+ default: {
+ native_type.has_type = false;
+ } break;
+ }
+ }
- if (!isfunction) {
- for (int i = 0; i < cl->variables.size(); i++) {
+ if (!native_type.has_type) {
+ break;
+ }
- options.insert(String(cl->variables[i].identifier));
- }
+ StringName class_name = native_type.native_type;
+ if (!ClassDB::class_exists(class_name)) {
+ class_name = String("_") + class_name;
+ if (!ClassDB::class_exists(class_name)) {
+ break;
+ }
+ }
- for (int i = 0; i < cl->constant_expressions.size(); i++) {
+ bool use_type_hint = EditorSettings::get_singleton()->get_setting("text_editor/completion/add_type_hints").operator bool();
- options.insert(String(cl->constant_expressions[i].identifier));
- }
- }
+ List<MethodInfo> virtual_methods;
+ ClassDB::get_virtual_methods(class_name, &virtual_methods);
+ for (List<MethodInfo>::Element *E = virtual_methods.front(); E; E = E->next()) {
- } else {
- code = ""; //well, then no code
- }
- }
-
- if (code == "") {
- //use class directly, no code was found
- if (!isfunction) {
- for (const Map<StringName, Variant>::Element *E = scr->get_constants().front(); E; E = E->next()) {
- options.insert(E->key());
- }
- }
- for (const Map<StringName, GDScriptFunction *>::Element *E = scr->get_member_functions().front(); E; E = E->next()) {
- if (E->get()->get_argument_count())
- options.insert(String(E->key()) + "()");
- else
- options.insert(String(E->key()) + "(");
- }
-
- for (const Set<StringName>::Element *E = scr->get_members().front(); E; E = E->next()) {
- options.insert(E->get());
- }
- }
+ MethodInfo &mi = E->get();
+ String method_hint = mi.name;
+ if (method_hint.find(":") != -1) {
+ method_hint = method_hint.get_slice(":", 0);
+ }
+ method_hint += "(";
- if (scr->get_base().is_valid())
- scr = scr->get_base().ptr();
- else
- scr = NULL;
+ if (mi.arguments.size()) {
+ for (int i = 0; i < mi.arguments.size(); i++) {
+ if (i > 0) {
+ method_hint += ", ";
+ }
+ String arg = mi.arguments[i].name;
+ if (arg.find(":") != -1) {
+ arg = arg.substr(0, arg.find(":"));
+ }
+ method_hint += arg;
+ if (use_type_hint && mi.arguments[i].type != Variant::NIL) {
+ method_hint += " : ";
+ if (mi.arguments[i].type == Variant::OBJECT && mi.arguments[i].class_name != StringName()) {
+ method_hint += mi.arguments[i].class_name.operator String();
+ } else {
+ method_hint += Variant::get_type_name(mi.arguments[i].type);
}
}
}
-
- if (!isfunction) {
- ClassDB::get_integer_constant_list(t.obj_type, r_options);
-
- List<PropertyInfo> pinfo;
- ClassDB::get_property_list(t.obj_type, &pinfo);
-
- for (List<PropertyInfo>::Element *E = pinfo.front(); E; E = E->next()) {
- if (E->get().usage & (PROPERTY_USAGE_GROUP | PROPERTY_USAGE_CATEGORY))
- continue;
- if (String(E->get().name).find("/") != -1)
- continue;
- r_options->push_back(E->get().name);
- }
+ }
+ method_hint += ")";
+ if (use_type_hint && (mi.return_val.type != Variant::NIL || !(mi.return_val.usage & PROPERTY_USAGE_NIL_IS_VARIANT))) {
+ method_hint += " -> ";
+ if (mi.return_val.type == Variant::NIL) {
+ method_hint += "void";
+ } else if (mi.return_val.type == Variant::OBJECT && mi.return_val.class_name != StringName()) {
+ method_hint += mi.return_val.class_name.operator String();
+ } else {
+ method_hint += Variant::get_type_name(mi.return_val.type);
}
+ }
+ method_hint += ":";
- List<MethodInfo> mi;
- ClassDB::get_method_list(t.obj_type, &mi, false, true);
- for (List<MethodInfo>::Element *E = mi.front(); E; E = E->next()) {
-
- if (E->get().name.begins_with("_"))
- continue;
+ options.insert(method_hint);
+ }
+ } break;
+ case GDScriptParser::COMPLETION_YIELD: {
+ const GDScriptParser::Node *node = parser.get_completion_node();
- if (E->get().arguments.size())
- options.insert(E->get().name + "(");
- else
- options.insert(E->get().name + "()");
- }
- } else {
+ GDScriptCompletionContext c = context;
+ c.line = node->line;
+ GDScriptCompletionIdentifier type;
+ if (!_guess_expression_type(c, node, type)) {
+ break;
+ }
- //check InputEvent hint
- {
- if (t.value.get_type() == Variant::NIL) {
- Variant::CallError ce;
- t.value = Variant::construct(t.type, NULL, 0, ce);
+ GDScriptParser::DataType base_type = type.type;
+ while (base_type.has_type) {
+ switch (base_type.kind) {
+ case GDScriptParser::DataType::CLASS: {
+ for (int i = 0; i < base_type.class_type->_signals.size(); i++) {
+ options.insert("\"" + base_type.class_type->_signals[i].name.operator String() + "\"");
}
-
- if (!isfunction) {
- List<PropertyInfo> pl;
- t.value.get_property_list(&pl);
- for (List<PropertyInfo>::Element *E = pl.front(); E; E = E->next()) {
-
- if (String(E->get().name).find("/") == -1)
- options.insert(E->get().name);
+ base_type = base_type.class_type->base_type;
+ } break;
+ case GDScriptParser::DataType::SCRIPT:
+ case GDScriptParser::DataType::GDSCRIPT: {
+ Ref<Script> scr = base_type.script_type;
+ if (scr.is_valid()) {
+ List<MethodInfo> signals;
+ scr->get_script_signal_list(&signals);
+ for (List<MethodInfo>::Element *E = signals.front(); E; E = E->next()) {
+ options.insert("\"" + E->get().name + "\"");
+ }
+ Ref<Script> base_script = scr->get_base_script();
+ if (base_script.is_valid()) {
+ base_type.script_type = base_script;
+ } else {
+ base_type.kind = GDScriptParser::DataType::NATIVE;
+ base_type.native_type = scr->get_instance_base_type();
+ }
+ } else {
+ base_type.has_type = false;
+ }
+ } break;
+ case GDScriptParser::DataType::NATIVE: {
+ base_type.has_type = false;
+
+ StringName class_name = base_type.native_type;
+ if (!ClassDB::class_exists(class_name)) {
+ class_name = String("_") + class_name;
+ if (!ClassDB::class_exists(class_name)) {
+ break;
}
}
- List<MethodInfo> mi;
- t.value.get_method_list(&mi);
- for (List<MethodInfo>::Element *E = mi.front(); E; E = E->next()) {
- if (E->get().arguments.size())
- options.insert(E->get().name + "(");
- else
- options.insert(E->get().name + "()");
+ List<MethodInfo> signals;
+ ClassDB::get_signal_list(class_name, &signals);
+ for (List<MethodInfo>::Element *E = signals.front(); E; E = E->next()) {
+ options.insert("\"" + E->get().name + "\"");
}
+ } break;
+ default: {
+ base_type.has_type = false;
}
}
}
-
} break;
- case GDScriptParser::COMPLETION_CALL_ARGUMENTS: {
-
- _find_call_arguments(context, p.get_completion_node(), p.get_completion_line(), p.get_completion_argument_index(), options, r_forced, r_call_hint);
+ case GDScriptParser::COMPLETION_RESOURCE_PATH: {
+ if (EditorSettings::get_singleton()->get("text_editor/completion/complete_file_paths")) {
+ _get_directory_contents(EditorFileSystem::get_singleton()->get_filesystem(), options);
+ r_forced = true;
+ }
} break;
- case GDScriptParser::COMPLETION_VIRTUAL_FUNC: {
-
- GDScriptCompletionIdentifier cid = _get_native_class(context);
-
- if (cid.obj_type != StringName()) {
- List<MethodInfo> vm;
- ClassDB::get_virtual_methods(cid.obj_type, &vm);
- for (List<MethodInfo>::Element *E = vm.front(); E; E = E->next()) {
+ case GDScriptParser::COMPLETION_ASSIGN: {
+ GDScriptCompletionIdentifier type;
+ if (!_guess_expression_type(context, parser.get_completion_node(), type)) {
+ break;
+ }
- MethodInfo &mi = E->get();
- String m = mi.name;
- if (m.find(":") != -1)
- m = m.substr(0, m.find(":"));
- m += "(";
-
- if (mi.arguments.size()) {
- for (int i = 0; i < mi.arguments.size(); i++) {
- if (i > 0)
- m += ", ";
- String n = mi.arguments[i].name;
- if (n.find(":") != -1)
- n = n.substr(0, n.find(":"));
- m += n;
+ if (!type.enumeration.empty()) {
+ _find_enumeration_candidates(type.enumeration, options);
+ r_forced = options.size() > 0;
+ }
+ } break;
+ case GDScriptParser::COMPLETION_TYPE_HINT: {
+ const GDScriptParser::ClassNode *clss = context._class;
+ while (clss) {
+ for (Map<StringName, GDScriptParser::ClassNode::Constant>::Element *E = clss->constant_expressions.front(); E; E = E->next()) {
+ GDScriptCompletionIdentifier constant;
+ GDScriptCompletionContext c = context;
+ c.function = NULL;
+ c.block = NULL;
+ c.line = E->value().expression->line;
+ if (_guess_expression_type(c, E->value().expression, constant)) {
+ if (constant.type.has_type && constant.type.is_meta_type) {
+ options.insert(E->key().operator String());
}
}
- m += "):";
+ }
+ for (int i = 0; i < clss->subclasses.size(); i++) {
+ if (clss->subclasses[i]->name != StringName()) {
+ options.insert(clss->subclasses[i]->name.operator String());
+ }
+ }
+ clss = clss->owner;
+ for (int i = 0; i < Variant::VARIANT_MAX; i++) {
+ options.insert(Variant::get_type_name((Variant::Type)i));
+ }
+ }
- options.insert(m);
+ List<StringName> native_classes;
+ ClassDB::get_class_list(&native_classes);
+ for (List<StringName>::Element *E = native_classes.front(); E; E = E->next()) {
+ String class_name = E->get().operator String();
+ if (class_name.begins_with("_")) {
+ class_name = class_name.right(1);
+ }
+ if (Engine::get_singleton()->has_singleton(class_name)) {
+ continue;
}
+ options.insert(class_name);
}
- } break;
- case GDScriptParser::COMPLETION_YIELD: {
- const GDScriptParser::Node *node = p.get_completion_node();
+ // Named scripts
+ List<StringName> named_scripts;
+ ScriptServer::get_global_class_list(&named_scripts);
+ for (List<StringName>::Element *E = named_scripts.front(); E; E = E->next()) {
+ options.insert(E->get().operator String());
+ }
- GDScriptCompletionIdentifier t;
- if (!_guess_expression_type(context, node, p.get_completion_line(), t))
+ if (parser.get_completion_identifier_is_function()) {
+ options.insert("void");
+ }
+ r_forced = true;
+ } break;
+ case GDScriptParser::COMPLETION_TYPE_HINT_INDEX: {
+ GDScriptCompletionIdentifier base;
+ String index = parser.get_completion_cursor().operator String();
+ if (!_guess_identifier_type(context, index.get_slice(".", 0), base)) {
break;
+ }
- if (t.type == Variant::OBJECT && t.obj_type != StringName()) {
+ GDScriptCompletionContext c = context;
+ c._class = NULL;
+ c.function = NULL;
+ c.block = NULL;
+ bool finding = true;
+ index = index.right(index.find(".") + 1);
+ while (index.find(".") != -1) {
+ String id = index.get_slice(".", 0);
- List<MethodInfo> sigs;
- ClassDB::get_signal_list(t.obj_type, &sigs);
- for (List<MethodInfo>::Element *E = sigs.front(); E; E = E->next()) {
- options.insert("\"" + E->get().name + "\"");
- r_forced = true;
+ GDScriptCompletionIdentifier sub_base;
+ if (!_guess_identifier_type_from_base(c, base, id, sub_base)) {
+ finding = false;
+ break;
}
+ index = index.right(index.find(".") + 1);
+ base = sub_base;
}
- } break;
- case GDScriptParser::COMPLETION_RESOURCE_PATH: {
-
- if (EditorSettings::get_singleton()->get("text_editor/completion/complete_file_paths")) {
- get_directory_contents(EditorFileSystem::get_singleton()->get_filesystem(), options);
- r_forced = true;
+ if (!finding) {
+ break;
}
- } break;
- case GDScriptParser::COMPLETION_ASSIGN: {
-#if defined(DEBUG_METHODS_ENABLED) && defined(TOOLS_ENABLED)
- GDScriptCompletionIdentifier ci;
- if (_guess_expression_type(context, p.get_completion_node(), p.get_completion_line(), ci)) {
-
- String enumeration = ci.enumeration;
- if (enumeration.find(".") != -1) {
- //class constant
- List<StringName> constants;
- String cls = enumeration.get_slice(".", 0);
- String enm = enumeration.get_slice(".", 1);
-
- ClassDB::get_enum_constants(cls, enm, &constants);
- //constants.sort_custom<StringName::AlphCompare>();
- for (List<StringName>::Element *E = constants.front(); E; E = E->next()) {
- String add = cls + "." + E->get();
- r_options->push_back(add);
- r_forced = true;
- }
- } else {
-
- //global constant
- StringName current_enum = enumeration;
+ GDScriptParser::DataType base_type = base.type;
+ while (base_type.has_type) {
+ switch (base_type.kind) {
+ case GDScriptParser::DataType::CLASS: {
+ if (base_type.class_type) {
+ for (Map<StringName, GDScriptParser::ClassNode::Constant>::Element *E = base_type.class_type->constant_expressions.front(); E; E = E->next()) {
+ GDScriptCompletionIdentifier constant;
+ GDScriptCompletionContext c = context;
+ c._class = base_type.class_type;
+ c.function = NULL;
+ c.block = NULL;
+ c.line = E->value().expression->line;
+ if (_guess_expression_type(c, E->value().expression, constant)) {
+ if (constant.type.has_type && constant.type.is_meta_type) {
+ options.insert(E->key().operator String());
+ }
+ }
+ }
+ for (int i = 0; i < base_type.class_type->subclasses.size(); i++) {
+ if (base_type.class_type->subclasses[i]->name != StringName()) {
+ options.insert(base_type.class_type->subclasses[i]->name.operator String());
+ }
+ }
- for (int i = 0; i < GlobalConstants::get_global_constant_count(); i++) {
- if (GlobalConstants::get_global_constant_enum(i) == current_enum) {
- r_options->push_back(GlobalConstants::get_global_constant_name(i));
- r_forced = true;
+ base_type = base_type.class_type->base_type;
+ } else {
+ base_type.has_type = false;
}
- }
- //global
+ } break;
+ case GDScriptParser::DataType::SCRIPT:
+ case GDScriptParser::DataType::GDSCRIPT: {
+ Ref<Script> scr = base_type.script_type;
+ if (scr.is_valid()) {
+ Map<StringName, Variant> constants;
+ scr->get_constants(&constants);
+ for (Map<StringName, Variant>::Element *E = constants.front(); E; E = E->next()) {
+ Ref<Script> const_scr = E->value();
+ if (const_scr.is_valid()) {
+ options.insert(E->key().operator String());
+ }
+ }
+ Ref<Script> base_script = scr->get_base_script();
+ if (base_script.is_valid()) {
+ base_type.script_type = base_script;
+ } else {
+ base_type.has_type = false;
+ }
+ } else {
+ base_type.has_type = false;
+ }
+ } break;
+ default: {
+ base_type.has_type = false;
+ } break;
}
}
-#endif
+ r_forced = options.size() > 0;
} break;
}
@@ -2531,6 +2844,8 @@ Error GDScriptLanguage::complete_code(const String &p_code, const String &p_base
#endif
+//////// END COMPLETION //////////
+
String GDScriptLanguage::_get_indentation() const {
#ifdef TOOLS_ENABLED
if (Engine::get_singleton()->is_editor_hint()) {
@@ -2616,6 +2931,185 @@ void GDScriptLanguage::auto_indent_code(String &p_code, int p_from_line, int p_t
#ifdef TOOLS_ENABLED
+static Error _lookup_symbol_from_base(const GDScriptParser::DataType &p_base, const String &p_symbol, bool p_is_function, GDScriptLanguage::LookupResult &r_result) {
+ GDScriptParser::DataType base_type = p_base;
+
+ while (base_type.has_type) {
+ switch (base_type.kind) {
+ case GDScriptParser::DataType::CLASS: {
+ if (base_type.class_type) {
+ if (p_is_function) {
+ for (int i = 0; i < base_type.class_type->functions.size(); i++) {
+ if (base_type.class_type->functions[i]->name == p_symbol) {
+ r_result.type = ScriptLanguage::LookupResult::RESULT_SCRIPT_LOCATION;
+ r_result.location = base_type.class_type->functions[i]->line;
+ return OK;
+ }
+ }
+ for (int i = 0; i < base_type.class_type->static_functions.size(); i++) {
+ if (base_type.class_type->static_functions[i]->name == p_symbol) {
+ r_result.type = ScriptLanguage::LookupResult::RESULT_SCRIPT_LOCATION;
+ r_result.location = base_type.class_type->static_functions[i]->line;
+ return OK;
+ }
+ }
+ } else {
+ if (base_type.class_type->constant_expressions.has(p_symbol)) {
+ r_result.type = ScriptLanguage::LookupResult::RESULT_SCRIPT_LOCATION;
+ r_result.location = base_type.class_type->constant_expressions[p_symbol].expression->line;
+ return OK;
+ }
+
+ for (int i = 0; i < base_type.class_type->variables.size(); i++) {
+ if (base_type.class_type->variables[i].identifier == p_symbol) {
+ r_result.type = ScriptLanguage::LookupResult::RESULT_SCRIPT_LOCATION;
+ r_result.location = base_type.class_type->variables[i].line;
+ return OK;
+ }
+ }
+ }
+ }
+ base_type = base_type.class_type->base_type;
+ } break;
+ case GDScriptParser::DataType::SCRIPT:
+ case GDScriptParser::DataType::GDSCRIPT: {
+ Ref<Script> scr = base_type.script_type;
+ if (scr.is_valid()) {
+ int line = scr->get_member_line(p_symbol);
+ if (line >= 0) {
+ r_result.type = ScriptLanguage::LookupResult::RESULT_SCRIPT_LOCATION;
+ r_result.location = line;
+ r_result.script = scr;
+ return OK;
+ }
+ Ref<Script> base_script = scr->get_base_script();
+ if (base_script.is_valid()) {
+ base_type.script_type = base_script;
+ } else {
+ base_type.kind = GDScriptParser::DataType::NATIVE;
+ base_type.native_type = scr->get_instance_base_type();
+ }
+ } else {
+ base_type.has_type = false;
+ }
+ } break;
+ case GDScriptParser::DataType::NATIVE: {
+ StringName class_name = base_type.native_type;
+ if (!ClassDB::class_exists(class_name)) {
+ class_name = String("_") + class_name;
+ if (!ClassDB::class_exists(class_name)) {
+ base_type.has_type = false;
+ break;
+ }
+ }
+
+ if (ClassDB::has_method(class_name, p_symbol, true)) {
+ r_result.type = ScriptLanguage::LookupResult::RESULT_CLASS_METHOD;
+ r_result.class_name = base_type.native_type;
+ r_result.class_member = p_symbol;
+ return OK;
+ }
+
+ List<MethodInfo> virtual_methods;
+ ClassDB::get_virtual_methods(class_name, &virtual_methods, true);
+ for (List<MethodInfo>::Element *E = virtual_methods.front(); E; E = E->next()) {
+ if (E->get().name == p_symbol) {
+ r_result.type = ScriptLanguage::LookupResult::RESULT_CLASS_METHOD;
+ r_result.class_name = base_type.native_type;
+ r_result.class_member = p_symbol;
+ return OK;
+ }
+ }
+
+ StringName enum_name = ClassDB::get_integer_constant_enum(class_name, p_symbol, true);
+ if (enum_name != StringName()) {
+ r_result.type = ScriptLanguage::LookupResult::RESULT_CLASS_ENUM;
+ r_result.class_name = base_type.native_type;
+ r_result.class_member = enum_name;
+ return OK;
+ }
+
+ List<String> constants;
+ ClassDB::get_integer_constant_list(class_name, &constants, true);
+ for (List<String>::Element *E = constants.front(); E; E = E->next()) {
+ if (E->get() == p_symbol) {
+ r_result.type = ScriptLanguage::LookupResult::RESULT_CLASS_CONSTANT;
+ r_result.class_name = base_type.native_type;
+ r_result.class_member = p_symbol;
+ return OK;
+ }
+ }
+
+ List<PropertyInfo> properties;
+ ClassDB::get_property_list(class_name, &properties, true);
+ for (List<PropertyInfo>::Element *E = properties.front(); E; E = E->next()) {
+ if (E->get().name == p_symbol) {
+ r_result.type = ScriptLanguage::LookupResult::RESULT_CLASS_PROPERTY;
+ r_result.class_name = base_type.native_type;
+ r_result.class_member = p_symbol;
+ return OK;
+ }
+ }
+
+ StringName parent = ClassDB::get_parent_class(class_name);
+ if (parent != StringName()) {
+ if (String(parent).begins_with("_")) {
+ base_type.native_type = String(parent).right(1);
+ } else {
+ base_type.native_type = parent;
+ }
+ } else {
+ base_type.has_type = false;
+ }
+ } break;
+ case GDScriptParser::DataType::BUILTIN: {
+ base_type.has_type = false;
+
+ if (Variant::has_numeric_constant(base_type.builtin_type, p_symbol)) {
+ r_result.type = ScriptLanguage::LookupResult::RESULT_CLASS_CONSTANT;
+ r_result.class_name = Variant::get_type_name(base_type.builtin_type);
+ r_result.class_member = p_symbol;
+ return OK;
+ }
+
+ Variant v;
+ REF v_ref;
+ if (base_type.builtin_type == Variant::OBJECT) {
+ v_ref.instance();
+ v = v_ref;
+ } else {
+ Variant::CallError err;
+ v = Variant::construct(base_type.builtin_type, NULL, 0, err);
+ if (err.error != Variant::CallError::CALL_OK) {
+ break;
+ }
+ }
+
+ if (v.has_method(p_symbol)) {
+ r_result.type = ScriptLanguage::LookupResult::RESULT_CLASS_METHOD;
+ r_result.class_name = Variant::get_type_name(base_type.builtin_type);
+ r_result.class_member = p_symbol;
+ return OK;
+ }
+
+ bool valid = false;
+ v.get(p_symbol, &valid);
+ if (valid) {
+ r_result.type = ScriptLanguage::LookupResult::RESULT_CLASS_PROPERTY;
+ r_result.class_name = Variant::get_type_name(base_type.builtin_type);
+ r_result.class_member = p_symbol;
+ return OK;
+ }
+ } break;
+ default: {
+ base_type.has_type = false;
+ } break;
+ }
+ }
+
+ return ERR_CANT_RESOLVE;
+}
+
Error GDScriptLanguage::lookup_code(const String &p_code, const String &p_symbol, const String &p_base_path, Object *p_owner, LookupResult &r_result) {
//before parsing, try the usual stuff
@@ -2623,6 +3117,13 @@ Error GDScriptLanguage::lookup_code(const String &p_code, const String &p_symbol
r_result.type = ScriptLanguage::LookupResult::RESULT_CLASS;
r_result.class_name = p_symbol;
return OK;
+ } else {
+ String under_prefix = "_" + p_symbol;
+ if (ClassDB::class_exists(under_prefix)) {
+ r_result.type = ScriptLanguage::LookupResult::RESULT_CLASS;
+ r_result.class_name = p_symbol;
+ return OK;
+ }
}
for (int i = 0; i < Variant::VARIANT_MAX; i++) {
@@ -2650,17 +3151,18 @@ Error GDScriptLanguage::lookup_code(const String &p_code, const String &p_symbol
return OK;
}
- GDScriptParser p;
- p.parse(p_code, p_base_path, false, "", true);
+ GDScriptParser parser;
+ parser.parse(p_code, p_base_path, false, "", true);
- if (p.get_completion_type() == GDScriptParser::COMPLETION_NONE)
+ if (parser.get_completion_type() == GDScriptParser::COMPLETION_NONE) {
return ERR_CANT_RESOLVE;
+ }
GDScriptCompletionContext context;
-
- context._class = p.get_completion_class();
- context.block = p.get_completion_block();
- context.function = p.get_completion_function();
+ context._class = parser.get_completion_class();
+ context.function = parser.get_completion_function();
+ context.block = parser.get_completion_block();
+ context.line = parser.get_completion_line();
context.base = p_owner;
context.base_path = p_base_path;
@@ -2675,171 +3177,68 @@ Error GDScriptLanguage::lookup_code(const String &p_code, const String &p_symbol
}
}
- bool isfunction = false;
+ bool is_function = false;
- switch (p.get_completion_type()) {
-
- case GDScriptParser::COMPLETION_GET_NODE:
- case GDScriptParser::COMPLETION_NONE: {
- } break;
+ switch (parser.get_completion_type()) {
case GDScriptParser::COMPLETION_BUILT_IN_TYPE_CONSTANT: {
-
r_result.type = ScriptLanguage::LookupResult::RESULT_CLASS_CONSTANT;
- r_result.class_name = Variant::get_type_name(p.get_completion_built_in_constant());
+ r_result.class_name = Variant::get_type_name(parser.get_completion_built_in_constant());
r_result.class_member = p_symbol;
return OK;
-
} break;
+ case GDScriptParser::COMPLETION_PARENT_FUNCTION:
case GDScriptParser::COMPLETION_FUNCTION: {
-
- if (context._class && context._class->functions.size()) {
- for (int i = 0; i < context._class->functions.size(); i++) {
- if (context._class->functions[i]->name == p_symbol) {
- r_result.type = ScriptLanguage::LookupResult::RESULT_SCRIPT_LOCATION;
- r_result.location = context._class->functions[i]->line;
- return OK;
- }
- }
- }
-
- Ref<GDScript> parent = _get_parent_class(context);
- while (parent.is_valid()) {
- int line = parent->get_member_line(p_symbol);
- if (line >= 0) {
- r_result.type = ScriptLanguage::LookupResult::RESULT_SCRIPT_LOCATION;
- r_result.location = line;
- r_result.script = parent;
- return OK;
- }
-
- parent = parent->get_base();
- }
-
- GDScriptCompletionIdentifier identifier = _get_native_class(context);
- print_line("identifier: " + String(identifier.obj_type));
-
- if (ClassDB::has_method(identifier.obj_type, p_symbol)) {
-
- r_result.type = ScriptLanguage::LookupResult::RESULT_CLASS_METHOD;
- r_result.class_name = identifier.obj_type;
- r_result.class_member = p_symbol;
- return OK;
- }
-
- } break;
+ is_function = true;
+ } // fallthrough
case GDScriptParser::COMPLETION_IDENTIFIER: {
- //check if a function
- if (p.get_completion_identifier_is_function()) {
- if (context._class && context._class->functions.size()) {
- for (int i = 0; i < context._class->functions.size(); i++) {
- if (context._class->functions[i]->name == p_symbol) {
- r_result.type = ScriptLanguage::LookupResult::RESULT_SCRIPT_LOCATION;
- r_result.location = context._class->functions[i]->line;
- return OK;
- }
- }
- }
-
- Ref<GDScript> parent = _get_parent_class(context);
- while (parent.is_valid()) {
- int line = parent->get_member_line(p_symbol);
- if (line >= 0) {
- r_result.type = ScriptLanguage::LookupResult::RESULT_SCRIPT_LOCATION;
- r_result.location = line;
- r_result.script = parent;
- return OK;
- }
-
- parent = parent->get_base();
- }
-
- GDScriptCompletionIdentifier identifier = _get_native_class(context);
-
- if (ClassDB::has_method(identifier.obj_type, p_symbol)) {
+ if (!is_function) {
+ is_function = parser.get_completion_identifier_is_function();
+ }
- r_result.type = ScriptLanguage::LookupResult::RESULT_CLASS_METHOD;
- r_result.class_name = identifier.obj_type;
- r_result.class_member = p_symbol;
- return OK;
+ GDScriptParser::DataType base_type;
+ if (context._class) {
+ if (parser.get_completion_type() != GDScriptParser::COMPLETION_PARENT_FUNCTION) {
+ base_type.has_type = true;
+ base_type.kind = GDScriptParser::DataType::CLASS;
+ base_type.class_type = const_cast<GDScriptParser::ClassNode *>(context._class);
+ } else {
+ base_type = context._class->base_type;
}
} else {
+ break;
+ }
- GDScriptCompletionIdentifier gdi = _get_native_class(context);
- if (gdi.obj_type != StringName()) {
- bool valid;
- Variant::Type t = ClassDB::get_property_type(gdi.obj_type, p_symbol, &valid);
- if (t != Variant::NIL && valid) {
- r_result.type = ScriptLanguage::LookupResult::RESULT_CLASS_PROPERTY;
- r_result.class_name = gdi.obj_type;
- r_result.class_member = p_symbol;
- return OK;
- }
- }
-
+ if (!is_function && context.block) {
+ // Lookup local variables
const GDScriptParser::BlockNode *block = context.block;
- //search in blocks going up (local var?)
while (block) {
-
- for (int i = 0; i < block->statements.size(); i++) {
-
- if (block->statements[i]->line > p.get_completion_line())
- continue;
-
- if (block->statements[i]->type == GDScriptParser::BlockNode::TYPE_LOCAL_VAR) {
-
- const GDScriptParser::LocalVarNode *lv = static_cast<const GDScriptParser::LocalVarNode *>(block->statements[i]);
-
- if (lv->assign && lv->name == p_symbol) {
-
- r_result.type = ScriptLanguage::LookupResult::RESULT_SCRIPT_LOCATION;
- r_result.location = block->statements[i]->line;
- return OK;
- }
- }
+ if (block->variables.has(p_symbol)) {
+ r_result.type = ScriptLanguage::LookupResult::RESULT_SCRIPT_LOCATION;
+ r_result.location = block->variables[p_symbol]->line;
+ return OK;
}
block = block->parent_block;
}
+ }
- //guess from function arguments
- if (context.function && context.function->name != StringName()) {
-
- for (int i = 0; i < context.function->arguments.size(); i++) {
-
- if (context.function->arguments[i] == p_symbol) {
- r_result.type = ScriptLanguage::LookupResult::RESULT_SCRIPT_LOCATION;
- r_result.location = context.function->line;
- return OK;
- }
- }
- }
-
- //guess in class constants
-
- for (int i = 0; i < context._class->constant_expressions.size(); i++) {
-
- if (context._class->constant_expressions[i].identifier == p_symbol) {
+ if (context.function && context.function->name != StringName()) {
+ // Lookup function arguments
+ for (int i = 0; i < context.function->arguments.size(); i++) {
+ if (context.function->arguments[i] == p_symbol) {
r_result.type = ScriptLanguage::LookupResult::RESULT_SCRIPT_LOCATION;
- r_result.location = context._class->constant_expressions[i].expression->line;
+ r_result.location = context.function->line;
return OK;
}
}
+ }
- //guess in class variables
- if (!(context.function && context.function->_static)) {
-
- for (int i = 0; i < context._class->variables.size(); i++) {
-
- if (context._class->variables[i].identifier == p_symbol) {
-
- r_result.type = ScriptLanguage::LookupResult::RESULT_SCRIPT_LOCATION;
- r_result.location = context._class->variables[i].line;
- return OK;
- }
- }
- }
+ if (_lookup_symbol_from_base(base_type, p_symbol, is_function, r_result) == OK) {
+ return OK;
+ }
- //guess in autoloads as singletons
+ if (!is_function) {
+ // Guess in autoloads as singletons
List<PropertyInfo> props;
ProjectSettings::get_singleton()->get_property_list(&props);
@@ -2856,8 +3255,8 @@ Error GDScriptLanguage::lookup_code(const String &p_code, const String &p_symbol
String script = path.substr(1, path.length());
if (!script.ends_with(".gd")) {
- //not a script, try find the script anyway,
- //may have some success
+ // Not a script, try find the script anyway,
+ // may have some success
script = script.get_basename() + ".gd";
}
@@ -2872,7 +3271,7 @@ Error GDScriptLanguage::lookup_code(const String &p_code, const String &p_symbol
}
}
- //global
+ // Global
Map<StringName, int> classes = GDScriptLanguage::get_singleton()->get_global_map();
if (classes.has(p_symbol)) {
Variant value = GDScriptLanguage::get_singleton()->get_global_array()[classes[p_symbol]];
@@ -2918,152 +3317,31 @@ Error GDScriptLanguage::lookup_code(const String &p_code, const String &p_symbol
}
}
}
-
- } break;
- case GDScriptParser::COMPLETION_PARENT_FUNCTION: {
-
} break;
- case GDScriptParser::COMPLETION_METHOD:
- isfunction = true;
+ case GDScriptParser::COMPLETION_METHOD: {
+ is_function = true;
+ } // fallthrough
case GDScriptParser::COMPLETION_INDEX: {
-
- const GDScriptParser::Node *node = p.get_completion_node();
- if (node->type != GDScriptParser::Node::TYPE_OPERATOR)
+ const GDScriptParser::Node *node = parser.get_completion_node();
+ if (node->type != GDScriptParser::Node::TYPE_OPERATOR) {
+ break;
+ }
+ GDScriptCompletionIdentifier base;
+ if (!_guess_expression_type(context, static_cast<const GDScriptParser::OperatorNode *>(node)->arguments[0], base)) {
break;
-
- GDScriptCompletionIdentifier t;
- if (_guess_expression_type(context, static_cast<const GDScriptParser::OperatorNode *>(node)->arguments[0], p.get_completion_line(), t)) {
-
- if (t.type == Variant::OBJECT && t.obj_type == "GDScriptNativeClass") {
- //native enum
- Ref<GDScriptNativeClass> gdn = t.value;
- if (gdn.is_valid()) {
- r_result.type = ScriptLanguage::LookupResult::RESULT_CLASS_CONSTANT;
- r_result.class_name = gdn->get_name();
- r_result.class_member = p_symbol;
- return OK;
- }
- } else if (t.type == Variant::OBJECT && t.obj_type != StringName()) {
-
- Ref<GDScript> on_script;
-
- if (t.value.get_type()) {
- Object *obj = t.value;
-
- if (obj) {
-
- on_script = obj->get_script();
-
- if (on_script.is_valid()) {
- int loc = on_script->get_member_line(p_symbol);
- if (loc >= 0) {
- r_result.script = on_script;
- r_result.type = ScriptLanguage::LookupResult::RESULT_SCRIPT_LOCATION;
- r_result.location = loc;
- return OK;
- }
- }
- }
- }
-
- if (ClassDB::has_method(t.obj_type, p_symbol)) {
-
- r_result.type = ScriptLanguage::LookupResult::RESULT_CLASS_METHOD;
- r_result.class_name = t.obj_type;
- r_result.class_member = p_symbol;
- return OK;
- }
-
- StringName enumName = ClassDB::get_integer_constant_enum(t.obj_type, p_symbol, true);
- if (enumName != StringName()) {
- r_result.type = ScriptLanguage::LookupResult::RESULT_CLASS_ENUM;
- r_result.class_name = t.obj_type;
- r_result.class_member = enumName;
- return OK;
- }
-
- bool success;
- ClassDB::get_integer_constant(t.obj_type, p_symbol, &success);
- if (success) {
- r_result.type = ScriptLanguage::LookupResult::RESULT_CLASS_CONSTANT;
- r_result.class_name = t.obj_type;
- r_result.class_member = p_symbol;
- return OK;
- }
-
- ClassDB::get_property_type(t.obj_type, p_symbol, &success);
-
- if (success) {
- r_result.type = ScriptLanguage::LookupResult::RESULT_CLASS_PROPERTY;
- r_result.class_name = t.obj_type;
- r_result.class_member = p_symbol;
- return OK;
- }
-
- } else {
-
- Variant::CallError ce;
- Variant v = Variant::construct(t.type, NULL, 0, ce);
-
- bool valid;
- v.get_numeric_constant_value(t.type, p_symbol, &valid);
- if (valid) {
- r_result.type = ScriptLanguage::LookupResult::RESULT_CLASS_CONSTANT;
- r_result.class_name = Variant::get_type_name(t.type);
- r_result.class_member = p_symbol;
- return OK;
- }
-
- //todo check all inputevent types for property
-
- v.get(p_symbol, &valid);
-
- if (valid) {
- r_result.type = ScriptLanguage::LookupResult::RESULT_CLASS_PROPERTY;
- r_result.class_name = Variant::get_type_name(t.type);
- r_result.class_member = p_symbol;
- return OK;
- }
-
- if (v.has_method(p_symbol)) {
-
- r_result.type = ScriptLanguage::LookupResult::RESULT_CLASS_METHOD;
- r_result.class_name = Variant::get_type_name(t.type);
- r_result.class_member = p_symbol;
- return OK;
- }
- }
}
- } break;
- case GDScriptParser::COMPLETION_CALL_ARGUMENTS: {
-
- return ERR_CANT_RESOLVE;
+ if (_lookup_symbol_from_base(base.type, p_symbol, is_function, r_result) == OK) {
+ return OK;
+ }
} break;
case GDScriptParser::COMPLETION_VIRTUAL_FUNC: {
+ GDScriptParser::DataType base_type = context._class->base_type;
- GDScriptCompletionIdentifier cid = _get_native_class(context);
-
- if (cid.obj_type != StringName()) {
- List<MethodInfo> vm;
- ClassDB::get_virtual_methods(cid.obj_type, &vm);
- for (List<MethodInfo>::Element *E = vm.front(); E; E = E->next()) {
-
- if (p_symbol == E->get().name) {
-
- r_result.type = ScriptLanguage::LookupResult::RESULT_CLASS_METHOD;
- r_result.class_name = cid.obj_type;
- r_result.class_member = p_symbol;
- return OK;
- }
- }
+ if (_lookup_symbol_from_base(base_type, p_symbol, true, r_result) == OK) {
+ return OK;
}
} break;
- case GDScriptParser::COMPLETION_YIELD: {
-
- return ERR_CANT_RESOLVE;
-
- } break;
}
return ERR_CANT_RESOLVE;
diff --git a/modules/gdscript/gdscript_function.cpp b/modules/gdscript/gdscript_function.cpp
index 10599f0c38..6a08d86904 100644
--- a/modules/gdscript/gdscript_function.cpp
+++ b/modules/gdscript/gdscript_function.cpp
@@ -200,6 +200,12 @@ static String _get_var_type(const Variant *p_type) {
&&OPCODE_ASSIGN, \
&&OPCODE_ASSIGN_TRUE, \
&&OPCODE_ASSIGN_FALSE, \
+ &&OPCODE_ASSIGN_TYPED_BUILTIN, \
+ &&OPCODE_ASSIGN_TYPED_NATIVE, \
+ &&OPCODE_ASSIGN_TYPED_SCRIPT, \
+ &&OPCODE_CAST_TO_BUILTIN, \
+ &&OPCODE_CAST_TO_NATIVE, \
+ &&OPCODE_CAST_TO_SCRIPT, \
&&OPCODE_CONSTRUCT, \
&&OPCODE_CONSTRUCT_ARRAY, \
&&OPCODE_CONSTRUCT_DICTIONARY, \
@@ -318,10 +324,28 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a
if (_stack_size) {
stack = (Variant *)aptr;
- for (int i = 0; i < p_argcount; i++)
- memnew_placement(&stack[i], Variant(*p_args[i]));
- for (int i = p_argcount; i < _stack_size; i++)
+ for (int i = 0; i < p_argcount; i++) {
+ if (!argument_types[i].has_type) {
+ memnew_placement(&stack[i], Variant(*p_args[i]));
+ continue;
+ }
+
+ if (!argument_types[i].is_type(*p_args[i], true)) {
+ r_err.error = Variant::CallError::CALL_ERROR_INVALID_ARGUMENT;
+ r_err.argument = i;
+ r_err.expected = argument_types[i].kind == GDScriptDataType::BUILTIN ? argument_types[i].builtin_type : Variant::OBJECT;
+ return Variant();
+ }
+ if (argument_types[i].kind == GDScriptDataType::BUILTIN) {
+ Variant arg = Variant::construct(argument_types[i].builtin_type, &p_args[i], 1, r_err);
+ memnew_placement(&stack[i], Variant(arg));
+ } else {
+ memnew_placement(&stack[i], Variant(*p_args[i]));
+ }
+ }
+ for (int i = p_argcount; i < _stack_size; i++) {
memnew_placement(&stack[i], Variant);
+ }
} else {
stack = NULL;
}
@@ -709,6 +733,199 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a
}
DISPATCH_OPCODE;
+ OPCODE(OPCODE_ASSIGN_TYPED_BUILTIN) {
+
+ CHECK_SPACE(4);
+ Variant::Type var_type = (Variant::Type)_code_ptr[ip + 1];
+ GET_VARIANT_PTR(dst, 2);
+ GET_VARIANT_PTR(src, 3);
+
+ GD_ERR_BREAK(var_type < 0 || var_type >= Variant::VARIANT_MAX);
+
+ if (src->get_type() != var_type) {
+ err_text = "Trying to assign value of type '" + Variant::get_type_name(src->get_type()) +
+ "' to a variable of type '" + Variant::get_type_name(var_type) + "'.";
+ OPCODE_BREAK;
+ }
+
+ *dst = *src;
+
+ ip += 4;
+ }
+ DISPATCH_OPCODE;
+
+ OPCODE(OPCODE_ASSIGN_TYPED_NATIVE) {
+
+ CHECK_SPACE(4);
+ GET_VARIANT_PTR(type, 1);
+ GET_VARIANT_PTR(dst, 2);
+ GET_VARIANT_PTR(src, 3);
+
+ GDScriptNativeClass *nc = Object::cast_to<GDScriptNativeClass>(type->operator Object *());
+ GD_ERR_BREAK(!nc);
+ Object *src_obj = src->operator Object *();
+ GD_ERR_BREAK(!src_obj);
+
+ if (!ClassDB::is_parent_class(src_obj->get_class_name(), nc->get_name())) {
+ err_text = "Trying to assign value of type '" + src_obj->get_class_name() +
+ "' to a variable of type '" + nc->get_name() + "'.";
+ OPCODE_BREAK;
+ }
+
+ *dst = *src;
+
+ ip += 4;
+ }
+ DISPATCH_OPCODE;
+
+ OPCODE(OPCODE_ASSIGN_TYPED_SCRIPT) {
+
+ CHECK_SPACE(4);
+ GET_VARIANT_PTR(type, 1);
+ GET_VARIANT_PTR(dst, 2);
+ GET_VARIANT_PTR(src, 3);
+
+ Script *base_type = Object::cast_to<Script>(type->operator Object *());
+
+ GD_ERR_BREAK(!base_type);
+
+ if (src->get_type() != Variant::OBJECT && src->get_type() != Variant::NIL) {
+ err_text = "Trying to assign a non-object value to a variable of type '" + base_type->get_path().get_file() + "'.";
+ OPCODE_BREAK;
+ }
+
+ if (src->get_type() != Variant::NIL && src->operator Object *() != NULL) {
+
+ ScriptInstance *scr_inst = src->operator Object *()->get_script_instance();
+ if (!scr_inst) {
+ err_text = "Trying to assign value of type '" + src->operator Object *()->get_class_name() +
+ "' to a variable of type '" + base_type->get_path().get_file() + "'.";
+ OPCODE_BREAK;
+ }
+
+ Script *src_type = src->operator Object *()->get_script_instance()->get_script().ptr();
+ bool valid = false;
+
+ while (src_type) {
+ if (src_type == base_type) {
+ valid = true;
+ break;
+ }
+ src_type = src_type->get_base_script().ptr();
+ }
+
+ if (!valid) {
+ err_text = "Trying to assign value of type '" + src->operator Object *()->get_script_instance()->get_script()->get_path().get_file() +
+ "' to a variable of type '" + base_type->get_path().get_file() + "'.";
+ OPCODE_BREAK;
+ }
+ }
+
+ *dst = *src;
+
+ ip += 4;
+ }
+ DISPATCH_OPCODE;
+
+ OPCODE(OPCODE_CAST_TO_BUILTIN) {
+
+ CHECK_SPACE(4);
+ Variant::Type to_type = (Variant::Type)_code_ptr[ip + 1];
+ GET_VARIANT_PTR(src, 2);
+ GET_VARIANT_PTR(dst, 3);
+
+ GD_ERR_BREAK(to_type < 0 || to_type >= Variant::VARIANT_MAX);
+
+ Variant::CallError err;
+ *dst = Variant::construct(to_type, (const Variant **)&src, 1, err);
+
+#ifdef DEBUG_ENABLED
+ if (err.error != Variant::CallError::CALL_OK) {
+ err_text = "Invalid cast: could not convert value to '" + Variant::get_type_name(to_type) + "'.";
+ OPCODE_BREAK;
+ }
+#endif
+
+ ip += 4;
+ }
+ DISPATCH_OPCODE;
+
+ OPCODE(OPCODE_CAST_TO_NATIVE) {
+
+ CHECK_SPACE(4);
+ GET_VARIANT_PTR(to_type, 1);
+ GET_VARIANT_PTR(src, 2);
+ GET_VARIANT_PTR(dst, 3);
+
+ GDScriptNativeClass *nc = Object::cast_to<GDScriptNativeClass>(to_type->operator Object *());
+ GD_ERR_BREAK(!nc);
+
+#ifdef DEBUG_ENABLED
+ if (src->get_type() != Variant::OBJECT && src->get_type() != Variant::NIL) {
+ err_text = "Invalid cast: can't convert a non-object value to an object type.";
+ OPCODE_BREAK;
+ }
+#endif
+ Object *src_obj = src->operator Object *();
+
+ if (src_obj && !ClassDB::is_parent_class(src_obj->get_class_name(), nc->get_name())) {
+ *dst = Variant(); // invalid cast, assign NULL
+ } else {
+ *dst = *src;
+ }
+
+ ip += 4;
+ }
+ DISPATCH_OPCODE;
+
+ OPCODE(OPCODE_CAST_TO_SCRIPT) {
+
+ CHECK_SPACE(4);
+ GET_VARIANT_PTR(to_type, 1);
+ GET_VARIANT_PTR(src, 2);
+ GET_VARIANT_PTR(dst, 3);
+
+ Script *base_type = Object::cast_to<Script>(to_type->operator Object *());
+
+ GD_ERR_BREAK(!base_type);
+
+#ifdef DEBUG_ENABLED
+ if (src->get_type() != Variant::OBJECT && src->get_type() != Variant::NIL) {
+ err_text = "Trying to assign a non-object value to a variable of type '" + base_type->get_path().get_file() + "'.";
+ OPCODE_BREAK;
+ }
+#endif
+
+ bool valid = false;
+
+ if (src->get_type() != Variant::NIL && src->operator Object *() != NULL) {
+
+ ScriptInstance *scr_inst = src->operator Object *()->get_script_instance();
+
+ if (scr_inst) {
+
+ Script *src_type = src->operator Object *()->get_script_instance()->get_script().ptr();
+
+ while (src_type) {
+ if (src_type == base_type) {
+ valid = true;
+ break;
+ }
+ src_type = src_type->get_base_script().ptr();
+ }
+ }
+ }
+
+ if (valid) {
+ *dst = *src; // Valid cast, copy the source object
+ } else {
+ *dst = Variant(); // invalid cast, assign NULL
+ }
+
+ ip += 4;
+ }
+ DISPATCH_OPCODE;
+
OPCODE(OPCODE_CONSTRUCT) {
CHECK_SPACE(2);
@@ -1370,6 +1587,15 @@ int GDScriptFunction::get_default_argument_addr(int p_idx) const {
return default_arguments[p_idx];
}
+GDScriptDataType GDScriptFunction::get_return_type() const {
+ return return_type;
+}
+
+GDScriptDataType GDScriptFunction::get_argument_type(int p_idx) const {
+ ERR_FAIL_INDEX_V(p_idx, argument_types.size(), GDScriptDataType());
+ return argument_types[p_idx];
+}
+
StringName GDScriptFunction::get_name() const {
return name;
diff --git a/modules/gdscript/gdscript_function.h b/modules/gdscript/gdscript_function.h
index 770d5c8733..3ce84290fd 100644
--- a/modules/gdscript/gdscript_function.h
+++ b/modules/gdscript/gdscript_function.h
@@ -42,6 +42,95 @@
class GDScriptInstance;
class GDScript;
+struct GDScriptDataType {
+ bool has_type;
+ enum {
+ BUILTIN,
+ NATIVE,
+ SCRIPT,
+ GDSCRIPT
+ } kind;
+ Variant::Type builtin_type;
+ StringName native_type;
+ Ref<Script> script_type;
+
+ bool is_type(const Variant &p_variant, bool p_allow_implicit_conversion = false) const {
+ if (!has_type) return true; // Can't type check
+
+ switch (kind) {
+ case BUILTIN: {
+ Variant::Type var_type = p_variant.get_type();
+ bool valid = builtin_type == var_type;
+ if (!valid && p_allow_implicit_conversion) {
+ valid = Variant::can_convert_strict(var_type, builtin_type);
+ }
+ return valid;
+ } break;
+ case NATIVE: {
+ if (p_variant.get_type() == Variant::NIL) {
+ return true;
+ }
+ if (p_variant.get_type() != Variant::OBJECT) {
+ return false;
+ }
+ Object *obj = p_variant.operator Object *();
+ if (obj && !ClassDB::is_parent_class(obj->get_class_name(), native_type)) {
+ return false;
+ }
+ return true;
+ } break;
+ case SCRIPT:
+ case GDSCRIPT: {
+ if (p_variant.get_type() == Variant::NIL) {
+ return true;
+ }
+ if (p_variant.get_type() != Variant::OBJECT) {
+ return false;
+ }
+ Object *obj = p_variant.operator Object *();
+ Ref<Script> base = obj && obj->get_script_instance() ? obj->get_script_instance()->get_script() : NULL;
+ bool valid = false;
+ while (base.is_valid()) {
+ if (base == script_type) {
+ valid = true;
+ break;
+ }
+ base = base->get_base_script();
+ }
+ return valid;
+ } break;
+ }
+ return false;
+ }
+
+ operator PropertyInfo() const {
+ PropertyInfo info;
+ if (has_type) {
+ switch (kind) {
+ case BUILTIN: {
+ info.type = builtin_type;
+ } break;
+ case NATIVE: {
+ info.type = Variant::OBJECT;
+ info.class_name = native_type;
+ } break;
+ case SCRIPT:
+ case GDSCRIPT: {
+ info.type = Variant::OBJECT;
+ info.class_name = script_type->get_instance_base_type();
+ } break;
+ }
+ } else {
+ info.type = Variant::NIL;
+ info.usage |= PROPERTY_USAGE_NIL_IS_VARIANT;
+ }
+ return info;
+ }
+
+ GDScriptDataType() :
+ has_type(false) {}
+};
+
class GDScriptFunction {
public:
enum Opcode {
@@ -56,6 +145,12 @@ public:
OPCODE_ASSIGN,
OPCODE_ASSIGN_TRUE,
OPCODE_ASSIGN_FALSE,
+ OPCODE_ASSIGN_TYPED_BUILTIN,
+ OPCODE_ASSIGN_TYPED_NATIVE,
+ OPCODE_ASSIGN_TYPED_SCRIPT,
+ OPCODE_CAST_TO_BUILTIN,
+ OPCODE_CAST_TO_NATIVE,
+ OPCODE_CAST_TO_SCRIPT,
OPCODE_CONSTRUCT, //only for basic types!!
OPCODE_CONSTRUCT_ARRAY,
OPCODE_CONSTRUCT_DICTIONARY,
@@ -139,6 +234,8 @@ private:
#endif
Vector<int> default_arguments;
Vector<int> code;
+ Vector<GDScriptDataType> argument_types;
+ GDScriptDataType return_type;
#ifdef TOOLS_ENABLED
Vector<StringName> arg_names;
@@ -199,6 +296,8 @@ public:
int get_max_stack_size() const;
int get_default_argument_count() const;
int get_default_argument_addr(int p_idx) const;
+ GDScriptDataType get_return_type() const;
+ GDScriptDataType get_argument_type(int p_idx) const;
GDScript *get_script() const { return _script; }
StringName get_source() const { return source; }
diff --git a/modules/gdscript/gdscript_functions.cpp b/modules/gdscript/gdscript_functions.cpp
index ce91e7dff3..7e98b6ced9 100644
--- a/modules/gdscript/gdscript_functions.cpp
+++ b/modules/gdscript/gdscript_functions.cpp
@@ -1663,7 +1663,7 @@ MethodInfo GDScriptFunctions::get_info(Function p_func) {
MethodInfo mi("weakref", PropertyInfo(Variant::OBJECT, "obj"));
mi.return_val.type = Variant::OBJECT;
- mi.return_val.name = "WeakRef";
+ mi.return_val.class_name = "WeakRef";
return mi;
@@ -1672,19 +1672,20 @@ MethodInfo GDScriptFunctions::get_info(Function p_func) {
MethodInfo mi("funcref", PropertyInfo(Variant::OBJECT, "instance"), PropertyInfo(Variant::STRING, "funcname"));
mi.return_val.type = Variant::OBJECT;
- mi.return_val.name = "FuncRef";
+ mi.return_val.class_name = "FuncRef";
return mi;
} break;
case TYPE_CONVERT: {
- MethodInfo mi("convert", PropertyInfo(Variant::NIL, "what"), PropertyInfo(Variant::INT, "type"));
- mi.return_val.type = Variant::OBJECT;
+ MethodInfo mi("convert", PropertyInfo(Variant::NIL, "what", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_NIL_IS_VARIANT), PropertyInfo(Variant::INT, "type"));
+ mi.return_val.type = Variant::NIL;
+ mi.return_val.usage |= PROPERTY_USAGE_NIL_IS_VARIANT;
return mi;
} break;
case TYPE_OF: {
- MethodInfo mi("typeof", PropertyInfo(Variant::NIL, "what"));
+ MethodInfo mi("typeof", PropertyInfo(Variant::NIL, "what", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_NIL_IS_VARIANT));
mi.return_val.type = Variant::INT;
return mi;
@@ -1760,7 +1761,7 @@ MethodInfo GDScriptFunctions::get_info(Function p_func) {
} break;
case VAR_TO_STR: {
- MethodInfo mi("var2str", PropertyInfo(Variant::NIL, "var"));
+ MethodInfo mi("var2str", PropertyInfo(Variant::NIL, "var", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_NIL_IS_VARIANT));
mi.return_val.type = Variant::STRING;
return mi;
@@ -1773,7 +1774,7 @@ MethodInfo GDScriptFunctions::get_info(Function p_func) {
return mi;
} break;
case VAR_TO_BYTES: {
- MethodInfo mi("var2bytes", PropertyInfo(Variant::NIL, "var"));
+ MethodInfo mi("var2bytes", PropertyInfo(Variant::NIL, "var", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_NIL_IS_VARIANT));
mi.return_val.type = Variant::POOL_BYTE_ARRAY;
return mi;
@@ -1796,7 +1797,7 @@ MethodInfo GDScriptFunctions::get_info(Function p_func) {
MethodInfo mi("load", PropertyInfo(Variant::STRING, "path"));
mi.return_val.type = Variant::OBJECT;
- mi.return_val.name = "Resource";
+ mi.return_val.class_name = "Resource";
return mi;
} break;
case INST2DICT: {
@@ -1826,13 +1827,13 @@ MethodInfo GDScriptFunctions::get_info(Function p_func) {
} break;
case TO_JSON: {
- MethodInfo mi("to_json", PropertyInfo(Variant::NIL, "var"));
+ MethodInfo mi("to_json", PropertyInfo(Variant::NIL, "var", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_NIL_IS_VARIANT));
mi.return_val.type = Variant::STRING;
return mi;
} break;
case HASH: {
- MethodInfo mi("hash", PropertyInfo(Variant::NIL, "var"));
+ MethodInfo mi("hash", PropertyInfo(Variant::NIL, "var", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_NIL_IS_VARIANT));
mi.return_val.type = Variant::INT;
return mi;
} break;
@@ -1868,7 +1869,7 @@ MethodInfo GDScriptFunctions::get_info(Function p_func) {
return mi;
} break;
case LEN: {
- MethodInfo mi("len", PropertyInfo(Variant::NIL, "var"));
+ MethodInfo mi("len", PropertyInfo(Variant::NIL, "var", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_NIL_IS_VARIANT));
mi.return_val.type = Variant::INT;
return mi;
} break;
diff --git a/modules/gdscript/gdscript_parser.cpp b/modules/gdscript/gdscript_parser.cpp
index d62112d3f1..ac53f33e9e 100644
--- a/modules/gdscript/gdscript_parser.cpp
+++ b/modules/gdscript/gdscript_parser.cpp
@@ -30,6 +30,10 @@
#include "gdscript_parser.h"
+#include "core/core_string_names.h"
+#include "core/engine.h"
+#include "core/project_settings.h"
+#include "core/reference.h"
#include "gdscript.h"
#include "io/resource_loader.h"
#include "os/file_access.h"
@@ -138,8 +142,9 @@ bool GDScriptParser::_parse_arguments(Node *p_parent, Vector<Node *> &p_args, bo
}
Node *arg = _parse_expression(p_parent, p_static);
- if (!arg)
+ if (!arg) {
return false;
+ }
p_args.push_back(arg);
@@ -263,6 +268,7 @@ GDScriptParser::Node *GDScriptParser::_parse_expression(Node *p_parent, bool p_s
bool need_identifier = true;
bool done = false;
+ int line = tokenizer->get_token_line();
while (!done) {
@@ -330,16 +336,19 @@ GDScriptParser::Node *GDScriptParser::_parse_expression(Node *p_parent, bool p_s
OperatorNode *op = alloc_node<OperatorNode>();
op->op = OperatorNode::OP_CALL;
-
+ op->line = line;
op->arguments.push_back(alloc_node<SelfNode>());
+ op->arguments[0]->line = line;
IdentifierNode *funcname = alloc_node<IdentifierNode>();
funcname->name = "get_node";
-
+ funcname->line = line;
op->arguments.push_back(funcname);
ConstantNode *nodepath = alloc_node<ConstantNode>();
nodepath->value = NodePath(StringName(path));
+ nodepath->datatype = _type_from_variant(nodepath->value);
+ nodepath->line = line;
op->arguments.push_back(nodepath);
expr = op;
@@ -353,6 +362,7 @@ GDScriptParser::Node *GDScriptParser::_parse_expression(Node *p_parent, bool p_s
//constant defined by tokenizer
ConstantNode *constant = alloc_node<ConstantNode>();
constant->value = tokenizer->get_token_constant();
+ constant->datatype = _type_from_variant(constant->value);
tokenizer->advance();
expr = constant;
} else if (tokenizer->get_token() == GDScriptTokenizer::TK_CONST_PI) {
@@ -360,6 +370,7 @@ GDScriptParser::Node *GDScriptParser::_parse_expression(Node *p_parent, bool p_s
//constant defined by tokenizer
ConstantNode *constant = alloc_node<ConstantNode>();
constant->value = Math_PI;
+ constant->datatype = _type_from_variant(constant->value);
tokenizer->advance();
expr = constant;
} else if (tokenizer->get_token() == GDScriptTokenizer::TK_CONST_TAU) {
@@ -367,6 +378,7 @@ GDScriptParser::Node *GDScriptParser::_parse_expression(Node *p_parent, bool p_s
//constant defined by tokenizer
ConstantNode *constant = alloc_node<ConstantNode>();
constant->value = Math_TAU;
+ constant->datatype = _type_from_variant(constant->value);
tokenizer->advance();
expr = constant;
} else if (tokenizer->get_token() == GDScriptTokenizer::TK_CONST_INF) {
@@ -374,6 +386,7 @@ GDScriptParser::Node *GDScriptParser::_parse_expression(Node *p_parent, bool p_s
//constant defined by tokenizer
ConstantNode *constant = alloc_node<ConstantNode>();
constant->value = Math_INF;
+ constant->datatype = _type_from_variant(constant->value);
tokenizer->advance();
expr = constant;
} else if (tokenizer->get_token() == GDScriptTokenizer::TK_CONST_NAN) {
@@ -381,6 +394,7 @@ GDScriptParser::Node *GDScriptParser::_parse_expression(Node *p_parent, bool p_s
//constant defined by tokenizer
ConstantNode *constant = alloc_node<ConstantNode>();
constant->value = Math_NAN;
+ constant->datatype = _type_from_variant(constant->value);
tokenizer->advance();
expr = constant;
} else if (tokenizer->get_token() == GDScriptTokenizer::TK_PR_PRELOAD) {
@@ -419,15 +433,13 @@ GDScriptParser::Node *GDScriptParser::_parse_expression(Node *p_parent, bool p_s
}
if (subexpr->type == Node::TYPE_IDENTIFIER) {
IdentifierNode *in = static_cast<IdentifierNode *>(subexpr);
- Vector<ClassNode::Constant> ce = current_class->constant_expressions;
// Try to find the constant expression by the identifier
- for (int i = 0; i < ce.size(); ++i) {
- if (ce[i].identifier == in->name) {
- if (ce[i].expression->type == Node::TYPE_CONSTANT) {
- cn = static_cast<ConstantNode *>(ce[i].expression);
- found_constant = true;
- }
+ if (current_class->constant_expressions.has(in->name)) {
+ Node *cn_exp = current_class->constant_expressions[in->name].expression;
+ if (cn_exp->type == Node::TYPE_CONSTANT) {
+ cn = static_cast<ConstantNode *>(cn_exp);
+ found_constant = true;
}
}
}
@@ -480,15 +492,28 @@ GDScriptParser::Node *GDScriptParser::_parse_expression(Node *p_parent, bool p_s
_set_error("Expected ')' after 'preload' path");
return NULL;
}
+
+ Ref<GDScript> gds = res;
+ if (gds.is_valid() && !gds->is_valid()) {
+ _set_error("Could not fully preload the script, possible cyclic reference or compilation error.");
+ return NULL;
+ }
+
tokenizer->advance();
ConstantNode *constant = alloc_node<ConstantNode>();
constant->value = res;
+ constant->datatype = _type_from_variant(constant->value);
expr = constant;
} else if (tokenizer->get_token() == GDScriptTokenizer::TK_PR_YIELD) {
- //constant defined by tokenizer
+ if (!current_function) {
+ _set_error("yield() can only be used inside function blocks.");
+ return NULL;
+ }
+
+ current_function->has_yield = true;
tokenizer->advance();
if (tokenizer->get_token() != GDScriptTokenizer::TK_PARENTHESIS_OPEN) {
@@ -618,6 +643,7 @@ GDScriptParser::Node *GDScriptParser::_parse_expression(Node *p_parent, bool p_s
ConstantNode *cn = alloc_node<ConstantNode>();
cn->value = Variant::get_numeric_constant_value(bi_type, identifier);
+ cn->datatype = _type_from_variant(cn->value);
expr = cn;
}
@@ -695,24 +721,56 @@ GDScriptParser::Node *GDScriptParser::_parse_expression(Node *p_parent, bool p_s
const ClassNode *cln = current_class;
bool bfn = false;
StringName identifier;
+ int id_line = tokenizer->get_token_line();
if (_get_completable_identifier(COMPLETION_IDENTIFIER, identifier)) {
}
- if (p_parsing_constant) {
- for (int i = 0; i < cln->constant_expressions.size(); ++i) {
-
- if (cln->constant_expressions[i].identifier == identifier) {
+ BlockNode *b = current_block;
+ while (b) {
+ if (b->variables.has(identifier)) {
+ IdentifierNode *id = alloc_node<IdentifierNode>();
+ LocalVarNode *lv = b->variables[identifier];
+ id->name = identifier;
+ id->declared_block = b;
+ id->line = id_line;
+ expr = id;
+ bfn = true;
- expr = cln->constant_expressions[i].expression;
- bfn = true;
- break;
+ switch (tokenizer->get_token()) {
+ case GDScriptTokenizer::TK_OP_ASSIGN_ADD:
+ case GDScriptTokenizer::TK_OP_ASSIGN_BIT_AND:
+ case GDScriptTokenizer::TK_OP_ASSIGN_BIT_OR:
+ case GDScriptTokenizer::TK_OP_ASSIGN_BIT_XOR:
+ case GDScriptTokenizer::TK_OP_ASSIGN_DIV:
+ case GDScriptTokenizer::TK_OP_ASSIGN_MOD:
+ case GDScriptTokenizer::TK_OP_ASSIGN_MUL:
+ case GDScriptTokenizer::TK_OP_ASSIGN_SHIFT_LEFT:
+ case GDScriptTokenizer::TK_OP_ASSIGN_SHIFT_RIGHT:
+ case GDScriptTokenizer::TK_OP_ASSIGN_SUB: {
+ if (lv->assignments == 0 && !lv->datatype.has_type) {
+ _set_error("Using assignment with operation on a variable that was never assigned.");
+ return NULL;
+ }
+ } // fallthrough
+ case GDScriptTokenizer::TK_OP_ASSIGN: {
+ lv->assignments += 1;
+ }
}
+ break;
}
+ b = b->parent_block;
+ }
- if (GDScriptLanguage::get_singleton()->get_global_map().has(identifier)) {
+ if (!bfn && p_parsing_constant) {
+ if (cln->constant_expressions.has(identifier)) {
+ expr = cln->constant_expressions[identifier].expression;
+ bfn = true;
+ } else if (GDScriptLanguage::get_singleton()->get_global_map().has(identifier)) {
//check from constants
ConstantNode *constant = alloc_node<ConstantNode>();
constant->value = GDScriptLanguage::get_singleton()->get_global_array()[GDScriptLanguage::get_singleton()->get_global_map()[identifier]];
+ constant->datatype = _type_from_variant(constant->value);
+ constant->line = id_line;
expr = constant;
bfn = true;
}
@@ -729,6 +787,7 @@ GDScriptParser::Node *GDScriptParser::_parse_expression(Node *p_parent, bool p_s
if (!bfn) {
IdentifierNode *id = alloc_node<IdentifierNode>();
id->name = identifier;
+ id->line = id_line;
expr = id;
}
@@ -902,6 +961,7 @@ GDScriptParser::Node *GDScriptParser::_parse_expression(Node *p_parent, bool p_s
//lua style identifier, easier to write
ConstantNode *cn = alloc_node<ConstantNode>();
cn->value = tokenizer->get_token_literal();
+ cn->datatype = _type_from_variant(cn->value);
key = cn;
tokenizer->advance(2);
expecting = DICT_EXPECT_VALUE;
@@ -941,7 +1001,7 @@ GDScriptParser::Node *GDScriptParser::_parse_expression(Node *p_parent, bool p_s
expr = dict;
- } else if (tokenizer->get_token() == GDScriptTokenizer::TK_PERIOD && (tokenizer->is_token_literal(1) || tokenizer->get_token(1) == GDScriptTokenizer::TK_CURSOR) && tokenizer->get_token(2) == GDScriptTokenizer::TK_PARENTHESIS_OPEN) {
+ } else if (tokenizer->get_token() == GDScriptTokenizer::TK_PERIOD && (tokenizer->is_token_literal(1) || tokenizer->get_token(1) == GDScriptTokenizer::TK_CURSOR)) {
// We check with is_token_literal, as this allows us to use match/sync/etc. as a name
// parent call
@@ -953,17 +1013,23 @@ GDScriptParser::Node *GDScriptParser::_parse_expression(Node *p_parent, bool p_s
op->arguments.push_back(self);
forbidden for now */
StringName identifier;
- if (_get_completable_identifier(COMPLETION_PARENT_FUNCTION, identifier)) {
- //indexing stuff
- }
+ bool is_completion = _get_completable_identifier(COMPLETION_PARENT_FUNCTION, identifier) && for_completion;
IdentifierNode *id = alloc_node<IdentifierNode>();
id->name = identifier;
op->arguments.push_back(id);
- tokenizer->advance(1);
- if (!_parse_arguments(op, op->arguments, p_static))
- return NULL;
+ if (tokenizer->get_token() != GDScriptTokenizer::TK_PARENTHESIS_OPEN) {
+ if (!is_completion) {
+ _set_error("Expected '(' for parent function call.");
+ return NULL;
+ }
+ } else {
+ tokenizer->advance();
+ if (!_parse_arguments(op, op->arguments, p_static)) {
+ return NULL;
+ }
+ }
expr = op;
@@ -1087,6 +1153,26 @@ GDScriptParser::Node *GDScriptParser::_parse_expression(Node *p_parent, bool p_s
break;
}
+ /*****************/
+ /* Parse Casting */
+ /*****************/
+
+ bool has_casting = expr->type == Node::TYPE_CAST;
+ if (tokenizer->get_token() == GDScriptTokenizer::TK_PR_AS) {
+ if (has_casting) {
+ _set_error("Unexpected 'as'.");
+ return NULL;
+ }
+ CastNode *cn = alloc_node<CastNode>();
+ if (!_parse_type(cn->cast_type)) {
+ _set_error("Expected type after 'as'.");
+ return NULL;
+ }
+ has_casting = true;
+ cn->source_node = expr;
+ expr = cn;
+ }
+
/******************/
/* Parse Operator */
/******************/
@@ -1110,7 +1196,7 @@ GDScriptParser::Node *GDScriptParser::_parse_expression(Node *p_parent, bool p_s
//assign, if allowed is only allowed on the first operator
#define _VALIDATE_ASSIGN \
- if (!p_allow_assign) { \
+ if (!p_allow_assign || has_casting) { \
_set_error("Unexpected assign."); \
return NULL; \
} \
@@ -1338,11 +1424,11 @@ GDScriptParser::Node *GDScriptParser::_parse_expression(Node *p_parent, bool p_s
if (next_op >= (expression.size() - 2) || expression[next_op + 2].op != OperatorNode::OP_TERNARY_ELSE) {
_set_error("Expected else after ternary if.");
- ERR_FAIL_V(NULL);
+ return NULL;
}
if (next_op >= (expression.size() - 3)) {
_set_error("Expected value after ternary else.");
- ERR_FAIL_V(NULL);
+ return NULL;
}
OperatorNode *op = alloc_node<OperatorNode>();
@@ -1450,13 +1536,13 @@ GDScriptParser::Node *GDScriptParser::_reduce_expression(Node *p_node, bool p_to
ConstantNode *cn = alloc_node<ConstantNode>();
Array arr;
- //print_line("mk array "+itos(!p_to_const));
arr.resize(an->elements.size());
for (int i = 0; i < an->elements.size(); i++) {
ConstantNode *acn = static_cast<ConstantNode *>(an->elements[i]);
arr[i] = acn->value;
}
cn->value = arr;
+ cn->datatype = _type_from_variant(cn->value);
return cn;
}
@@ -1490,6 +1576,7 @@ GDScriptParser::Node *GDScriptParser::_reduce_expression(Node *p_node, bool p_to
dict[key_c->value] = value_c->value;
}
cn->value = dict;
+ cn->datatype = _type_from_variant(cn->value);
return cn;
}
@@ -1591,6 +1678,7 @@ GDScriptParser::Node *GDScriptParser::_reduce_expression(Node *p_node, bool p_to
ConstantNode *cn = alloc_node<ConstantNode>();
cn->value = v;
+ cn->datatype = _type_from_variant(v);
return cn;
} else if (op->arguments[0]->type == Node::TYPE_BUILT_IN_FUNCTION && last_not_constant == 0) {
@@ -1620,24 +1708,9 @@ GDScriptParser::Node *GDScriptParser::_reduce_expression(Node *p_node, bool p_to
ConstantNode *cn = alloc_node<ConstantNode>();
cn->value = v;
+ cn->datatype = _type_from_variant(v);
return cn;
-
- } /*else if (op->arguments[0]->type==Node::TYPE_CONSTANT && op->arguments[1]->type==Node::TYPE_IDENTIFIER) {
-
- ConstantNode *ca = static_cast<ConstantNode*>(op->arguments[0]);
- IdentifierNode *ib = static_cast<IdentifierNode*>(op->arguments[1]);
-
- bool valid;
- Variant v = ca->value.get_named(ib->name,&valid);
- if (!valid) {
- _set_error("invalid index '"+String(ib->name)+"' in constant expression");
- return op;
- }
-
- ConstantNode *cn = alloc_node<ConstantNode>();
- cn->value=v;
- return cn;
- }*/
+ }
return op;
@@ -1658,13 +1731,14 @@ GDScriptParser::Node *GDScriptParser::_reduce_expression(Node *p_node, bool p_to
ConstantNode *cn = alloc_node<ConstantNode>();
cn->value = v;
+ cn->datatype = _type_from_variant(v);
return cn;
}
return op;
}
- //validate assignment (don't assign to cosntant expression
+ //validate assignment (don't assign to constant expression
switch (op->op) {
case OperatorNode::OP_ASSIGN:
@@ -1711,6 +1785,7 @@ GDScriptParser::Node *GDScriptParser::_reduce_expression(Node *p_node, bool p_to
} \
ConstantNode *cn = alloc_node<ConstantNode>(); \
cn->value = res; \
+ cn->datatype = _type_from_variant(res); \
return cn;
#define _REDUCE_BINARY(m_vop) \
@@ -1724,6 +1799,7 @@ GDScriptParser::Node *GDScriptParser::_reduce_expression(Node *p_node, bool p_to
} \
ConstantNode *cn = alloc_node<ConstantNode>(); \
cn->value = res; \
+ cn->datatype = _type_from_variant(res); \
return cn;
switch (op->op) {
@@ -1799,6 +1875,13 @@ GDScriptParser::Node *GDScriptParser::_reduce_expression(Node *p_node, bool p_to
case OperatorNode::OP_BIT_XOR: {
_REDUCE_BINARY(Variant::OP_BIT_XOR);
} break;
+ case OperatorNode::OP_TERNARY_IF: {
+ if (static_cast<ConstantNode *>(op->arguments[0])->value.booleanize()) {
+ return op->arguments[1];
+ } else {
+ return op->arguments[2];
+ }
+ } break;
default: { ERR_FAIL_V(op); }
}
@@ -1905,6 +1988,15 @@ GDScriptParser::PatternNode *GDScriptParser::_parse_pattern(bool p_static) {
tokenizer->advance();
pattern->pt_type = GDScriptParser::PatternNode::PT_BIND;
pattern->bind = tokenizer->get_token_identifier();
+ // Check if binding is already used
+ if (current_block->variables.has(pattern->bind)) {
+ _set_error("Binding name of '" + pattern->bind.operator String() + "' was already used in the pattern.");
+ return NULL;
+ }
+ // Create local variable for proper identifier detection later
+ LocalVarNode *lv = alloc_node<LocalVarNode>();
+ lv->name = pattern->bind;
+ current_block->variables.insert(lv->name, lv);
tokenizer->advance();
} break;
// dictionary
@@ -2038,18 +2130,34 @@ void GDScriptParser::_parse_pattern_block(BlockNode *p_block, Vector<PatternBran
}
PatternBranchNode *branch = alloc_node<PatternBranchNode>();
+ branch->body = alloc_node<BlockNode>();
+ branch->body->parent_block = p_block;
+ p_block->sub_blocks.push_back(branch->body);
+ current_block = branch->body;
branch->patterns.push_back(_parse_pattern(p_static));
if (!branch->patterns[0]) {
return;
}
+ bool has_binding = branch->patterns[0]->pt_type == PatternNode::PT_BIND;
+ bool catch_all = has_binding || branch->patterns[0]->pt_type == PatternNode::PT_WILDCARD;
+
while (tokenizer->get_token() == GDScriptTokenizer::TK_COMMA) {
tokenizer->advance();
branch->patterns.push_back(_parse_pattern(p_static));
if (!branch->patterns[branch->patterns.size() - 1]) {
return;
}
+
+ PatternNode::PatternType pt = branch->patterns[branch->patterns.size() - 1]->pt_type;
+
+ if (pt == PatternNode::PT_BIND) {
+ _set_error("Cannot use bindings with multipattern.");
+ return;
+ }
+
+ catch_all = catch_all || pt == PatternNode::PT_WILDCARD;
}
if (!_enter_indent_block()) {
@@ -2057,54 +2165,76 @@ void GDScriptParser::_parse_pattern_block(BlockNode *p_block, Vector<PatternBran
return;
}
- branch->body = alloc_node<BlockNode>();
- branch->body->parent_block = p_block;
- p_block->sub_blocks.push_back(branch->body);
- current_block = branch->body;
-
_parse_block(branch->body, p_static);
current_block = p_block;
+ if (catch_all && branch->body->has_return) {
+ p_block->has_return = true;
+ }
+
p_branches.push_back(branch);
}
}
void GDScriptParser::_generate_pattern(PatternNode *p_pattern, Node *p_node_to_match, Node *&p_resulting_node, Map<StringName, Node *> &p_bindings) {
+
+ const DataType &to_match_type = p_node_to_match->get_datatype();
+
switch (p_pattern->pt_type) {
case PatternNode::PT_CONSTANT: {
- // typecheck
- BuiltInFunctionNode *typeof_node = alloc_node<BuiltInFunctionNode>();
- typeof_node->function = GDScriptFunctions::TYPE_OF;
+ DataType pattern_type = _reduce_node_type(p_pattern->constant);
+ if (error_set) {
+ return;
+ }
+
+ OperatorNode *type_comp = NULL;
+
+ // static type check if possible
+ if (pattern_type.has_type && to_match_type.has_type) {
+ if (!_is_type_compatible(to_match_type, pattern_type) && !_is_type_compatible(pattern_type, to_match_type)) {
+ _set_error("Pattern type (" + pattern_type.to_string() + ") is not compatible with the type of the value to match (" + to_match_type.to_string() + ").",
+ p_pattern->line);
+ return;
+ }
+ } else {
+ // runtime typecheck
+ BuiltInFunctionNode *typeof_node = alloc_node<BuiltInFunctionNode>();
+ typeof_node->function = GDScriptFunctions::TYPE_OF;
- OperatorNode *typeof_match_value = alloc_node<OperatorNode>();
- typeof_match_value->op = OperatorNode::OP_CALL;
- typeof_match_value->arguments.push_back(typeof_node);
- typeof_match_value->arguments.push_back(p_node_to_match);
+ OperatorNode *typeof_match_value = alloc_node<OperatorNode>();
+ typeof_match_value->op = OperatorNode::OP_CALL;
+ typeof_match_value->arguments.push_back(typeof_node);
+ typeof_match_value->arguments.push_back(p_node_to_match);
- OperatorNode *typeof_pattern_value = alloc_node<OperatorNode>();
- typeof_pattern_value->op = OperatorNode::OP_CALL;
- typeof_pattern_value->arguments.push_back(typeof_node);
- typeof_pattern_value->arguments.push_back(p_pattern->constant);
+ OperatorNode *typeof_pattern_value = alloc_node<OperatorNode>();
+ typeof_pattern_value->op = OperatorNode::OP_CALL;
+ typeof_pattern_value->arguments.push_back(typeof_node);
+ typeof_pattern_value->arguments.push_back(p_pattern->constant);
- OperatorNode *type_comp = alloc_node<OperatorNode>();
- type_comp->op = OperatorNode::OP_EQUAL;
- type_comp->arguments.push_back(typeof_match_value);
- type_comp->arguments.push_back(typeof_pattern_value);
+ type_comp = alloc_node<OperatorNode>();
+ type_comp->op = OperatorNode::OP_EQUAL;
+ type_comp->arguments.push_back(typeof_match_value);
+ type_comp->arguments.push_back(typeof_pattern_value);
+ }
- // comare the actual values
+ // compare the actual values
OperatorNode *value_comp = alloc_node<OperatorNode>();
value_comp->op = OperatorNode::OP_EQUAL;
value_comp->arguments.push_back(p_pattern->constant);
value_comp->arguments.push_back(p_node_to_match);
- OperatorNode *comparison = alloc_node<OperatorNode>();
- comparison->op = OperatorNode::OP_AND;
- comparison->arguments.push_back(type_comp);
- comparison->arguments.push_back(value_comp);
+ if (type_comp) {
+ OperatorNode *full_comparison = alloc_node<OperatorNode>();
+ full_comparison->op = OperatorNode::OP_AND;
+ full_comparison->arguments.push_back(type_comp);
+ full_comparison->arguments.push_back(value_comp);
- p_resulting_node = comparison;
+ p_resulting_node = full_comparison;
+ } else {
+ p_resulting_node = value_comp;
+ }
} break;
case PatternNode::PT_BIND: {
@@ -2129,22 +2259,32 @@ void GDScriptParser::_generate_pattern(PatternNode *p_pattern, Node *p_node_to_m
// typeof(value_to_match) == TYPE_ARRAY && value_to_match.size() == length
{
- // typecheck
- BuiltInFunctionNode *typeof_node = alloc_node<BuiltInFunctionNode>();
- typeof_node->function = GDScriptFunctions::TYPE_OF;
-
- OperatorNode *typeof_match_value = alloc_node<OperatorNode>();
- typeof_match_value->op = OperatorNode::OP_CALL;
- typeof_match_value->arguments.push_back(typeof_node);
- typeof_match_value->arguments.push_back(p_node_to_match);
-
- IdentifierNode *typeof_array = alloc_node<IdentifierNode>();
- typeof_array->name = "TYPE_ARRAY";
-
- OperatorNode *type_comp = alloc_node<OperatorNode>();
- type_comp->op = OperatorNode::OP_EQUAL;
- type_comp->arguments.push_back(typeof_match_value);
- type_comp->arguments.push_back(typeof_array);
+ OperatorNode *type_comp = NULL;
+ // static type check if possible
+ if (to_match_type.has_type) {
+ // must be an array
+ if (to_match_type.kind != DataType::BUILTIN || to_match_type.builtin_type != Variant::ARRAY) {
+ _set_error("Cannot match an array pattern with a non-array expression.", p_pattern->line);
+ return;
+ }
+ } else {
+ // runtime typecheck
+ BuiltInFunctionNode *typeof_node = alloc_node<BuiltInFunctionNode>();
+ typeof_node->function = GDScriptFunctions::TYPE_OF;
+
+ OperatorNode *typeof_match_value = alloc_node<OperatorNode>();
+ typeof_match_value->op = OperatorNode::OP_CALL;
+ typeof_match_value->arguments.push_back(typeof_node);
+ typeof_match_value->arguments.push_back(p_node_to_match);
+
+ IdentifierNode *typeof_array = alloc_node<IdentifierNode>();
+ typeof_array->name = "TYPE_ARRAY";
+
+ type_comp = alloc_node<OperatorNode>();
+ type_comp->op = OperatorNode::OP_EQUAL;
+ type_comp->arguments.push_back(typeof_match_value);
+ type_comp->arguments.push_back(typeof_array);
+ }
// size
ConstantNode *length = alloc_node<ConstantNode>();
@@ -2163,12 +2303,16 @@ void GDScriptParser::_generate_pattern(PatternNode *p_pattern, Node *p_node_to_m
length_comparison->arguments.push_back(call);
length_comparison->arguments.push_back(length);
- OperatorNode *type_and_length_comparison = alloc_node<OperatorNode>();
- type_and_length_comparison->op = OperatorNode::OP_AND;
- type_and_length_comparison->arguments.push_back(type_comp);
- type_and_length_comparison->arguments.push_back(length_comparison);
+ if (type_comp) {
+ OperatorNode *type_and_length_comparison = alloc_node<OperatorNode>();
+ type_and_length_comparison->op = OperatorNode::OP_AND;
+ type_and_length_comparison->arguments.push_back(type_comp);
+ type_and_length_comparison->arguments.push_back(length_comparison);
- p_resulting_node = type_and_length_comparison;
+ p_resulting_node = type_and_length_comparison;
+ } else {
+ p_resulting_node = length_comparison;
+ }
}
for (int i = 0; i < p_pattern->array.size(); i++) {
@@ -2208,22 +2352,32 @@ void GDScriptParser::_generate_pattern(PatternNode *p_pattern, Node *p_node_to_m
// typeof(value_to_match) == TYPE_DICTIONARY && value_to_match.size() == length
{
- // typecheck
- BuiltInFunctionNode *typeof_node = alloc_node<BuiltInFunctionNode>();
- typeof_node->function = GDScriptFunctions::TYPE_OF;
-
- OperatorNode *typeof_match_value = alloc_node<OperatorNode>();
- typeof_match_value->op = OperatorNode::OP_CALL;
- typeof_match_value->arguments.push_back(typeof_node);
- typeof_match_value->arguments.push_back(p_node_to_match);
-
- IdentifierNode *typeof_dictionary = alloc_node<IdentifierNode>();
- typeof_dictionary->name = "TYPE_DICTIONARY";
-
- OperatorNode *type_comp = alloc_node<OperatorNode>();
- type_comp->op = OperatorNode::OP_EQUAL;
- type_comp->arguments.push_back(typeof_match_value);
- type_comp->arguments.push_back(typeof_dictionary);
+ OperatorNode *type_comp = NULL;
+ // static type check if possible
+ if (to_match_type.has_type) {
+ // must be an dictionary
+ if (to_match_type.kind != DataType::BUILTIN || to_match_type.builtin_type != Variant::DICTIONARY) {
+ _set_error("Cannot match an dictionary pattern with a non-dictionary expression.", p_pattern->line);
+ return;
+ }
+ } else {
+ // runtime typecheck
+ BuiltInFunctionNode *typeof_node = alloc_node<BuiltInFunctionNode>();
+ typeof_node->function = GDScriptFunctions::TYPE_OF;
+
+ OperatorNode *typeof_match_value = alloc_node<OperatorNode>();
+ typeof_match_value->op = OperatorNode::OP_CALL;
+ typeof_match_value->arguments.push_back(typeof_node);
+ typeof_match_value->arguments.push_back(p_node_to_match);
+
+ IdentifierNode *typeof_dictionary = alloc_node<IdentifierNode>();
+ typeof_dictionary->name = "TYPE_DICTIONARY";
+
+ type_comp = alloc_node<OperatorNode>();
+ type_comp->op = OperatorNode::OP_EQUAL;
+ type_comp->arguments.push_back(typeof_match_value);
+ type_comp->arguments.push_back(typeof_dictionary);
+ }
// size
ConstantNode *length = alloc_node<ConstantNode>();
@@ -2242,12 +2396,16 @@ void GDScriptParser::_generate_pattern(PatternNode *p_pattern, Node *p_node_to_m
length_comparison->arguments.push_back(call);
length_comparison->arguments.push_back(length);
- OperatorNode *type_and_length_comparison = alloc_node<OperatorNode>();
- type_and_length_comparison->op = OperatorNode::OP_AND;
- type_and_length_comparison->arguments.push_back(type_comp);
- type_and_length_comparison->arguments.push_back(length_comparison);
+ if (type_comp) {
+ OperatorNode *type_and_length_comparison = alloc_node<OperatorNode>();
+ type_and_length_comparison->op = OperatorNode::OP_AND;
+ type_and_length_comparison->arguments.push_back(type_comp);
+ type_and_length_comparison->arguments.push_back(length_comparison);
- p_resulting_node = type_and_length_comparison;
+ p_resulting_node = type_and_length_comparison;
+ } else {
+ p_resulting_node = length_comparison;
+ }
}
for (Map<ConstantNode *, PatternNode *>::Element *e = p_pattern->dictionary.front(); e; e = e->next()) {
@@ -2308,9 +2466,20 @@ void GDScriptParser::_generate_pattern(PatternNode *p_pattern, Node *p_node_to_m
}
}
-void GDScriptParser::_transform_match_statment(BlockNode *p_block, MatchNode *p_match_statement) {
+void GDScriptParser::_transform_match_statment(MatchNode *p_match_statement) {
IdentifierNode *id = alloc_node<IdentifierNode>();
id->name = "#match_value";
+ id->line = p_match_statement->line;
+ id->datatype = _reduce_node_type(p_match_statement->val_to_match);
+ if (id->datatype.has_type) {
+ _mark_line_as_safe(id->line);
+ } else {
+ _mark_line_as_unsafe(id->line);
+ }
+
+ if (error_set) {
+ return;
+ }
for (int i = 0; i < p_match_statement->branches.size(); i++) {
@@ -2323,11 +2492,16 @@ void GDScriptParser::_transform_match_statment(BlockNode *p_block, MatchNode *p_
for (int j = 0; j < branch->patterns.size(); j++) {
PatternNode *pattern = branch->patterns[j];
+ _mark_line_as_safe(pattern->line);
Map<StringName, Node *> bindings;
- Node *resulting_node;
+ Node *resulting_node = NULL;
_generate_pattern(pattern, id, resulting_node, bindings);
+ if (!resulting_node) {
+ return;
+ }
+
if (!binding.empty() && !bindings.empty()) {
_set_error("Multipatterns can't contain bindings");
return;
@@ -2335,6 +2509,14 @@ void GDScriptParser::_transform_match_statment(BlockNode *p_block, MatchNode *p_
binding = bindings;
}
+ // Result is always a boolean
+ DataType resulting_node_type;
+ resulting_node_type.has_type = true;
+ resulting_node_type.is_constant = true;
+ resulting_node_type.kind = DataType::BUILTIN;
+ resulting_node_type.builtin_type = Variant::BOOL;
+ resulting_node->set_datatype(resulting_node_type);
+
if (compiled_branch.compiled_pattern) {
OperatorNode *or_node = alloc_node<OperatorNode>();
or_node->op = OperatorNode::OP_OR;
@@ -2350,12 +2532,19 @@ void GDScriptParser::_transform_match_statment(BlockNode *p_block, MatchNode *p_
// prepare the body ...hehe
for (Map<StringName, Node *>::Element *e = binding.front(); e; e = e->next()) {
- LocalVarNode *local_var = alloc_node<LocalVarNode>();
- local_var->name = e->key();
+ if (!branch->body->variables.has(e->key())) {
+ _set_error("Parser bug: missing pattern bind variable.", branch->line);
+ ERR_FAIL();
+ }
+
+ LocalVarNode *local_var = branch->body->variables[e->key()];
local_var->assign = e->value();
+ local_var->set_datatype(local_var->assign->get_datatype());
IdentifierNode *id = alloc_node<IdentifierNode>();
id->name = local_var->name;
+ id->declared_block = branch->body;
+ id->set_datatype(local_var->assign->get_datatype());
OperatorNode *op = alloc_node<OperatorNode>();
op->op = OperatorNode::OP_ASSIGN;
@@ -2413,7 +2602,21 @@ void GDScriptParser::_parse_block(BlockNode *p_block, bool p_static) {
}
switch (token) {
-
+ case GDScriptTokenizer::TK_EOF:
+ case GDScriptTokenizer::TK_ERROR:
+ case GDScriptTokenizer::TK_NEWLINE:
+ case GDScriptTokenizer::TK_CF_PASS: {
+ // will check later
+ } break;
+ default: {
+ // TODO: Make this a warning
+ /*if (p_block->has_return) {
+ _set_error("Unreacheable code.");
+ return;
+ }*/
+ } break;
+ }
+ switch (token) {
case GDScriptTokenizer::TK_EOF:
p_block->end_line = tokenizer->get_token_line();
case GDScriptTokenizer::TK_ERROR: {
@@ -2443,6 +2646,7 @@ void GDScriptParser::_parse_block(BlockNode *p_block, bool p_static) {
_set_error("Expected ';' or <NewLine>.");
return;
}
+ _mark_line_as_safe(tokenizer->get_token_line());
tokenizer->advance();
if (tokenizer->get_token() == GDScriptTokenizer::TK_SEMICOLON) {
// Ignore semicolon after 'pass'
@@ -2453,6 +2657,7 @@ void GDScriptParser::_parse_block(BlockNode *p_block, bool p_static) {
//variale declaration and (eventual) initialization
tokenizer->advance();
+ int var_line = tokenizer->get_token_line();
if (!tokenizer->is_token_literal(0, true)) {
_set_error("Expected identifier for local variable name.");
@@ -2470,24 +2675,34 @@ void GDScriptParser::_parse_block(BlockNode *p_block, bool p_static) {
}
BlockNode *check_block = p_block;
while (check_block) {
- for (int i = 0; i < check_block->variables.size(); i++) {
- if (n == check_block->variables[i]) {
- _set_error("Variable '" + String(n) + "' already defined in the scope (at line: " + itos(check_block->variable_lines[i]) + ").");
- return;
- }
+ if (check_block->variables.has(n)) {
+ _set_error("Variable '" + String(n) + "' already defined in the scope (at line: " + itos(check_block->variables[n]->line) + ").");
+ return;
}
check_block = check_block->parent_block;
}
- int var_line = tokenizer->get_token_line();
-
//must know when the local variable is declared
LocalVarNode *lv = alloc_node<LocalVarNode>();
lv->name = n;
+ lv->line = var_line;
p_block->statements.push_back(lv);
Node *assigned = NULL;
+ if (tokenizer->get_token() == GDScriptTokenizer::TK_COLON) {
+ if (tokenizer->get_token(1) == GDScriptTokenizer::TK_OP_ASSIGN) {
+ lv->datatype = DataType();
+#ifdef DEBUG_ENABLED
+ lv->datatype.infer_type = true;
+#endif
+ tokenizer->advance();
+ } else if (!_parse_type(lv->datatype)) {
+ _set_error("Expected type for variable.");
+ return;
+ }
+ }
+
if (tokenizer->get_token() == GDScriptTokenizer::TK_OP_ASSIGN) {
tokenizer->advance();
@@ -2499,26 +2714,36 @@ void GDScriptParser::_parse_block(BlockNode *p_block, bool p_static) {
return;
}
- lv->assign = subexpr;
+ lv->assignments++;
assigned = subexpr;
} else {
ConstantNode *c = alloc_node<ConstantNode>();
- c->value = Variant();
+ if (lv->datatype.has_type && lv->datatype.kind == DataType::BUILTIN) {
+ Variant::CallError err;
+ c->value = Variant::construct(lv->datatype.builtin_type, NULL, 0, err);
+ } else {
+ c->value = Variant();
+ }
+ c->line = var_line;
assigned = c;
}
//must be added later, to avoid self-referencing.
- p_block->variables.push_back(n); //line?
- p_block->variable_lines.push_back(var_line);
+ p_block->variables.insert(n, lv);
IdentifierNode *id = alloc_node<IdentifierNode>();
id->name = n;
+ id->declared_block = p_block;
+ id->line = var_line;
OperatorNode *op = alloc_node<OperatorNode>();
op->op = OperatorNode::OP_ASSIGN;
op->arguments.push_back(id);
op->arguments.push_back(assigned);
+ op->line = var_line;
p_block->statements.push_back(op);
+ lv->assign_op = op;
+ lv->assign = assigned;
if (!_end_statement()) {
_set_error("Expected end of statement (var)");
@@ -2563,6 +2788,9 @@ void GDScriptParser::_parse_block(BlockNode *p_block, bool p_static) {
return;
p_block->statements.push_back(cf_if);
+ bool all_have_return = cf_if->body->has_return;
+ bool have_else = false;
+
while (true) {
while (tokenizer->get_token() == GDScriptTokenizer::TK_NEWLINE && _parse_newline())
@@ -2619,6 +2847,8 @@ void GDScriptParser::_parse_block(BlockNode *p_block, bool p_static) {
if (error_set)
return;
+ all_have_return = all_have_return && cf_else->body->has_return;
+
} else if (tokenizer->get_token() == GDScriptTokenizer::TK_CF_ELSE) {
if (tab_level.back()->get() > indent_level) {
@@ -2642,12 +2872,19 @@ void GDScriptParser::_parse_block(BlockNode *p_block, bool p_static) {
if (error_set)
return;
+ all_have_return = all_have_return && cf_if->body_else->has_return;
+ have_else = true;
+
break; //after else, exit
} else
break;
}
+ cf_if->body->has_return = all_have_return;
+ // If there's no else block, path out of the if might not have a return
+ p_block->has_return = all_have_return && have_else;
+
} break;
case GDScriptTokenizer::TK_CF_WHILE: {
@@ -2680,6 +2917,7 @@ void GDScriptParser::_parse_block(BlockNode *p_block, bool p_static) {
current_block = p_block;
if (error_set)
return;
+ p_block->has_return = cf_while->body->has_return;
p_block->statements.push_back(cf_while);
} break;
case GDScriptTokenizer::TK_CF_FOR: {
@@ -2711,6 +2949,9 @@ void GDScriptParser::_parse_block(BlockNode *p_block, bool p_static) {
return;
}
+ DataType iter_type;
+ iter_type.is_constant = true;
+
if (container->type == Node::TYPE_OPERATOR) {
OperatorNode *op = static_cast<OperatorNode *>(container);
@@ -2745,6 +2986,7 @@ void GDScriptParser::_parse_block(BlockNode *p_block, bool p_static) {
case 2: cn->value = Vector2(constants[0], constants[1]); break;
case 3: cn->value = Vector3(constants[0], constants[1], constants[2]); break;
}
+ cn->datatype = _type_from_variant(cn->value);
container = cn;
} else {
OperatorNode *on = alloc_node<OperatorNode>();
@@ -2766,6 +3008,10 @@ void GDScriptParser::_parse_block(BlockNode *p_block, bool p_static) {
container = on;
}
}
+
+ iter_type.has_type = true;
+ iter_type.kind = DataType::BUILTIN;
+ iter_type.builtin_type = Variant::INT;
}
}
@@ -2789,15 +3035,20 @@ void GDScriptParser::_parse_block(BlockNode *p_block, bool p_static) {
// this is for checking variable for redefining
// inside this _parse_block
- cf_for->body->variables.push_back(id->name);
- cf_for->body->variable_lines.push_back(id->line);
+ LocalVarNode *lv = alloc_node<LocalVarNode>();
+ lv->name = id->name;
+ lv->line = id->line;
+ lv->assignments++;
+ id->declared_block = cf_for->body;
+ lv->set_datatype(iter_type);
+ id->set_datatype(iter_type);
+ cf_for->body->variables.insert(id->name, lv);
_parse_block(cf_for->body, p_static);
- cf_for->body->variables.remove(0);
- cf_for->body->variable_lines.remove(0);
current_block = p_block;
if (error_set)
return;
+ p_block->has_return = cf_for->body->has_return;
p_block->statements.push_back(cf_for);
} break;
case GDScriptTokenizer::TK_CF_CONTINUE: {
@@ -2827,6 +3078,7 @@ void GDScriptParser::_parse_block(BlockNode *p_block, bool p_static) {
tokenizer->advance();
ControlFlowNode *cf_return = alloc_node<ControlFlowNode>();
cf_return->cf_type = ControlFlowNode::CF_RETURN;
+ cf_return->line = tokenizer->get_token_line(-1);
if (tokenizer->get_token() == GDScriptTokenizer::TK_SEMICOLON || tokenizer->get_token() == GDScriptTokenizer::TK_NEWLINE || tokenizer->get_token() == GDScriptTokenizer::TK_EOF) {
//expect end of statement
@@ -2850,6 +3102,7 @@ void GDScriptParser::_parse_block(BlockNode *p_block, bool p_static) {
return;
}
}
+ p_block->has_return = true;
} break;
case GDScriptTokenizer::TK_CF_MATCH: {
@@ -2882,12 +3135,14 @@ void GDScriptParser::_parse_block(BlockNode *p_block, bool p_static) {
_parse_pattern_block(compiled_branches, match_node->branches, p_static);
- _transform_match_statment(compiled_branches, match_node);
+ if (error_set) return;
ControlFlowNode *match_cf_node = alloc_node<ControlFlowNode>();
match_cf_node->cf_type = ControlFlowNode::CF_MATCH;
match_cf_node->match = match_node;
+ match_cf_node->body = compiled_branches;
+ p_block->has_return = p_block->has_return || compiled_branches->has_return;
p_block->statements.push_back(match_cf_node);
_end_statement();
@@ -2938,16 +3193,6 @@ void GDScriptParser::_parse_block(BlockNode *p_block, bool p_static) {
}
} break;
- /*
- case GDScriptTokenizer::TK_CF_LOCAL: {
-
- if (tokenizer->get_token(1)!=GDScriptTokenizer::TK_SEMICOLON && tokenizer->get_token(1)!=GDScriptTokenizer::TK_NEWLINE ) {
-
- _set_error("Expected ';' or <NewLine>.");
- }
- tokenizer->advance();
- } break;
- */
}
}
}
@@ -3103,6 +3348,7 @@ void GDScriptParser::_parse_class(ClassNode *p_class) {
} break;
case GDScriptTokenizer::TK_PR_EXTENDS: {
+ _mark_line_as_safe(tokenizer->get_token_line());
_parse_extends(p_class);
if (error_set)
return;
@@ -3131,6 +3377,11 @@ void GDScriptParser::_parse_class(ClassNode *p_class) {
return;
}
+ if (ClassDB::class_exists(p_class->name)) {
+ _set_error("Class '" + p_class->name + "' shadows a native class.");
+ return;
+ }
+
tokenizer->advance(2);
} break;
@@ -3150,7 +3401,6 @@ void GDScriptParser::_parse_class(ClassNode *p_class) {
//class inside class :D
StringName name;
- StringName extends;
if (tokenizer->get_token(1) != GDScriptTokenizer::TK_IDENTIFIER) {
@@ -3160,10 +3410,30 @@ void GDScriptParser::_parse_class(ClassNode *p_class) {
name = tokenizer->get_token_identifier(1);
tokenizer->advance(2);
+ // Check if name is shadowing something else
+ if (ClassDB::class_exists(name) || ClassDB::class_exists("_" + name.operator String())) {
+ _set_error("Class '" + String(name) + "' shadows a native class.");
+ return;
+ }
if (ScriptServer::is_global_class(name)) {
_set_error("Can't override name of unique global class '" + name + "' already exists at path: " + ScriptServer::get_global_class_path(p_class->name));
return;
}
+ ClassNode *outer_class = p_class;
+ while (outer_class) {
+ for (int i = 0; i < outer_class->subclasses.size(); i++) {
+ if (outer_class->subclasses[i]->name == name) {
+ _set_error("Another class named '" + String(name) + "' already exists in this scope (at line " + itos(outer_class->subclasses[i]->line) + ").");
+ return;
+ }
+ }
+ if (outer_class->constant_expressions.has(name)) {
+ _set_error("A constant named '" + String(name) + "' already exists in the outer class scope (at line" + itos(outer_class->constant_expressions[name].expression->line) + ").");
+ return;
+ }
+
+ outer_class = outer_class->owner;
+ }
ClassNode *newclass = alloc_node<ClassNode>();
newclass->initializer = alloc_node<BlockNode>();
@@ -3249,6 +3519,7 @@ void GDScriptParser::_parse_class(ClassNode *p_class) {
tokenizer->advance();
Vector<StringName> arguments;
+ Vector<DataType> argument_types;
Vector<Node *> default_values;
int fnline = tokenizer->get_token_line();
@@ -3279,6 +3550,15 @@ void GDScriptParser::_parse_class(ClassNode *p_class) {
tokenizer->advance();
+ DataType argtype;
+ if (tokenizer->get_token() == GDScriptTokenizer::TK_COLON) {
+ if (!_parse_type(argtype)) {
+ _set_error("Expected type for argument.");
+ return;
+ }
+ }
+ argument_types.push_back(argtype);
+
if (defaulting && tokenizer->get_token() != GDScriptTokenizer::TK_OP_ASSIGN) {
_set_error("Default parameter expected.");
@@ -3296,9 +3576,11 @@ void GDScriptParser::_parse_class(ClassNode *p_class) {
OperatorNode *on = alloc_node<OperatorNode>();
on->op = OperatorNode::OP_ASSIGN;
+ on->line = fnline;
IdentifierNode *in = alloc_node<IdentifierNode>();
in->name = argname;
+ in->line = fnline;
on->arguments.push_back(in);
on->arguments.push_back(defval);
@@ -3335,6 +3617,11 @@ void GDScriptParser::_parse_class(ClassNode *p_class) {
if (name == "_init") {
+ if (_static) {
+ _set_error("Constructor cannot be static.");
+ return;
+ }
+
if (p_class->extends_used) {
OperatorNode *cparent = alloc_node<OperatorNode>();
@@ -3349,6 +3636,7 @@ void GDScriptParser::_parse_class(ClassNode *p_class) {
tokenizer->advance();
if (tokenizer->get_token() != GDScriptTokenizer::TK_PARENTHESIS_OPEN) {
_set_error("expected '(' for parent constructor arguments.");
+ return;
}
tokenizer->advance();
@@ -3386,6 +3674,15 @@ void GDScriptParser::_parse_class(ClassNode *p_class) {
}
}
+ DataType return_type;
+ if (tokenizer->get_token() == GDScriptTokenizer::TK_FORWARD_ARROW) {
+
+ if (!_parse_type(return_type, true)) {
+ _set_error("Expected return type for function.");
+ return;
+ }
+ }
+
if (!_enter_indent_block(block)) {
_set_error("Indented block expected.");
@@ -3394,7 +3691,9 @@ void GDScriptParser::_parse_class(ClassNode *p_class) {
FunctionNode *function = alloc_node<FunctionNode>();
function->name = name;
+ function->return_type = return_type;
function->arguments = arguments;
+ function->argument_types = argument_types;
function->default_values = default_values;
function->_static = _static;
function->line = fnline;
@@ -4109,10 +4408,37 @@ void GDScriptParser::_parse_class(ClassNode *p_class) {
member.line = tokenizer->get_token_line();
member.rpc_mode = rpc_mode;
+ if (current_class->constant_expressions.has(member.identifier)) {
+ _set_error("A constant named '" + String(member.identifier) + "' alread exists in this class (at line: " +
+ itos(current_class->constant_expressions[member.identifier].expression->line) + ").");
+ return;
+ }
+
+ for (int i = 0; i < current_class->variables.size(); i++) {
+ if (current_class->variables[i].identifier == member.identifier) {
+ _set_error("Variable '" + String(member.identifier) + "' alread exists in this class (at line: " +
+ itos(current_class->variables[i].line) + ").");
+ return;
+ }
+ }
+
tokenizer->advance();
rpc_mode = MultiplayerAPI::RPC_MODE_DISABLED;
+ if (tokenizer->get_token() == GDScriptTokenizer::TK_COLON) {
+ if (tokenizer->get_token(1) == GDScriptTokenizer::TK_OP_ASSIGN) {
+ member.data_type = DataType();
+#ifdef DEBUG_ENABLED
+ member.data_type.infer_type = true;
+#endif
+ tokenizer->advance();
+ } else if (!_parse_type(member.data_type)) {
+ _set_error("Expected type for class variable.");
+ return;
+ }
+ }
+
if (tokenizer->get_token() == GDScriptTokenizer::TK_OP_ASSIGN) {
#ifdef DEBUG_ENABLED
@@ -4144,42 +4470,31 @@ void GDScriptParser::_parse_class(ClassNode *p_class) {
member.expression = subexpr;
- if (autoexport) {
- if (1) /*(subexpr->type==Node::TYPE_ARRAY) {
-
- member._export.type=Variant::ARRAY;
-
- } else if (subexpr->type==Node::TYPE_DICTIONARY) {
-
- member._export.type=Variant::DICTIONARY;
-
- } else*/
- {
+ if (autoexport && !member.data_type.has_type) {
- if (subexpr->type != Node::TYPE_CONSTANT) {
+ if (subexpr->type != Node::TYPE_CONSTANT) {
- _set_error("Type-less export needs a constant expression assigned to infer type.");
- return;
- }
+ _set_error("Type-less export needs a constant expression assigned to infer type.");
+ return;
+ }
- ConstantNode *cn = static_cast<ConstantNode *>(subexpr);
- if (cn->value.get_type() == Variant::NIL) {
+ ConstantNode *cn = static_cast<ConstantNode *>(subexpr);
+ if (cn->value.get_type() == Variant::NIL) {
- _set_error("Can't accept a null constant expression for inferring export type.");
+ _set_error("Can't accept a null constant expression for inferring export type.");
+ return;
+ }
+ member._export.type = cn->value.get_type();
+ member._export.usage |= PROPERTY_USAGE_SCRIPT_VARIABLE;
+ if (cn->value.get_type() == Variant::OBJECT) {
+ Object *obj = cn->value;
+ Resource *res = Object::cast_to<Resource>(obj);
+ if (res == NULL) {
+ _set_error("Exported constant not a type or resource.");
return;
}
- member._export.type = cn->value.get_type();
- member._export.usage |= PROPERTY_USAGE_SCRIPT_VARIABLE;
- if (cn->value.get_type() == Variant::OBJECT) {
- Object *obj = cn->value;
- Resource *res = Object::cast_to<Resource>(obj);
- if (res == NULL) {
- _set_error("Exported constant not a type or resource.");
- return;
- }
- member._export.hint = PROPERTY_HINT_RESOURCE_TYPE;
- member._export.hint_string = res->get_class();
- }
+ member._export.hint = PROPERTY_HINT_RESOURCE_TYPE;
+ member._export.hint_string = res->get_class();
}
}
#ifdef TOOLS_ENABLED
@@ -4213,15 +4528,36 @@ void GDScriptParser::_parse_class(ClassNode *p_class) {
else
p_class->initializer->statements.push_back(op);
- } else {
+ member.initial_assignment = op;
- if (autoexport) {
+ } else {
+ if (autoexport && !member.data_type.has_type) {
_set_error("Type-less export needs a constant expression assigned to infer type.");
return;
}
}
+ if (autoexport && member.data_type.has_type) {
+ if (member.data_type.kind == DataType::BUILTIN) {
+ member._export.type = member.data_type.builtin_type;
+ } else if (member.data_type.kind == DataType::NATIVE) {
+ if (ClassDB::is_parent_class(member.data_type.native_type, "Resource")) {
+ member._export.type = Variant::OBJECT;
+ member._export.hint = PROPERTY_HINT_RESOURCE_TYPE;
+ member._export.usage |= PROPERTY_USAGE_SCRIPT_VARIABLE;
+ member._export.class_name = member.data_type.native_type;
+ } else {
+ _set_error("Invalid export type. Only built-in and native resource types can be exported.", member.line);
+ return;
+ }
+
+ } else {
+ _set_error("Invalid export type. Only built-in and native resource types can be exported.", member.line);
+ return;
+ }
+ }
+
if (tokenizer->get_token() == GDScriptTokenizer::TK_PR_SETGET) {
tokenizer->advance();
@@ -4258,7 +4594,7 @@ void GDScriptParser::_parse_class(ClassNode *p_class) {
}
} break;
case GDScriptTokenizer::TK_PR_CONST: {
- //variale declaration and (eventual) initialization
+ // constant declaration and initialization
ClassNode::Constant constant;
@@ -4269,9 +4605,38 @@ void GDScriptParser::_parse_class(ClassNode *p_class) {
return;
}
- constant.identifier = tokenizer->get_token_literal();
+ StringName const_id = tokenizer->get_token_literal();
+ int line = tokenizer->get_token_line();
+
+ if (current_class->constant_expressions.has(const_id)) {
+ _set_error("Constant '" + String(const_id) + "' alread exists in this class (at line: " +
+ itos(current_class->constant_expressions[const_id].expression->line) + ").");
+ return;
+ }
+
+ for (int i = 0; i < current_class->variables.size(); i++) {
+ if (current_class->variables[i].identifier == const_id) {
+ _set_error("A variable named '" + String(const_id) + "' alread exists in this class (at line: " +
+ itos(current_class->variables[i].line) + ").");
+ return;
+ }
+ }
+
tokenizer->advance();
+ if (tokenizer->get_token() == GDScriptTokenizer::TK_COLON) {
+ if (tokenizer->get_token(1) == GDScriptTokenizer::TK_OP_ASSIGN) {
+ constant.type = DataType();
+#ifdef DEBUG_ENABLED
+ constant.type.infer_type = true;
+#endif
+ tokenizer->advance();
+ } else if (!_parse_type(constant.type)) {
+ _set_error("Expected type for class constant.");
+ return;
+ }
+ }
+
if (tokenizer->get_token() != GDScriptTokenizer::TK_OP_ASSIGN) {
_set_error("Constant expects assignment.");
return;
@@ -4288,14 +4653,16 @@ void GDScriptParser::_parse_class(ClassNode *p_class) {
}
if (subexpr->type != Node::TYPE_CONSTANT) {
- _set_error("Expected constant expression");
+ _set_error("Expected constant expression", line);
+ return;
}
+ subexpr->line = line;
constant.expression = subexpr;
- p_class->constant_expressions.push_back(constant);
+ p_class->constant_expressions.insert(const_id, constant);
if (!_end_statement()) {
- _set_error("Expected end of statement (constant)");
+ _set_error("Expected end of statement (constant)", line);
return;
}
@@ -4338,7 +4705,7 @@ void GDScriptParser::_parse_class(ClassNode *p_class) {
} else { // tokenizer->is_token_literal(0, true)
ClassNode::Constant constant;
- constant.identifier = tokenizer->get_token_literal();
+ StringName const_id = tokenizer->get_token_literal();
tokenizer->advance();
@@ -4355,22 +4722,25 @@ void GDScriptParser::_parse_class(ClassNode *p_class) {
if (subexpr->type != Node::TYPE_CONSTANT) {
_set_error("Expected constant expression");
+ return;
}
- const ConstantNode *subexpr_const = static_cast<const ConstantNode *>(subexpr);
+ ConstantNode *subexpr_const = static_cast<ConstantNode *>(subexpr);
if (subexpr_const->value.get_type() != Variant::INT) {
_set_error("Expected an int value for enum");
+ return;
}
last_assign = subexpr_const->value;
- constant.expression = subexpr;
+ constant.expression = subexpr_const;
} else {
last_assign = last_assign + 1;
ConstantNode *cn = alloc_node<ConstantNode>();
cn->value = last_assign;
+ cn->datatype = _type_from_variant(cn->value);
constant.expression = cn;
}
@@ -4380,20 +4750,25 @@ void GDScriptParser::_parse_class(ClassNode *p_class) {
if (enum_name != "") {
const ConstantNode *cn = static_cast<const ConstantNode *>(constant.expression);
- enum_dict[constant.identifier] = cn->value;
+ enum_dict[const_id] = cn->value;
}
- p_class->constant_expressions.push_back(constant);
+ constant.type.has_type = true;
+ constant.type.kind = DataType::BUILTIN;
+ constant.type.builtin_type = Variant::INT;
+ p_class->constant_expressions.insert(const_id, constant);
}
}
if (enum_name != "") {
ClassNode::Constant enum_constant;
- enum_constant.identifier = enum_name;
ConstantNode *cn = alloc_node<ConstantNode>();
cn->value = enum_dict;
+ cn->datatype = _type_from_variant(cn->value);
+
enum_constant.expression = cn;
- p_class->constant_expressions.push_back(enum_constant);
+ enum_constant.type = cn->datatype;
+ p_class->constant_expressions.insert(enum_name, enum_constant);
}
if (!_end_statement()) {
@@ -4423,6 +4798,2711 @@ void GDScriptParser::_parse_class(ClassNode *p_class) {
}
}
+void GDScriptParser::_determine_inheritance(ClassNode *p_class) {
+
+ if (p_class->extends_used) {
+ //do inheritance
+ String path = p_class->extends_file;
+
+ Ref<GDScript> script;
+ StringName native;
+ ClassNode *base_class = NULL;
+
+ if (path != "") {
+ //path (and optionally subclasses)
+
+ if (path.is_rel_path()) {
+
+ String base = base_path;
+
+ if (base == "" || base.is_rel_path()) {
+ _set_error("Could not resolve relative path for parent class: " + path, p_class->line);
+ return;
+ }
+ path = base.plus_file(path).simplify_path();
+ }
+ script = ResourceLoader::load(path);
+ if (script.is_null()) {
+ _set_error("Could not load base class: " + path, p_class->line);
+ return;
+ }
+ if (!script->is_valid()) {
+
+ _set_error("Script not fully loaded (cyclic preload?): " + path, p_class->line);
+ return;
+ }
+
+ if (p_class->extends_class.size()) {
+
+ for (int i = 0; i < p_class->extends_class.size(); i++) {
+
+ String sub = p_class->extends_class[i];
+ if (script->get_subclasses().has(sub)) {
+
+ Ref<Script> subclass = script->get_subclasses()[sub]; //avoid reference from disappearing
+ script = subclass;
+ } else {
+
+ _set_error("Could not find subclass: " + sub, p_class->line);
+ return;
+ }
+ }
+ }
+
+ } else {
+
+ if (p_class->extends_class.size() == 0) {
+ _set_error("Parser bug: undecidable inheritance.", p_class->line);
+ ERR_FAIL();
+ }
+ //look around for the subclasses
+
+ int extend_iter = 1;
+ String base = p_class->extends_class[0];
+ ClassNode *p = p_class->owner;
+ Ref<GDScript> base_script;
+
+ if (ScriptServer::is_global_class(base)) {
+ base_script = ResourceLoader::load(ScriptServer::get_global_class_path(base));
+ if (!base_script.is_valid()) {
+ _set_error("Class '" + base + "' could not be fully loaded (script error or cyclic inheritance).", p_class->line);
+ return;
+ }
+ p = NULL;
+ }
+
+ while (p) {
+
+ bool found = false;
+
+ for (int i = 0; i < p->subclasses.size(); i++) {
+ if (p->subclasses[i]->name == base) {
+ ClassNode *test = p->subclasses[i];
+ while (test) {
+ if (test == p_class) {
+ _set_error("Cyclic inheritance.", test->line);
+ return;
+ }
+ if (test->base_type.kind == DataType::CLASS) {
+ test = test->base_type.class_type;
+ } else {
+ break;
+ }
+ }
+ found = true;
+ if (extend_iter < p_class->extends_class.size()) {
+ // Keep looking at current classes if possible
+ base = p_class->extends_class[extend_iter++];
+ p = p->subclasses[i];
+ } else {
+ base_class = p->subclasses[i];
+ }
+ break;
+ }
+ }
+
+ if (base_class) break;
+ if (found) continue;
+
+ if (p->constant_expressions.has(base)) {
+ if (!p->constant_expressions[base].expression->type == Node::TYPE_CONSTANT) {
+ _set_error("Could not resolve constant '" + base + "'.", p_class->line);
+ return;
+ }
+ const ConstantNode *cn = static_cast<const ConstantNode *>(p->constant_expressions[base].expression);
+ base_script = cn->value;
+ if (base_script.is_null()) {
+ _set_error("Constant is not a class: " + base, p_class->line);
+ return;
+ }
+ break;
+ }
+
+ p = p->owner;
+ }
+
+ if (base_script.is_valid()) {
+
+ String ident = base;
+
+ for (int i = extend_iter; i < p_class->extends_class.size(); i++) {
+
+ String subclass = p_class->extends_class[i];
+
+ ident += ("." + subclass);
+
+ if (base_script->get_subclasses().has(subclass)) {
+
+ base_script = base_script->get_subclasses()[subclass];
+ } else if (base_script->get_constants().has(subclass)) {
+
+ Ref<GDScript> new_base_class = base_script->get_constants()[subclass];
+ if (new_base_class.is_null()) {
+ _set_error("Constant is not a class: " + ident, p_class->line);
+ return;
+ }
+ base_script = new_base_class;
+ } else {
+
+ _set_error("Could not find subclass: " + ident, p_class->line);
+ return;
+ }
+ }
+
+ script = base_script;
+
+ } else if (!base_class) {
+
+ if (p_class->extends_class.size() > 1) {
+
+ _set_error("Invalid inheritance (unknown class + subclasses)", p_class->line);
+ return;
+ }
+ //if not found, try engine classes
+ if (!GDScriptLanguage::get_singleton()->get_global_map().has(base)) {
+
+ _set_error("Unknown class: '" + base + "'", p_class->line);
+ return;
+ }
+
+ native = base;
+ }
+ }
+
+ if (base_class) {
+ p_class->base_type.has_type = true;
+ p_class->base_type.kind = DataType::CLASS;
+ p_class->base_type.class_type = base_class;
+ } else if (script.is_valid()) {
+ p_class->base_type.has_type = true;
+ p_class->base_type.kind = DataType::GDSCRIPT;
+ p_class->base_type.script_type = script;
+ p_class->base_type.native_type = script->get_instance_base_type();
+ } else if (native != StringName()) {
+ p_class->base_type.has_type = true;
+ p_class->base_type.kind = DataType::NATIVE;
+ p_class->base_type.native_type = native;
+ } else {
+ _set_error("Could not determine inheritance", p_class->line);
+ return;
+ }
+
+ } else {
+ // without extends, implicitly extend Reference
+ p_class->base_type.has_type = true;
+ p_class->base_type.kind = DataType::NATIVE;
+ p_class->base_type.native_type = "Reference";
+ }
+
+ // Recursively determine subclasses
+ for (int i = 0; i < p_class->subclasses.size(); i++) {
+ _determine_inheritance(p_class->subclasses[i]);
+ }
+}
+
+String GDScriptParser::DataType::to_string() const {
+ if (!has_type) return "var";
+ switch (kind) {
+ case BUILTIN: {
+ if (builtin_type == Variant::NIL) return "null";
+ return Variant::get_type_name(builtin_type);
+ } break;
+ case NATIVE: {
+ if (is_meta_type) {
+ return "GDScriptNativeClass";
+ }
+ return native_type.operator String();
+ } break;
+
+ case GDSCRIPT: {
+ Ref<GDScript> gds = script_type;
+ const String &gds_class = gds->get_script_class_name();
+ if (!gds_class.empty()) {
+ return gds_class;
+ }
+ } // fallthrough
+ case SCRIPT: {
+ if (is_meta_type) {
+ return script_type->get_class_name().operator String();
+ }
+ String name = script_type->get_name();
+ if (name != String()) {
+ return name;
+ }
+ name = script_type->get_path().get_file();
+ if (name != String()) {
+ return name;
+ }
+ return native_type.operator String();
+ } break;
+ case CLASS: {
+ ERR_FAIL_COND_V(!class_type, String());
+ if (is_meta_type) {
+ return "GDScript";
+ }
+ if (class_type->name == StringName()) {
+ return "self";
+ }
+ return class_type->name.operator String();
+ } break;
+ }
+
+ return "Unresolved";
+}
+
+bool GDScriptParser::_parse_type(DataType &r_type, bool p_can_be_void) {
+ tokenizer->advance();
+ r_type.has_type = true;
+
+ bool finished = false;
+ bool can_index = false;
+ String full_name;
+
+ if (tokenizer->get_token() == GDScriptTokenizer::TK_CURSOR) {
+ completion_cursor = StringName();
+ completion_type = COMPLETION_TYPE_HINT;
+ completion_class = current_class;
+ completion_function = current_function;
+ completion_line = tokenizer->get_token_line();
+ completion_argument = 0;
+ completion_block = current_block;
+ completion_found = true;
+ completion_ident_is_call = p_can_be_void;
+ tokenizer->advance();
+ }
+
+ switch (tokenizer->get_token()) {
+ case GDScriptTokenizer::TK_PR_VOID: {
+ if (!p_can_be_void) {
+ return false;
+ }
+ r_type.kind = DataType::BUILTIN;
+ r_type.builtin_type = Variant::NIL;
+ } break;
+ case GDScriptTokenizer::TK_BUILT_IN_TYPE: {
+ r_type.builtin_type = tokenizer->get_token_type();
+ if (tokenizer->get_token_type() == Variant::OBJECT) {
+ r_type.kind = DataType::NATIVE;
+ r_type.native_type = "Object";
+ } else {
+ r_type.kind = DataType::BUILTIN;
+ }
+ } break;
+ case GDScriptTokenizer::TK_IDENTIFIER: {
+ r_type.native_type = tokenizer->get_token_identifier();
+ if (ClassDB::class_exists(r_type.native_type) || ClassDB::class_exists("_" + r_type.native_type.operator String())) {
+ r_type.kind = DataType::NATIVE;
+ } else {
+ r_type.kind = DataType::UNRESOLVED;
+ can_index = true;
+ full_name = r_type.native_type;
+ }
+ } break;
+ default: {
+ return false;
+ }
+ }
+
+ tokenizer->advance();
+
+ if (tokenizer->get_token() == GDScriptTokenizer::TK_CURSOR) {
+ completion_cursor = r_type.native_type;
+ completion_type = COMPLETION_TYPE_HINT;
+ completion_class = current_class;
+ completion_function = current_function;
+ completion_line = tokenizer->get_token_line();
+ completion_argument = 0;
+ completion_block = current_block;
+ completion_found = true;
+ completion_ident_is_call = p_can_be_void;
+ tokenizer->advance();
+ }
+
+ if (can_index) {
+ while (!finished) {
+ switch (tokenizer->get_token()) {
+ case GDScriptTokenizer::TK_PERIOD: {
+ if (!can_index) {
+ _set_error("Unexpected '.'.");
+ return false;
+ }
+ can_index = false;
+ tokenizer->advance();
+ } break;
+ case GDScriptTokenizer::TK_IDENTIFIER: {
+ if (can_index) {
+ _set_error("Unexpected identifier.");
+ return false;
+ }
+
+ StringName id;
+ bool has_completion = _get_completable_identifier(COMPLETION_TYPE_HINT_INDEX, id);
+ if (id == StringName()) {
+ id = "@temp";
+ }
+
+ full_name += "." + id.operator String();
+ can_index = true;
+ if (has_completion) {
+ completion_cursor = full_name;
+ }
+ } break;
+ default: {
+ finished = true;
+ } break;
+ }
+ }
+
+ if (tokenizer->get_token(-1) == GDScriptTokenizer::TK_PERIOD) {
+ _set_error("Expected subclass identifier.");
+ return false;
+ }
+
+ r_type.native_type = full_name;
+ }
+
+ return true;
+}
+
+GDScriptParser::DataType GDScriptParser::_resolve_type(const DataType &p_source, int p_line) {
+ if (!p_source.has_type) return p_source;
+ if (p_source.kind != DataType::UNRESOLVED) return p_source;
+
+ Vector<String> full_name = p_source.native_type.operator String().split(".", false);
+ int name_part = 0;
+
+ DataType result;
+ result.has_type = true;
+
+ while (name_part < full_name.size()) {
+
+ bool found = false;
+ StringName id = full_name[name_part];
+ DataType base_type = result;
+
+ ClassNode *p = NULL;
+ if (name_part == 0) {
+ if (ScriptServer::is_global_class(id)) {
+ String script_path = ScriptServer::get_global_class_path(id);
+ if (script_path == self_path) {
+ result.kind = DataType::CLASS;
+ result.class_type = current_class;
+ } else {
+ Ref<Script> script = ResourceLoader::load(script_path);
+ Ref<GDScript> gds = script;
+ if (gds.is_valid()) {
+ if (!gds->is_valid()) {
+ _set_error("Class '" + id + "' could not be fully loaded (script error or cyclic inheritance).", p_line);
+ return DataType();
+ }
+ result.kind = DataType::GDSCRIPT;
+ result.script_type = gds;
+ } else if (script.is_valid()) {
+ result.kind = DataType::SCRIPT;
+ result.script_type = script;
+ } else {
+ _set_error("Class '" + id + "' was found in global scope but its script could not be loaded.", p_line);
+ return DataType();
+ }
+ }
+ name_part++;
+ continue;
+ } else {
+ p = current_class;
+ }
+ } else if (base_type.kind == DataType::CLASS) {
+ p = base_type.class_type;
+ }
+ while (p) {
+ if (p->constant_expressions.has(id)) {
+ if (p->constant_expressions[id].expression->type != Node::TYPE_CONSTANT) {
+ _set_error("Parser bug: unresolved constant.", p_line);
+ ERR_FAIL_V(result);
+ }
+ const ConstantNode *cn = static_cast<const ConstantNode *>(p->constant_expressions[id].expression);
+ Ref<GDScript> gds = cn->value;
+ if (gds.is_valid()) {
+ result.kind = DataType::GDSCRIPT;
+ result.script_type = gds;
+ found = true;
+ } else {
+ Ref<Script> scr = cn->value;
+ if (scr.is_valid()) {
+ result.kind = DataType::SCRIPT;
+ result.script_type = scr;
+ found = true;
+ }
+ }
+ break;
+ }
+
+ // Inner classes
+ ClassNode *outer_class = p;
+ while (outer_class) {
+ for (int i = 0; i < outer_class->subclasses.size(); i++) {
+ if (outer_class->subclasses[i] == p) {
+ continue;
+ }
+ if (outer_class->subclasses[i]->name == id) {
+ found = true;
+ result.kind = DataType::CLASS;
+ result.class_type = outer_class->subclasses[i];
+ break;
+ }
+ }
+ if (found) {
+ break;
+ }
+ outer_class = outer_class->owner;
+ }
+
+ if (!found && p->base_type.kind == DataType::CLASS) {
+ p = p->base_type.class_type;
+ } else {
+ base_type = p->base_type;
+ break;
+ }
+ }
+
+ // Still look for class constants in parent script
+ if (!found && (base_type.kind == DataType::GDSCRIPT || base_type.kind == DataType::SCRIPT)) {
+ Ref<Script> scr = base_type.script_type;
+ ERR_FAIL_COND_V(scr.is_null(), result);
+ Map<StringName, Variant> constants;
+ scr->get_constants(&constants);
+
+ if (constants.has(id)) {
+ Ref<GDScript> gds = constants[id];
+
+ if (gds.is_valid()) {
+ result.kind = DataType::GDSCRIPT;
+ result.script_type = gds;
+ found = true;
+ } else {
+ Ref<Script> scr = constants[id];
+ if (scr.is_valid()) {
+ result.kind = DataType::SCRIPT;
+ result.script_type = scr;
+ found = true;
+ }
+ }
+ }
+ }
+
+ if (!found && !for_completion) {
+ String base;
+ if (name_part == 0) {
+ base = "self";
+ } else {
+ base = result.to_string();
+ }
+ _set_error("Identifier '" + String(id) + "' is not a valid type (not a script or class), or could not be found on base '" +
+ base + "'.",
+ p_line);
+ return DataType();
+ }
+
+ name_part++;
+ }
+
+ return result;
+}
+
+GDScriptParser::DataType GDScriptParser::_type_from_variant(const Variant &p_value) const {
+ DataType result;
+ result.has_type = true;
+ result.is_constant = true;
+ result.kind = DataType::BUILTIN;
+ result.builtin_type = p_value.get_type();
+
+ if (result.builtin_type == Variant::OBJECT) {
+ Object *obj = p_value.operator Object *();
+ if (!obj) {
+ return DataType();
+ }
+ result.native_type = obj->get_class_name();
+ Ref<Script> scr = p_value;
+ if (scr.is_valid()) {
+ result.is_meta_type = true;
+ } else {
+ result.is_meta_type = false;
+ scr = obj->get_script();
+ }
+ if (scr.is_valid()) {
+ result.script_type = scr;
+ Ref<GDScript> gds = scr;
+ if (gds.is_valid()) {
+ result.kind = DataType::GDSCRIPT;
+ } else {
+ result.kind = DataType::SCRIPT;
+ }
+ result.native_type = scr->get_instance_base_type();
+ } else {
+ result.kind = DataType::NATIVE;
+ }
+ }
+
+ return result;
+}
+
+GDScriptParser::DataType GDScriptParser::_type_from_property(const PropertyInfo &p_property, bool p_nil_is_variant) const {
+ DataType ret;
+ if (p_property.type == Variant::NIL && (p_nil_is_variant || (p_property.usage & PROPERTY_USAGE_NIL_IS_VARIANT))) {
+ // Variant
+ return ret;
+ }
+ ret.has_type = true;
+ ret.builtin_type = p_property.type;
+ if (p_property.type == Variant::OBJECT) {
+ ret.kind = DataType::NATIVE;
+ ret.native_type = p_property.class_name == StringName() ? "Object" : p_property.class_name;
+ } else {
+ ret.kind = DataType::BUILTIN;
+ }
+ return ret;
+}
+
+GDScriptParser::DataType GDScriptParser::_type_from_gdtype(const GDScriptDataType &p_gdtype) const {
+ DataType result;
+ if (!p_gdtype.has_type) {
+ return result;
+ }
+
+ result.has_type = true;
+ result.builtin_type = p_gdtype.builtin_type;
+ result.native_type = p_gdtype.native_type;
+ result.script_type = p_gdtype.script_type;
+
+ switch (p_gdtype.kind) {
+ case GDScriptDataType::BUILTIN: {
+ result.kind = DataType::BUILTIN;
+ } break;
+ case GDScriptDataType::NATIVE: {
+ result.kind = DataType::NATIVE;
+ } break;
+ case GDScriptDataType::GDSCRIPT: {
+ result.kind = DataType::GDSCRIPT;
+ } break;
+ case GDScriptDataType::SCRIPT: {
+ result.kind = DataType::SCRIPT;
+ } break;
+ }
+ return result;
+}
+
+GDScriptParser::DataType GDScriptParser::_get_operation_type(const Variant::Operator p_op, const DataType &p_a, const DataType &p_b, bool &r_valid) const {
+ if (!p_a.has_type || !p_b.has_type) {
+ r_valid = true;
+ return DataType();
+ }
+
+ Variant::Type a_type = p_a.kind == DataType::BUILTIN ? p_a.builtin_type : Variant::OBJECT;
+ Variant::Type b_type = p_b.kind == DataType::BUILTIN ? p_b.builtin_type : Variant::OBJECT;
+
+ Variant a;
+ REF a_ref;
+ if (a_type == Variant::OBJECT) {
+ a_ref.instance();
+ a = a_ref;
+ } else {
+ Variant::CallError err;
+ a = Variant::construct(a_type, NULL, 0, err);
+ if (err.error != Variant::CallError::CALL_OK) {
+ r_valid = false;
+ return DataType();
+ }
+ }
+ Variant b;
+ REF b_ref;
+ if (b_type == Variant::OBJECT) {
+ b_ref.instance();
+ b = b_ref;
+ } else {
+ Variant::CallError err;
+ b = Variant::construct(b_type, NULL, 0, err);
+ if (err.error != Variant::CallError::CALL_OK) {
+ r_valid = false;
+ return DataType();
+ }
+ }
+
+ // Avoid division by zero
+ if (a_type == Variant::INT || a_type == Variant::REAL) {
+ Variant::evaluate(Variant::OP_ADD, a, 1, a, r_valid);
+ }
+ if (b_type == Variant::INT || b_type == Variant::REAL) {
+ Variant::evaluate(Variant::OP_ADD, b, 1, b, r_valid);
+ }
+
+ Variant ret;
+ Variant::evaluate(p_op, a, b, ret, r_valid);
+
+ if (r_valid) {
+ return _type_from_variant(ret);
+ }
+
+ return DataType();
+}
+
+Variant::Operator GDScriptParser::_get_variant_operation(const OperatorNode::Operator &p_op) const {
+ switch (p_op) {
+ case OperatorNode::OP_NEG: {
+ return Variant::OP_NEGATE;
+ } break;
+ case OperatorNode::OP_POS: {
+ return Variant::OP_POSITIVE;
+ } break;
+ case OperatorNode::OP_NOT: {
+ return Variant::OP_NOT;
+ } break;
+ case OperatorNode::OP_BIT_INVERT: {
+ return Variant::OP_BIT_NEGATE;
+ } break;
+ case OperatorNode::OP_IN: {
+ return Variant::OP_IN;
+ } break;
+ case OperatorNode::OP_EQUAL: {
+ return Variant::OP_EQUAL;
+ } break;
+ case OperatorNode::OP_NOT_EQUAL: {
+ return Variant::OP_NOT_EQUAL;
+ } break;
+ case OperatorNode::OP_LESS: {
+ return Variant::OP_LESS;
+ } break;
+ case OperatorNode::OP_LESS_EQUAL: {
+ return Variant::OP_LESS_EQUAL;
+ } break;
+ case OperatorNode::OP_GREATER: {
+ return Variant::OP_GREATER;
+ } break;
+ case OperatorNode::OP_GREATER_EQUAL: {
+ return Variant::OP_GREATER_EQUAL;
+ } break;
+ case OperatorNode::OP_AND: {
+ return Variant::OP_AND;
+ } break;
+ case OperatorNode::OP_OR: {
+ return Variant::OP_OR;
+ } break;
+ case OperatorNode::OP_ASSIGN_ADD:
+ case OperatorNode::OP_ADD: {
+ return Variant::OP_ADD;
+ } break;
+ case OperatorNode::OP_ASSIGN_SUB:
+ case OperatorNode::OP_SUB: {
+ return Variant::OP_SUBTRACT;
+ } break;
+ case OperatorNode::OP_ASSIGN_MUL:
+ case OperatorNode::OP_MUL: {
+ return Variant::OP_MULTIPLY;
+ } break;
+ case OperatorNode::OP_ASSIGN_DIV:
+ case OperatorNode::OP_DIV: {
+ return Variant::OP_DIVIDE;
+ } break;
+ case OperatorNode::OP_ASSIGN_MOD:
+ case OperatorNode::OP_MOD: {
+ return Variant::OP_MODULE;
+ } break;
+ case OperatorNode::OP_ASSIGN_BIT_AND:
+ case OperatorNode::OP_BIT_AND: {
+ return Variant::OP_BIT_AND;
+ } break;
+ case OperatorNode::OP_ASSIGN_BIT_OR:
+ case OperatorNode::OP_BIT_OR: {
+ return Variant::OP_BIT_OR;
+ } break;
+ case OperatorNode::OP_ASSIGN_BIT_XOR:
+ case OperatorNode::OP_BIT_XOR: {
+ return Variant::OP_BIT_XOR;
+ } break;
+ case OperatorNode::OP_ASSIGN_SHIFT_LEFT:
+ case OperatorNode::OP_SHIFT_LEFT: {
+ return Variant::OP_SHIFT_LEFT;
+ }
+ case OperatorNode::OP_ASSIGN_SHIFT_RIGHT:
+ case OperatorNode::OP_SHIFT_RIGHT: {
+ return Variant::OP_SHIFT_RIGHT;
+ }
+ default: {
+ return Variant::OP_MAX;
+ } break;
+ }
+}
+
+bool GDScriptParser::_is_type_compatible(const DataType &p_container, const DataType &p_expression, bool p_allow_implicit_conversion) const {
+ // Ignore for completion
+ if (!check_types || for_completion) {
+ return true;
+ }
+ // Can't test if not all have type
+ if (!p_container.has_type || !p_expression.has_type) {
+ return true;
+ }
+
+ // Should never get here unresolved
+ ERR_FAIL_COND_V(p_container.kind == DataType::UNRESOLVED, false);
+ ERR_FAIL_COND_V(p_expression.kind == DataType::UNRESOLVED, false);
+
+ if (p_container.kind == DataType::BUILTIN && p_expression.kind == DataType::BUILTIN) {
+ bool valid = p_container.builtin_type == p_expression.builtin_type;
+ if (p_allow_implicit_conversion) {
+ valid = valid || (p_container.builtin_type == Variant::INT && p_expression.builtin_type == Variant::REAL);
+ valid = valid || (p_container.builtin_type == Variant::REAL && p_expression.builtin_type == Variant::INT);
+ valid = valid || (p_container.builtin_type == Variant::STRING && p_expression.builtin_type == Variant::NODE_PATH);
+ valid = valid || (p_container.builtin_type == Variant::NODE_PATH && p_expression.builtin_type == Variant::STRING);
+ valid = valid || (p_container.builtin_type == Variant::BOOL && p_expression.builtin_type == Variant::REAL);
+ valid = valid || (p_container.builtin_type == Variant::BOOL && p_expression.builtin_type == Variant::INT);
+ valid = valid || (p_container.builtin_type == Variant::INT && p_expression.builtin_type == Variant::BOOL);
+ valid = valid || (p_container.builtin_type == Variant::REAL && p_expression.builtin_type == Variant::BOOL);
+ }
+ return valid;
+ }
+
+ if (p_container.kind == DataType::BUILTIN || (p_expression.kind == DataType::BUILTIN && p_expression.builtin_type != Variant::NIL)) {
+ // Can't mix built-ins with objects
+ return false;
+ }
+
+ // From now on everything is objects, check polymorphism
+ // The container must be the same class or a superclass of the expression
+
+ if (p_expression.kind == DataType::BUILTIN && p_expression.builtin_type == Variant::NIL) {
+ // Null can be assigned to object types
+ return true;
+ }
+
+ StringName expr_native;
+ Ref<Script> expr_script;
+ ClassNode *expr_class = NULL;
+
+ switch (p_expression.kind) {
+ case DataType::NATIVE: {
+ if (p_container.kind != DataType::NATIVE) {
+ // Non-native type can't be a superclass of a native type
+ return false;
+ }
+ if (p_expression.is_meta_type) {
+ expr_native = GDScriptNativeClass::get_class_static();
+ } else {
+ expr_native = p_expression.native_type;
+ }
+ } break;
+ case DataType::SCRIPT:
+ case DataType::GDSCRIPT: {
+ if (p_container.kind == DataType::CLASS) {
+ // This cannot be resolved without cyclic dependencies, so just bail out
+ return false;
+ }
+ if (p_expression.is_meta_type) {
+ expr_native = p_expression.script_type->get_class_name();
+ } else {
+ expr_script = p_expression.script_type;
+ expr_native = expr_script->get_instance_base_type();
+ }
+ } break;
+ case DataType::CLASS: {
+ if (p_expression.is_meta_type) {
+ expr_native = GDScript::get_class_static();
+ } else {
+ expr_class = p_expression.class_type;
+ ClassNode *base = expr_class;
+ while (base->base_type.kind == DataType::CLASS) {
+ base = base->base_type.class_type;
+ }
+ expr_native = base->base_type.native_type;
+ expr_script = base->base_type.script_type;
+ }
+ }
+ }
+
+ switch (p_container.kind) {
+ case DataType::NATIVE: {
+ if (p_container.is_meta_type) {
+ return ClassDB::is_parent_class(expr_native, GDScriptNativeClass::get_class_static());
+ } else {
+ return ClassDB::is_parent_class(expr_native, p_container.native_type);
+ }
+ } break;
+ case DataType::SCRIPT:
+ case DataType::GDSCRIPT: {
+ if (p_container.is_meta_type) {
+ return ClassDB::is_parent_class(expr_native, GDScript::get_class_static());
+ }
+ if (expr_class == head && p_container.script_type->get_path() == self_path) {
+ // Special case: container is self script and expression is self
+ return true;
+ }
+ while (expr_script.is_valid()) {
+ if (expr_script == p_container.script_type) {
+ return true;
+ }
+ expr_script = expr_script->get_base_script();
+ }
+ return false;
+ } break;
+ case DataType::CLASS: {
+ if (p_container.is_meta_type) {
+ return ClassDB::is_parent_class(expr_native, GDScript::get_class_static());
+ }
+ if (p_container.class_type == head && expr_script.is_valid() && expr_script->get_path() == self_path) {
+ // Special case: container is self and expression is self script
+ return true;
+ }
+ while (expr_class) {
+ if (expr_class == p_container.class_type) {
+ return true;
+ }
+ expr_class = expr_class->base_type.class_type;
+ }
+ return false;
+ }
+ }
+
+ return false;
+}
+
+GDScriptParser::DataType GDScriptParser::_reduce_node_type(Node *p_node) {
+ if (p_node->get_datatype().has_type) {
+ return p_node->get_datatype();
+ }
+
+ DataType node_type;
+
+ switch (p_node->type) {
+ case Node::TYPE_CONSTANT: {
+ node_type = _type_from_variant(static_cast<ConstantNode *>(p_node)->value);
+ } break;
+ case Node::TYPE_ARRAY: {
+ node_type.has_type = true;
+ node_type.kind = DataType::BUILTIN;
+ node_type.builtin_type = Variant::ARRAY;
+ } break;
+ case Node::TYPE_DICTIONARY: {
+ node_type.has_type = true;
+ node_type.kind = DataType::BUILTIN;
+ node_type.builtin_type = Variant::DICTIONARY;
+ } break;
+ case Node::TYPE_SELF: {
+ node_type.has_type = true;
+ node_type.kind = DataType::CLASS;
+ node_type.class_type = current_class;
+ } break;
+ case Node::TYPE_IDENTIFIER: {
+ IdentifierNode *id = static_cast<IdentifierNode *>(p_node);
+ if (id->declared_block) {
+ node_type = id->declared_block->variables[id->name]->get_datatype();
+ } else if (id->name == "#match_value") {
+ // It's a special id just for the match statetement, ignore
+ break;
+ } else if (current_function && current_function->arguments.find(id->name) >= 0) {
+ int idx = current_function->arguments.find(id->name);
+ node_type = current_function->argument_types[idx];
+ } else {
+ node_type = _reduce_identifier_type(NULL, id->name, id->line);
+ }
+ } break;
+ case Node::TYPE_CAST: {
+ CastNode *cn = static_cast<CastNode *>(p_node);
+
+ DataType source_type = _reduce_node_type(cn->source_node);
+ cn->cast_type = _resolve_type(cn->cast_type, cn->line);
+ if (source_type.has_type) {
+
+ bool valid = false;
+ if (check_types) {
+ if (cn->cast_type.kind == DataType::BUILTIN && source_type.kind == DataType::BUILTIN) {
+ valid = Variant::can_convert(source_type.builtin_type, cn->cast_type.builtin_type);
+ }
+ if (cn->cast_type.kind != DataType::BUILTIN && source_type.kind != DataType::BUILTIN) {
+ valid = _is_type_compatible(cn->cast_type, source_type) || _is_type_compatible(source_type, cn->cast_type);
+ }
+
+ if (!valid) {
+ _set_error("Invalid cast. Cannot convert from '" + source_type.to_string() +
+ "' to '" + cn->cast_type.to_string() + "'.",
+ cn->line);
+ return DataType();
+ }
+ }
+ } else {
+ _mark_line_as_unsafe(cn->line);
+ }
+
+ node_type = cn->cast_type;
+
+ } break;
+ case Node::TYPE_OPERATOR: {
+ OperatorNode *op = static_cast<OperatorNode *>(p_node);
+
+ switch (op->op) {
+ case OperatorNode::OP_CALL:
+ case OperatorNode::OP_PARENT_CALL: {
+ node_type = _reduce_function_call_type(op);
+ } break;
+ case OperatorNode::OP_YIELD: {
+ if (op->arguments.size() == 2) {
+ DataType base_type = _reduce_node_type(op->arguments[0]);
+ DataType signal_type = _reduce_node_type(op->arguments[1]);
+ // TODO: Check if signal exists when it's a constant
+ if (base_type.has_type && base_type.kind == DataType::BUILTIN && base_type.builtin_type != Variant::NIL && base_type.builtin_type != Variant::OBJECT) {
+ _set_error("First argument of 'yield()' must be an object.", op->line);
+ return DataType();
+ }
+ if (signal_type.has_type && (signal_type.kind != DataType::BUILTIN || signal_type.builtin_type != Variant::STRING)) {
+ _set_error("Second argument of 'yield()' must be a string.", op->line);
+ return DataType();
+ }
+ }
+ // yield can return anything
+ node_type.has_type = false;
+ } break;
+ case OperatorNode::OP_IS: {
+
+ if (op->arguments.size() != 2) {
+ _set_error("Parser bug: binary operation without 2 arguments.", op->line);
+ ERR_FAIL_V(DataType());
+ }
+
+ DataType value_type = _reduce_node_type(op->arguments[0]);
+ DataType type_type = _reduce_node_type(op->arguments[1]);
+
+ if (check_types && type_type.has_type) {
+ if (!type_type.is_meta_type && (type_type.kind != DataType::NATIVE || !ClassDB::is_parent_class(type_type.native_type, "Script"))) {
+ _set_error("Invalid 'is' test: right operand is not a type (not a native type nor a script).", op->line);
+ return DataType();
+ }
+ type_type.is_meta_type = false; // Test the actual type
+ if (!_is_type_compatible(type_type, value_type) && !_is_type_compatible(value_type, type_type)) {
+ // TODO: Make this a warning?
+ _set_error("A value of type '" + value_type.to_string() + "' will never be an instance of '" + type_type.to_string() + "'.", op->line);
+ return DataType();
+ }
+ }
+
+ node_type.has_type = true;
+ node_type.is_constant = true;
+ node_type.is_meta_type = false;
+ node_type.kind = DataType::BUILTIN;
+ node_type.builtin_type = Variant::BOOL;
+ } break;
+ // Unary operators
+ case OperatorNode::OP_NEG:
+ case OperatorNode::OP_POS:
+ case OperatorNode::OP_NOT:
+ case OperatorNode::OP_BIT_INVERT: {
+
+ DataType argument_type = _reduce_node_type(op->arguments[0]);
+ if (!argument_type.has_type) {
+ break;
+ }
+
+ Variant::Operator var_op = _get_variant_operation(op->op);
+ bool valid = false;
+ node_type = _get_operation_type(var_op, argument_type, argument_type, valid);
+
+ if (check_types && !valid) {
+ _set_error("Invalid operand type ('" + argument_type.to_string() +
+ "') to unary operator '" + Variant::get_operator_name(var_op) + "'.",
+ op->line, op->column);
+ return DataType();
+ }
+
+ } break;
+ // Binary operators
+ case OperatorNode::OP_IN:
+ case OperatorNode::OP_EQUAL:
+ case OperatorNode::OP_NOT_EQUAL:
+ case OperatorNode::OP_LESS:
+ case OperatorNode::OP_LESS_EQUAL:
+ case OperatorNode::OP_GREATER:
+ case OperatorNode::OP_GREATER_EQUAL:
+ case OperatorNode::OP_AND:
+ case OperatorNode::OP_OR:
+ case OperatorNode::OP_ADD:
+ case OperatorNode::OP_SUB:
+ case OperatorNode::OP_MUL:
+ case OperatorNode::OP_DIV:
+ case OperatorNode::OP_MOD:
+ case OperatorNode::OP_SHIFT_LEFT:
+ case OperatorNode::OP_SHIFT_RIGHT:
+ case OperatorNode::OP_BIT_AND:
+ case OperatorNode::OP_BIT_OR:
+ case OperatorNode::OP_BIT_XOR: {
+
+ if (op->arguments.size() != 2) {
+ _set_error("Parser bug: binary operation without 2 arguments.", op->line);
+ ERR_FAIL_V(DataType());
+ }
+
+ DataType argument_a_type = _reduce_node_type(op->arguments[0]);
+ DataType argument_b_type = _reduce_node_type(op->arguments[1]);
+ if (!argument_a_type.has_type || !argument_b_type.has_type) {
+ _mark_line_as_unsafe(op->line);
+ break;
+ }
+
+ Variant::Operator var_op = _get_variant_operation(op->op);
+ bool valid = false;
+ node_type = _get_operation_type(var_op, argument_a_type, argument_b_type, valid);
+
+ if (check_types && !valid) {
+ _set_error("Invalid operand types ('" + argument_a_type.to_string() + "' and '" +
+ argument_b_type.to_string() + "') to operator '" + Variant::get_operator_name(var_op) + "'.",
+ op->line, op->column);
+ return DataType();
+ }
+
+ } break;
+ // Ternary operators
+ case OperatorNode::OP_TERNARY_IF: {
+ if (op->arguments.size() != 3) {
+ _set_error("Parser bug: ternary operation without 3 arguments");
+ ERR_FAIL_V(DataType());
+ }
+
+ DataType true_type = _reduce_node_type(op->arguments[1]);
+ DataType false_type = _reduce_node_type(op->arguments[2]);
+
+ // If types are equal, then the expression is of the same type
+ // If they are compatible, return the broader type
+ if (true_type == false_type || _is_type_compatible(true_type, false_type)) {
+ node_type = true_type;
+ } else if (_is_type_compatible(false_type, true_type)) {
+ node_type = false_type;
+ }
+
+ // TODO: Warn if types aren't compatible
+
+ } break;
+ // Assignment should never happen within an expression
+ case OperatorNode::OP_ASSIGN:
+ case OperatorNode::OP_ASSIGN_ADD:
+ case OperatorNode::OP_ASSIGN_SUB:
+ case OperatorNode::OP_ASSIGN_MUL:
+ case OperatorNode::OP_ASSIGN_DIV:
+ case OperatorNode::OP_ASSIGN_MOD:
+ case OperatorNode::OP_ASSIGN_SHIFT_LEFT:
+ case OperatorNode::OP_ASSIGN_SHIFT_RIGHT:
+ case OperatorNode::OP_ASSIGN_BIT_AND:
+ case OperatorNode::OP_ASSIGN_BIT_OR:
+ case OperatorNode::OP_ASSIGN_BIT_XOR:
+ case OperatorNode::OP_INIT_ASSIGN: {
+
+ _set_error("Assignment inside expression is not allowed (parser bug?).", op->line);
+ return DataType();
+
+ } break;
+ case OperatorNode::OP_INDEX_NAMED: {
+ if (op->arguments.size() != 2) {
+ _set_error("Parser bug: named index with invalid arguments.", op->line);
+ ERR_FAIL_V(DataType());
+ }
+ if (op->arguments[1]->type != Node::TYPE_IDENTIFIER) {
+ _set_error("Parser bug: named index without identifier argument.", op->line);
+ ERR_FAIL_V(DataType());
+ }
+
+ DataType base_type = _reduce_node_type(op->arguments[0]);
+ IdentifierNode *member_id = static_cast<IdentifierNode *>(op->arguments[1]);
+
+ if (base_type.has_type) {
+ if (check_types && base_type.kind == DataType::BUILTIN) {
+ // Variant type, just test if it's possible
+ DataType result;
+ switch (base_type.builtin_type) {
+ case Variant::NIL:
+ case Variant::DICTIONARY: {
+ result.has_type = false;
+ } break;
+ default: {
+ Variant::CallError err;
+ Variant temp = Variant::construct(base_type.builtin_type, NULL, 0, err);
+
+ bool valid = false;
+ Variant res = temp.get(member_id->name.operator String(), &valid);
+
+ if (valid) {
+ result = _type_from_variant(res);
+ } else if (check_types) {
+ _set_error("Can't get index '" + String(member_id->name.operator String()) + "' on base '" +
+ base_type.to_string() + "'.",
+ op->line);
+ return DataType();
+ }
+ } break;
+ }
+ result.is_constant = false;
+ node_type = result;
+ } else {
+ node_type = _reduce_identifier_type(&base_type, member_id->name, op->line);
+ }
+ } else {
+ _mark_line_as_unsafe(op->line);
+ }
+ if (error_set) {
+ return DataType();
+ }
+ } break;
+ case OperatorNode::OP_INDEX: {
+
+ if (op->arguments[1]->type == Node::TYPE_CONSTANT) {
+ ConstantNode *cn = static_cast<ConstantNode *>(op->arguments[1]);
+ if (cn->value.get_type() == Variant::STRING) {
+ // Treat this as named indexing
+
+ IdentifierNode *id = alloc_node<IdentifierNode>();
+ id->name = cn->value.operator StringName();
+
+ op->op = OperatorNode::OP_INDEX_NAMED;
+ op->arguments[1] = id;
+
+ return _reduce_node_type(op);
+ }
+ }
+
+ DataType base_type = _reduce_node_type(op->arguments[0]);
+ DataType index_type = _reduce_node_type(op->arguments[1]);
+
+ if (!base_type.has_type) {
+ _mark_line_as_unsafe(op->line);
+ break;
+ }
+
+ if (check_types && index_type.has_type) {
+ if (base_type.kind == DataType::BUILTIN) {
+ // Check if indexing is valid
+ bool error = index_type.kind != DataType::BUILTIN;
+ if (!error) {
+ switch (base_type.builtin_type) {
+ // Expect int or real as index
+ case Variant::POOL_BYTE_ARRAY:
+ case Variant::POOL_COLOR_ARRAY:
+ case Variant::POOL_INT_ARRAY:
+ case Variant::POOL_REAL_ARRAY:
+ case Variant::POOL_STRING_ARRAY:
+ case Variant::POOL_VECTOR2_ARRAY:
+ case Variant::POOL_VECTOR3_ARRAY:
+ case Variant::ARRAY:
+ case Variant::STRING: {
+ error = index_type.builtin_type != Variant::INT && index_type.builtin_type != Variant::REAL;
+ } break;
+ // Expect String only
+ case Variant::RECT2:
+ case Variant::PLANE:
+ case Variant::QUAT:
+ case Variant::AABB:
+ case Variant::OBJECT: {
+ error = index_type.builtin_type != Variant::STRING;
+ } break;
+ // Expect String or number
+ case Variant::VECTOR2:
+ case Variant::VECTOR3:
+ case Variant::TRANSFORM2D:
+ case Variant::BASIS:
+ case Variant::TRANSFORM: {
+ error = index_type.builtin_type != Variant::INT && index_type.builtin_type != Variant::REAL &&
+ index_type.builtin_type != Variant::STRING;
+ } break;
+ // Expect String or int
+ case Variant::COLOR: {
+ error = index_type.builtin_type != Variant::INT && index_type.builtin_type != Variant::STRING;
+ } break;
+ }
+ }
+ if (error) {
+ _set_error("Invalid index type (" + index_type.to_string() + ") for base '" + base_type.to_string() + "'.",
+ op->line);
+ return DataType();
+ }
+
+ if (op->arguments[1]->type == GDScriptParser::Node::TYPE_CONSTANT) {
+ ConstantNode *cn = static_cast<ConstantNode *>(op->arguments[1]);
+ // Index is a constant, just try it if possible
+ switch (base_type.builtin_type) {
+ // Arrays/string have variable indexing, can't test directly
+ case Variant::STRING:
+ case Variant::ARRAY:
+ case Variant::DICTIONARY:
+ case Variant::POOL_BYTE_ARRAY:
+ case Variant::POOL_COLOR_ARRAY:
+ case Variant::POOL_INT_ARRAY:
+ case Variant::POOL_REAL_ARRAY:
+ case Variant::POOL_STRING_ARRAY:
+ case Variant::POOL_VECTOR2_ARRAY:
+ case Variant::POOL_VECTOR3_ARRAY: {
+ break;
+ }
+ default: {
+ Variant::CallError err;
+ Variant temp = Variant::construct(base_type.builtin_type, NULL, 0, err);
+
+ bool valid = false;
+ Variant res = temp.get(cn->value, &valid);
+
+ if (valid) {
+ node_type = _type_from_variant(res);
+ node_type.is_constant = false;
+ } else if (check_types) {
+ _set_error("Can't get index '" + String(cn->value) + "' on base '" +
+ base_type.to_string() + "'.",
+ op->line);
+ return DataType();
+ }
+ } break;
+ }
+ } else {
+ _mark_line_as_unsafe(op->line);
+ }
+ } else if (!for_completion && (index_type.kind != DataType::BUILTIN || index_type.builtin_type != Variant::STRING)) {
+ _set_error("Only strings can be used as index in the base type '" + base_type.to_string() + "'.", op->line);
+ return DataType();
+ }
+ }
+ if (check_types && !node_type.has_type) {
+ // Can infer indexing type for some variant types
+ DataType result;
+ result.has_type = true;
+ result.kind = DataType::BUILTIN;
+ switch (base_type.builtin_type) {
+ // Can't index at all
+ case Variant::NIL:
+ case Variant::BOOL:
+ case Variant::INT:
+ case Variant::REAL:
+ case Variant::NODE_PATH:
+ case Variant::_RID: {
+ _set_error("Can't index on a value of type '" + base_type.to_string() + "'.", op->line);
+ return DataType();
+ } break;
+ // Return int
+ case Variant::POOL_BYTE_ARRAY:
+ case Variant::POOL_INT_ARRAY: {
+ result.builtin_type = Variant::INT;
+ } break;
+ // Return real
+ case Variant::POOL_REAL_ARRAY:
+ case Variant::VECTOR2:
+ case Variant::VECTOR3:
+ case Variant::QUAT: {
+ result.builtin_type = Variant::REAL;
+ } break;
+ // Return color
+ case Variant::POOL_COLOR_ARRAY: {
+ result.builtin_type = Variant::COLOR;
+ } break;
+ // Return string
+ case Variant::POOL_STRING_ARRAY:
+ case Variant::STRING: {
+ result.builtin_type = Variant::STRING;
+ } break;
+ // Return Vector2
+ case Variant::POOL_VECTOR2_ARRAY:
+ case Variant::TRANSFORM2D:
+ case Variant::RECT2: {
+ result.builtin_type = Variant::VECTOR2;
+ } break;
+ // Return Vector3
+ case Variant::POOL_VECTOR3_ARRAY:
+ case Variant::AABB:
+ case Variant::BASIS: {
+ result.builtin_type = Variant::VECTOR3;
+ } break;
+ // Depends on the index
+ case Variant::TRANSFORM:
+ case Variant::PLANE:
+ case Variant::COLOR:
+ default: {
+ result.has_type = false;
+ } break;
+ }
+ node_type = result;
+ }
+ } break;
+ default: {
+ _set_error("Parser bug: unhandled operation.", op->line);
+ ERR_FAIL_V(DataType());
+ }
+ }
+ } break;
+ }
+
+ p_node->set_datatype(_resolve_type(node_type, p_node->line));
+ return node_type;
+}
+
+bool GDScriptParser::_get_function_signature(DataType &p_base_type, const StringName &p_function, DataType &r_return_type, List<DataType> &r_arg_types, int &r_default_arg_count, bool &r_static, bool &r_vararg) const {
+
+ r_static = false;
+ r_default_arg_count = 0;
+
+ DataType original_type = p_base_type;
+ ClassNode *base = NULL;
+ FunctionNode *callee = NULL;
+
+ if (p_base_type.kind == DataType::CLASS) {
+ base = p_base_type.class_type;
+ }
+
+ // Look up the current file (parse tree)
+ while (!callee && base) {
+ for (int i = 0; i < base->static_functions.size(); i++) {
+ FunctionNode *func = base->static_functions[i];
+ if (p_function == func->name) {
+ r_static = true;
+ callee = func;
+ break;
+ }
+ }
+ if (!callee && !p_base_type.is_meta_type) {
+ for (int i = 0; i < base->functions.size(); i++) {
+ FunctionNode *func = base->functions[i];
+ if (p_function == func->name) {
+ callee = func;
+ break;
+ }
+ }
+ }
+ p_base_type = base->base_type;
+ if (p_base_type.kind == DataType::CLASS) {
+ base = p_base_type.class_type;
+ } else {
+ break;
+ }
+ }
+
+ if (callee) {
+ r_return_type = callee->get_datatype();
+ for (int i = 0; i < callee->argument_types.size(); i++) {
+ r_arg_types.push_back(callee->argument_types[i]);
+ }
+ r_default_arg_count = callee->default_values.size();
+ return true;
+ }
+
+ // Nothing in current file, check parent script
+ Ref<GDScript> base_gdscript;
+ Ref<Script> base_script;
+ StringName native;
+ if (p_base_type.kind == DataType::GDSCRIPT) {
+ base_gdscript = p_base_type.script_type;
+ } else if (p_base_type.kind == DataType::SCRIPT) {
+ base_script = p_base_type.script_type;
+ } else if (p_base_type.kind == DataType::NATIVE) {
+ native = p_base_type.native_type;
+ }
+
+ while (base_gdscript.is_valid()) {
+ native = base_gdscript->get_instance_base_type();
+
+ Map<StringName, GDScriptFunction *> funcs = base_gdscript->get_member_functions();
+
+ if (funcs.has(p_function)) {
+ GDScriptFunction *f = funcs[p_function];
+ r_static = f->is_static();
+ r_default_arg_count = f->get_default_argument_count();
+ r_return_type = _type_from_gdtype(f->get_return_type());
+ for (int i = 0; i < f->get_argument_count(); i++) {
+ r_arg_types.push_back(_type_from_gdtype(f->get_argument_type(i)));
+ }
+ return true;
+ }
+
+ base_gdscript = base_gdscript->get_base_script();
+ }
+
+ while (base_script.is_valid()) {
+ native = base_script->get_instance_base_type();
+ MethodInfo mi = base_script->get_method_info(p_function);
+
+ if (!(mi == MethodInfo())) {
+ r_return_type = _type_from_property(mi.return_val, false);
+ r_default_arg_count = mi.default_arguments.size();
+ for (List<PropertyInfo>::Element *E = mi.arguments.front(); E; E = E->next()) {
+ r_arg_types.push_back(_type_from_property(E->get()));
+ }
+ return true;
+ }
+ base_script = base_script->get_base_script();
+ }
+
+#ifdef DEBUG_METHODS_ENABLED
+
+ // Only native remains
+ if (!ClassDB::class_exists(native)) {
+ native = "_" + native.operator String();
+ }
+ if (!ClassDB::class_exists(native)) {
+ if (!check_types) return false;
+ ERR_EXPLAIN("Parser bug: Class '" + String(native) + "' not found.");
+ ERR_FAIL_V(false);
+ }
+
+ MethodBind *method = ClassDB::get_method(native, p_function);
+
+ if (!method) {
+ // Try virtual methods
+ List<MethodInfo> virtuals;
+ ClassDB::get_virtual_methods(native, &virtuals);
+
+ for (const List<MethodInfo>::Element *E = virtuals.front(); E; E = E->next()) {
+ const MethodInfo &mi = E->get();
+ if (mi.name == p_function) {
+ r_default_arg_count = mi.default_arguments.size();
+ for (const List<PropertyInfo>::Element *pi = mi.arguments.front(); pi; pi = pi->next()) {
+ r_arg_types.push_back(_type_from_property(pi->get()));
+ }
+ r_return_type = _type_from_property(mi.return_val, false);
+ r_vararg = mi.flags & METHOD_FLAG_VARARG;
+ return true;
+ }
+ }
+
+ // If the base is a script, it might be trying to access members of the Script class itself
+ if (original_type.is_meta_type && !(p_function == "new") && (original_type.kind == DataType::SCRIPT || original_type.kind == DataType::GDSCRIPT)) {
+ method = ClassDB::get_method(original_type.script_type->get_class_name(), p_function);
+
+ if (method) {
+ r_static = true;
+ } else {
+ // Try virtual methods of the script type
+ virtuals.clear();
+ ClassDB::get_virtual_methods(original_type.script_type->get_class_name(), &virtuals);
+ for (const List<MethodInfo>::Element *E = virtuals.front(); E; E = E->next()) {
+ const MethodInfo &mi = E->get();
+ if (mi.name == p_function) {
+ r_default_arg_count = mi.default_arguments.size();
+ for (const List<PropertyInfo>::Element *pi = mi.arguments.front(); pi; pi = pi->next()) {
+ r_arg_types.push_back(_type_from_property(pi->get()));
+ }
+ r_return_type = _type_from_property(mi.return_val, false);
+ r_static = true;
+ r_vararg = mi.flags & METHOD_FLAG_VARARG;
+ return true;
+ }
+ }
+ return false;
+ }
+ } else {
+ return false;
+ }
+ }
+
+ r_default_arg_count = method->get_default_argument_count();
+ r_return_type = _type_from_property(method->get_return_info(), false);
+ r_vararg = method->is_vararg();
+
+ for (int i = 0; i < method->get_argument_count(); i++) {
+ r_arg_types.push_back(_type_from_property(method->get_argument_info(i)));
+ }
+ return true;
+#else
+ return false;
+#endif
+}
+
+GDScriptParser::DataType GDScriptParser::_reduce_function_call_type(const OperatorNode *p_call) {
+ if (p_call->arguments.size() < 1) {
+ _set_error("Parser bug: function call without enough arguments.", p_call->line);
+ ERR_FAIL_V(DataType());
+ }
+
+ DataType return_type;
+ List<DataType> arg_types;
+ int default_args_count = 0;
+ int arg_count = p_call->arguments.size();
+ String callee_name;
+ bool is_vararg = false;
+
+ switch (p_call->arguments[0]->type) {
+ case GDScriptParser::Node::TYPE_TYPE: {
+ // Built-in constructor, special case
+ TypeNode *tn = static_cast<TypeNode *>(p_call->arguments[0]);
+
+ Vector<DataType> par_types;
+ par_types.resize(p_call->arguments.size() - 1);
+ for (int i = 1; i < p_call->arguments.size(); i++) {
+ par_types[i - 1] = _reduce_node_type(p_call->arguments[i]);
+ }
+
+ if (error_set) return DataType();
+
+ bool match = false;
+ List<MethodInfo> constructors;
+ Variant::get_constructor_list(tn->vtype, &constructors);
+ PropertyInfo return_type;
+
+ for (List<MethodInfo>::Element *E = constructors.front(); E; E = E->next()) {
+ MethodInfo &mi = E->get();
+
+ if (p_call->arguments.size() - 1 < mi.arguments.size() - mi.default_arguments.size()) {
+ continue;
+ }
+ if (p_call->arguments.size() - 1 > mi.arguments.size()) {
+ continue;
+ }
+
+ bool types_match = true;
+ for (int i = 0; i < par_types.size(); i++) {
+ DataType arg_type;
+ if (mi.arguments[i].type != Variant::NIL) {
+ arg_type.has_type = true;
+ arg_type.kind = mi.arguments[i].type == Variant::OBJECT ? DataType::NATIVE : DataType::BUILTIN;
+ arg_type.builtin_type = mi.arguments[i].type;
+ arg_type.native_type = mi.arguments[i].class_name;
+ }
+
+ if (!_is_type_compatible(arg_type, par_types[i], true)) {
+ types_match = false;
+ break;
+ }
+ }
+
+ if (types_match) {
+ match = true;
+ return_type = mi.return_val;
+ break;
+ }
+ }
+
+ if (match) {
+ return _type_from_property(return_type, false);
+ } else if (check_types) {
+ String err = "No constructor of '";
+ err += Variant::get_type_name(tn->vtype);
+ err += "' matches the signature '";
+ err += Variant::get_type_name(tn->vtype) + "(";
+ for (int i = 0; i < par_types.size(); i++) {
+ if (i > 0) err += ", ";
+ err += par_types[i].to_string();
+ }
+ err += ")'.";
+ _set_error(err, p_call->line, p_call->column);
+ return DataType();
+ }
+ return DataType();
+ } break;
+ case GDScriptParser::Node::TYPE_BUILT_IN_FUNCTION: {
+ BuiltInFunctionNode *func = static_cast<BuiltInFunctionNode *>(p_call->arguments[0]);
+ MethodInfo mi = GDScriptFunctions::get_info(func->function);
+
+ return_type = _type_from_property(mi.return_val, false);
+
+ // Check arguments
+
+ is_vararg = mi.flags & METHOD_FLAG_VARARG;
+
+ default_args_count = mi.default_arguments.size();
+ callee_name = mi.name;
+ arg_count -= 1;
+
+ // Check each argument type
+ for (List<PropertyInfo>::Element *E = mi.arguments.front(); E; E = E->next()) {
+ arg_types.push_back(_type_from_property(E->get()));
+ }
+ } break;
+ default: {
+ if (p_call->op == OperatorNode::OP_CALL && p_call->arguments.size() < 2) {
+ _set_error("Parser bug: self method call without enough arguments.", p_call->line);
+ ERR_FAIL_V(DataType());
+ }
+
+ int arg_id = p_call->op == OperatorNode::OP_CALL ? 1 : 0;
+
+ if (p_call->arguments[arg_id]->type != Node::TYPE_IDENTIFIER) {
+ _set_error("Parser bug: invalid function call argument.", p_call->line);
+ ERR_FAIL_V(DataType());
+ }
+
+ IdentifierNode *func_id = static_cast<IdentifierNode *>(p_call->arguments[arg_id]);
+ callee_name = func_id->name;
+ arg_count -= 1 + arg_id;
+
+ DataType base_type;
+ if (p_call->op == OperatorNode::OP_PARENT_CALL) {
+ base_type = current_class->base_type;
+ } else {
+ base_type = _reduce_node_type(p_call->arguments[0]);
+ }
+
+ if (!base_type.has_type || (base_type.kind == DataType::BUILTIN && base_type.builtin_type == Variant::NIL)) {
+ _mark_line_as_unsafe(p_call->line);
+ return DataType();
+ }
+
+ if (base_type.kind == DataType::BUILTIN) {
+ Variant::CallError err;
+ Variant tmp = Variant::construct(base_type.builtin_type, NULL, 0, err);
+
+ if (check_types) {
+ if (!tmp.has_method(callee_name)) {
+ _set_error("Method '" + callee_name + "' is not declared on base '" + base_type.to_string() + "'.", p_call->line);
+ return DataType();
+ }
+
+ default_args_count = Variant::get_method_default_arguments(base_type.builtin_type, callee_name).size();
+ const Vector<Variant::Type> &var_arg_types = Variant::get_method_argument_types(base_type.builtin_type, callee_name);
+
+ for (int i = 0; i < var_arg_types.size(); i++) {
+ DataType argtype;
+ if (var_arg_types[i] != Variant::NIL) {
+ argtype.has_type = true;
+ argtype.kind = DataType::BUILTIN;
+ argtype.builtin_type = var_arg_types[i];
+ }
+ arg_types.push_back(argtype);
+ }
+ }
+
+ return_type.has_type = true;
+ return_type.kind = DataType::BUILTIN;
+ return_type.builtin_type = Variant::get_method_return_type(base_type.builtin_type, callee_name);
+ break;
+ }
+
+ DataType original_type = base_type;
+ bool is_initializer = callee_name == "new";
+ bool is_static = false;
+ bool valid = false;
+
+ if (is_initializer && original_type.is_meta_type) {
+ // Try to check it as initializer
+ base_type = original_type;
+ callee_name = "_init";
+ base_type.is_meta_type = false;
+
+ valid = _get_function_signature(base_type, callee_name, return_type, arg_types,
+ default_args_count, is_static, is_vararg);
+
+ if (valid) {
+ return_type = original_type;
+ return_type.is_meta_type = false;
+ }
+ }
+
+ if (!valid) {
+ base_type = original_type;
+ return_type = DataType();
+ valid = _get_function_signature(base_type, callee_name, return_type, arg_types,
+ default_args_count, is_static, is_vararg);
+ }
+
+ if (!valid) {
+#ifdef DEBUG_ENABLED
+ if (p_call->arguments[0]->type == Node::TYPE_SELF) {
+ _set_error("Method '" + callee_name + "' is not declared in the current class.", p_call->line);
+ return DataType();
+ }
+ _mark_line_as_unsafe(p_call->line);
+#endif
+ return DataType();
+ }
+
+#ifdef DEBUG_ENABLED
+ if (current_function && !for_completion && !is_static && p_call->arguments[0]->type == Node::TYPE_SELF && current_function->_static) {
+ if (current_function && current_function->_static && p_call->arguments[0]->type == Node::TYPE_SELF) {
+ _set_error("Can't call non-static function from a static function.", p_call->line);
+ return DataType();
+ }
+ }
+
+ if (check_types && !is_static && !is_initializer && base_type.is_meta_type) {
+ _set_error("Non-static function '" + String(callee_name) + "' can only be called from an instance.", p_call->line);
+ return DataType();
+ }
+#endif
+ } break;
+ }
+
+ if (!check_types) {
+ return return_type;
+ }
+
+ if (arg_count < arg_types.size() - default_args_count) {
+ _set_error("Too few arguments for '" + callee_name + "()' call. Expected at least " + itos(arg_types.size() - default_args_count) + ".", p_call->line);
+ return return_type;
+ }
+ if (!is_vararg && arg_count > arg_types.size()) {
+ _set_error("Too many arguments for '" + callee_name + "()' call. Expected at most " + itos(arg_types.size()) + ".", p_call->line);
+ return return_type;
+ }
+
+ int arg_diff = p_call->arguments.size() - arg_count;
+ for (int i = arg_diff; i < p_call->arguments.size(); i++) {
+ DataType par_type = _reduce_node_type(p_call->arguments[i]);
+
+ if ((i - arg_diff) >= arg_types.size()) {
+ continue;
+ }
+
+ if (!par_type.has_type) {
+ _mark_line_as_unsafe(p_call->line);
+ } else if (!_is_type_compatible(arg_types[i - arg_diff], par_type, true)) {
+ // Supertypes are acceptable for dynamic compliance
+ if (!_is_type_compatible(par_type, arg_types[i - arg_diff])) {
+ _set_error("At '" + callee_name + "()' call, argument " + itos(i - arg_diff + 1) + ". Assigned type (" +
+ par_type.to_string() + ") doesn't match the function argument's type (" +
+ arg_types[i - arg_diff].to_string() + ").",
+ p_call->line);
+ return DataType();
+ } else {
+ _mark_line_as_unsafe(p_call->line);
+ }
+ }
+ }
+
+ return return_type;
+}
+
+bool GDScriptParser::_get_member_type(const DataType &p_base_type, const StringName &p_member, DataType &r_member_type) const {
+ DataType base_type = p_base_type;
+
+ // Check classes in current file
+ ClassNode *base = NULL;
+ if (base_type.kind == DataType::CLASS) {
+ base = base_type.class_type;
+ }
+
+ while (base) {
+ if (base->constant_expressions.has(p_member)) {
+ r_member_type = base->constant_expressions[p_member].expression->get_datatype();
+ return true;
+ }
+
+ if (!base_type.is_meta_type) {
+ for (int i = 0; i < base->variables.size(); i++) {
+ ClassNode::Member m = base->variables[i];
+ if (m.identifier == p_member) {
+ r_member_type = m.data_type;
+ return true;
+ }
+ }
+ } else {
+ for (int i = 0; i < base->subclasses.size(); i++) {
+ ClassNode *c = base->subclasses[i];
+ if (c->name == p_member) {
+ DataType class_type;
+ class_type.has_type = true;
+ class_type.is_constant = true;
+ class_type.is_meta_type = true;
+ class_type.kind = DataType::CLASS;
+ class_type.class_type = c;
+ r_member_type = class_type;
+ return true;
+ }
+ }
+ }
+
+ base_type = base->base_type;
+ if (base_type.kind == DataType::CLASS) {
+ base = base_type.class_type;
+ } else {
+ break;
+ }
+ }
+
+ Ref<GDScript> gds;
+ if (base_type.kind == DataType::GDSCRIPT) {
+ gds = base_type.script_type;
+ }
+
+ Ref<Script> scr;
+ if (base_type.kind == DataType::SCRIPT) {
+ scr = base_type.script_type;
+ }
+
+ StringName native;
+ if (base_type.kind == DataType::NATIVE) {
+ native = base_type.native_type;
+ }
+
+ // Check GDScripts
+ while (gds.is_valid()) {
+ if (gds->get_constants().has(p_member)) {
+ Variant c = gds->get_constants()[p_member];
+ r_member_type = _type_from_variant(c);
+ return true;
+ }
+
+ if (!base_type.is_meta_type) {
+ if (gds->get_members().has(p_member)) {
+ r_member_type = _type_from_gdtype(gds->get_member_type(p_member));
+ return true;
+ }
+ }
+
+ native = gds->get_instance_base_type();
+ if (gds->get_base_script().is_valid()) {
+ gds = gds->get_base_script();
+ scr = gds->get_base_script();
+ bool is_meta = base_type.is_meta_type;
+ base_type = _type_from_variant(scr.operator Variant());
+ base_type.is_meta_type = is_meta;
+ } else {
+ break;
+ }
+ }
+
+ // Check other script types
+ while (scr.is_valid()) {
+ Map<StringName, Variant> constants;
+ scr->get_constants(&constants);
+ if (constants.has(p_member)) {
+ r_member_type = _type_from_variant(constants[p_member]);
+ return true;
+ }
+
+ List<PropertyInfo> properties;
+ scr->get_script_property_list(&properties);
+ for (List<PropertyInfo>::Element *E = properties.front(); E; E = E->next()) {
+ if (E->get().name == p_member) {
+ r_member_type = _type_from_property(E->get());
+ return true;
+ }
+ }
+
+ base_type = _type_from_variant(scr.operator Variant());
+ native = scr->get_instance_base_type();
+ scr = scr->get_base_script();
+ }
+
+ // Check ClassDB
+ if (!ClassDB::class_exists(native)) {
+ native = "_" + native.operator String();
+ }
+ if (!ClassDB::class_exists(native)) {
+ if (!check_types) return false;
+ ERR_EXPLAIN("Parser bug: Class '" + String(native) + "' not found.");
+ ERR_FAIL_V(false);
+ }
+
+ bool valid = false;
+ ClassDB::get_integer_constant(native, p_member, &valid);
+ if (valid) {
+ DataType ct;
+ ct.has_type = true;
+ ct.is_constant = true;
+ ct.kind = DataType::BUILTIN;
+ ct.builtin_type = Variant::INT;
+ r_member_type = ct;
+ return true;
+ }
+
+ if (!base_type.is_meta_type) {
+ List<PropertyInfo> properties;
+ ClassDB::get_property_list(native, &properties);
+ for (List<PropertyInfo>::Element *E = properties.front(); E; E = E->next()) {
+ if (E->get().name == p_member) {
+ // Check if a getter exists
+ StringName getter_name = ClassDB::get_property_getter(native, p_member);
+ if (getter_name != StringName()) {
+ // Use the getter return type
+#ifdef DEBUG_METHODS_ENABLED
+ MethodBind *getter_method = ClassDB::get_method(native, getter_name);
+ if (getter_method) {
+ r_member_type = _type_from_property(getter_method->get_return_info());
+ } else {
+ r_member_type = DataType();
+ }
+#else
+ r_member_type = DataType();
+#endif
+ } else {
+ r_member_type = _type_from_property(E->get());
+ }
+ return true;
+ }
+ }
+ }
+
+ // If the base is a script, it might be trying to access members of the Script class itself
+ if (p_base_type.is_meta_type && (p_base_type.kind == DataType::SCRIPT || p_base_type.kind == DataType::GDSCRIPT)) {
+ native = p_base_type.script_type->get_class_name();
+ ClassDB::get_integer_constant(native, p_member, &valid);
+ if (valid) {
+ DataType ct;
+ ct.has_type = true;
+ ct.is_constant = true;
+ ct.kind = DataType::BUILTIN;
+ ct.builtin_type = Variant::INT;
+ r_member_type = ct;
+ return true;
+ }
+
+ List<PropertyInfo> properties;
+ ClassDB::get_property_list(native, &properties);
+ for (List<PropertyInfo>::Element *E = properties.front(); E; E = E->next()) {
+ if (E->get().name == p_member) {
+ // Check if a getter exists
+ StringName getter_name = ClassDB::get_property_getter(native, p_member);
+ if (getter_name != StringName()) {
+ // Use the getter return type
+#ifdef DEBUG_METHODS_ENABLED
+ MethodBind *getter_method = ClassDB::get_method(native, getter_name);
+ if (getter_method) {
+ r_member_type = _type_from_property(getter_method->get_return_info());
+ } else {
+ r_member_type = DataType();
+ }
+#else
+ r_member_type = DataType();
+#endif
+ } else {
+ r_member_type = _type_from_property(E->get());
+ }
+ return true;
+ }
+ }
+ }
+
+ return false;
+}
+
+GDScriptParser::DataType GDScriptParser::_reduce_identifier_type(const DataType *p_base_type, const StringName &p_identifier, int p_line) {
+
+ if (p_base_type && !p_base_type->has_type) {
+ return DataType();
+ }
+
+ DataType base_type;
+
+ // Check classes in current file
+ ClassNode *base = NULL;
+ if (!p_base_type) {
+ // Possibly this is a global, check first
+
+ if (ClassDB::class_exists(p_identifier) || ClassDB::class_exists("_" + p_identifier.operator String())) {
+ DataType result;
+ result.has_type = true;
+ result.is_constant = true;
+ result.is_meta_type = true;
+ if (Engine::get_singleton()->has_singleton(p_identifier) || Engine::get_singleton()->has_singleton("_" + p_identifier.operator String())) {
+ result.is_meta_type = false;
+ }
+ result.kind = DataType::NATIVE;
+ result.native_type = p_identifier;
+ return result;
+ }
+
+ ClassNode *outer_class = current_class;
+ while (outer_class) {
+ if (outer_class->name == p_identifier) {
+ DataType result;
+ result.has_type = true;
+ result.is_constant = true;
+ result.is_meta_type = true;
+ result.kind = DataType::CLASS;
+ result.class_type = outer_class;
+ return result;
+ }
+ for (int i = 0; i < outer_class->subclasses.size(); i++) {
+ if (outer_class->subclasses[i] == current_class) {
+ continue;
+ }
+ if (outer_class->subclasses[i]->name == p_identifier) {
+ DataType result;
+ result.has_type = true;
+ result.is_constant = true;
+ result.is_meta_type = true;
+ result.kind = DataType::CLASS;
+ result.class_type = outer_class->subclasses[i];
+ return result;
+ }
+ }
+ outer_class = outer_class->owner;
+ }
+
+ if (ScriptServer::is_global_class(p_identifier)) {
+ Ref<Script> scr = ResourceLoader::load(ScriptServer::get_global_class_path(p_identifier));
+ if (scr.is_valid()) {
+ DataType result;
+ result.has_type = true;
+ result.script_type = scr;
+ result.is_meta_type = true;
+ Ref<GDScript> gds = scr;
+ if (gds.is_valid()) {
+ if (!gds->is_valid()) {
+ _set_error("Class '" + p_identifier + "' could not be fully loaded (script error or cyclic inheritance).");
+ return DataType();
+ }
+ result.kind = DataType::GDSCRIPT;
+ } else {
+ result.kind = DataType::SCRIPT;
+ }
+ return result;
+ }
+ _set_error("Class '" + p_identifier + "' was found in global scope but its script could not be loaded.");
+ return DataType();
+ }
+
+ if (GDScriptLanguage::get_singleton()->get_global_map().has(p_identifier)) {
+ int idx = GDScriptLanguage::get_singleton()->get_global_map()[p_identifier];
+ Variant g = GDScriptLanguage::get_singleton()->get_global_array()[idx];
+ return _type_from_variant(g);
+ }
+
+ if (GDScriptLanguage::get_singleton()->get_named_globals_map().has(p_identifier)) {
+ Variant g = GDScriptLanguage::get_singleton()->get_named_globals_map()[p_identifier];
+ return _type_from_variant(g);
+ }
+
+ // Non-tool singletons aren't loaded, check project settings
+ List<PropertyInfo> props;
+ ProjectSettings::get_singleton()->get_property_list(&props);
+
+ for (List<PropertyInfo>::Element *E = props.front(); E; E = E->next()) {
+ String s = E->get().name;
+ if (!s.begins_with("autoload/")) {
+ continue;
+ }
+ String name = s.get_slice("/", 1);
+ if (name == p_identifier) {
+ String script = ProjectSettings::get_singleton()->get(s);
+ if (script.begins_with("*")) {
+ script = script.right(1);
+ }
+ if (!script.begins_with("res://")) {
+ script = "res://" + script;
+ }
+ Ref<Script> singleton = ResourceLoader::load(script);
+ if (singleton.is_valid()) {
+ DataType result;
+ result.has_type = true;
+ result.script_type = singleton;
+
+ Ref<GDScript> gds = singleton;
+ if (gds.is_valid()) {
+ if (!gds->is_valid()) {
+ _set_error("Couldn't fully load singleton script '" + p_identifier + "' (possible cyclic reference or parse error).", p_line);
+ return DataType();
+ }
+ result.kind = DataType::GDSCRIPT;
+ } else {
+ result.kind = DataType::SCRIPT;
+ }
+ }
+ }
+ }
+
+ // Nothing found, keep looking in local scope
+
+ base = current_class;
+ base_type.has_type = true;
+ base_type.is_constant = true;
+ base_type.kind = DataType::CLASS;
+ base_type.class_type = base;
+ } else {
+ base_type = *p_base_type;
+ if (base_type.kind == DataType::CLASS) {
+ base = base_type.class_type;
+ }
+ }
+
+ DataType member_type;
+
+ if (_get_member_type(base_type, p_identifier, member_type)) {
+ return member_type;
+ }
+
+ if (!p_base_type) {
+ // This means looking in the current class, which type is always known
+ _set_error("Identifier '" + p_identifier.operator String() + "' is not declared in the current scope.", p_line);
+ }
+
+ _mark_line_as_unsafe(p_line);
+ return DataType();
+}
+
+void GDScriptParser::_check_class_level_types(ClassNode *p_class) {
+
+ _mark_line_as_safe(p_class->line);
+
+ // Constants
+ for (Map<StringName, ClassNode::Constant>::Element *E = p_class->constant_expressions.front(); E; E = E->next()) {
+ ClassNode::Constant &c = E->get();
+ _mark_line_as_safe(c.expression->line);
+ DataType cont = _resolve_type(c.type, c.expression->line);
+ DataType expr = _resolve_type(c.expression->get_datatype(), c.expression->line);
+
+ if (!_is_type_compatible(cont, expr)) {
+ _set_error("Constant value type (" + expr.to_string() + ") is not compatible with declared type (" + cont.to_string() + ").",
+ c.expression->line);
+ return;
+ }
+
+ expr.is_constant = true;
+ c.type = expr;
+ c.expression->set_datatype(expr);
+ }
+
+ // Function declarations
+ for (int i = 0; i < p_class->static_functions.size(); i++) {
+ _check_function_types(p_class->static_functions[i]);
+ if (error_set) return;
+ }
+
+ for (int i = 0; i < p_class->functions.size(); i++) {
+ _check_function_types(p_class->functions[i]);
+ if (error_set) return;
+ }
+
+ // Class variables
+ for (int i = 0; i < p_class->variables.size(); i++) {
+ ClassNode::Member &v = p_class->variables[i];
+
+ DataType tmp;
+ if (_get_member_type(p_class->base_type, v.identifier, tmp)) {
+ _set_error("Member '" + String(v.identifier) + "' already exists in parent class.", v.line);
+ return;
+ }
+
+ _mark_line_as_safe(v.line);
+ v.data_type = _resolve_type(v.data_type, v.line);
+
+ if (v.expression) {
+ DataType expr_type = _reduce_node_type(v.expression);
+
+ if (!_is_type_compatible(v.data_type, expr_type)) {
+ // Try supertype test
+ if (_is_type_compatible(expr_type, v.data_type)) {
+ _mark_line_as_unsafe(v.line);
+ } else {
+ // Try with implicit conversion
+ if (v.data_type.kind != DataType::BUILTIN || !_is_type_compatible(v.data_type, expr_type, true)) {
+ _set_error("Assigned expression type (" + expr_type.to_string() + ") doesn't match the variable's type (" +
+ v.data_type.to_string() + ").",
+ v.line);
+ return;
+ }
+
+ // Replace assigment with implict conversion
+ BuiltInFunctionNode *convert = alloc_node<BuiltInFunctionNode>();
+ convert->line = v.line;
+ convert->function = GDScriptFunctions::TYPE_CONVERT;
+
+ ConstantNode *tgt_type = alloc_node<ConstantNode>();
+ tgt_type->line = v.line;
+ tgt_type->value = (int)v.data_type.builtin_type;
+
+ OperatorNode *convert_call = alloc_node<OperatorNode>();
+ convert_call->line = v.line;
+ convert_call->op = OperatorNode::OP_CALL;
+ convert_call->arguments.push_back(convert);
+ convert_call->arguments.push_back(v.expression);
+ convert_call->arguments.push_back(tgt_type);
+
+ v.expression = convert_call;
+ v.initial_assignment->arguments[1] = convert_call;
+ }
+ }
+
+ if (v.data_type.infer_type) {
+ if (!expr_type.has_type) {
+ _set_error("Assigned value does not have a set type, variable type cannot be inferred.", v.line);
+ return;
+ }
+ v.data_type = expr_type;
+ v.data_type.is_constant = false;
+ }
+ } else if (v.data_type.has_type && v.data_type.kind == DataType::BUILTIN) {
+ // Create default value based on the type
+ IdentifierNode *id = alloc_node<IdentifierNode>();
+ id->line = v.line;
+ id->name = v.identifier;
+
+ ConstantNode *init = alloc_node<ConstantNode>();
+ init->line = v.line;
+ Variant::CallError err;
+ init->value = Variant::construct(v.data_type.builtin_type, NULL, 0, err);
+
+ OperatorNode *op = alloc_node<OperatorNode>();
+ op->line = v.line;
+ op->op = OperatorNode::OP_INIT_ASSIGN;
+ op->arguments.push_back(id);
+ op->arguments.push_back(init);
+
+ p_class->initializer->statements.push_front(op);
+ v.initial_assignment = op;
+#ifdef DEBUG_ENABLED
+ NewLineNode *nl = alloc_node<NewLineNode>();
+ nl->line = v.line - 1;
+ p_class->initializer->statements.push_front(nl);
+#endif
+ }
+
+ // Check export hint
+ if (v.data_type.has_type && v._export.type != Variant::NIL) {
+ DataType export_type = _type_from_property(v._export);
+ if (!_is_type_compatible(v.data_type, export_type, true)) {
+ _set_error("Export hint type (" + export_type.to_string() + ") doesn't match the variable's type (" +
+ v.data_type.to_string() + ").",
+ v.line);
+ return;
+ }
+ }
+
+ // Setter and getter
+ if (v.setter == StringName() && v.getter == StringName()) continue;
+
+ bool found_getter = false;
+ bool found_setter = false;
+ for (int j = 0; j < p_class->functions.size(); j++) {
+ if (v.setter == p_class->functions[j]->name) {
+ found_setter = true;
+ FunctionNode *setter = p_class->functions[j];
+
+ if (setter->arguments.size() != 1) {
+ _set_error("Setter function needs to receive exactly 1 argument. See '" + setter->name +
+ "()' definition at line " + itos(setter->line) + ".",
+ v.line);
+ return;
+ }
+ if (!_is_type_compatible(v.data_type, setter->argument_types[0])) {
+ _set_error("Setter argument type (" + setter->argument_types[0].to_string() +
+ ") doesn't match the variable's type (" + v.data_type.to_string() + "). See '" +
+ setter->name + "()' definition at line " + itos(setter->line) + ".",
+ v.line);
+ return;
+ }
+ continue;
+ }
+ if (v.getter == p_class->functions[j]->name) {
+ found_getter = true;
+ FunctionNode *getter = p_class->functions[j];
+
+ if (getter->arguments.size() != 0) {
+ _set_error("Getter function can't receive arguments. See '" + getter->name +
+ "()' definition at line " + itos(getter->line) + ".",
+ v.line);
+ return;
+ }
+ if (!_is_type_compatible(v.data_type, getter->get_datatype())) {
+ _set_error("Getter return type (" + getter->get_datatype().to_string() +
+ ") doesn't match the variable's type (" + v.data_type.to_string() +
+ "). See '" + getter->name + "()' definition at line " + itos(getter->line) + ".",
+ v.line);
+ return;
+ }
+ }
+ if (found_getter && found_setter) break;
+ }
+
+ if ((found_getter || v.getter == StringName()) && (found_setter || v.setter == StringName())) continue;
+
+ // Check for static functions
+ for (int j = 0; j < p_class->static_functions.size(); j++) {
+ if (v.setter == p_class->static_functions[j]->name) {
+ FunctionNode *setter = p_class->static_functions[j];
+ _set_error("Setter can't be a static function. See '" + setter->name + "()' definition at line " + itos(setter->line) + ".", v.line);
+ return;
+ }
+ if (v.getter == p_class->static_functions[j]->name) {
+ FunctionNode *getter = p_class->static_functions[j];
+ _set_error("Getter can't be a static function. See '" + getter->name + "()' definition at line " + itos(getter->line) + ".", v.line);
+ return;
+ }
+ }
+
+ if (!found_setter && v.setter != StringName()) {
+ _set_error("Setter function is not defined.", v.line);
+ return;
+ }
+
+ if (!found_getter && v.getter != StringName()) {
+ _set_error("Getter function is not defined.", v.line);
+ return;
+ }
+ }
+
+ // Inner classes
+ for (int i = 0; i < p_class->subclasses.size(); i++) {
+ current_class = p_class->subclasses[i];
+ _check_class_level_types(current_class);
+ if (error_set) return;
+ current_class = p_class;
+ }
+}
+
+void GDScriptParser::_check_function_types(FunctionNode *p_function) {
+
+ p_function->return_type = _resolve_type(p_function->return_type, p_function->line);
+
+ // Arguments
+ int defaults_ofs = p_function->arguments.size() - p_function->default_values.size();
+ for (int i = 0; i < p_function->arguments.size(); i++) {
+
+ // Resolve types
+ p_function->argument_types[i] = _resolve_type(p_function->argument_types[i], p_function->line);
+
+ if (i >= defaults_ofs) {
+ if (p_function->default_values[i - defaults_ofs]->type != Node::TYPE_OPERATOR) {
+ _set_error("Parser bug: invalid argument default value.", p_function->line, p_function->column);
+ return;
+ }
+
+ OperatorNode *op = static_cast<OperatorNode *>(p_function->default_values[i - defaults_ofs]);
+
+ if (op->op != OperatorNode::OP_ASSIGN || op->arguments.size() != 2) {
+ _set_error("Parser bug: invalid argument default value operation.", p_function->line);
+ return;
+ }
+
+ DataType def_type = _reduce_node_type(op->arguments[1]);
+
+ if (!_is_type_compatible(p_function->argument_types[i], def_type, true)) {
+ String arg_name = p_function->arguments[i];
+ _set_error("Value type (" + def_type.to_string() + ") doesn't match the type of argument '" +
+ arg_name + "' (" + p_function->arguments[i] + ")",
+ p_function->line);
+ }
+ }
+ }
+
+ if (!(p_function->name == "_init")) {
+ // Signature for the initializer may vary
+ DataType return_type;
+ List<DataType> arg_types;
+ int default_arg_count = 0;
+ bool _static = false;
+ bool vararg = false;
+
+ DataType base_type = current_class->base_type;
+ if (_get_function_signature(base_type, p_function->name, return_type, arg_types, default_arg_count, _static, vararg)) {
+ bool valid = _static == p_function->_static;
+ valid = valid && return_type == p_function->return_type;
+ valid = valid && p_function->default_values.size() >= default_arg_count;
+ valid = valid && arg_types.size() == p_function->arguments.size();
+ int i = 0;
+ for (List<DataType>::Element *E = arg_types.front(); valid && E; E = E->next()) {
+ valid = valid && E->get() == p_function->argument_types[i++];
+ }
+
+ if (!valid) {
+ _set_error("Function signature doesn't match the parent.", p_function->line);
+ return;
+ }
+ }
+ } else {
+ if (p_function->return_type.has_type && (p_function->return_type.kind != DataType::BUILTIN || p_function->return_type.builtin_type != Variant::NIL)) {
+ _set_error("Constructor cannot return a value.", p_function->line);
+ return;
+ }
+ }
+
+ if (p_function->return_type.has_type && (p_function->return_type.kind != DataType::BUILTIN || p_function->return_type.builtin_type != Variant::NIL)) {
+ if (!p_function->body->has_return) {
+ _set_error("Non-void function must return a value in all possible paths.", p_function->line);
+ return;
+ }
+ }
+
+ if (p_function->has_yield) {
+ // yield() will make the function return a GDScriptFunctionState, so the type is ambiguous
+ p_function->return_type.has_type = false;
+ }
+}
+
+void GDScriptParser::_check_class_blocks_types(ClassNode *p_class) {
+
+ // Function blocks
+ for (int i = 0; i < p_class->static_functions.size(); i++) {
+ current_function = p_class->static_functions[i];
+ current_block = current_function->body;
+ _mark_line_as_safe(current_function->line);
+ _check_block_types(current_block);
+ current_block = NULL;
+ current_function = NULL;
+ if (error_set) return;
+ }
+
+ for (int i = 0; i < p_class->functions.size(); i++) {
+ current_function = p_class->functions[i];
+ current_block = current_function->body;
+ _mark_line_as_safe(current_function->line);
+ _check_block_types(current_block);
+ current_block = NULL;
+ current_function = NULL;
+ if (error_set) return;
+ }
+
+ // Inner classes
+ for (int i = 0; i < p_class->subclasses.size(); i++) {
+ current_class = p_class->subclasses[i];
+ _check_class_blocks_types(current_class);
+ if (error_set) return;
+ current_class = p_class;
+ }
+}
+
+void GDScriptParser::_check_block_types(BlockNode *p_block) {
+
+ Node *last_var_assign = NULL;
+
+ // Check each statement
+ for (List<Node *>::Element *E = p_block->statements.front(); E; E = E->next()) {
+ Node *statement = E->get();
+ switch (statement->type) {
+ case Node::TYPE_NEWLINE:
+ case Node::TYPE_BREAKPOINT:
+ case Node::TYPE_ASSERT: {
+ // Nothing to do
+ } break;
+ case Node::TYPE_LOCAL_VAR: {
+ LocalVarNode *lv = static_cast<LocalVarNode *>(statement);
+ lv->datatype = _resolve_type(lv->datatype, lv->line);
+ _mark_line_as_safe(lv->line);
+
+ if (lv->assign) {
+ DataType assign_type = _reduce_node_type(lv->assign);
+ if (!_is_type_compatible(lv->datatype, assign_type)) {
+ // Try supertype test
+ if (_is_type_compatible(assign_type, lv->datatype)) {
+ _mark_line_as_unsafe(lv->line);
+ } else {
+ // Try implict conversion
+ if (lv->datatype.kind != DataType::BUILTIN || !_is_type_compatible(lv->datatype, assign_type, true)) {
+ _set_error("Assigned value type (" + assign_type.to_string() + ") doesn't match the variable's type (" +
+ lv->datatype.to_string() + ").",
+ lv->line);
+ return;
+ }
+ // Replace assigment with implict conversion
+ BuiltInFunctionNode *convert = alloc_node<BuiltInFunctionNode>();
+ convert->line = lv->line;
+ convert->function = GDScriptFunctions::TYPE_CONVERT;
+
+ ConstantNode *tgt_type = alloc_node<ConstantNode>();
+ tgt_type->line = lv->line;
+ tgt_type->value = (int)lv->datatype.builtin_type;
+
+ OperatorNode *convert_call = alloc_node<OperatorNode>();
+ convert_call->line = lv->line;
+ convert_call->op = OperatorNode::OP_CALL;
+ convert_call->arguments.push_back(convert);
+ convert_call->arguments.push_back(lv->assign);
+ convert_call->arguments.push_back(tgt_type);
+
+ lv->assign = convert_call;
+ lv->assign_op->arguments[1] = convert_call;
+ }
+ }
+ if (lv->datatype.infer_type) {
+ if (!assign_type.has_type) {
+ _set_error("Assigned value does not have a set type, variable type cannot be inferred.", lv->line);
+ return;
+ }
+ lv->datatype = assign_type;
+ lv->datatype.is_constant = false;
+ }
+ if (lv->datatype.has_type && !assign_type.has_type) {
+ _mark_line_as_unsafe(lv->line);
+ }
+ }
+ last_var_assign = lv->assign;
+
+ // TODO: Make a warning
+ /*
+ if (lv->assignments == 0) {
+ _set_error("Variable '" + String(lv->name) + "' is never assigned.", lv->line);
+ return;
+ }
+ */
+ } break;
+ case Node::TYPE_OPERATOR: {
+ OperatorNode *op = static_cast<OperatorNode *>(statement);
+
+ switch (op->op) {
+ case OperatorNode::OP_ASSIGN:
+ case OperatorNode::OP_ASSIGN_ADD:
+ case OperatorNode::OP_ASSIGN_SUB:
+ case OperatorNode::OP_ASSIGN_MUL:
+ case OperatorNode::OP_ASSIGN_DIV:
+ case OperatorNode::OP_ASSIGN_MOD:
+ case OperatorNode::OP_ASSIGN_SHIFT_LEFT:
+ case OperatorNode::OP_ASSIGN_SHIFT_RIGHT:
+ case OperatorNode::OP_ASSIGN_BIT_AND:
+ case OperatorNode::OP_ASSIGN_BIT_OR:
+ case OperatorNode::OP_ASSIGN_BIT_XOR: {
+ if (op->arguments.size() < 2) {
+ _set_error("Parser bug: operation without enough arguments.", op->line, op->column);
+ return;
+ }
+
+ if (op->arguments[1] == last_var_assign) {
+ // Assignment was already checked
+ break;
+ }
+
+ _mark_line_as_safe(op->line);
+
+ DataType lh_type = _reduce_node_type(op->arguments[0]);
+
+ if (error_set) {
+ return;
+ }
+
+ if (!lh_type.has_type) {
+ if (op->arguments[0]->type == Node::TYPE_OPERATOR) {
+ _mark_line_as_unsafe(op->line);
+ }
+ } else if (lh_type.is_constant) {
+ _set_error("Cannot assign a new value to a constant.", op->line);
+ return;
+ }
+
+ DataType rh_type;
+ if (op->op != OperatorNode::OP_ASSIGN) {
+ // Validate operation
+ DataType arg_type = _reduce_node_type(op->arguments[1]);
+ if (!arg_type.has_type) {
+ _mark_line_as_unsafe(op->line);
+ break;
+ }
+
+ Variant::Operator oper = _get_variant_operation(op->op);
+ bool valid = false;
+ rh_type = _get_operation_type(oper, lh_type, arg_type, valid);
+
+ if (!valid) {
+ _set_error("Invalid operand types ('" + lh_type.to_string() + "' and '" + arg_type.to_string() +
+ "') to assignment operator '" + Variant::get_operator_name(oper) + "'.",
+ op->line);
+ return;
+ }
+ } else {
+ rh_type = _reduce_node_type(op->arguments[1]);
+ }
+
+ if (!_is_type_compatible(lh_type, rh_type)) {
+ // Try supertype test
+ if (_is_type_compatible(rh_type, lh_type)) {
+ _mark_line_as_unsafe(op->line);
+ } else {
+ // Try implict conversion
+ if (lh_type.kind != DataType::BUILTIN || !_is_type_compatible(lh_type, rh_type, true)) {
+ _set_error("Assigned value type (" + rh_type.to_string() + ") doesn't match the variable's type (" +
+ lh_type.to_string() + ").",
+ op->line);
+ return;
+ }
+ // Replace assigment with implict conversion
+ BuiltInFunctionNode *convert = alloc_node<BuiltInFunctionNode>();
+ convert->line = op->line;
+ convert->function = GDScriptFunctions::TYPE_CONVERT;
+
+ ConstantNode *tgt_type = alloc_node<ConstantNode>();
+ tgt_type->line = op->line;
+ tgt_type->value = (int)lh_type.builtin_type;
+
+ OperatorNode *convert_call = alloc_node<OperatorNode>();
+ convert_call->line = op->line;
+ convert_call->op = OperatorNode::OP_CALL;
+ convert_call->arguments.push_back(convert);
+ convert_call->arguments.push_back(op->arguments[1]);
+ convert_call->arguments.push_back(tgt_type);
+
+ op->arguments[1] = convert_call;
+ }
+ }
+ if (!rh_type.has_type && (op->op != OperatorNode::OP_ASSIGN || lh_type.has_type || op->arguments[0]->type == Node::TYPE_OPERATOR)) {
+ _mark_line_as_unsafe(op->line);
+ }
+ } break;
+ case OperatorNode::OP_CALL:
+ case OperatorNode::OP_PARENT_CALL: {
+ _mark_line_as_safe(op->line);
+ _reduce_function_call_type(op);
+ if (error_set) return;
+ } break;
+ default: {
+ _mark_line_as_safe(op->line);
+ _reduce_node_type(op); // Test for safety anyway
+ // TODO: Make this a warning
+ /*_set_error("Standalone expression, nothing is done in this line.", statement->line);
+ return; */
+ }
+ }
+ } break;
+ case Node::TYPE_CONTROL_FLOW: {
+ ControlFlowNode *cf = static_cast<ControlFlowNode *>(statement);
+ _mark_line_as_safe(cf->line);
+
+ switch (cf->cf_type) {
+ case ControlFlowNode::CF_RETURN: {
+ DataType function_type = current_function->get_datatype();
+
+ DataType ret_type;
+ if (cf->arguments.size() > 0) {
+ ret_type = _reduce_node_type(cf->arguments[0]);
+ if (error_set) {
+ return;
+ }
+ }
+
+ if (!function_type.has_type) break;
+
+ if (function_type.kind == DataType::BUILTIN && function_type.builtin_type == Variant::NIL) {
+ // Return void, should not have arguments
+ if (cf->arguments.size() > 0) {
+ _set_error("Void function cannot return a value.", cf->line, cf->column);
+ return;
+ }
+ } else {
+ // Return something, cannot be empty
+ if (cf->arguments.size() == 0) {
+ _set_error("Non-void function must return a value.", cf->line, cf->column);
+ return;
+ }
+
+ if (!_is_type_compatible(function_type, ret_type)) {
+ _set_error("Returned value type (" + ret_type.to_string() + ") doesn't match the function return type (" +
+ function_type.to_string() + ").",
+ cf->line, cf->column);
+ return;
+ }
+ }
+ } break;
+ case ControlFlowNode::CF_MATCH: {
+ MatchNode *match_node = cf->match;
+ _transform_match_statment(match_node);
+ } break;
+ default: {
+ if (cf->body_else) {
+ _mark_line_as_safe(cf->body_else->line);
+ }
+ for (int i = 0; i < cf->arguments.size(); i++) {
+ _reduce_node_type(cf->arguments[i]);
+ }
+ } break;
+ }
+ } break;
+ case Node::TYPE_CONSTANT: {
+ ConstantNode *cn = static_cast<ConstantNode *>(statement);
+ // Strings are fine since they can be multiline comments
+ if (cn->value.get_type() == Variant::STRING) {
+ break;
+ }
+ } // falthrough
+ default: {
+ _mark_line_as_safe(statement->line);
+ _reduce_node_type(statement); // Test for safety anyway
+ // TODO: Make this a warning
+ /* _set_error("Standalone expression, nothing is done in this line.", statement->line);
+ return; */
+ }
+ }
+ }
+
+ // Parse sub blocks
+ for (int i = 0; i < p_block->sub_blocks.size(); i++) {
+ current_block = p_block->sub_blocks[i];
+ _check_block_types(current_block);
+ current_block = p_block;
+ if (error_set) return;
+ }
+}
+
void GDScriptParser::_set_error(const String &p_error, int p_line, int p_column) {
if (error_set)
@@ -4467,10 +7547,39 @@ Error GDScriptParser::_parse(const String &p_base_path) {
_set_error("Parse Error: " + tokenizer->get_token_error());
}
+ if (error_set && !for_completion) {
+ return ERR_PARSE_ERROR;
+ }
+
+ _determine_inheritance(main_class);
+
+ if (error_set) {
+ return ERR_PARSE_ERROR;
+ }
+
+ current_class = main_class;
+ current_function = NULL;
+ current_block = NULL;
+#ifdef DEBUG_ENABLED
+ if (for_completion) check_types = false;
+#else
+ check_types = false;
+#endif
+
+ // Resolve all class-level stuff before getting into function blocks
+ _check_class_level_types(main_class);
+
if (error_set) {
+ return ERR_PARSE_ERROR;
+ }
+ // Resolve the function blocks
+ _check_class_blocks_types(main_class);
+
+ if (error_set) {
return ERR_PARSE_ERROR;
}
+
return OK;
}
@@ -4488,7 +7597,7 @@ Error GDScriptParser::parse_bytecode(const Vector<uint8_t> &p_bytecode, const St
return ret;
}
-Error GDScriptParser::parse(const String &p_code, const String &p_base_path, bool p_just_validate, const String &p_self_path, bool p_for_completion) {
+Error GDScriptParser::parse(const String &p_code, const String &p_base_path, bool p_just_validate, const String &p_self_path, bool p_for_completion, Set<int> *r_safe_lines) {
clear();
@@ -4498,6 +7607,9 @@ Error GDScriptParser::parse(const String &p_code, const String &p_base_path, boo
validating = p_just_validate;
for_completion = p_for_completion;
+#ifdef DEBUG_ENABLED
+ safe_lines = r_safe_lines;
+#endif // DEBUG_ENABLED
tokenizer = tt;
Error ret = _parse(p_base_path);
memdelete(tt);
@@ -4550,7 +7662,11 @@ void GDScriptParser::clear() {
pending_newline = -1;
parenthesis = 0;
current_export.type = Variant::NIL;
+ check_types = true;
error = "";
+#ifdef DEBUG_ENABLED
+ safe_lines = NULL;
+#endif // DEBUG_ENABLED
}
GDScriptParser::CompletionType GDScriptParser::get_completion_type() {
diff --git a/modules/gdscript/gdscript_parser.h b/modules/gdscript/gdscript_parser.h
index b88a59537c..48f256b4c6 100644
--- a/modules/gdscript/gdscript_parser.h
+++ b/modules/gdscript/gdscript_parser.h
@@ -37,8 +37,68 @@
#include "object.h"
#include "script_language.h"
+struct GDScriptDataType;
+
class GDScriptParser {
public:
+ struct ClassNode;
+
+ struct DataType {
+ enum {
+ BUILTIN,
+ NATIVE,
+ SCRIPT,
+ GDSCRIPT,
+ CLASS,
+ UNRESOLVED
+ } kind;
+
+ bool has_type;
+ bool is_constant;
+ bool is_meta_type; // Whether the value can be used as a type
+ bool infer_type;
+
+ Variant::Type builtin_type;
+ StringName native_type;
+ Ref<Script> script_type;
+ ClassNode *class_type;
+
+ String to_string() const;
+
+ bool operator==(const DataType &other) const {
+ if (!has_type || !other.has_type) {
+ return true; // Can be considered equal for parsing purpose
+ }
+ if (kind != other.kind) {
+ return false;
+ }
+ switch (kind) {
+ case BUILTIN: {
+ return builtin_type == other.builtin_type;
+ } break;
+ case NATIVE: {
+ return native_type == other.native_type;
+ } break;
+ case GDSCRIPT:
+ case SCRIPT: {
+ return script_type == other.script_type;
+ } break;
+ case CLASS: {
+ return class_type == other.class_type;
+ } break;
+ }
+ return false;
+ }
+
+ DataType() :
+ has_type(false),
+ is_constant(false),
+ is_meta_type(false),
+ infer_type(false),
+ builtin_type(Variant::NIL),
+ class_type(NULL) {}
+ };
+
struct Node {
enum Type {
@@ -55,6 +115,7 @@ public:
TYPE_OPERATOR,
TYPE_CONTROL_FLOW,
TYPE_LOCAL_VAR,
+ TYPE_CAST,
TYPE_ASSERT,
TYPE_BREAKPOINT,
TYPE_NEWLINE,
@@ -65,11 +126,17 @@ public:
int column;
Type type;
+ virtual DataType get_datatype() const { return DataType(); }
+ virtual void set_datatype(const DataType &p_datatype) {}
+
virtual ~Node() {}
};
struct FunctionNode;
struct BlockNode;
+ struct ConstantNode;
+ struct LocalVarNode;
+ struct OperatorNode;
struct ClassNode : public Node {
@@ -78,6 +145,7 @@ public:
bool extends_used;
StringName extends_file;
Vector<StringName> extends_class;
+ DataType base_type;
struct Member {
PropertyInfo _export;
@@ -85,15 +153,17 @@ public:
Variant default_value;
#endif
StringName identifier;
+ DataType data_type;
StringName setter;
StringName getter;
int line;
Node *expression;
+ OperatorNode *initial_assignment;
MultiplayerAPI::RPCMode rpc_mode;
};
struct Constant {
- StringName identifier;
Node *expression;
+ DataType type;
};
struct Signal {
@@ -103,7 +173,7 @@ public:
Vector<ClassNode *> subclasses;
Vector<Member> variables;
- Vector<Constant> constant_expressions;
+ Map<StringName, Constant> constant_expressions;
Vector<FunctionNode *> functions;
Vector<FunctionNode *> static_functions;
Vector<Signal> _signals;
@@ -126,15 +196,22 @@ public:
bool _static;
MultiplayerAPI::RPCMode rpc_mode;
+ bool has_yield;
StringName name;
+ DataType return_type;
Vector<StringName> arguments;
+ Vector<DataType> argument_types;
Vector<Node *> default_values;
BlockNode *body;
+ virtual DataType get_datatype() const { return return_type; }
+ virtual void set_datatype(const DataType &p_datatype) { return_type = p_datatype; }
+
FunctionNode() {
type = TYPE_FUNCTION;
_static = false;
rpc_mode = MultiplayerAPI::RPC_MODE_DISABLED;
+ has_yield = false;
}
};
@@ -142,10 +219,9 @@ public:
ClassNode *parent_class;
BlockNode *parent_block;
- Map<StringName, int> locals;
List<Node *> statements;
- Vector<StringName> variables;
- Vector<int> variable_lines;
+ Map<StringName, LocalVarNode *> variables;
+ bool has_return;
Node *if_condition; //tiny hack to improve code completion on if () blocks
@@ -158,6 +234,7 @@ public:
end_line = -1;
parent_block = NULL;
parent_class = NULL;
+ has_return = false;
}
};
@@ -174,28 +251,53 @@ public:
struct IdentifierNode : public Node {
StringName name;
- IdentifierNode() { type = TYPE_IDENTIFIER; }
+ BlockNode *declared_block; // Simplify lookup by checking if it is declared locally
+ DataType datatype;
+ virtual DataType get_datatype() const { return datatype; }
+ virtual void set_datatype(const DataType &p_datatype) { datatype = p_datatype; }
+ IdentifierNode() {
+ type = TYPE_IDENTIFIER;
+ declared_block = NULL;
+ }
};
struct LocalVarNode : public Node {
StringName name;
Node *assign;
+ OperatorNode *assign_op;
+ int assignments;
+ DataType datatype;
+ virtual DataType get_datatype() const { return datatype; }
+ virtual void set_datatype(const DataType &p_datatype) { datatype = p_datatype; }
LocalVarNode() {
type = TYPE_LOCAL_VAR;
assign = NULL;
+ assign_op = NULL;
+ assignments = 0;
}
};
struct ConstantNode : public Node {
Variant value;
+ DataType datatype;
+ virtual DataType get_datatype() const { return datatype; }
+ virtual void set_datatype(const DataType &p_datatype) { datatype = p_datatype; }
ConstantNode() { type = TYPE_CONSTANT; }
};
struct ArrayNode : public Node {
Vector<Node *> elements;
- ArrayNode() { type = TYPE_ARRAY; }
+ DataType datatype;
+ virtual DataType get_datatype() const { return datatype; }
+ virtual void set_datatype(const DataType &p_datatype) { datatype = p_datatype; }
+ ArrayNode() {
+ type = TYPE_ARRAY;
+ datatype.has_type = true;
+ datatype.kind = DataType::BUILTIN;
+ datatype.builtin_type = Variant::ARRAY;
+ }
};
struct DictionaryNode : public Node {
@@ -207,7 +309,15 @@ public:
};
Vector<Pair> elements;
- DictionaryNode() { type = TYPE_DICTIONARY; }
+ DataType datatype;
+ virtual DataType get_datatype() const { return datatype; }
+ virtual void set_datatype(const DataType &p_datatype) { datatype = p_datatype; }
+ DictionaryNode() {
+ type = TYPE_DICTIONARY;
+ datatype.has_type = true;
+ datatype.kind = DataType::BUILTIN;
+ datatype.builtin_type = Variant::DICTIONARY;
+ }
};
struct SelfNode : public Node {
@@ -229,10 +339,6 @@ public:
OP_POS,
OP_NOT,
OP_BIT_INVERT,
- OP_PREINC,
- OP_PREDEC,
- OP_INC,
- OP_DEC,
//binary operators (in precedence order)
OP_IN,
OP_EQUAL,
@@ -273,6 +379,9 @@ public:
Operator op;
Vector<Node *> arguments;
+ DataType datatype;
+ virtual DataType get_datatype() const { return datatype; }
+ virtual void set_datatype(const DataType &p_datatype) { datatype = p_datatype; }
OperatorNode() { type = TYPE_OPERATOR; }
};
@@ -340,6 +449,15 @@ public:
}
};
+ struct CastNode : public Node {
+ Node *source_node;
+ DataType cast_type;
+ DataType return_type;
+ virtual DataType get_datatype() const { return return_type; }
+ virtual void set_datatype(const DataType &p_datatype) { return_type = p_datatype; }
+ CastNode() { type = TYPE_CAST; }
+ };
+
struct AssertNode : public Node {
Node *condition;
AssertNode() { type = TYPE_ASSERT; }
@@ -362,76 +480,6 @@ public:
};
};
- /*
- struct OperatorNode : public Node {
-
- DataType return_cache;
- Operator op;
- Vector<Node*> arguments;
- virtual DataType get_datatype() const { return return_cache; }
-
- OperatorNode() { type=TYPE_OPERATOR; return_cache=TYPE_VOID; }
- };
-
- struct VariableNode : public Node {
-
- DataType datatype_cache;
- StringName name;
- virtual DataType get_datatype() const { return datatype_cache; }
-
- VariableNode() { type=TYPE_VARIABLE; datatype_cache=TYPE_VOID; }
- };
-
- struct ConstantNode : public Node {
-
- DataType datatype;
- Variant value;
- virtual DataType get_datatype() const { return datatype; }
-
- ConstantNode() { type=TYPE_CONSTANT; }
- };
-
- struct BlockNode : public Node {
-
- Map<StringName,DataType> variables;
- List<Node*> statements;
- BlockNode() { type=TYPE_BLOCK; }
- };
-
- struct ControlFlowNode : public Node {
-
- FlowOperation flow_op;
- Vector<Node*> statements;
- ControlFlowNode() { type=TYPE_CONTROL_FLOW; flow_op=FLOW_OP_IF;}
- };
-
- struct MemberNode : public Node {
-
- DataType datatype;
- StringName name;
- Node* owner;
- virtual DataType get_datatype() const { return datatype; }
- MemberNode() { type=TYPE_MEMBER; }
- };
-
-
- struct ProgramNode : public Node {
-
- struct Function {
- StringName name;
- FunctionNode*function;
- };
-
- Map<StringName,DataType> builtin_variables;
- Map<StringName,DataType> preexisting_variables;
-
- Vector<Function> functions;
- BlockNode *body;
-
- ProgramNode() { type=TYPE_PROGRAM; }
- };
-*/
-
enum CompletionType {
COMPLETION_NONE,
COMPLETION_BUILT_IN_TYPE_CONSTANT,
@@ -446,6 +494,8 @@ public:
COMPLETION_VIRTUAL_FUNC,
COMPLETION_YIELD,
COMPLETION_ASSIGN,
+ COMPLETION_TYPE_HINT,
+ COMPLETION_TYPE_HINT_INDEX,
};
private:
@@ -463,6 +513,10 @@ private:
String error;
int error_line;
int error_column;
+ bool check_types;
+#ifdef DEBUG_ENABLED
+ Set<int> *safe_lines;
+#endif // DEBUG_ENABLED
int pending_newline;
@@ -507,7 +561,7 @@ private:
PatternNode *_parse_pattern(bool p_static);
void _parse_pattern_block(BlockNode *p_block, Vector<PatternBranchNode *> &p_branches, bool p_static);
- void _transform_match_statment(BlockNode *p_block, MatchNode *p_match_statement);
+ void _transform_match_statment(MatchNode *p_match_statement);
void _generate_pattern(PatternNode *p_pattern, Node *p_node_to_match, Node *&p_resulting_node, Map<StringName, Node *> &p_bindings);
void _parse_block(BlockNode *p_block, bool p_static);
@@ -515,13 +569,43 @@ private:
void _parse_class(ClassNode *p_class);
bool _end_statement();
+ void _determine_inheritance(ClassNode *p_class);
+ bool _parse_type(DataType &r_type, bool p_can_be_void = false);
+ DataType _resolve_type(const DataType &p_source, int p_line);
+ DataType _type_from_variant(const Variant &p_value) const;
+ DataType _type_from_property(const PropertyInfo &p_property, bool p_nil_is_variant = true) const;
+ DataType _type_from_gdtype(const GDScriptDataType &p_gdtype) const;
+ DataType _get_operation_type(const Variant::Operator p_op, const DataType &p_a, const DataType &p_b, bool &r_valid) const;
+ Variant::Operator _get_variant_operation(const OperatorNode::Operator &p_op) const;
+ bool _get_function_signature(DataType &p_base_type, const StringName &p_function, DataType &r_return_type, List<DataType> &r_arg_types, int &r_default_arg_count, bool &r_static, bool &r_vararg) const;
+ bool _get_member_type(const DataType &p_base_type, const StringName &p_member, DataType &r_member_type) const;
+ bool _is_type_compatible(const DataType &p_container, const DataType &p_expression, bool p_allow_implicit_conversion = false) const;
+
+ DataType _reduce_node_type(Node *p_node);
+ DataType _reduce_function_call_type(const OperatorNode *p_call);
+ DataType _reduce_identifier_type(const DataType *p_base_type, const StringName &p_identifier, int p_line);
+ void _check_class_level_types(ClassNode *p_class);
+ void _check_class_blocks_types(ClassNode *p_class);
+ void _check_function_types(FunctionNode *p_function);
+ void _check_block_types(BlockNode *p_block);
+ _FORCE_INLINE_ void _mark_line_as_safe(int p_line) const {
+#ifdef DEBUG_ENABLED
+ if (safe_lines) safe_lines->insert(p_line);
+#endif // DEBUG_ENABLED
+ }
+ _FORCE_INLINE_ void _mark_line_as_unsafe(int p_line) const {
+#ifdef DEBUG_ENABLED
+ if (safe_lines) safe_lines->erase(p_line);
+#endif // DEBUG_ENABLED
+ }
+
Error _parse(const String &p_base_path);
public:
String get_error() const;
int get_error_line() const;
int get_error_column() const;
- Error parse(const String &p_code, const String &p_base_path = "", bool p_just_validate = false, const String &p_self_path = "", bool p_for_completion = false);
+ Error parse(const String &p_code, const String &p_base_path = "", bool p_just_validate = false, const String &p_self_path = "", bool p_for_completion = false, Set<int> *r_safe_lines = NULL);
Error parse_bytecode(const Vector<uint8_t> &p_bytecode, const String &p_base_path = "", const String &p_self_path = "");
bool is_tool_script() const;
diff --git a/modules/gdscript/gdscript_tokenizer.cpp b/modules/gdscript/gdscript_tokenizer.cpp
index 1d30871e3f..940bdcbc8d 100644
--- a/modules/gdscript/gdscript_tokenizer.cpp
+++ b/modules/gdscript/gdscript_tokenizer.cpp
@@ -101,6 +101,8 @@ const char *GDScriptTokenizer::token_names[TK_MAX] = {
"setget",
"const",
"var",
+ "as",
+ "void",
"enum",
"preload",
"assert",
@@ -125,6 +127,7 @@ const char *GDScriptTokenizer::token_names[TK_MAX] = {
"'.'",
"'?'",
"':'",
+ "'->'",
"'$'",
"'\\n'",
"PI",
@@ -197,6 +200,8 @@ static const _kws _keyword_list[] = {
{ GDScriptTokenizer::TK_PR_EXPORT, "export" },
{ GDScriptTokenizer::TK_PR_SETGET, "setget" },
{ GDScriptTokenizer::TK_PR_VAR, "var" },
+ { GDScriptTokenizer::TK_PR_AS, "as" },
+ { GDScriptTokenizer::TK_PR_VOID, "void" },
{ GDScriptTokenizer::TK_PR_PRELOAD, "preload" },
{ GDScriptTokenizer::TK_PR_ASSERT, "assert" },
{ GDScriptTokenizer::TK_PR_YIELD, "yield" },
@@ -707,11 +712,9 @@ void GDScriptTokenizerText::_advance() {
if (GETCHAR(1) == '=') {
_make_token(TK_OP_ASSIGN_SUB);
INCPOS(1);
- /*
- } else if (GETCHAR(1)=='-') {
- _make_token(TK_OP_MINUS_MINUS);
+ } else if (GETCHAR(1) == '>') {
+ _make_token(TK_FORWARD_ARROW);
INCPOS(1);
- */
} else {
_make_token(TK_OP_SUB);
}
diff --git a/modules/gdscript/gdscript_tokenizer.h b/modules/gdscript/gdscript_tokenizer.h
index c1f611fe73..5bd303224c 100644
--- a/modules/gdscript/gdscript_tokenizer.h
+++ b/modules/gdscript/gdscript_tokenizer.h
@@ -106,6 +106,8 @@ public:
TK_PR_SETGET,
TK_PR_CONST,
TK_PR_VAR,
+ TK_PR_AS,
+ TK_PR_VOID,
TK_PR_ENUM,
TK_PR_PRELOAD,
TK_PR_ASSERT,
@@ -131,6 +133,7 @@ public:
TK_QUESTION_MARK,
TK_COLON,
TK_DOLLAR,
+ TK_FORWARD_ARROW,
TK_NEWLINE,
TK_CONST_PI,
TK_CONST_TAU,
diff --git a/modules/mono/csharp_script.cpp b/modules/mono/csharp_script.cpp
index 62a6b96bb5..996e73a4bb 100644
--- a/modules/mono/csharp_script.cpp
+++ b/modules/mono/csharp_script.cpp
@@ -121,7 +121,7 @@ void CSharpLanguage::init() {
#ifdef TOOLS_ENABLED
EditorNode::add_init_callback(&gdsharp_editor_init_callback);
- GLOBAL_DEF("mono/export/include_scripts_content", true);
+ GLOBAL_DEF("mono/export/include_scripts_content", false);
#endif
}
@@ -1609,7 +1609,7 @@ void CSharpScript::load_script_signals(GDMonoClass *p_class, GDMonoClass *p_nati
bool CSharpScript::_get_signal(GDMonoClass *p_class, GDMonoClass *p_delegate, Vector<Argument> &params) {
if (p_delegate->has_attribute(CACHED_CLASS(SignalAttribute))) {
- MonoType *raw_type = GDMonoClass::get_raw_type(p_delegate);
+ MonoType *raw_type = p_delegate->get_mono_type();
if (mono_type_get_type(raw_type) == MONO_TYPE_CLASS) {
// Arguments are accessibles as arguments of .Invoke method
diff --git a/modules/mono/csharp_script.h b/modules/mono/csharp_script.h
index df597ba776..7f9732c297 100644
--- a/modules/mono/csharp_script.h
+++ b/modules/mono/csharp_script.h
@@ -292,7 +292,7 @@ public:
virtual Ref<Script> get_template(const String &p_class_name, const String &p_base_class_name) const;
virtual bool is_using_templates();
virtual void make_template(const String &p_class_name, const String &p_base_class_name, Ref<Script> &p_script);
- /* TODO */ virtual bool validate(const String &p_script, int &r_line_error, int &r_col_error, String &r_test_error, const String &p_path, List<String> *r_functions) const { return true; }
+ /* TODO */ virtual bool validate(const String &p_script, int &r_line_error, int &r_col_error, String &r_test_error, const String &p_path, List<String> *r_functions, Set<int> *r_safe_lines = NULL) const { return true; }
virtual String validate_path(const String &p_path) const;
virtual Script *create_script() const;
virtual bool has_named_classes() const;
diff --git a/modules/mono/editor/bindings_generator.cpp b/modules/mono/editor/bindings_generator.cpp
index 6fa317ee70..307a7d3e94 100644
--- a/modules/mono/editor/bindings_generator.cpp
+++ b/modules/mono/editor/bindings_generator.cpp
@@ -100,8 +100,6 @@
#define C_METHOD_MONOSTR_FROM_GODOT C_NS_MONOMARSHAL "::mono_string_from_godot"
#define C_METHOD_MONOARRAY_TO(m_type) C_NS_MONOMARSHAL "::mono_array_to_" #m_type
#define C_METHOD_MONOARRAY_FROM(m_type) C_NS_MONOMARSHAL "::" #m_type "_to_mono_array"
-#define C_METHOD_MANAGED_TO_DICT C_NS_MONOMARSHAL "::mono_object_to_Dictionary"
-#define C_METHOD_MANAGED_FROM_DICT C_NS_MONOMARSHAL "::Dictionary_to_mono_object"
#define BINDINGS_GENERATOR_VERSION UINT32_C(2)
@@ -1338,7 +1336,6 @@ Error BindingsGenerator::_generate_cs_method(const BindingsGenerator::TypeInterf
} else if (return_type->cs_out.empty()) {
p_output.push_back("return " + im_call + ";\n");
} else {
- p_output.push_back(INDENT3);
p_output.push_back(sformat(return_type->cs_out, im_call, return_type->cs_type, return_type->im_type_out));
p_output.push_back("\n");
}
@@ -2344,7 +2341,6 @@ void BindingsGenerator::_populate_builtin_type_interfaces() {
#define INSERT_ARRAY(m_type, m_proxy_t) INSERT_ARRAY_FULL(m_type, m_type, m_proxy_t)
- INSERT_ARRAY(Array, object);
INSERT_ARRAY(PoolIntArray, int);
INSERT_ARRAY_FULL(PoolByteArray, PoolByteArray, byte);
@@ -2362,20 +2358,36 @@ void BindingsGenerator::_populate_builtin_type_interfaces() {
#undef INSERT_ARRAY
+ // Array
+ itype = TypeInterface();
+ itype.name = "Array";
+ itype.cname = itype.name;
+ itype.proxy_name = "Array";
+ itype.c_out = "\treturn memnew(Array(%1));\n";
+ itype.c_type = itype.name;
+ itype.c_type_in = itype.c_type + "*";
+ itype.c_type_out = itype.c_type + "*";
+ itype.cs_type = itype.proxy_name;
+ itype.cs_in = "%0." CS_SMETHOD_GETINSTANCE "()";
+ itype.cs_out = "return new Array(%0);";
+ itype.im_type_in = "IntPtr";
+ itype.im_type_out = "IntPtr";
+ builtin_types.insert(itype.cname, itype);
+
// Dictionary
itype = TypeInterface();
itype.name = "Dictionary";
itype.cname = itype.name;
- itype.proxy_name = "Dictionary<object, object>";
- itype.c_in = "\t%0 %1_in = " C_METHOD_MANAGED_TO_DICT "(%1);\n";
- itype.c_out = "\treturn " C_METHOD_MANAGED_FROM_DICT "(%1);\n";
- itype.c_arg_in = "&%s_in";
+ itype.proxy_name = "Dictionary";
+ itype.c_out = "\treturn memnew(Dictionary(%1));\n";
itype.c_type = itype.name;
- itype.c_type_in = "MonoObject*";
- itype.c_type_out = "MonoObject*";
+ itype.c_type_in = itype.c_type + "*";
+ itype.c_type_out = itype.c_type + "*";
itype.cs_type = itype.proxy_name;
- itype.im_type_in = itype.proxy_name;
- itype.im_type_out = itype.proxy_name;
+ itype.cs_in = "%0." CS_SMETHOD_GETINSTANCE "()";
+ itype.cs_out = "return new Dictionary(%0);";
+ itype.im_type_in = "IntPtr";
+ itype.im_type_out = "IntPtr";
builtin_types.insert(itype.cname, itype);
// void (fictitious type to represent the return type of methods that do not return anything)
diff --git a/modules/mono/editor/godotsharp_editor.cpp b/modules/mono/editor/godotsharp_editor.cpp
index 998da8bda3..faeb58e5a7 100644
--- a/modules/mono/editor/godotsharp_editor.cpp
+++ b/modules/mono/editor/godotsharp_editor.cpp
@@ -278,13 +278,11 @@ GodotSharpEditor::GodotSharpEditor(EditorNode *p_editor) {
about_label->set_autowrap(true);
String about_text =
String("C# support in Godot Engine is a brand new feature and a work in progress.\n") +
- "It is at the alpha stage and thus not suitable for use in production.\n\n" +
- "As of Godot 3.0, C# support is not feature-complete and may crash in some situations. " +
- "Bugs and usability issues will be addressed gradually over 3.0.x and 3.x releases, " +
+ "It is currently in an alpha stage and is not suitable for use in production.\n\n" +
+ "As of Godot 3.1, C# support is not feature-complete and may crash in some situations. " +
+ "Bugs and usability issues will be addressed gradually over future 3.x releases, " +
"including compatibility breaking changes as new features are implemented for a better overall C# experience.\n\n" +
- "The main missing feature is the ability to export games using C# assemblies - you will therefore be able to develop and run games in the editor, " +
- "but not to share them as standalone binaries yet. This feature is of course high on the priority list and should be available as soon as possible.\n\n" +
- "If you experience issues with this Mono build, please report them on Godot's issue tracker with details about your system, Mono version, IDE, etc.:\n\n" +
+ "If you experience issues with this Mono build, please report them on Godot's issue tracker with details about your system, Mono version, IDE, etc:\n\n" +
" https://github.com/godotengine/godot/issues\n\n" +
"Your critical feedback at this stage will play a great role in shaping the C# support in future releases, so thank you!";
about_label->set_text(about_text);
diff --git a/modules/mono/glue/collections_glue.cpp b/modules/mono/glue/collections_glue.cpp
new file mode 100644
index 0000000000..0551c1991a
--- /dev/null
+++ b/modules/mono/glue/collections_glue.cpp
@@ -0,0 +1,240 @@
+/*************************************************************************/
+/* collections_glue.cpp */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md) */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+
+#include "collections_glue.h"
+
+#include <mono/metadata/exception.h>
+
+#include "../mono_gd/gd_mono_class.h"
+
+Array *godot_icall_Array_Ctor() {
+ return memnew(Array);
+}
+
+void godot_icall_Array_Dtor(Array *ptr) {
+ memdelete(ptr);
+}
+
+MonoObject *godot_icall_Array_At(Array *ptr, int index) {
+ if (index < 0 || index > ptr->size()) {
+ GDMonoUtils::set_pending_exception(mono_get_exception_index_out_of_range());
+ return NULL;
+ }
+ return GDMonoMarshal::variant_to_mono_object(ptr->operator[](index));
+}
+
+void godot_icall_Array_SetAt(Array *ptr, int index, MonoObject *value) {
+ if (index < 0 || index > ptr->size()) {
+ GDMonoUtils::set_pending_exception(mono_get_exception_index_out_of_range());
+ return;
+ }
+ ptr->operator[](index) = GDMonoMarshal::mono_object_to_variant(value);
+}
+
+int godot_icall_Array_Count(Array *ptr) {
+ return ptr->size();
+}
+
+void godot_icall_Array_Add(Array *ptr, MonoObject *item) {
+ ptr->append(GDMonoMarshal::mono_object_to_variant(item));
+}
+
+void godot_icall_Array_Clear(Array *ptr) {
+ ptr->clear();
+}
+
+bool godot_icall_Array_Contains(Array *ptr, MonoObject *item) {
+ return ptr->find(GDMonoMarshal::mono_object_to_variant(item)) != -1;
+}
+
+void godot_icall_Array_CopyTo(Array *ptr, MonoArray *array, int array_index) {
+ int count = ptr->size();
+
+ if (mono_array_length(array) < (array_index + count)) {
+ MonoException *exc = mono_get_exception_argument("", "Destination array was not long enough. Check destIndex and length, and the array's lower bounds.");
+ GDMonoUtils::set_pending_exception(exc);
+ return;
+ }
+
+ for (int i = 0; i < count; i++) {
+ MonoObject *boxed = GDMonoMarshal::variant_to_mono_object(ptr->operator[](i));
+ mono_array_setref(array, array_index, boxed);
+ array_index++;
+ }
+}
+
+int godot_icall_Array_IndexOf(Array *ptr, MonoObject *item) {
+ return ptr->find(GDMonoMarshal::mono_object_to_variant(item));
+}
+
+void godot_icall_Array_Insert(Array *ptr, int index, MonoObject *item) {
+ if (index < 0 || index > ptr->size()) {
+ GDMonoUtils::set_pending_exception(mono_get_exception_index_out_of_range());
+ return;
+ }
+ ptr->insert(index, GDMonoMarshal::mono_object_to_variant(item));
+}
+
+bool godot_icall_Array_Remove(Array *ptr, MonoObject *item) {
+ int idx = ptr->find(GDMonoMarshal::mono_object_to_variant(item));
+ if (idx >= 0) {
+ ptr->remove(idx);
+ return true;
+ }
+ return false;
+}
+
+void godot_icall_Array_RemoveAt(Array *ptr, int index) {
+ if (index < 0 || index > ptr->size()) {
+ GDMonoUtils::set_pending_exception(mono_get_exception_index_out_of_range());
+ return;
+ }
+ ptr->remove(index);
+}
+
+Dictionary *godot_icall_Dictionary_Ctor() {
+ return memnew(Dictionary);
+}
+
+void godot_icall_Dictionary_Dtor(Dictionary *ptr) {
+ memdelete(ptr);
+}
+
+MonoObject *godot_icall_Dictionary_GetValue(Dictionary *ptr, MonoObject *key) {
+ Variant *ret = ptr->getptr(GDMonoMarshal::mono_object_to_variant(key));
+ if (ret == NULL) {
+ MonoObject *exc = mono_object_new(mono_domain_get(), CACHED_CLASS(KeyNotFoundException)->get_mono_ptr());
+#ifdef DEBUG_ENABLED
+ CRASH_COND(!exc);
+#endif
+ GDMonoUtils::runtime_object_init(exc);
+ GDMonoUtils::set_pending_exception((MonoException *)exc);
+ return NULL;
+ }
+ return GDMonoMarshal::variant_to_mono_object(ret);
+}
+
+void godot_icall_Dictionary_SetValue(Dictionary *ptr, MonoObject *key, MonoObject *value) {
+ ptr->operator[](GDMonoMarshal::mono_object_to_variant(key)) = GDMonoMarshal::mono_object_to_variant(value);
+}
+
+Array *godot_icall_Dictionary_Keys(Dictionary *ptr) {
+ return memnew(Array(ptr->keys()));
+}
+
+Array *godot_icall_Dictionary_Values(Dictionary *ptr) {
+ return memnew(Array(ptr->values()));
+}
+
+int godot_icall_Dictionary_Count(Dictionary *ptr) {
+ return ptr->size();
+}
+
+void godot_icall_Dictionary_Add(Dictionary *ptr, MonoObject *key, MonoObject *value) {
+ Variant varKey = GDMonoMarshal::mono_object_to_variant(key);
+ Variant *ret = ptr->getptr(varKey);
+ if (ret != NULL) {
+ GDMonoUtils::set_pending_exception(mono_get_exception_argument("key", "An element with the same key already exists"));
+ return;
+ }
+ ptr->operator[](varKey) = GDMonoMarshal::mono_object_to_variant(value);
+}
+
+void godot_icall_Dictionary_Clear(Dictionary *ptr) {
+ ptr->clear();
+}
+
+bool godot_icall_Dictionary_Contains(Dictionary *ptr, MonoObject *key, MonoObject *value) {
+ // no dupes
+ Variant *ret = ptr->getptr(GDMonoMarshal::mono_object_to_variant(key));
+ return ret != NULL && *ret == GDMonoMarshal::mono_object_to_variant(value);
+}
+
+bool godot_icall_Dictionary_ContainsKey(Dictionary *ptr, MonoObject *key) {
+ return ptr->has(GDMonoMarshal::mono_object_to_variant(key));
+}
+
+bool godot_icall_Dictionary_RemoveKey(Dictionary *ptr, MonoObject *key) {
+ return ptr->erase_checked(GDMonoMarshal::mono_object_to_variant(key));
+}
+
+bool godot_icall_Dictionary_Remove(Dictionary *ptr, MonoObject *key, MonoObject *value) {
+ Variant varKey = GDMonoMarshal::mono_object_to_variant(key);
+
+ // no dupes
+ Variant *ret = ptr->getptr(varKey);
+ if (ret != NULL && *ret == GDMonoMarshal::mono_object_to_variant(value)) {
+ ptr->erase_checked(varKey);
+ return true;
+ }
+
+ return false;
+}
+
+bool godot_icall_Dictionary_TryGetValue(Dictionary *ptr, MonoObject *key, MonoObject **value) {
+ Variant *ret = ptr->getptr(GDMonoMarshal::mono_object_to_variant(key));
+ if (ret == NULL) {
+ *value = NULL;
+ return false;
+ }
+ *value = GDMonoMarshal::variant_to_mono_object(ret);
+ return true;
+}
+
+void godot_register_collections_icalls() {
+ mono_add_internal_call("Godot.Array::godot_icall_Array_Ctor", (void *)godot_icall_Array_Ctor);
+ mono_add_internal_call("Godot.Array::godot_icall_Array_Dtor", (void *)godot_icall_Array_Dtor);
+ mono_add_internal_call("Godot.Array::godot_icall_Array_At", (void *)godot_icall_Array_At);
+ mono_add_internal_call("Godot.Array::godot_icall_Array_SetAt", (void *)godot_icall_Array_SetAt);
+ mono_add_internal_call("Godot.Array::godot_icall_Array_Count", (void *)godot_icall_Array_Count);
+ mono_add_internal_call("Godot.Array::godot_icall_Array_Add", (void *)godot_icall_Array_Add);
+ mono_add_internal_call("Godot.Array::godot_icall_Array_Clear", (void *)godot_icall_Array_Clear);
+ mono_add_internal_call("Godot.Array::godot_icall_Array_Contains", (void *)godot_icall_Array_Contains);
+ mono_add_internal_call("Godot.Array::godot_icall_Array_CopyTo", (void *)godot_icall_Array_CopyTo);
+ mono_add_internal_call("Godot.Array::godot_icall_Array_IndexOf", (void *)godot_icall_Array_IndexOf);
+ mono_add_internal_call("Godot.Array::godot_icall_Array_Insert", (void *)godot_icall_Array_Insert);
+ mono_add_internal_call("Godot.Array::godot_icall_Array_Remove", (void *)godot_icall_Array_Remove);
+ mono_add_internal_call("Godot.Array::godot_icall_Array_RemoveAt", (void *)godot_icall_Array_RemoveAt);
+
+ mono_add_internal_call("Godot.Dictionary::godot_icall_Dictionary_Ctor", (void *)godot_icall_Dictionary_Ctor);
+ mono_add_internal_call("Godot.Dictionary::godot_icall_Dictionary_Dtor", (void *)godot_icall_Dictionary_Dtor);
+ mono_add_internal_call("Godot.Dictionary::godot_icall_Dictionary_GetValue", (void *)godot_icall_Dictionary_GetValue);
+ mono_add_internal_call("Godot.Dictionary::godot_icall_Dictionary_SetValue", (void *)godot_icall_Dictionary_SetValue);
+ mono_add_internal_call("Godot.Dictionary::godot_icall_Dictionary_Keys", (void *)godot_icall_Dictionary_Keys);
+ mono_add_internal_call("Godot.Dictionary::godot_icall_Dictionary_Values", (void *)godot_icall_Dictionary_Values);
+ mono_add_internal_call("Godot.Dictionary::godot_icall_Dictionary_Count", (void *)godot_icall_Dictionary_Count);
+ mono_add_internal_call("Godot.Dictionary::godot_icall_Dictionary_Add", (void *)godot_icall_Dictionary_Add);
+ mono_add_internal_call("Godot.Dictionary::godot_icall_Dictionary_Clear", (void *)godot_icall_Dictionary_Clear);
+ mono_add_internal_call("Godot.Dictionary::godot_icall_Dictionary_Contains", (void *)godot_icall_Dictionary_Contains);
+ mono_add_internal_call("Godot.Dictionary::godot_icall_Dictionary_ContainsKey", (void *)godot_icall_Dictionary_ContainsKey);
+ mono_add_internal_call("Godot.Dictionary::godot_icall_Dictionary_RemoveKey", (void *)godot_icall_Dictionary_RemoveKey);
+ mono_add_internal_call("Godot.Dictionary::godot_icall_Dictionary_Remove", (void *)godot_icall_Dictionary_Remove);
+ mono_add_internal_call("Godot.Dictionary::godot_icall_Dictionary_TryGetValue", (void *)godot_icall_Dictionary_TryGetValue);
+}
diff --git a/modules/mono/glue/collections_glue.h b/modules/mono/glue/collections_glue.h
new file mode 100644
index 0000000000..eb5ecfb725
--- /dev/null
+++ b/modules/mono/glue/collections_glue.h
@@ -0,0 +1,100 @@
+/*************************************************************************/
+/* collections_glue.h */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md) */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+
+#ifndef COLLECTIONS_GLUE_H
+#define COLLECTIONS_GLUE_H
+
+#include "core/array.h"
+
+#include "../mono_gd/gd_mono_marshal.h"
+
+// Array
+
+Array *godot_icall_Array_Ctor();
+
+void godot_icall_Array_Dtor(Array *ptr);
+
+MonoObject *godot_icall_Array_At(Array *ptr, int index);
+
+void godot_icall_Array_SetAt(Array *ptr, int index, MonoObject *value);
+
+int godot_icall_Array_Count(Array *ptr);
+
+void godot_icall_Array_Add(Array *ptr, MonoObject *item);
+
+void godot_icall_Array_Clear(Array *ptr);
+
+bool godot_icall_Array_Contains(Array *ptr, MonoObject *item);
+
+void godot_icall_Array_CopyTo(Array *ptr, MonoArray *array, int array_index);
+
+int godot_icall_Array_IndexOf(Array *ptr, MonoObject *item);
+
+void godot_icall_Array_Insert(Array *ptr, int index, MonoObject *item);
+
+bool godot_icall_Array_Remove(Array *ptr, MonoObject *item);
+
+void godot_icall_Array_RemoveAt(Array *ptr, int index);
+
+// Dictionary
+
+Dictionary *godot_icall_Dictionary_Ctor();
+
+void godot_icall_Dictionary_Dtor(Dictionary *ptr);
+
+MonoObject *godot_icall_Dictionary_GetValue(Dictionary *ptr, MonoObject *key);
+
+void godot_icall_Dictionary_SetValue(Dictionary *ptr, MonoObject *key, MonoObject *value);
+
+Array *godot_icall_Dictionary_Keys(Dictionary *ptr);
+
+Array *godot_icall_Dictionary_Values(Dictionary *ptr);
+
+int godot_icall_Dictionary_Count(Dictionary *ptr);
+
+void godot_icall_Dictionary_Add(Dictionary *ptr, MonoObject *key, MonoObject *value);
+
+void godot_icall_Dictionary_Clear(Dictionary *ptr);
+
+bool godot_icall_Dictionary_Contains(Dictionary *ptr, MonoObject *key, MonoObject *value);
+
+bool godot_icall_Dictionary_ContainsKey(Dictionary *ptr, MonoObject *key);
+
+bool godot_icall_Dictionary_RemoveKey(Dictionary *ptr, MonoObject *key);
+
+bool godot_icall_Dictionary_Remove(Dictionary *ptr, MonoObject *key, MonoObject *value);
+
+bool godot_icall_Dictionary_TryGetValue(Dictionary *ptr, MonoObject *key, MonoObject **value);
+
+// Register internal calls
+
+void godot_register_collections_icalls();
+
+#endif // COLLECTIONS_GLUE_H
diff --git a/modules/mono/glue/cs_files/Array.cs b/modules/mono/glue/cs_files/Array.cs
new file mode 100644
index 0000000000..51f57daef4
--- /dev/null
+++ b/modules/mono/glue/cs_files/Array.cs
@@ -0,0 +1,335 @@
+using System;
+using System.Collections.Generic;
+using System.Collections;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+namespace Godot
+{
+ class ArraySafeHandle : SafeHandle
+ {
+ public ArraySafeHandle(IntPtr handle) : base(IntPtr.Zero, true)
+ {
+ this.handle = handle;
+ }
+
+ public override bool IsInvalid
+ {
+ get
+ {
+ return handle == IntPtr.Zero;
+ }
+ }
+
+ protected override bool ReleaseHandle()
+ {
+ Array.godot_icall_Array_Dtor(handle);
+ return true;
+ }
+ }
+
+ public class Array : IList<object>, ICollection<object>, IEnumerable<object>, IDisposable
+ {
+ [MethodImpl(MethodImplOptions.InternalCall)]
+ internal extern static IntPtr godot_icall_Array_Ctor();
+
+ [MethodImpl(MethodImplOptions.InternalCall)]
+ internal extern static void godot_icall_Array_Dtor(IntPtr ptr);
+
+ [MethodImpl(MethodImplOptions.InternalCall)]
+ internal extern static object godot_icall_Array_At(IntPtr ptr, int index);
+
+ [MethodImpl(MethodImplOptions.InternalCall)]
+ internal extern static void godot_icall_Array_SetAt(IntPtr ptr, int index, object value);
+
+ [MethodImpl(MethodImplOptions.InternalCall)]
+ internal extern static int godot_icall_Array_Count(IntPtr ptr);
+
+ [MethodImpl(MethodImplOptions.InternalCall)]
+ internal extern static void godot_icall_Array_Add(IntPtr ptr, object item);
+
+ [MethodImpl(MethodImplOptions.InternalCall)]
+ internal extern static void godot_icall_Array_Clear(IntPtr ptr);
+
+ [MethodImpl(MethodImplOptions.InternalCall)]
+ internal extern static bool godot_icall_Array_Contains(IntPtr ptr, object item);
+
+ [MethodImpl(MethodImplOptions.InternalCall)]
+ internal extern static void godot_icall_Array_CopyTo(IntPtr ptr, object[] array, int arrayIndex);
+
+ [MethodImpl(MethodImplOptions.InternalCall)]
+ internal extern static int godot_icall_Array_IndexOf(IntPtr ptr, object item);
+
+ [MethodImpl(MethodImplOptions.InternalCall)]
+ internal extern static void godot_icall_Array_Insert(IntPtr ptr, int index, object item);
+
+ [MethodImpl(MethodImplOptions.InternalCall)]
+ internal extern static bool godot_icall_Array_Remove(IntPtr ptr, object item);
+
+ [MethodImpl(MethodImplOptions.InternalCall)]
+ internal extern static void godot_icall_Array_RemoveAt(IntPtr ptr, int index);
+
+ ArraySafeHandle safeHandle;
+ bool disposed = false;
+
+ public Array()
+ {
+ safeHandle = new ArraySafeHandle(godot_icall_Array_Ctor());
+ }
+
+ internal Array(ArraySafeHandle handle)
+ {
+ safeHandle = handle;
+ }
+
+ internal Array(IntPtr handle)
+ {
+ safeHandle = new ArraySafeHandle(handle);
+ }
+
+ internal IntPtr GetPtr()
+ {
+ return safeHandle.DangerousGetHandle();
+ }
+
+ public void Dispose()
+ {
+ Dispose(true);
+ }
+
+ protected virtual void Dispose(bool disposing)
+ {
+ if (disposed)
+ return;
+
+ if (safeHandle != null)
+ {
+ safeHandle.Dispose();
+ safeHandle = null;
+ }
+
+ disposed = true;
+ }
+
+ public object this[int index]
+ {
+ get
+ {
+ return godot_icall_Array_At(GetPtr(), index);
+ }
+ set
+ {
+ godot_icall_Array_SetAt(GetPtr(), index, value);
+ }
+ }
+
+ public int Count
+ {
+ get
+ {
+ return godot_icall_Array_Count(GetPtr());
+ }
+ }
+
+ public bool IsReadOnly
+ {
+ get
+ {
+ return false;
+ }
+ }
+
+ public void Add(object item)
+ {
+ godot_icall_Array_Add(GetPtr(), item);
+ }
+
+ public void Clear()
+ {
+ godot_icall_Array_Clear(GetPtr());
+ }
+
+ public bool Contains(object item)
+ {
+ return godot_icall_Array_Contains(GetPtr(), item);
+ }
+
+ public void CopyTo(object[] array, int arrayIndex)
+ {
+ if (array == null)
+ throw new ArgumentNullException(nameof(array), "Value cannot be null.");
+
+ if (arrayIndex < 0)
+ throw new ArgumentOutOfRangeException(nameof(arrayIndex), "Number was less than the array's lower bound in the first dimension.");
+
+ // Internal call may throw ArgumentException
+ godot_icall_Array_CopyTo(GetPtr(), array, arrayIndex);
+ }
+
+ public IEnumerator<object> GetEnumerator()
+ {
+ int count = Count;
+
+ for (int i = 0; i < count; i++)
+ {
+ yield return godot_icall_Array_At(GetPtr(), i);
+ }
+ }
+
+ public int IndexOf(object item)
+ {
+ return godot_icall_Array_IndexOf(GetPtr(), item);
+ }
+
+ public void Insert(int index, object item)
+ {
+ godot_icall_Array_Insert(GetPtr(), index, item);
+ }
+
+ public bool Remove(object item)
+ {
+ return godot_icall_Array_Remove(GetPtr(), item);
+ }
+
+ public void RemoveAt(int index)
+ {
+ godot_icall_Array_RemoveAt(GetPtr(), index);
+ }
+
+ IEnumerator IEnumerable.GetEnumerator()
+ {
+ return GetEnumerator();
+ }
+ }
+
+ public class Array<T> : IList<T>, ICollection<T>, IEnumerable<T>
+ {
+ Array objectArray;
+
+ public Array()
+ {
+ objectArray = new Array();
+ }
+
+ public Array(Array array)
+ {
+ objectArray = array;
+ }
+
+ internal Array(IntPtr handle)
+ {
+ objectArray = new Array(handle);
+ }
+
+ internal Array(ArraySafeHandle handle)
+ {
+ objectArray = new Array(handle);
+ }
+
+ public static explicit operator Array(Array<T> from)
+ {
+ return from.objectArray;
+ }
+
+ public T this[int index]
+ {
+ get
+ {
+ return (T)objectArray[index];
+ }
+ set
+ {
+ objectArray[index] = value;
+ }
+ }
+
+ public int Count
+ {
+ get
+ {
+ return objectArray.Count;
+ }
+ }
+
+ public bool IsReadOnly
+ {
+ get
+ {
+ return objectArray.IsReadOnly;
+ }
+ }
+
+ public void Add(T item)
+ {
+ objectArray.Add(item);
+ }
+
+ public void Clear()
+ {
+ objectArray.Clear();
+ }
+
+ public bool Contains(T item)
+ {
+ return objectArray.Contains(item);
+ }
+
+ public void CopyTo(T[] array, int arrayIndex)
+ {
+ if (array == null)
+ throw new ArgumentNullException(nameof(array), "Value cannot be null.");
+
+ if (arrayIndex < 0)
+ throw new ArgumentOutOfRangeException(nameof(arrayIndex), "Number was less than the array's lower bound in the first dimension.");
+
+ // TODO This may be quite slow because every element access is an internal call.
+ // It could be moved entirely to an internal call if we find out how to do the cast there.
+
+ int count = objectArray.Count;
+
+ if (array.Length < (arrayIndex + count))
+ throw new ArgumentException("Destination array was not long enough. Check destIndex and length, and the array's lower bounds.");
+
+ for (int i = 0; i < count; i++)
+ {
+ array[arrayIndex] = (T)objectArray[i];
+ arrayIndex++;
+ }
+ }
+
+ public IEnumerator<T> GetEnumerator()
+ {
+ int count = objectArray.Count;
+
+ for (int i = 0; i < count; i++)
+ {
+ yield return (T)objectArray[i];
+ }
+ }
+
+ public int IndexOf(T item)
+ {
+ return objectArray.IndexOf(item);
+ }
+
+ public void Insert(int index, T item)
+ {
+ objectArray.Insert(index, item);
+ }
+
+ public bool Remove(T item)
+ {
+ return objectArray.Remove(item);
+ }
+
+ public void RemoveAt(int index)
+ {
+ objectArray.RemoveAt(index);
+ }
+
+ IEnumerator IEnumerable.GetEnumerator()
+ {
+ return GetEnumerator();
+ }
+ }
+}
diff --git a/modules/mono/glue/cs_files/Dictionary.cs b/modules/mono/glue/cs_files/Dictionary.cs
new file mode 100644
index 0000000000..57a1960ad9
--- /dev/null
+++ b/modules/mono/glue/cs_files/Dictionary.cs
@@ -0,0 +1,401 @@
+using System;
+using System.Collections.Generic;
+using System.Collections;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+namespace Godot
+{
+ class DictionarySafeHandle : SafeHandle
+ {
+ public DictionarySafeHandle(IntPtr handle) : base(IntPtr.Zero, true)
+ {
+ this.handle = handle;
+ }
+
+ public override bool IsInvalid
+ {
+ get
+ {
+ return handle == IntPtr.Zero;
+ }
+ }
+
+ protected override bool ReleaseHandle()
+ {
+ Dictionary.godot_icall_Dictionary_Dtor(handle);
+ return true;
+ }
+ }
+
+ public class Dictionary :
+ IDictionary<object, object>,
+ ICollection<KeyValuePair<object, object>>,
+ IEnumerable<KeyValuePair<object, object>>,
+ IDisposable
+ {
+ [MethodImpl(MethodImplOptions.InternalCall)]
+ internal extern static IntPtr godot_icall_Dictionary_Ctor();
+
+ [MethodImpl(MethodImplOptions.InternalCall)]
+ internal extern static void godot_icall_Dictionary_Dtor(IntPtr ptr);
+
+ [MethodImpl(MethodImplOptions.InternalCall)]
+ internal extern static object godot_icall_Dictionary_GetValue(IntPtr ptr, object key);
+
+ [MethodImpl(MethodImplOptions.InternalCall)]
+ internal extern static void godot_icall_Dictionary_SetValue(IntPtr ptr, object key, object value);
+
+ [MethodImpl(MethodImplOptions.InternalCall)]
+ internal extern static IntPtr godot_icall_Dictionary_Keys(IntPtr ptr);
+
+ [MethodImpl(MethodImplOptions.InternalCall)]
+ internal extern static IntPtr godot_icall_Dictionary_Values(IntPtr ptr);
+
+ [MethodImpl(MethodImplOptions.InternalCall)]
+ internal extern static int godot_icall_Dictionary_Count(IntPtr ptr);
+
+ [MethodImpl(MethodImplOptions.InternalCall)]
+ internal extern static void godot_icall_Dictionary_Add(IntPtr ptr, object key, object value);
+
+ [MethodImpl(MethodImplOptions.InternalCall)]
+ internal extern static void godot_icall_Dictionary_Clear(IntPtr ptr);
+
+ [MethodImpl(MethodImplOptions.InternalCall)]
+ internal extern static bool godot_icall_Dictionary_Contains(IntPtr ptr, object key, object value);
+
+ [MethodImpl(MethodImplOptions.InternalCall)]
+ internal extern static bool godot_icall_Dictionary_ContainsKey(IntPtr ptr, object key);
+
+ [MethodImpl(MethodImplOptions.InternalCall)]
+ internal extern static bool godot_icall_Dictionary_RemoveKey(IntPtr ptr, object key);
+
+ [MethodImpl(MethodImplOptions.InternalCall)]
+ internal extern static bool godot_icall_Dictionary_Remove(IntPtr ptr, object key, object value);
+
+ [MethodImpl(MethodImplOptions.InternalCall)]
+ internal extern static bool godot_icall_Dictionary_TryGetValue(IntPtr ptr, object key, out object value);
+
+ DictionarySafeHandle safeHandle;
+ bool disposed = false;
+
+ public Dictionary()
+ {
+ safeHandle = new DictionarySafeHandle(godot_icall_Dictionary_Ctor());
+ }
+
+ internal Dictionary(DictionarySafeHandle handle)
+ {
+ safeHandle = handle;
+ }
+
+ internal Dictionary(IntPtr handle)
+ {
+ safeHandle = new DictionarySafeHandle(handle);
+ }
+
+ internal IntPtr GetPtr()
+ {
+ return safeHandle.DangerousGetHandle();
+ }
+
+ public void Dispose()
+ {
+ Dispose(true);
+ }
+
+ protected virtual void Dispose(bool disposing)
+ {
+ if (disposed)
+ return;
+
+ if (safeHandle != null)
+ {
+ safeHandle.Dispose();
+ safeHandle = null;
+ }
+
+ disposed = true;
+ }
+
+ public object this[object key]
+ {
+ get
+ {
+ return godot_icall_Dictionary_GetValue(GetPtr(), key);
+ }
+ set
+ {
+ godot_icall_Dictionary_SetValue(GetPtr(), key, value);
+ }
+ }
+
+ public ICollection<object> Keys
+ {
+ get
+ {
+ IntPtr handle = godot_icall_Dictionary_Keys(GetPtr());
+ return new Array(new ArraySafeHandle(handle));
+ }
+ }
+
+ public ICollection<object> Values
+ {
+ get
+ {
+ IntPtr handle = godot_icall_Dictionary_Values(GetPtr());
+ return new Array(new ArraySafeHandle(handle));
+ }
+ }
+
+ public int Count
+ {
+ get
+ {
+ return godot_icall_Dictionary_Count(GetPtr());
+ }
+ }
+
+ public bool IsReadOnly
+ {
+ get
+ {
+ return false;
+ }
+ }
+
+ public void Add(object key, object value)
+ {
+ godot_icall_Dictionary_Add(GetPtr(), key, value);
+ }
+
+ public void Add(KeyValuePair<object, object> item)
+ {
+ Add(item.Key, item.Value);
+ }
+
+ public void Clear()
+ {
+ godot_icall_Dictionary_Clear(GetPtr());
+ }
+
+ public bool Contains(KeyValuePair<object, object> item)
+ {
+ return godot_icall_Dictionary_Contains(GetPtr(), item.Key, item.Value);
+ }
+
+ public bool ContainsKey(object key)
+ {
+ return godot_icall_Dictionary_ContainsKey(GetPtr(), key);
+ }
+
+ public void CopyTo(KeyValuePair<object, object>[] array, int arrayIndex)
+ {
+ // TODO 3 internal calls, can reduce to 1
+ Array keys = (Array)Keys;
+ Array values = (Array)Values;
+ int count = Count;
+
+ for (int i = 0; i < count; i++)
+ {
+ // TODO 2 internal calls, can reduce to 1
+ array[arrayIndex] = new KeyValuePair<object, object>(keys[i], values[i]);
+ arrayIndex++;
+ }
+ }
+
+ public IEnumerator<KeyValuePair<object, object>> GetEnumerator()
+ {
+ // TODO 3 internal calls, can reduce to 1
+ Array keys = (Array)Keys;
+ Array values = (Array)Values;
+ int count = Count;
+
+ for (int i = 0; i < count; i++)
+ {
+ // TODO 2 internal calls, can reduce to 1
+ yield return new KeyValuePair<object, object>(keys[i], values[i]);
+ }
+ }
+
+ public bool Remove(object key)
+ {
+ return godot_icall_Dictionary_RemoveKey(GetPtr(), key);
+ }
+
+ public bool Remove(KeyValuePair<object, object> item)
+ {
+ return godot_icall_Dictionary_Remove(GetPtr(), item.Key, item.Value);
+ }
+
+ public bool TryGetValue(object key, out object value)
+ {
+ object retValue;
+ bool found = godot_icall_Dictionary_TryGetValue(GetPtr(), key, out retValue);
+ value = found ? retValue : default(object);
+ return found;
+ }
+
+ IEnumerator IEnumerable.GetEnumerator()
+ {
+ return GetEnumerator();
+ }
+ }
+
+
+ public class Dictionary<TKey, TValue> :
+ IDictionary<TKey, TValue>,
+ ICollection<KeyValuePair<TKey, TValue>>,
+ IEnumerable<KeyValuePair<TKey, TValue>>
+ {
+ Dictionary objectDict;
+
+ public Dictionary()
+ {
+ objectDict = new Dictionary();
+ }
+
+ public Dictionary(Dictionary dictionary)
+ {
+ objectDict = dictionary;
+ }
+
+ internal Dictionary(IntPtr handle)
+ {
+ objectDict = new Dictionary(handle);
+ }
+
+ internal Dictionary(DictionarySafeHandle handle)
+ {
+ objectDict = new Dictionary(handle);
+ }
+
+ public static explicit operator Dictionary(Dictionary<TKey, TValue> from)
+ {
+ return from.objectDict;
+ }
+
+ public TValue this[TKey key]
+ {
+ get
+ {
+ return (TValue)objectDict[key];
+ }
+ set
+ {
+ objectDict[key] = value;
+ }
+ }
+
+ public ICollection<TKey> Keys
+ {
+ get
+ {
+ IntPtr handle = Dictionary.godot_icall_Dictionary_Keys(objectDict.GetPtr());
+ return new Array<TKey>(new ArraySafeHandle(handle));
+ }
+ }
+
+ public ICollection<TValue> Values
+ {
+ get
+ {
+ IntPtr handle = Dictionary.godot_icall_Dictionary_Values(objectDict.GetPtr());
+ return new Array<TValue>(new ArraySafeHandle(handle));
+ }
+ }
+
+ public int Count
+ {
+ get
+ {
+ return objectDict.Count;
+ }
+ }
+
+ public bool IsReadOnly
+ {
+ get
+ {
+ return objectDict.IsReadOnly;
+ }
+ }
+
+ public void Add(TKey key, TValue value)
+ {
+ objectDict.Add(key, value);
+ }
+
+ public void Add(KeyValuePair<TKey, TValue> item)
+ {
+ objectDict.Add(item.Key, item.Value);
+ }
+
+ public void Clear()
+ {
+ objectDict.Clear();
+ }
+
+ public bool Contains(KeyValuePair<TKey, TValue> item)
+ {
+ return objectDict.Contains(new KeyValuePair<object, object>(item.Key, item.Value));
+ }
+
+ public bool ContainsKey(TKey key)
+ {
+ return objectDict.ContainsKey(key);
+ }
+
+ public void CopyTo(KeyValuePair<TKey, TValue>[] array, int arrayIndex)
+ {
+ // TODO 3 internal calls, can reduce to 1
+ Array<TKey> keys = (Array<TKey>)Keys;
+ Array<TValue> values = (Array<TValue>)Values;
+ int count = Count;
+
+ for (int i = 0; i < count; i++)
+ {
+ // TODO 2 internal calls, can reduce to 1
+ array[arrayIndex] = new KeyValuePair<TKey, TValue>(keys[i], values[i]);
+ arrayIndex++;
+ }
+ }
+
+ public IEnumerator<KeyValuePair<TKey, TValue>> GetEnumerator()
+ {
+ // TODO 3 internal calls, can reduce to 1
+ Array<TKey> keys = (Array<TKey>)Keys;
+ Array<TValue> values = (Array<TValue>)Values;
+ int count = Count;
+
+ for (int i = 0; i < count; i++)
+ {
+ // TODO 2 internal calls, can reduce to 1
+ yield return new KeyValuePair<TKey, TValue>(keys[i], values[i]);
+ }
+ }
+
+ public bool Remove(TKey key)
+ {
+ return objectDict.Remove(key);
+ }
+
+ public bool Remove(KeyValuePair<TKey, TValue> item)
+ {
+ return objectDict.Remove(new KeyValuePair<object, object>(item.Key, item.Value));
+ }
+
+ public bool TryGetValue(TKey key, out TValue value)
+ {
+ object retValue;
+ bool found = objectDict.TryGetValue(key, out retValue);
+ value = found ? (TValue)retValue : default(TValue);
+ return found;
+ }
+
+ IEnumerator IEnumerable.GetEnumerator()
+ {
+ return GetEnumerator();
+ }
+ }
+}
diff --git a/modules/mono/glue/cs_files/GD.cs b/modules/mono/glue/cs_files/GD.cs
index ec1534cb9a..e2457ff98b 100644
--- a/modules/mono/glue/cs_files/GD.cs
+++ b/modules/mono/glue/cs_files/GD.cs
@@ -1,4 +1,9 @@
using System;
+#if REAL_T_IS_DOUBLE
+using real_t = System.Double;
+#else
+using real_t = System.Single;
+#endif
// TODO: Add comments describing what this class does. It is not obvious.
@@ -16,22 +21,22 @@ namespace Godot
return NativeCalls.godot_icall_Godot_convert(what, type);
}
- public static float Db2Linear(float db)
+ public static real_t Db2Linear(real_t db)
{
- return (float)Math.Exp(db * 0.11512925464970228420089957273422);
+ return (real_t)Math.Exp(db * 0.11512925464970228420089957273422);
}
- public static float Dectime(float value, float amount, float step)
+ public static real_t DecTime(real_t value, real_t amount, real_t step)
{
- float sgn = value < 0 ? -1.0f : 1.0f;
- float val = Mathf.Abs(value);
+ real_t sgn = Mathf.Sign(value);
+ real_t val = Mathf.Abs(value);
val -= amount * step;
- if (val < 0.0f)
- val = 0.0f;
+ if (val < 0)
+ val = 0;
return val * sgn;
}
- public static FuncRef Funcref(Object instance, string funcname)
+ public static FuncRef FuncRef(Object instance, string funcname)
{
var ret = new FuncRef();
ret.SetInstance(instance);
@@ -49,9 +54,9 @@ namespace Godot
return NativeCalls.godot_icall_Godot_instance_from_id(instanceId);
}
- public static double Linear2Db(double linear)
+ public static real_t Linear2Db(real_t linear)
{
- return Math.Log(linear) * 8.6858896380650365530225783783321;
+ return (real_t)(Math.Log(linear) * 8.6858896380650365530225783783321);
}
public static Resource Load(string path)
@@ -69,22 +74,22 @@ namespace Godot
Print(System.Environment.StackTrace);
}
- public static void Printerr(params object[] what)
+ public static void PrintErr(params object[] what)
{
NativeCalls.godot_icall_Godot_printerr(what);
}
- public static void Printraw(params object[] what)
+ public static void PrintRaw(params object[] what)
{
NativeCalls.godot_icall_Godot_printraw(what);
}
- public static void Prints(params object[] what)
+ public static void PrintS(params object[] what)
{
NativeCalls.godot_icall_Godot_prints(what);
}
- public static void Printt(params object[] what)
+ public static void PrintT(params object[] what)
{
NativeCalls.godot_icall_Godot_printt(what);
}
@@ -183,7 +188,7 @@ namespace Godot
return NativeCalls.godot_icall_Godot_var2str(var);
}
- public static WeakRef Weakref(Object obj)
+ public static WeakRef WeakRef(Object obj)
{
return NativeCalls.godot_icall_Godot_weakref(Object.GetPtr(obj));
}
diff --git a/modules/mono/glue/cs_files/MarshalUtils.cs b/modules/mono/glue/cs_files/MarshalUtils.cs
index ff4477cc6c..6ad4b3dcb2 100644
--- a/modules/mono/glue/cs_files/MarshalUtils.cs
+++ b/modules/mono/glue/cs_files/MarshalUtils.cs
@@ -1,36 +1,17 @@
using System;
-using System.Collections.Generic;
namespace Godot
{
- internal static class MarshalUtils
+ static class MarshalUtils
{
- private static Dictionary<object, object> ArraysToDictionary(object[] keys, object[] values)
+ static bool IsArrayGenericType(Type type)
{
- var ret = new Dictionary<object, object>();
-
- for (int i = 0; i < keys.Length; i++)
- {
- ret.Add(keys[i], values[i]);
- }
-
- return ret;
- }
-
- private static void DictionaryToArrays(Dictionary<object, object> from, out object[] keysTo, out object[] valuesTo)
- {
- var keys = from.Keys;
- keysTo = new object[keys.Count];
- keys.CopyTo(keysTo, 0);
-
- var values = from.Values;
- valuesTo = new object[values.Count];
- values.CopyTo(valuesTo, 0);
+ return type.GetGenericTypeDefinition() == typeof(Array<>);
}
- private static Type GetDictionaryType()
+ static bool IsDictionaryGenericType(Type type)
{
- return typeof(Dictionary<object, object>);
+ return type.GetGenericTypeDefinition() == typeof(Dictionary<, >);
}
}
}
diff --git a/modules/mono/glue/cs_files/VERSION.txt b/modules/mono/glue/cs_files/VERSION.txt
index b8626c4cff..7ed6ff82de 100755
--- a/modules/mono/glue/cs_files/VERSION.txt
+++ b/modules/mono/glue/cs_files/VERSION.txt
@@ -1 +1 @@
-4
+5
diff --git a/modules/mono/glue/glue_header.h b/modules/mono/glue/glue_header.h
index cedc8e9992..6a6f3062b4 100644
--- a/modules/mono/glue/glue_header.h
+++ b/modules/mono/glue/glue_header.h
@@ -29,6 +29,7 @@
/*************************************************************************/
#include "builtin_types_glue.h"
+#include "collections_glue.h"
#include "../csharp_script.h"
#include "../mono_gd/gd_mono_class.h"
@@ -308,4 +309,5 @@ MonoObject *godot_icall_Godot_weakref(Object *p_obj) {
void godot_register_header_icalls() {
godot_register_builtin_type_icalls();
+ godot_register_collections_icalls();
}
diff --git a/modules/mono/mono_gd/gd_mono_class.cpp b/modules/mono/mono_gd/gd_mono_class.cpp
index 66339d7ae6..e2597a7d42 100644
--- a/modules/mono/mono_gd/gd_mono_class.cpp
+++ b/modules/mono/mono_gd/gd_mono_class.cpp
@@ -33,23 +33,37 @@
#include <mono/metadata/attrdefs.h>
#include "gd_mono_assembly.h"
+#include "gd_mono_marshal.h"
-MonoType *GDMonoClass::get_raw_type(GDMonoClass *p_class) {
+String GDMonoClass::get_full_name(MonoClass *p_mono_class) {
+ // mono_type_get_full_name is not exposed to embedders, but this seems to do the job
+ MonoReflectionType *type_obj = mono_type_get_object(mono_domain_get(), get_mono_type(p_mono_class));
- return mono_class_get_type(p_class->get_mono_ptr());
-}
+ MonoException *exc = NULL;
+ GD_MONO_BEGIN_RUNTIME_INVOKE;
+ MonoString *str = mono_object_to_string((MonoObject *)type_obj, (MonoObject **)&exc);
+ GD_MONO_END_RUNTIME_INVOKE;
+ UNLIKELY_UNHANDLED_EXCEPTION(exc);
-bool GDMonoClass::is_assignable_from(GDMonoClass *p_from) const {
+ return GDMonoMarshal::mono_string_to_godot(str);
+}
- return mono_class_is_assignable_from(mono_class, p_from->mono_class);
+MonoType *GDMonoClass::get_mono_type(MonoClass *p_mono_class) {
+ return mono_class_get_type(p_mono_class);
}
String GDMonoClass::get_full_name() const {
+ return get_full_name(mono_class);
+}
- String res = namespace_name;
- if (res.length())
- res += ".";
- return res + class_name;
+MonoType *GDMonoClass::get_mono_type() {
+ // Care, you cannot compare MonoType pointers
+ return get_mono_type(mono_class);
+}
+
+bool GDMonoClass::is_assignable_from(GDMonoClass *p_from) const {
+
+ return mono_class_is_assignable_from(mono_class, p_from->mono_class);
}
GDMonoClass *GDMonoClass::get_parent_class() {
diff --git a/modules/mono/mono_gd/gd_mono_class.h b/modules/mono/mono_gd/gd_mono_class.h
index 417c138594..f81ab84cd0 100644
--- a/modules/mono/mono_gd/gd_mono_class.h
+++ b/modules/mono/mono_gd/gd_mono_class.h
@@ -98,7 +98,11 @@ class GDMonoClass {
GDMonoClass(const StringName &p_namespace, const StringName &p_name, MonoClass *p_class, GDMonoAssembly *p_assembly);
public:
- static MonoType *get_raw_type(GDMonoClass *p_class);
+ static String get_full_name(MonoClass *p_mono_class);
+ static MonoType *get_mono_type(MonoClass *p_mono_class);
+
+ String get_full_name() const;
+ MonoType *get_mono_type();
bool is_assignable_from(GDMonoClass *p_from) const;
@@ -108,8 +112,6 @@ public:
_FORCE_INLINE_ MonoClass *get_mono_ptr() const { return mono_class; }
_FORCE_INLINE_ const GDMonoAssembly *get_assembly() const { return assembly; }
- String get_full_name() const;
-
GDMonoClass *get_parent_class();
#ifdef TOOLS_ENABLED
diff --git a/modules/mono/mono_gd/gd_mono_field.cpp b/modules/mono/mono_gd/gd_mono_field.cpp
index 3b91777ed4..d3a673dc1b 100644
--- a/modules/mono/mono_gd/gd_mono_field.cpp
+++ b/modules/mono/mono_gd/gd_mono_field.cpp
@@ -148,7 +148,7 @@ void GDMonoField::set_value_from_variant(MonoObject *p_object, const Variant &p_
case MONO_TYPE_ARRAY:
case MONO_TYPE_SZARRAY: {
- MonoArrayType *array_type = mono_type_get_array_type(GDMonoClass::get_raw_type(type.type_class));
+ MonoArrayType *array_type = mono_type_get_array_type(type.type_class->get_mono_type());
if (array_type->eklass == CACHED_CLASS_RAW(MonoObject))
SET_FROM_ARRAY_AND_BREAK(Array);
@@ -200,6 +200,18 @@ void GDMonoField::set_value_from_variant(MonoObject *p_object, const Variant &p_
break;
}
+ if (CACHED_CLASS(Dictionary) == type_class) {
+ MonoObject *managed = GDMonoUtils::create_managed_from(p_value.operator Dictionary(), CACHED_CLASS(Dictionary));
+ mono_field_set_value(p_object, mono_field, managed);
+ break;
+ }
+
+ if (CACHED_CLASS(Array) == type_class) {
+ MonoObject *managed = GDMonoUtils::create_managed_from(p_value.operator Array(), CACHED_CLASS(Array));
+ mono_field_set_value(p_object, mono_field, managed);
+ break;
+ }
+
ERR_EXPLAIN(String() + "Attempted to set the value of a field of unmarshallable type: " + type_class->get_name());
ERR_FAIL();
} break;
@@ -248,10 +260,13 @@ void GDMonoField::set_value_from_variant(MonoObject *p_object, const Variant &p_
break;
}
case Variant::DICTIONARY: {
- MonoObject *managed = GDMonoMarshal::Dictionary_to_mono_object(p_value.operator Dictionary());
+ MonoObject *managed = GDMonoUtils::create_managed_from(p_value.operator Dictionary(), CACHED_CLASS(Dictionary));
+ mono_field_set_value(p_object, mono_field, managed);
+ } break;
+ case Variant::ARRAY: {
+ MonoObject *managed = GDMonoUtils::create_managed_from(p_value.operator Array(), CACHED_CLASS(Array));
mono_field_set_value(p_object, mono_field, managed);
} break;
- case Variant::ARRAY: SET_FROM_ARRAY_AND_BREAK(Array);
case Variant::POOL_BYTE_ARRAY: SET_FROM_ARRAY_AND_BREAK(PoolByteArray);
case Variant::POOL_INT_ARRAY: SET_FROM_ARRAY_AND_BREAK(PoolIntArray);
case Variant::POOL_REAL_ARRAY: SET_FROM_ARRAY_AND_BREAK(PoolRealArray);
@@ -265,8 +280,28 @@ void GDMonoField::set_value_from_variant(MonoObject *p_object, const Variant &p_
} break;
case MONO_TYPE_GENERICINST: {
- if (CACHED_RAW_MONO_CLASS(Dictionary) == type.type_class->get_mono_ptr()) {
- MonoObject *managed = GDMonoMarshal::Dictionary_to_mono_object(p_value.operator Dictionary());
+ MonoReflectionType *reftype = mono_type_get_object(SCRIPTS_DOMAIN, type.type_class->get_mono_type());
+
+ MonoException *exc = NULL;
+
+ GDMonoUtils::IsDictionaryGenericType type_is_dict = CACHED_METHOD_THUNK(MarshalUtils, IsDictionaryGenericType);
+ MonoBoolean is_dict = type_is_dict((MonoObject *)reftype, (MonoObject **)&exc);
+ UNLIKELY_UNHANDLED_EXCEPTION(exc);
+
+ if (is_dict) {
+ MonoObject *managed = GDMonoUtils::create_managed_from(p_value.operator Dictionary(), type.type_class);
+ mono_field_set_value(p_object, mono_field, managed);
+ break;
+ }
+
+ exc = NULL;
+
+ GDMonoUtils::IsArrayGenericType type_is_array = CACHED_METHOD_THUNK(MarshalUtils, IsArrayGenericType);
+ MonoBoolean is_array = type_is_array((MonoObject *)reftype, (MonoObject **)&exc);
+ UNLIKELY_UNHANDLED_EXCEPTION(exc);
+
+ if (is_array) {
+ MonoObject *managed = GDMonoUtils::create_managed_from(p_value.operator Array(), type.type_class);
mono_field_set_value(p_object, mono_field, managed);
break;
}
diff --git a/modules/mono/mono_gd/gd_mono_header.h b/modules/mono/mono_gd/gd_mono_header.h
index 2b5110f0b9..72a5439044 100644
--- a/modules/mono/mono_gd/gd_mono_header.h
+++ b/modules/mono/mono_gd/gd_mono_header.h
@@ -45,7 +45,8 @@ struct ManagedType {
GDMonoClass *type_class;
ManagedType() {
- type_class = 0;
+ type_encoding = 0;
+ type_class = NULL;
}
};
diff --git a/modules/mono/mono_gd/gd_mono_marshal.cpp b/modules/mono/mono_gd/gd_mono_marshal.cpp
index 5cd77d63e2..de91e71bab 100644
--- a/modules/mono/mono_gd/gd_mono_marshal.cpp
+++ b/modules/mono/mono_gd/gd_mono_marshal.cpp
@@ -120,7 +120,7 @@ Variant::Type managed_to_variant_type(const ManagedType &p_type) {
case MONO_TYPE_ARRAY:
case MONO_TYPE_SZARRAY: {
- MonoArrayType *array_type = mono_type_get_array_type(GDMonoClass::get_raw_type(p_type.type_class));
+ MonoArrayType *array_type = mono_type_get_array_type(p_type.type_class->get_mono_type());
if (array_type->eklass == CACHED_CLASS_RAW(MonoObject))
return Variant::ARRAY;
@@ -162,12 +162,36 @@ Variant::Type managed_to_variant_type(const ManagedType &p_type) {
if (CACHED_CLASS(RID) == type_class) {
return Variant::_RID;
}
+
+ if (CACHED_CLASS(Dictionary) == type_class) {
+ return Variant::DICTIONARY;
+ }
+
+ if (CACHED_CLASS(Array) == type_class) {
+ return Variant::ARRAY;
+ }
} break;
case MONO_TYPE_GENERICINST: {
- if (CACHED_RAW_MONO_CLASS(Dictionary) == p_type.type_class->get_mono_ptr()) {
+ MonoReflectionType *reftype = mono_type_get_object(SCRIPTS_DOMAIN, p_type.type_class->get_mono_type());
+
+ MonoException *exc = NULL;
+ GDMonoUtils::IsDictionaryGenericType type_is_dict = CACHED_METHOD_THUNK(MarshalUtils, IsDictionaryGenericType);
+ MonoBoolean is_dict = type_is_dict((MonoObject *)reftype, (MonoObject **)&exc);
+ UNLIKELY_UNHANDLED_EXCEPTION(exc);
+
+ if (is_dict) {
return Variant::DICTIONARY;
}
+
+ exc = NULL;
+ GDMonoUtils::IsArrayGenericType type_is_array = CACHED_METHOD_THUNK(MarshalUtils, IsArrayGenericType);
+ MonoBoolean is_array = type_is_array((MonoObject *)reftype, (MonoObject **)&exc);
+ UNLIKELY_UNHANDLED_EXCEPTION(exc);
+
+ if (is_array) {
+ return Variant::ARRAY;
+ }
} break;
default: {
@@ -216,6 +240,7 @@ MonoObject *variant_to_mono_object(const Variant *p_var) {
ManagedType type;
type.type_encoding = MONO_TYPE_OBJECT;
+ // type.type_class is not needed when we specify the MONO_TYPE_OBJECT encoding
return variant_to_mono_object(p_var, type);
}
@@ -315,7 +340,7 @@ MonoObject *variant_to_mono_object(const Variant *p_var, const ManagedType &p_ty
case MONO_TYPE_ARRAY:
case MONO_TYPE_SZARRAY: {
- MonoArrayType *array_type = mono_type_get_array_type(GDMonoClass::get_raw_type(p_type.type_class));
+ MonoArrayType *array_type = mono_type_get_array_type(p_type.type_class->get_mono_type());
if (array_type->eklass == CACHED_CLASS_RAW(MonoObject))
return (MonoObject *)Array_to_mono_array(p_var->operator Array());
@@ -360,6 +385,14 @@ MonoObject *variant_to_mono_object(const Variant *p_var, const ManagedType &p_ty
if (CACHED_CLASS(RID) == type_class) {
return GDMonoUtils::create_managed_from(p_var->operator RID());
}
+
+ if (CACHED_CLASS(Dictionary) == type_class) {
+ return GDMonoUtils::create_managed_from(p_var->operator Dictionary(), CACHED_CLASS(Dictionary));
+ }
+
+ if (CACHED_CLASS(Array) == type_class) {
+ return GDMonoUtils::create_managed_from(p_var->operator Array(), CACHED_CLASS(Array));
+ }
} break;
case MONO_TYPE_OBJECT: {
// Variant
@@ -411,9 +444,9 @@ MonoObject *variant_to_mono_object(const Variant *p_var, const ManagedType &p_ty
return GDMonoUtils::unmanaged_get_managed(p_var->operator Object *());
}
case Variant::DICTIONARY:
- return Dictionary_to_mono_object(p_var->operator Dictionary());
+ return GDMonoUtils::create_managed_from(p_var->operator Dictionary(), CACHED_CLASS(Dictionary));
case Variant::ARRAY:
- return (MonoObject *)Array_to_mono_array(p_var->operator Array());
+ return GDMonoUtils::create_managed_from(p_var->operator Array(), CACHED_CLASS(Array));
case Variant::POOL_BYTE_ARRAY:
return (MonoObject *)PoolByteArray_to_mono_array(p_var->operator PoolByteArray());
case Variant::POOL_INT_ARRAY:
@@ -433,8 +466,24 @@ MonoObject *variant_to_mono_object(const Variant *p_var, const ManagedType &p_ty
}
break;
case MONO_TYPE_GENERICINST: {
- if (CACHED_RAW_MONO_CLASS(Dictionary) == p_type.type_class->get_mono_ptr()) {
- return Dictionary_to_mono_object(p_var->operator Dictionary());
+ MonoReflectionType *reftype = mono_type_get_object(SCRIPTS_DOMAIN, p_type.type_class->get_mono_type());
+
+ MonoException *exc = NULL;
+ GDMonoUtils::IsDictionaryGenericType type_is_dict = CACHED_METHOD_THUNK(MarshalUtils, IsDictionaryGenericType);
+ MonoBoolean is_dict = type_is_dict((MonoObject *)reftype, (MonoObject **)&exc);
+ UNLIKELY_UNHANDLED_EXCEPTION(exc);
+
+ if (is_dict) {
+ return GDMonoUtils::create_managed_from(p_var->operator Dictionary(), p_type.type_class);
+ }
+
+ exc = NULL;
+ GDMonoUtils::IsArrayGenericType type_is_array = CACHED_METHOD_THUNK(MarshalUtils, IsArrayGenericType);
+ MonoBoolean is_array = type_is_array((MonoObject *)reftype, (MonoObject **)&exc);
+ UNLIKELY_UNHANDLED_EXCEPTION(exc);
+
+ if (is_array) {
+ return GDMonoUtils::create_managed_from(p_var->operator Array(), p_type.type_class);
}
} break;
} break;
@@ -452,7 +501,7 @@ Variant mono_object_to_variant(MonoObject *p_obj) {
GDMonoClass *tclass = GDMono::get_singleton()->get_class(mono_object_get_class(p_obj));
ERR_FAIL_COND_V(!tclass, Variant());
- MonoType *raw_type = tclass->get_raw_type(tclass);
+ MonoType *raw_type = tclass->get_mono_type();
ManagedType type;
@@ -531,7 +580,7 @@ Variant mono_object_to_variant(MonoObject *p_obj) {
case MONO_TYPE_ARRAY:
case MONO_TYPE_SZARRAY: {
- MonoArrayType *array_type = mono_type_get_array_type(GDMonoClass::get_raw_type(type.type_class));
+ MonoArrayType *array_type = mono_type_get_array_type(type.type_class->get_mono_type());
if (array_type->eklass == CACHED_CLASS_RAW(MonoObject))
return mono_array_to_Array((MonoArray *)p_obj);
@@ -579,11 +628,51 @@ Variant mono_object_to_variant(MonoObject *p_obj) {
RID *ptr = unbox<RID *>(CACHED_FIELD(RID, ptr)->get_value(p_obj));
return ptr ? Variant(*ptr) : Variant();
}
+
+ if (CACHED_CLASS(Array) == type_class) {
+ MonoException *exc = NULL;
+ GDMonoUtils::Array_GetPtr get_ptr = CACHED_METHOD_THUNK(Array, GetPtr);
+ Array *ptr = get_ptr(p_obj, (MonoObject **)&exc);
+ UNLIKELY_UNHANDLED_EXCEPTION(exc);
+ return ptr ? Variant(*ptr) : Variant();
+ }
+
+ if (CACHED_CLASS(Dictionary) == type_class) {
+ MonoException *exc = NULL;
+ GDMonoUtils::Dictionary_GetPtr get_ptr = CACHED_METHOD_THUNK(Dictionary, GetPtr);
+ Dictionary *ptr = get_ptr(p_obj, (MonoObject **)&exc);
+ UNLIKELY_UNHANDLED_EXCEPTION(exc);
+ return ptr ? Variant(*ptr) : Variant();
+ }
} break;
case MONO_TYPE_GENERICINST: {
- if (CACHED_RAW_MONO_CLASS(Dictionary) == type.type_class->get_mono_ptr()) {
- return mono_object_to_Dictionary(p_obj);
+ MonoReflectionType *reftype = mono_type_get_object(SCRIPTS_DOMAIN, type.type_class->get_mono_type());
+
+ MonoException *exc = NULL;
+
+ GDMonoUtils::IsDictionaryGenericType type_is_dict = CACHED_METHOD_THUNK(MarshalUtils, IsDictionaryGenericType);
+ MonoBoolean is_dict = type_is_dict((MonoObject *)reftype, (MonoObject **)&exc);
+ UNLIKELY_UNHANDLED_EXCEPTION(exc);
+
+ if (is_dict) {
+ MonoException *exc = NULL;
+ MonoObject *ret = type.type_class->get_method("GetPtr")->invoke(p_obj, &exc);
+ UNLIKELY_UNHANDLED_EXCEPTION(exc);
+ return *unbox<Dictionary *>(ret);
+ }
+
+ exc = NULL;
+
+ GDMonoUtils::IsArrayGenericType type_is_array = CACHED_METHOD_THUNK(MarshalUtils, IsArrayGenericType);
+ MonoBoolean is_array = type_is_array((MonoObject *)reftype, (MonoObject **)&exc);
+ UNLIKELY_UNHANDLED_EXCEPTION(exc);
+
+ if (is_array) {
+ MonoException *exc = NULL;
+ MonoObject *ret = type.type_class->get_method("GetPtr")->invoke(p_obj, &exc);
+ UNLIKELY_UNHANDLED_EXCEPTION(exc);
+ return *unbox<Array *>(ret);
}
} break;
}
@@ -822,66 +911,4 @@ PoolVector3Array mono_array_to_PoolVector3Array(MonoArray *p_array) {
return ret;
}
-
-MonoObject *Dictionary_to_mono_object(const Dictionary &p_dict) {
- MonoArray *keys = mono_array_new(mono_domain_get(), CACHED_CLASS_RAW(MonoObject), p_dict.size());
- MonoArray *values = mono_array_new(mono_domain_get(), CACHED_CLASS_RAW(MonoObject), p_dict.size());
-
- int i = 0;
- const Variant *dkey = NULL;
- while ((dkey = p_dict.next(dkey))) {
- mono_array_set(keys, MonoObject *, i, variant_to_mono_object(dkey));
- mono_array_set(values, MonoObject *, i, variant_to_mono_object(p_dict[*dkey]));
- i++;
- }
-
- GDMonoUtils::MarshalUtils_ArraysToDict arrays_to_dict = CACHED_METHOD_THUNK(MarshalUtils, ArraysToDictionary);
-
- MonoException *exc = NULL;
- GD_MONO_BEGIN_RUNTIME_INVOKE;
- MonoObject *ret = arrays_to_dict(keys, values, (MonoObject **)&exc);
- GD_MONO_END_RUNTIME_INVOKE;
-
- if (exc) {
- GDMonoUtils::set_pending_exception(exc);
- ERR_FAIL_V(NULL);
- }
-
- return ret;
-}
-
-Dictionary mono_object_to_Dictionary(MonoObject *p_dict) {
- Dictionary ret;
-
- if (!p_dict)
- return ret;
-
- GDMonoUtils::MarshalUtils_DictToArrays dict_to_arrays = CACHED_METHOD_THUNK(MarshalUtils, DictionaryToArrays);
-
- MonoArray *keys = NULL;
- MonoArray *values = NULL;
- MonoException *exc = NULL;
- GD_MONO_BEGIN_RUNTIME_INVOKE;
- dict_to_arrays(p_dict, &keys, &values, (MonoObject **)&exc);
- GD_MONO_END_RUNTIME_INVOKE;
-
- if (exc) {
- GDMonoUtils::set_pending_exception(exc);
- ERR_FAIL_V(Dictionary());
- }
-
- int length = mono_array_length(keys);
-
- for (int i = 0; i < length; i++) {
- MonoObject *key_obj = mono_array_get(keys, MonoObject *, i);
- MonoObject *value_obj = mono_array_get(values, MonoObject *, i);
-
- Variant key = key_obj ? mono_object_to_variant(key_obj) : Variant();
- Variant value = value_obj ? mono_object_to_variant(value_obj) : Variant();
-
- ret[key] = value;
- }
-
- return ret;
-}
} // namespace GDMonoMarshal
diff --git a/modules/mono/mono_gd/gd_mono_marshal.h b/modules/mono/mono_gd/gd_mono_marshal.h
index 6572408ab5..464f584a0a 100644
--- a/modules/mono/mono_gd/gd_mono_marshal.h
+++ b/modules/mono/mono_gd/gd_mono_marshal.h
@@ -143,11 +143,6 @@ PoolVector2Array mono_array_to_PoolVector2Array(MonoArray *p_array);
MonoArray *PoolVector3Array_to_mono_array(const PoolVector3Array &p_array);
PoolVector3Array mono_array_to_PoolVector3Array(MonoArray *p_array);
-// Dictionary
-
-MonoObject *Dictionary_to_mono_object(const Dictionary &p_dict);
-Dictionary mono_object_to_Dictionary(MonoObject *p_dict);
-
#ifdef YOLO_COPY
#define MARSHALLED_OUT(m_t, m_in, m_out) m_t *m_out = (m_t *)&m_in;
#define MARSHALLED_IN(m_t, m_in, m_out) m_t m_out = *reinterpret_cast<m_t *>(m_in);
diff --git a/modules/mono/mono_gd/gd_mono_property.cpp b/modules/mono/mono_gd/gd_mono_property.cpp
index 1f837a2d78..a1c710c26c 100644
--- a/modules/mono/mono_gd/gd_mono_property.cpp
+++ b/modules/mono/mono_gd/gd_mono_property.cpp
@@ -139,23 +139,8 @@ bool GDMonoProperty::has_setter() {
}
void GDMonoProperty::set_value(MonoObject *p_object, MonoObject *p_value, MonoException **r_exc) {
- MonoMethod *prop_method = mono_property_get_set_method(mono_property);
-
- MonoArray *params = mono_array_new(mono_domain_get(), CACHED_CLASS_RAW(MonoObject), 1);
- mono_array_set(params, MonoObject *, 0, p_value);
-
- MonoException *exc = NULL;
- GD_MONO_BEGIN_RUNTIME_INVOKE;
- mono_runtime_invoke_array(prop_method, p_object, params, (MonoObject **)&exc);
- GD_MONO_END_RUNTIME_INVOKE;
-
- if (exc) {
- if (r_exc) {
- *r_exc = exc;
- } else {
- GDMonoUtils::set_pending_exception(exc);
- }
- }
+ void *params[1] = { p_value };
+ set_value(p_object, params, r_exc);
}
void GDMonoProperty::set_value(MonoObject *p_object, void **p_params, MonoException **r_exc) {
diff --git a/modules/mono/mono_gd/gd_mono_utils.cpp b/modules/mono/mono_gd/gd_mono_utils.cpp
index a229552b76..7cd922138f 100644
--- a/modules/mono/mono_gd/gd_mono_utils.cpp
+++ b/modules/mono/mono_gd/gd_mono_utils.cpp
@@ -87,6 +87,8 @@ void MonoCache::clear_members() {
method_System_Diagnostics_StackTrace_ctor_Exception_bool = NULL;
#endif
+ class_KeyNotFoundException = NULL;
+
rawclass_Dictionary = NULL;
class_Vector2 = NULL;
@@ -107,6 +109,8 @@ void MonoCache::clear_members() {
class_Control = NULL;
class_Spatial = NULL;
class_WeakRef = NULL;
+ class_Array = NULL;
+ class_Dictionary = NULL;
class_MarshalUtils = NULL;
#ifdef DEBUG_ENABLED
@@ -134,8 +138,10 @@ void MonoCache::clear_members() {
field_Image_ptr = NULL;
field_RID_ptr = NULL;
- methodthunk_MarshalUtils_DictionaryToArrays = NULL;
- methodthunk_MarshalUtils_ArraysToDictionary = NULL;
+ methodthunk_Array_GetPtr = NULL;
+ methodthunk_Dictionary_GetPtr = NULL;
+ methodthunk_MarshalUtils_IsArrayGenericType = NULL;
+ methodthunk_MarshalUtils_IsDictionaryGenericType = NULL;
methodthunk_SignalAwaiter_SignalCallback = NULL;
methodthunk_SignalAwaiter_FailureCallback = NULL;
methodthunk_GodotTaskScheduler_Activate = NULL;
@@ -175,6 +181,8 @@ void update_corlib_cache() {
CACHE_METHOD_AND_CHECK(System_Diagnostics_StackTrace, ctor_Exception_bool, CACHED_CLASS(System_Diagnostics_StackTrace)->get_method_with_desc("System.Diagnostics.StackTrace:.ctor(System.Exception,bool)", true));
#endif
+ CACHE_CLASS_AND_CHECK(KeyNotFoundException, GDMono::get_singleton()->get_corlib_assembly()->get_class("System.Collections.Generic", "KeyNotFoundException"));
+
mono_cache.corlib_cache_updated = true;
}
@@ -198,6 +206,8 @@ void update_godot_api_cache() {
CACHE_CLASS_AND_CHECK(Control, GODOT_API_CLASS(Control));
CACHE_CLASS_AND_CHECK(Spatial, GODOT_API_CLASS(Spatial));
CACHE_CLASS_AND_CHECK(WeakRef, GODOT_API_CLASS(WeakRef));
+ CACHE_CLASS_AND_CHECK(Array, GODOT_API_CLASS(Array));
+ CACHE_CLASS_AND_CHECK(Dictionary, GODOT_API_CLASS(Dictionary));
CACHE_CLASS_AND_CHECK(MarshalUtils, GODOT_API_CLASS(MarshalUtils));
#ifdef DEBUG_ENABLED
@@ -224,8 +234,10 @@ void update_godot_api_cache() {
CACHE_FIELD_AND_CHECK(NodePath, ptr, CACHED_CLASS(NodePath)->get_field(BINDINGS_PTR_FIELD));
CACHE_FIELD_AND_CHECK(RID, ptr, CACHED_CLASS(RID)->get_field(BINDINGS_PTR_FIELD));
- CACHE_METHOD_THUNK_AND_CHECK(MarshalUtils, DictionaryToArrays, (MarshalUtils_DictToArrays)CACHED_CLASS(MarshalUtils)->get_method("DictionaryToArrays", 3)->get_thunk());
- CACHE_METHOD_THUNK_AND_CHECK(MarshalUtils, ArraysToDictionary, (MarshalUtils_ArraysToDict)CACHED_CLASS(MarshalUtils)->get_method("ArraysToDictionary", 2)->get_thunk());
+ CACHE_METHOD_THUNK_AND_CHECK(Array, GetPtr, (Array_GetPtr)GODOT_API_CLASS(Array)->get_method("GetPtr", 0)->get_thunk());
+ CACHE_METHOD_THUNK_AND_CHECK(Dictionary, GetPtr, (Dictionary_GetPtr)GODOT_API_CLASS(Dictionary)->get_method("GetPtr", 0)->get_thunk());
+ CACHE_METHOD_THUNK_AND_CHECK(MarshalUtils, IsArrayGenericType, (IsArrayGenericType)GODOT_API_CLASS(MarshalUtils)->get_method("IsArrayGenericType", 1)->get_thunk());
+ CACHE_METHOD_THUNK_AND_CHECK(MarshalUtils, IsDictionaryGenericType, (IsDictionaryGenericType)GODOT_API_CLASS(MarshalUtils)->get_method("IsDictionaryGenericType", 1)->get_thunk());
CACHE_METHOD_THUNK_AND_CHECK(SignalAwaiter, SignalCallback, (SignalAwaiter_SignalCallback)GODOT_API_CLASS(SignalAwaiter)->get_method("SignalCallback", 1)->get_thunk());
CACHE_METHOD_THUNK_AND_CHECK(SignalAwaiter, FailureCallback, (SignalAwaiter_FailureCallback)GODOT_API_CLASS(SignalAwaiter)->get_method("FailureCallback", 0)->get_thunk());
CACHE_METHOD_THUNK_AND_CHECK(GodotTaskScheduler, Activate, (GodotTaskScheduler_Activate)GODOT_API_CLASS(GodotTaskScheduler)->get_method("Activate", 0)->get_thunk());
@@ -234,24 +246,9 @@ void update_godot_api_cache() {
CACHE_METHOD_THUNK_AND_CHECK(DebuggingUtils, GetStackFrameInfo, (DebugUtils_StackFrameInfo)GODOT_API_CLASS(DebuggingUtils)->get_method("GetStackFrameInfo", 4)->get_thunk());
#endif
- {
- /*
- * TODO Right now we only support Dictionary<object, object>.
- * It would be great if we could support other key/value types
- * without forcing the user to copy the entries.
- */
- GDMonoMethod *method_get_dict_type = CACHED_CLASS(MarshalUtils)->get_method("GetDictionaryType", 0);
- ERR_FAIL_NULL(method_get_dict_type);
- MonoReflectionType *dict_refl_type = (MonoReflectionType *)method_get_dict_type->invoke(NULL);
- ERR_FAIL_NULL(dict_refl_type);
- MonoType *dict_type = mono_reflection_type_get_type(dict_refl_type);
- ERR_FAIL_NULL(dict_type);
-
- CACHE_RAW_MONO_CLASS_AND_CHECK(Dictionary, mono_class_from_mono_type(dict_type));
- }
-
+ // TODO Move to CSharpLanguage::init()
MonoObject *task_scheduler = mono_object_new(SCRIPTS_DOMAIN, GODOT_API_CLASS(GodotTaskScheduler)->get_mono_ptr());
- mono_runtime_object_init(task_scheduler);
+ GDMonoUtils::runtime_object_init(task_scheduler);
mono_cache.task_scheduler_handle = MonoGCHandle::create_strong(task_scheduler);
mono_cache.godot_api_cache_updated = true;
@@ -304,6 +301,12 @@ MonoThread *get_current_thread() {
return mono_thread_current();
}
+void runtime_object_init(MonoObject *p_this_obj) {
+ GD_MONO_BEGIN_RUNTIME_INVOKE;
+ mono_runtime_object_init(p_this_obj);
+ GD_MONO_END_RUNTIME_INVOKE;
+}
+
GDMonoClass *get_object_class(MonoObject *p_object) {
return GDMono::get_singleton()->get_class(mono_object_get_class(p_object));
}
@@ -358,7 +361,7 @@ MonoObject *create_managed_for_godot_object(GDMonoClass *p_class, const StringNa
CACHED_FIELD(GodotObject, ptr)->set_value_raw(mono_object, p_object);
// Construct
- mono_runtime_object_init(mono_object);
+ GDMonoUtils::runtime_object_init(mono_object);
return mono_object;
}
@@ -368,7 +371,7 @@ MonoObject *create_managed_from(const NodePath &p_from) {
ERR_FAIL_NULL_V(mono_object, NULL);
// Construct
- mono_runtime_object_init(mono_object);
+ GDMonoUtils::runtime_object_init(mono_object);
CACHED_FIELD(NodePath, ptr)->set_value_raw(mono_object, memnew(NodePath(p_from)));
@@ -380,13 +383,73 @@ MonoObject *create_managed_from(const RID &p_from) {
ERR_FAIL_NULL_V(mono_object, NULL);
// Construct
- mono_runtime_object_init(mono_object);
+ GDMonoUtils::runtime_object_init(mono_object);
CACHED_FIELD(RID, ptr)->set_value_raw(mono_object, memnew(RID(p_from)));
return mono_object;
}
+MonoObject *create_managed_from(const Array &p_from, GDMonoClass *p_class) {
+ MonoObject *mono_object = mono_object_new(SCRIPTS_DOMAIN, p_class->get_mono_ptr());
+ ERR_FAIL_NULL_V(mono_object, NULL);
+
+ // Search constructor that takes a pointer as parameter
+ MonoMethod *m;
+ void *iter = NULL;
+ while ((m = mono_class_get_methods(p_class->get_mono_ptr(), &iter))) {
+ if (strcmp(mono_method_get_name(m), ".ctor") == 0) {
+ MonoMethodSignature *sig = mono_method_signature(m);
+ void *front = NULL;
+ if (mono_signature_get_param_count(sig) == 1 &&
+ mono_class_from_mono_type(mono_signature_get_params(sig, &front)) == CACHED_CLASS(IntPtr)->get_mono_ptr()) {
+ break;
+ }
+ }
+ }
+
+ CRASH_COND(m == NULL);
+
+ Array *new_array = memnew(Array(p_from));
+ void *args[1] = { &new_array };
+
+ MonoException *exc = NULL;
+ mono_runtime_invoke(m, mono_object, args, (MonoObject **)&exc);
+ UNLIKELY_UNHANDLED_EXCEPTION(exc);
+
+ return mono_object;
+}
+
+MonoObject *create_managed_from(const Dictionary &p_from, GDMonoClass *p_class) {
+ MonoObject *mono_object = mono_object_new(SCRIPTS_DOMAIN, p_class->get_mono_ptr());
+ ERR_FAIL_NULL_V(mono_object, NULL);
+
+ // Search constructor that takes a pointer as parameter
+ MonoMethod *m;
+ void *iter = NULL;
+ while ((m = mono_class_get_methods(p_class->get_mono_ptr(), &iter))) {
+ if (strcmp(mono_method_get_name(m), ".ctor") == 0) {
+ MonoMethodSignature *sig = mono_method_signature(m);
+ void *front = NULL;
+ if (mono_signature_get_param_count(sig) == 1 &&
+ mono_class_from_mono_type(mono_signature_get_params(sig, &front)) == CACHED_CLASS(IntPtr)->get_mono_ptr()) {
+ break;
+ }
+ }
+ }
+
+ CRASH_COND(m == NULL);
+
+ Dictionary *new_dict = memnew(Dictionary(p_from));
+ void *args[1] = { &new_dict };
+
+ MonoException *exc = NULL;
+ mono_runtime_invoke(m, mono_object, args, (MonoObject **)&exc);
+ UNLIKELY_UNHANDLED_EXCEPTION(exc);
+
+ return mono_object;
+}
+
MonoDomain *create_domain(const String &p_friendly_name) {
MonoDomain *domain = mono_domain_create_appdomain((char *)p_friendly_name.utf8().get_data(), NULL);
@@ -400,10 +463,10 @@ MonoDomain *create_domain(const String &p_friendly_name) {
return domain;
}
-String get_exception_name_and_message(MonoException *p_ex) {
+String get_exception_name_and_message(MonoException *p_exc) {
String res;
- MonoClass *klass = mono_object_get_class((MonoObject *)p_ex);
+ MonoClass *klass = mono_object_get_class((MonoObject *)p_exc);
MonoType *type = mono_class_get_type(klass);
char *full_name = mono_type_full_name(type);
@@ -413,12 +476,24 @@ String get_exception_name_and_message(MonoException *p_ex) {
res += ": ";
MonoProperty *prop = mono_class_get_property_from_name(klass, "Message");
- MonoString *msg = (MonoString *)mono_property_get_value(prop, (MonoObject *)p_ex, NULL, NULL);
+ GD_MONO_BEGIN_RUNTIME_INVOKE;
+ MonoString *msg = (MonoString *)mono_property_get_value(prop, (MonoObject *)p_exc, NULL, NULL);
+ GD_MONO_END_RUNTIME_INVOKE;
res += GDMonoMarshal::mono_string_to_godot(msg);
return res;
}
+void set_exception_message(MonoException *p_exc, String message) {
+ MonoClass *klass = mono_object_get_class((MonoObject *)p_exc);
+ MonoProperty *prop = mono_class_get_property_from_name(klass, "Message");
+ MonoString *msg = GDMonoMarshal::mono_string_from_godot(message);
+ void *params[1] = { msg };
+ GD_MONO_BEGIN_RUNTIME_INVOKE;
+ mono_property_set_value(prop, (MonoObject *)p_exc, params, NULL);
+ GD_MONO_END_RUNTIME_INVOKE;
+}
+
void debug_print_unhandled_exception(MonoException *p_exc) {
print_unhandled_exception(p_exc);
debug_send_unhandled_exception_error(p_exc);
diff --git a/modules/mono/mono_gd/gd_mono_utils.h b/modules/mono/mono_gd/gd_mono_utils.h
index 4f8e5932cd..d6774ed41d 100644
--- a/modules/mono/mono_gd/gd_mono_utils.h
+++ b/modules/mono/mono_gd/gd_mono_utils.h
@@ -41,14 +41,24 @@
#include "object.h"
#include "reference.h"
+#define UNLIKELY_UNHANDLED_EXCEPTION(m_exc) \
+ if (unlikely(m_exc != NULL)) { \
+ GDMonoUtils::debug_unhandled_exception(m_exc); \
+ _UNREACHABLE_(); \
+ }
+
namespace GDMonoUtils {
-typedef MonoObject *(*MarshalUtils_DictToArrays)(MonoObject *, MonoArray **, MonoArray **, MonoObject **);
-typedef MonoObject *(*MarshalUtils_ArraysToDict)(MonoArray *, MonoArray *, MonoObject **);
+typedef Array *(*Array_GetPtr)(MonoObject *, MonoObject **);
+typedef Dictionary *(*Dictionary_GetPtr)(MonoObject *, MonoObject **);
typedef MonoObject *(*SignalAwaiter_SignalCallback)(MonoObject *, MonoArray *, MonoObject **);
typedef MonoObject *(*SignalAwaiter_FailureCallback)(MonoObject *, MonoObject **);
typedef MonoObject *(*GodotTaskScheduler_Activate)(MonoObject *, MonoObject **);
typedef MonoArray *(*StackTrace_GetFrames)(MonoObject *, MonoObject **);
+typedef MonoBoolean (*IsArrayGenericType)(MonoObject *, MonoObject **);
+typedef MonoBoolean (*IsDictionaryGenericType)(MonoObject *, MonoObject **);
+typedef MonoBoolean (*IsArrayGenericType)(MonoObject *, MonoObject **);
+typedef MonoBoolean (*IsDictionaryGenericType)(MonoObject *, MonoObject **);
typedef void (*DebugUtils_StackFrameInfo)(MonoObject *, MonoString **, int *, MonoString **, MonoObject **);
struct MonoCache {
@@ -79,6 +89,8 @@ struct MonoCache {
GDMonoMethod *method_System_Diagnostics_StackTrace_ctor_Exception_bool;
#endif
+ GDMonoClass *class_KeyNotFoundException;
+
MonoClass *rawclass_Dictionary;
// -----------------------------------------------
@@ -100,6 +112,8 @@ struct MonoCache {
GDMonoClass *class_Control;
GDMonoClass *class_Spatial;
GDMonoClass *class_WeakRef;
+ GDMonoClass *class_Array;
+ GDMonoClass *class_Dictionary;
GDMonoClass *class_MarshalUtils;
#ifdef DEBUG_ENABLED
@@ -127,8 +141,10 @@ struct MonoCache {
GDMonoField *field_Image_ptr;
GDMonoField *field_RID_ptr;
- MarshalUtils_DictToArrays methodthunk_MarshalUtils_DictionaryToArrays;
- MarshalUtils_ArraysToDict methodthunk_MarshalUtils_ArraysToDictionary;
+ Array_GetPtr methodthunk_Array_GetPtr;
+ Dictionary_GetPtr methodthunk_Dictionary_GetPtr;
+ IsArrayGenericType methodthunk_MarshalUtils_IsArrayGenericType;
+ IsDictionaryGenericType methodthunk_MarshalUtils_IsDictionaryGenericType;
SignalAwaiter_SignalCallback methodthunk_SignalAwaiter_SignalCallback;
SignalAwaiter_FailureCallback methodthunk_SignalAwaiter_FailureCallback;
GodotTaskScheduler_Activate methodthunk_GodotTaskScheduler_Activate;
@@ -175,6 +191,8 @@ _FORCE_INLINE_ bool is_main_thread() {
return mono_domain_get() != NULL && mono_thread_get_main() == mono_thread_current();
}
+void runtime_object_init(MonoObject *p_this_obj);
+
GDMonoClass *get_object_class(MonoObject *p_object);
GDMonoClass *type_get_proxy_class(const StringName &p_type);
GDMonoClass *get_class_native_base(GDMonoClass *p_class);
@@ -183,10 +201,13 @@ MonoObject *create_managed_for_godot_object(GDMonoClass *p_class, const StringNa
MonoObject *create_managed_from(const NodePath &p_from);
MonoObject *create_managed_from(const RID &p_from);
+MonoObject *create_managed_from(const Array &p_from, GDMonoClass *p_class);
+MonoObject *create_managed_from(const Dictionary &p_from, GDMonoClass *p_class);
MonoDomain *create_domain(const String &p_friendly_name);
-String get_exception_name_and_message(MonoException *p_ex);
+String get_exception_name_and_message(MonoException *p_exc);
+void set_exception_message(MonoException *p_exc, String message);
void debug_print_unhandled_exception(MonoException *p_exc);
void debug_send_unhandled_exception_error(MonoException *p_exc);
diff --git a/modules/visual_script/visual_script.cpp b/modules/visual_script/visual_script.cpp
index 9331092171..9dea7a9c9e 100644
--- a/modules/visual_script/visual_script.cpp
+++ b/modules/visual_script/visual_script.cpp
@@ -1333,6 +1333,19 @@ VisualScript::VisualScript() {
base_type = "Object";
}
+Set<int> VisualScript::get_output_sequence_ports_connected(const String &edited_func, int from_node) {
+ List<VisualScript::SequenceConnection> *sc = memnew(List<VisualScript::SequenceConnection>);
+ get_sequence_connection_list(edited_func, sc);
+ Set<int> connected;
+ for (List<VisualScript::SequenceConnection>::Element *E = sc->front(); E; E = E->next()) {
+ if (E->get().from_node == from_node) {
+ connected.insert(E->get().from_output);
+ }
+ }
+ memdelete(sc);
+ return connected;
+}
+
VisualScript::~VisualScript() {
while (!functions.empty()) {
@@ -2402,7 +2415,7 @@ void VisualScriptLanguage::make_template(const String &p_class_name, const Strin
script->set_instance_base_type(p_base_class_name);
}
-bool VisualScriptLanguage::validate(const String &p_script, int &r_line_error, int &r_col_error, String &r_test_error, const String &p_path, List<String> *r_functions) const {
+bool VisualScriptLanguage::validate(const String &p_script, int &r_line_error, int &r_col_error, String &r_test_error, const String &p_path, List<String> *r_functions, Set<int> *r_safe_lines) const {
return false;
}
diff --git a/modules/visual_script/visual_script.h b/modules/visual_script/visual_script.h
index aaa6dfea11..2ad72a40c0 100644
--- a/modules/visual_script/visual_script.h
+++ b/modules/visual_script/visual_script.h
@@ -319,6 +319,7 @@ public:
void custom_signal_swap_argument(const StringName &p_func, int p_argidx, int p_with_argidx);
void remove_custom_signal(const StringName &p_name);
void rename_custom_signal(const StringName &p_name, const StringName &p_new_name);
+ Set<int> get_output_sequence_ports_connected(const String &edited_func, int from_node);
void get_custom_signal_list(List<StringName> *r_custom_signals) const;
@@ -563,7 +564,7 @@ public:
virtual Ref<Script> get_template(const String &p_class_name, const String &p_base_class_name) const;
virtual bool is_using_templates();
virtual void make_template(const String &p_class_name, const String &p_base_class_name, Ref<Script> &p_script);
- virtual bool validate(const String &p_script, int &r_line_error, int &r_col_error, String &r_test_error, const String &p_path = "", List<String> *r_functions = NULL) const;
+ virtual bool validate(const String &p_script, int &r_line_error, int &r_col_error, String &r_test_error, const String &p_path = "", List<String> *r_functions = NULL, Set<int> *r_safe_lines = NULL) const;
virtual Script *create_script() const;
virtual bool has_named_classes() const;
virtual bool supports_builtin_mode() const;
diff --git a/modules/visual_script/visual_script_editor.cpp b/modules/visual_script/visual_script_editor.cpp
index 873cc293c9..ad6d32b567 100644
--- a/modules/visual_script/visual_script_editor.cpp
+++ b/modules/visual_script/visual_script_editor.cpp
@@ -33,8 +33,10 @@
#include "core/script_language.h"
#include "editor/editor_node.h"
#include "editor/editor_resource_preview.h"
+#include "object.h"
#include "os/input.h"
#include "os/keyboard.h"
+#include "variant.h"
#include "visual_script_expression.h"
#include "visual_script_flow_control.h"
#include "visual_script_func_nodes.h"
@@ -1325,6 +1327,12 @@ void VisualScriptEditor::_input(const Ref<InputEvent> &p_event) {
if (mb.is_valid() && !mb->is_pressed() && mb->get_button_index() == BUTTON_LEFT) {
revert_on_drag = String(); //so we can still drag functions
}
+
+ Ref<InputEventKey> k = p_event;
+ if (k.is_valid() && k->get_scancode() == KEY_A && k->get_shift() && k->is_pressed()) {
+ new_connect_node_select->select_from_visual_script(String(""));
+ accept_event();
+ }
}
void VisualScriptEditor::_members_gui_input(const Ref<InputEvent> &p_event) {
@@ -1780,7 +1788,7 @@ void VisualScriptEditor::drop_data_fw(const Point2 &p_point, const Variant &p_da
call->set_base_type(node->get_class());
n = call;
- method_select->select_method_from_instance(node);
+ method_select->select_from_instance(node);
selecting_method_id = base_id;
}
@@ -1917,7 +1925,7 @@ void VisualScriptEditor::drop_data_fw(const Point2 &p_point, const Variant &p_da
}
}
-void VisualScriptEditor::_selected_method(const String &p_method) {
+void VisualScriptEditor::_selected_method(const String &p_method, const String &p_type) {
Ref<VisualScriptFunctionCall> vsfc = script->get_node(edited_func, selecting_method_id);
if (!vsfc.is_valid())
@@ -1962,22 +1970,16 @@ void VisualScriptEditor::_button_resource_previewed(const String &p_path, const
void VisualScriptEditor::apply_code() {
}
-Ref<Script> VisualScriptEditor::get_edited_script() const {
-
+RES VisualScriptEditor::get_edited_resource() const {
return script;
}
-Vector<String> VisualScriptEditor::get_functions() {
-
- return Vector<String>();
-}
+void VisualScriptEditor::set_edited_resource(const RES &p_res) {
-void VisualScriptEditor::set_edited_script(const Ref<Script> &p_script) {
-
- script = p_script;
- signal_editor->script = p_script;
+ script = p_res;
+ signal_editor->script = script;
signal_editor->undo_redo = undo_redo;
- variable_editor->script = p_script;
+ variable_editor->script = script;
variable_editor->undo_redo = undo_redo;
script->connect("node_ports_changed", this, "_node_ports_changed");
@@ -1986,6 +1988,11 @@ void VisualScriptEditor::set_edited_script(const Ref<Script> &p_script) {
_update_available_nodes();
}
+Vector<String> VisualScriptEditor::get_functions() {
+
+ return Vector<String>();
+}
+
void VisualScriptEditor::reload_text() {
}
@@ -2436,33 +2443,19 @@ void VisualScriptEditor::_graph_connect_to_empty(const String &p_from, int p_fro
if (!vsn.is_valid())
return;
- if (p_from_slot < vsn->get_output_sequence_port_count()) {
+ port_action_pos = p_release_pos;
- port_action_popup->clear();
- port_action_popup->add_item(TTR("Condition"), CREATE_COND);
- port_action_popup->add_item(TTR("Sequence"), CREATE_SEQUENCE);
- port_action_popup->add_item(TTR("Switch"), CREATE_SWITCH);
- port_action_popup->add_item(TTR("Iterator"), CREATE_ITERATOR);
- port_action_popup->add_item(TTR("While"), CREATE_WHILE);
- port_action_popup->add_item(TTR("Return"), CREATE_RETURN);
+ if (p_from_slot < vsn->get_output_sequence_port_count()) {
port_action_node = p_from.to_int();
port_action_output = p_from_slot;
-
+ _port_action_menu(CREATE_ACTION);
} else {
- port_action_popup->clear();
- port_action_popup->add_item(TTR("Call"), CREATE_CALL);
- port_action_popup->add_item(TTR("Get"), CREATE_GET);
- port_action_popup->add_item(TTR("Set"), CREATE_SET);
port_action_output = p_from_slot - vsn->get_output_sequence_port_count();
port_action_node = p_from.to_int();
+ _port_action_menu(CREATE_CALL_SET_GET);
}
-
- port_action_pos = p_release_pos;
- port_action_popup->set_size(Size2(1, 1));
- port_action_popup->set_position(graph->get_global_position() + p_release_pos);
- port_action_popup->popup();
}
VisualScriptNode::TypeGuess VisualScriptEditor::_guess_output_type(int p_port_action_node, int p_port_action_output, Set<int> &visited_nodes) {
@@ -2530,168 +2523,202 @@ void VisualScriptEditor::_port_action_menu(int p_option) {
bool seq_connect = false;
- Ref<VisualScriptNode> vnode;
Set<int> vn;
switch (p_option) {
- case CREATE_CALL: {
-
+ case CREATE_CALL_SET_GET: {
Ref<VisualScriptFunctionCall> n;
n.instance();
- vnode = n;
VisualScriptNode::TypeGuess tg = _guess_output_type(port_action_node, port_action_output, vn);
- if (tg.type == Variant::OBJECT) {
- n->set_call_mode(VisualScriptFunctionCall::CALL_MODE_INSTANCE);
-
- if (tg.gdclass != StringName()) {
- n->set_base_type(tg.gdclass);
- } else {
- n->set_base_type("Object");
- }
+ if (tg.gdclass != StringName()) {
+ n->set_base_type(tg.gdclass);
+ } else {
+ n->set_base_type("Object");
+ }
+ String type_string = script->get_node(edited_func, port_action_node)->get_output_value_port_info(port_action_output).hint_string;
+ if (tg.type == Variant::OBJECT) {
if (tg.script.is_valid()) {
- n->set_base_script(tg.script->get_path());
- new_connect_node_select->select_method_from_script(tg.script);
+ new_connect_node_select->select_from_script(tg.script, "");
+ } else if (type_string != String()) {
+ new_connect_node_select->select_from_base_type(type_string);
} else {
- new_connect_node_select->select_method_from_base_type(n->get_base_type());
+ new_connect_node_select->select_from_base_type(n->get_base_type());
}
-
+ } else if (tg.type == Variant::NIL) {
+ new_connect_node_select->select_from_base_type("");
} else {
- n->set_call_mode(VisualScriptFunctionCall::CALL_MODE_BASIC_TYPE);
- n->set_basic_type(tg.type);
- new_connect_node_select->select_method_from_basic_type(tg.type);
+ new_connect_node_select->select_from_basic_type(tg.type);
}
-
} break;
- case CREATE_SET: {
-
- Ref<VisualScriptPropertySet> n;
- n.instance();
- vnode = n;
-
+ case CREATE_ACTION: {
+ seq_connect = true;
VisualScriptNode::TypeGuess tg = _guess_output_type(port_action_node, port_action_output, vn);
-
+ PropertyInfo property_info = script->get_node(edited_func, port_action_node)->get_output_value_port_info(port_action_output);
if (tg.type == Variant::OBJECT) {
- n->set_call_mode(VisualScriptPropertySet::CALL_MODE_INSTANCE);
-
- if (tg.gdclass != StringName()) {
- n->set_base_type(tg.gdclass);
+ if (property_info.type == Variant::OBJECT && property_info.hint_string != String()) {
+ new_connect_node_select->select_from_action(property_info.hint_string);
} else {
- n->set_base_type("Object");
+ new_connect_node_select->select_from_action("");
}
-
- if (tg.script.is_valid()) {
- n->set_base_script(tg.script->get_path());
- new_connect_node_select->select_property_from_script(tg.script);
- } else {
- new_connect_node_select->select_property_from_base_type(n->get_base_type());
- }
-
+ } else if (tg.type == Variant::NIL) {
+ new_connect_node_select->select_from_action("");
} else {
- n->set_call_mode(VisualScriptPropertySet::CALL_MODE_BASIC_TYPE);
- n->set_basic_type(tg.type);
- new_connect_node_select->select_property_from_basic_type(tg.type);
+ new_connect_node_select->select_from_action(Variant::get_type_name(tg.type));
}
} break;
- case CREATE_GET: {
+ }
+}
- Ref<VisualScriptPropertyGet> n;
- n.instance();
- vnode = n;
+void VisualScriptEditor::new_node(Ref<VisualScriptNode> vnode, Vector2 ofs) {
+ Set<int> vn;
+ Ref<VisualScriptNode> vnode_old = script->get_node(edited_func, port_action_node);
+ int new_id = script->get_available_id();
+ undo_redo->create_action(TTR("Add Node"));
+ undo_redo->add_do_method(script.ptr(), "add_node", edited_func, new_id, vnode, ofs);
+ undo_redo->add_undo_method(script.ptr(), "remove_node", edited_func, new_id);
+ undo_redo->add_do_method(this, "_update_graph", new_id);
+ undo_redo->add_undo_method(this, "_update_graph", new_id);
+ undo_redo->commit_action();
- VisualScriptNode::TypeGuess tg = _guess_output_type(port_action_node, port_action_output, vn);
+ port_action_new_node = new_id;
+}
- if (tg.type == Variant::OBJECT) {
- n->set_call_mode(VisualScriptPropertyGet::CALL_MODE_INSTANCE);
+void VisualScriptEditor::connect_data(Ref<VisualScriptNode> vnode_old, Ref<VisualScriptNode> vnode, int new_id) {
+ undo_redo->create_action(TTR("Connect Node Data"));
+ VisualScriptReturn *vnode_return = Object::cast_to<VisualScriptReturn>(vnode.ptr());
+ if (vnode_return != NULL && vnode_old->get_output_value_port_count() > 0) {
+ vnode_return->set_enable_return_value(true);
+ }
+ if (vnode_old->get_output_value_port_count() <= 0) {
+ undo_redo->commit_action();
+ return;
+ }
+ if (vnode->get_input_value_port_count() <= 0) {
+ undo_redo->commit_action();
+ return;
+ }
+ int port = port_action_output;
+ int value_count = vnode_old->get_output_value_port_count();
+ if (port >= value_count) {
+ port = 0;
+ }
+ int count = vnode_old->get_output_value_port_count() + vnode_old->get_output_sequence_port_count();
+ undo_redo->add_do_method(script.ptr(), "data_connect", edited_func, port_action_node, port, new_id, 0);
+ undo_redo->add_undo_method(script.ptr(), "data_disconnect", edited_func, port_action_node, port, new_id, 0);
+ undo_redo->commit_action();
+}
- if (tg.gdclass != StringName()) {
- n->set_base_type(tg.gdclass);
- } else {
- n->set_base_type("Object");
- }
+void VisualScriptEditor::_selected_connect_node(const String &p_text, const String &p_category) {
+ Vector2 ofs = graph->get_scroll_ofs() + port_action_pos;
+ if (graph->is_using_snap()) {
+ int snap = graph->get_snap();
+ ofs = ofs.snapped(Vector2(snap, snap));
+ }
+ ofs /= EDSCALE;
- if (tg.script.is_valid()) {
- n->set_base_script(tg.script->get_path());
- new_connect_node_select->select_property_from_script(tg.script);
- } else {
- new_connect_node_select->select_property_from_base_type(n->get_base_type());
- }
+ Set<int> vn;
+ if (p_category == "visualscript") {
+ Ref<VisualScriptNode> vnode_new = VisualScriptLanguage::singleton->create_node_from_name(p_text);
+ Ref<VisualScriptNode> vnode_old = script->get_node(edited_func, port_action_node);
+ int new_id = script->get_available_id();
+
+ if (Object::cast_to<VisualScriptOperator>(vnode_new.ptr())) {
+ Variant::Type type = script->get_node(edited_func, port_action_node)->get_output_value_port_info(port_action_output).type;
+ Object::cast_to<VisualScriptOperator>(vnode_new.ptr())->set_typed(type);
+ }
+
+ if (Object::cast_to<VisualScriptTypeCast>(vnode_new.ptr())) {
+ Variant::Type type = script->get_node(edited_func, port_action_node)->get_output_value_port_info(port_action_output).type;
+ String hint_name = script->get_node(edited_func, port_action_node)->get_output_value_port_info(port_action_output).hint_string;
+
+ if (type == Variant::OBJECT) {
+ Object::cast_to<VisualScriptTypeCast>(vnode_new.ptr())->set_base_type(hint_name);
+ } else if (type == Variant::NIL) {
+ Object::cast_to<VisualScriptTypeCast>(vnode_new.ptr())->set_base_type("");
} else {
- n->set_call_mode(VisualScriptPropertyGet::CALL_MODE_BASIC_TYPE);
- n->set_basic_type(tg.type);
- new_connect_node_select->select_property_from_basic_type(tg.type);
+ Object::cast_to<VisualScriptTypeCast>(vnode_new.ptr())->set_base_type(Variant::get_type_name(type));
}
+ }
+ undo_redo->create_action(TTR("Add Node"));
+ undo_redo->add_do_method(script.ptr(), "add_node", edited_func, new_id, vnode_new, ofs);
+ connect_seq(vnode_old, vnode_new, new_id);
+ connect_data(vnode_old, vnode_new, new_id);
+ undo_redo->add_undo_method(script.ptr(), "remove_node", edited_func, new_id);
+ undo_redo->add_do_method(this, "_update_graph");
+ undo_redo->add_undo_method(this, "_update_graph");
+ undo_redo->commit_action();
+ return;
+ }
- } break;
- case CREATE_COND: {
+ Ref<VisualScriptNode> vnode;
+
+ seq_connect = false;
+ if (p_category == String("method")) {
+
+ Ref<VisualScriptFunctionCall> n;
+ n.instance();
+ vnode = n;
+ } else if (p_category == String("set")) {
+
+ Ref<VisualScriptPropertySet> n;
+ n.instance();
+ n->set_property(p_text);
+ vnode = n;
+ } else if (p_category == String("get")) {
+
+ Ref<VisualScriptPropertyGet> n;
+ n.instance();
+ n->set_property(p_text);
+ vnode = n;
+ }
+
+ if (p_category == String("action")) {
+ if (p_text == "VisualScriptCondition") {
Ref<VisualScriptCondition> n;
n.instance();
vnode = n;
seq_connect = true;
+ }
+ if (p_text == "VisualScriptSwitch") {
- } break;
- case CREATE_SEQUENCE: {
-
- Ref<VisualScriptSequence> n;
+ Ref<VisualScriptSwitch> n;
n.instance();
vnode = n;
seq_connect = true;
+ } else if (p_text == "VisualScriptSequence") {
- } break;
- case CREATE_SWITCH: {
-
- Ref<VisualScriptSwitch> n;
+ Ref<VisualScriptSequence> n;
n.instance();
vnode = n;
seq_connect = true;
-
- } break;
- case CREATE_ITERATOR: {
+ } else if (p_text == "VisualScriptIterator") {
Ref<VisualScriptIterator> n;
n.instance();
vnode = n;
seq_connect = true;
-
- } break;
- case CREATE_WHILE: {
+ } else if (p_text == "VisualScriptWhile") {
Ref<VisualScriptWhile> n;
n.instance();
vnode = n;
seq_connect = true;
-
- } break;
- case CREATE_RETURN: {
+ } else if (p_text == "VisualScriptReturn") {
Ref<VisualScriptReturn> n;
n.instance();
vnode = n;
seq_connect = true;
-
- } break;
- }
-
- int new_id = script->get_available_id();
- undo_redo->create_action(TTR("Add Node"));
- undo_redo->add_do_method(script.ptr(), "add_node", edited_func, new_id, vnode, ofs);
- if (seq_connect) {
- undo_redo->add_do_method(script.ptr(), "sequence_connect", edited_func, port_action_node, port_action_output, new_id);
+ }
}
- undo_redo->add_undo_method(script.ptr(), "remove_node", edited_func, new_id);
- undo_redo->add_do_method(this, "_update_graph", new_id);
- undo_redo->add_undo_method(this, "_update_graph", new_id);
- undo_redo->commit_action();
- port_action_new_node = new_id;
-}
-
-void VisualScriptEditor::_selected_connect_node_method_or_setget(const String &p_text) {
+ new_node(vnode, ofs);
Ref<VisualScriptNode> vsn = script->get_node(edited_func, port_action_new_node);
@@ -2699,28 +2726,152 @@ void VisualScriptEditor::_selected_connect_node_method_or_setget(const String &p
Ref<VisualScriptFunctionCall> vsfc = vsn;
vsfc->set_function(p_text);
- script->data_connect(edited_func, port_action_node, port_action_output, port_action_new_node, 0);
+
+ VisualScriptNode::TypeGuess tg = _guess_output_type(port_action_node, port_action_output, vn);
+ if (tg.type == Variant::OBJECT) {
+ vsfc->set_call_mode(VisualScriptFunctionCall::CALL_MODE_INSTANCE);
+
+ if (tg.gdclass != StringName()) {
+ vsfc->set_base_type(tg.gdclass);
+
+ } else {
+ PropertyHint hint = script->get_node(edited_func, port_action_node)->get_output_value_port_info(port_action_output).hint;
+ String base_type = script->get_node(edited_func, port_action_node)->get_output_value_port_info(port_action_output).hint_string;
+
+ if (base_type != String() && hint == PROPERTY_HINT_TYPE_STRING) {
+ vsfc->set_base_type(base_type);
+ }
+ if (p_text == "call" || p_text == "call_deferred") {
+ vsfc->set_function("");
+ }
+ }
+ if (tg.script.is_valid()) {
+ vsfc->set_base_script(tg.script->get_path());
+ }
+ } else if (tg.type == Variant::NIL) {
+ vsfc->set_call_mode(VisualScriptFunctionCall::CALL_MODE_INSTANCE);
+ vsfc->set_base_type(script->get_instance_base_type());
+ } else {
+ vsfc->set_call_mode(VisualScriptFunctionCall::CALL_MODE_BASIC_TYPE);
+ vsfc->set_basic_type(tg.type);
+ }
}
if (Object::cast_to<VisualScriptPropertySet>(vsn.ptr())) {
Ref<VisualScriptPropertySet> vsp = vsn;
- vsp->set_property(p_text);
- script->data_connect(edited_func, port_action_node, port_action_output, port_action_new_node, 0);
+
+ VisualScriptNode::TypeGuess tg = _guess_output_type(port_action_node, port_action_output, vn);
+ if (tg.type == Variant::OBJECT) {
+ vsp->set_call_mode(VisualScriptPropertySet::CALL_MODE_INSTANCE);
+
+ if (tg.gdclass != StringName()) {
+ vsp->set_base_type(tg.gdclass);
+
+ } else {
+ PropertyHint hint = script->get_node(edited_func, port_action_node)->get_output_value_port_info(port_action_output).hint;
+ String base_type = script->get_node(edited_func, port_action_node)->get_output_value_port_info(port_action_output).hint_string;
+
+ if (base_type != String() && hint == PROPERTY_HINT_TYPE_STRING) {
+ vsp->set_base_type(base_type);
+ }
+ }
+ if (tg.script.is_valid()) {
+ vsp->set_base_script(tg.script->get_path());
+ }
+ } else if (tg.type == Variant::NIL) {
+ vsp->set_call_mode(VisualScriptPropertySet::CALL_MODE_INSTANCE);
+ vsp->set_base_type(script->get_instance_base_type());
+ } else {
+ vsp->set_call_mode(VisualScriptPropertySet::CALL_MODE_BASIC_TYPE);
+ vsp->set_basic_type(tg.type);
+ }
}
if (Object::cast_to<VisualScriptPropertyGet>(vsn.ptr())) {
-
Ref<VisualScriptPropertyGet> vsp = vsn;
- vsp->set_property(p_text);
- script->data_connect(edited_func, port_action_node, port_action_output, port_action_new_node, 0);
+
+ VisualScriptNode::TypeGuess tg = _guess_output_type(port_action_node, port_action_output, vn);
+ if (tg.type == Variant::OBJECT) {
+ vsp->set_call_mode(VisualScriptPropertyGet::CALL_MODE_INSTANCE);
+
+ if (tg.gdclass != StringName()) {
+ vsp->set_base_type(tg.gdclass);
+
+ } else {
+ PropertyHint hint = script->get_node(edited_func, port_action_node)->get_output_value_port_info(port_action_output).hint;
+ String base_type = script->get_node(edited_func, port_action_node)->get_output_value_port_info(port_action_output).hint_string;
+
+ if (base_type != String() && hint == PROPERTY_HINT_TYPE_STRING) {
+ vsp->set_base_type(base_type);
+ }
+ }
+ if (tg.script.is_valid()) {
+ vsp->set_base_script(tg.script->get_path());
+ }
+ } else if (tg.type == Variant::NIL) {
+ vsp->set_call_mode(VisualScriptPropertyGet::CALL_MODE_INSTANCE);
+ vsp->set_base_type(script->get_instance_base_type());
+ } else {
+ vsp->set_call_mode(VisualScriptPropertyGet::CALL_MODE_BASIC_TYPE);
+ vsp->set_basic_type(tg.type);
+ }
}
+ Ref<VisualScriptNode> vnode_old = script->get_node(edited_func, port_action_node);
+ connect_seq(vnode_old, vnode, port_action_new_node);
+ connect_data(vnode_old, vnode, port_action_new_node);
_update_graph(port_action_new_node);
_update_graph_connections();
}
-void VisualScriptEditor::_selected_new_virtual_method(const String &p_text) {
+void VisualScriptEditor::connect_seq(Ref<VisualScriptNode> vnode_old, Ref<VisualScriptNode> vnode_new, int new_id) {
+ int seq_count = vnode_old->get_output_sequence_port_count();
+ VisualScriptOperator *vnode_operator = Object::cast_to<VisualScriptOperator>(vnode_new.ptr());
+ if (vnode_operator != NULL && vnode_operator->has_input_sequence_port() == false) {
+ return;
+ }
+ VisualScriptConstructor *vnode_constructor = Object::cast_to<VisualScriptConstructor>(vnode_new.ptr());
+ if (vnode_constructor != NULL) {
+ return;
+ }
+ if (vnode_old->get_output_sequence_port_count() <= 0) {
+ return;
+ }
+ if (vnode_new->has_input_sequence_port() == false) {
+ return;
+ }
+ VisualScriptFunction *vnode_function = Object::cast_to<VisualScriptFunction>(vnode_old.ptr());
+ undo_redo->create_action(TTR("Connect Node Sequence"));
+ int pass_port = -vnode_old->get_output_sequence_port_count() + 1;
+ int return_port = port_action_output - 1;
+ if (vnode_old->get_output_value_port_info(port_action_output).name == String("pass") &&
+ !script->get_output_sequence_ports_connected(edited_func, port_action_node).has(pass_port)) {
+ undo_redo->add_do_method(script.ptr(), "sequence_connect", edited_func, port_action_node, pass_port, new_id);
+ undo_redo->add_undo_method(script.ptr(), "sequence_disconnect", edited_func, port_action_node, pass_port, new_id);
+ } else if (vnode_old->get_output_value_port_info(port_action_output).name == String("return") &&
+ !script->get_output_sequence_ports_connected(edited_func, port_action_node).has(return_port)) {
+ undo_redo->add_do_method(script.ptr(), "sequence_connect", edited_func, port_action_node, return_port, new_id);
+ undo_redo->add_undo_method(script.ptr(), "sequence_disconnect", edited_func, port_action_node, return_port, new_id);
+ } else {
+ for (int port = 0; port < vnode_old->get_output_sequence_port_count(); port++) {
+ int count = vnode_old->get_output_sequence_port_count();
+ if (port_action_output < count && !script->get_output_sequence_ports_connected(edited_func, port_action_node).has(port_action_output)) {
+ undo_redo->add_do_method(script.ptr(), "sequence_connect", edited_func, port_action_node, port_action_output, new_id);
+ undo_redo->add_undo_method(script.ptr(), "sequence_disconnect", edited_func, port_action_node, port_action_output, new_id);
+ break;
+ } else if (!script->get_output_sequence_ports_connected(edited_func, port_action_node).has(port)) {
+ undo_redo->add_do_method(script.ptr(), "sequence_connect", edited_func, port_action_node, port, new_id);
+ undo_redo->add_undo_method(script.ptr(), "sequence_disconnect", edited_func, port_action_node, port, new_id);
+ break;
+ }
+ }
+ }
+
+ undo_redo->commit_action();
+}
+
+void VisualScriptEditor::_selected_new_virtual_method(const String &p_text, const String &p_category) {
String name = p_text;
if (script->has_function(name)) {
@@ -2777,12 +2928,29 @@ void VisualScriptEditor::_selected_new_virtual_method(const String &p_text) {
_update_graph();
}
-void VisualScriptEditor::_cancel_connect_node_method_or_setget() {
-
- script->remove_node(edited_func, port_action_new_node);
+void VisualScriptEditor::_cancel_connect_node() {
+ // Causes crashes
+ //script->remove_node(edited_func, port_action_new_node);
_update_graph();
}
+void VisualScriptEditor::_create_new_node(const String &p_text, const String &p_category, const Vector2 &p_point) {
+ Vector2 ofs = graph->get_scroll_ofs() + p_point;
+ if (graph->is_using_snap()) {
+ int snap = graph->get_snap();
+ ofs = ofs.snapped(Vector2(snap, snap));
+ }
+ ofs /= EDSCALE;
+ Ref<VisualScriptNode> vnode = VisualScriptLanguage::singleton->create_node_from_name(p_text);
+ int new_id = script->get_available_id();
+ undo_redo->create_action(TTR("Add Node"));
+ undo_redo->add_do_method(script.ptr(), "add_node", edited_func, new_id, vnode, ofs);
+ undo_redo->add_undo_method(script.ptr(), "remove_node", edited_func, new_id);
+ undo_redo->add_do_method(this, "_update_graph");
+ undo_redo->add_undo_method(this, "_update_graph");
+ undo_redo->commit_action();
+}
+
void VisualScriptEditor::_default_value_changed() {
Ref<VisualScriptNode> vsn = script->get_node(edited_func, editing_id);
@@ -3286,10 +3454,11 @@ void VisualScriptEditor::_bind_methods() {
ClassDB::bind_method("_comment_node_resized", &VisualScriptEditor::_comment_node_resized);
ClassDB::bind_method("_button_resource_previewed", &VisualScriptEditor::_button_resource_previewed);
ClassDB::bind_method("_port_action_menu", &VisualScriptEditor::_port_action_menu);
- ClassDB::bind_method("_selected_connect_node_method_or_setget", &VisualScriptEditor::_selected_connect_node_method_or_setget);
+ ClassDB::bind_method("_selected_connect_node", &VisualScriptEditor::_selected_connect_node);
ClassDB::bind_method("_selected_new_virtual_method", &VisualScriptEditor::_selected_new_virtual_method);
- ClassDB::bind_method("_cancel_connect_node_method_or_setget", &VisualScriptEditor::_cancel_connect_node_method_or_setget);
+ ClassDB::bind_method("_cancel_connect_node", &VisualScriptEditor::_cancel_connect_node);
+ ClassDB::bind_method("_create_new_node", &VisualScriptEditor::_create_new_node);
ClassDB::bind_method("_expression_text_changed", &VisualScriptEditor::_expression_text_changed);
ClassDB::bind_method("get_drag_data_fw", &VisualScriptEditor::get_drag_data_fw);
@@ -3480,25 +3649,21 @@ VisualScriptEditor::VisualScriptEditor() {
add_child(default_value_edit);
default_value_edit->connect("variant_changed", this, "_default_value_changed");
- method_select = memnew(PropertySelector);
+ method_select = memnew(VisualScriptPropertySelector);
add_child(method_select);
method_select->connect("selected", this, "_selected_method");
error_line = -1;
- new_connect_node_select = memnew(PropertySelector);
+ new_connect_node_select = memnew(VisualScriptPropertySelector);
add_child(new_connect_node_select);
- new_connect_node_select->connect("selected", this, "_selected_connect_node_method_or_setget");
- new_connect_node_select->get_cancel()->connect("pressed", this, "_cancel_connect_node_method_or_setget");
+ new_connect_node_select->connect("selected", this, "_selected_connect_node");
+ new_connect_node_select->get_cancel()->connect("pressed", this, "_cancel_connect_node");
- new_virtual_method_select = memnew(PropertySelector);
+ new_virtual_method_select = memnew(VisualScriptPropertySelector);
add_child(new_virtual_method_select);
new_virtual_method_select->connect("selected", this, "_selected_new_virtual_method");
new_virtual_method_select->get_cancel()->connect("pressed", this, "_selected_new_virtual_method");
- port_action_popup = memnew(PopupMenu);
- add_child(port_action_popup);
- port_action_popup->connect("id_pressed", this, "_port_action_menu");
-
member_popup = memnew(PopupMenu);
add_child(member_popup);
members->connect("item_rmb_selected", this, "_member_rmb_selected");
@@ -3515,9 +3680,9 @@ VisualScriptEditor::~VisualScriptEditor() {
memdelete(variable_editor);
}
-static ScriptEditorBase *create_editor(const Ref<Script> &p_script) {
+static ScriptEditorBase *create_editor(const RES &p_resource) {
- if (Object::cast_to<VisualScript>(*p_script)) {
+ if (Object::cast_to<VisualScript>(*p_resource)) {
return memnew(VisualScriptEditor);
}
diff --git a/modules/visual_script/visual_script_editor.h b/modules/visual_script/visual_script_editor.h
index 0bd64d6a1d..0283f7b162 100644
--- a/modules/visual_script/visual_script_editor.h
+++ b/modules/visual_script/visual_script_editor.h
@@ -34,9 +34,9 @@
#include "editor/create_dialog.h"
#include "editor/plugins/script_editor_plugin.h"
#include "editor/property_editor.h"
-#include "editor/property_selector.h"
#include "scene/gui/graph_edit.h"
#include "visual_script.h"
+#include "visual_script_property_selector.h"
class VisualScriptEditorSignalEdit;
class VisualScriptEditorVariableEdit;
@@ -62,15 +62,8 @@ class VisualScriptEditor : public ScriptEditorBase {
enum PortAction {
- CREATE_CALL,
- CREATE_SET,
- CREATE_GET,
- CREATE_COND,
- CREATE_SEQUENCE,
- CREATE_SWITCH,
- CREATE_ITERATOR,
- CREATE_WHILE,
- CREATE_RETURN,
+ CREATE_CALL_SET_GET,
+ CREATE_ACTION,
};
enum MemberAction {
@@ -102,9 +95,9 @@ class VisualScriptEditor : public ScriptEditorBase {
AcceptDialog *edit_signal_dialog;
PropertyEditor *edit_signal_edit;
- PropertySelector *method_select;
- PropertySelector *new_connect_node_select;
- PropertySelector *new_virtual_method_select;
+ VisualScriptPropertySelector *method_select;
+ VisualScriptPropertySelector *new_connect_node_select;
+ VisualScriptPropertySelector *new_virtual_method_select;
VisualScriptEditorVariableEdit *variable_editor;
@@ -162,21 +155,29 @@ class VisualScriptEditor : public ScriptEditorBase {
static Clipboard *clipboard;
- PopupMenu *port_action_popup;
PopupMenu *member_popup;
-
MemberType member_type;
String member_name;
+ bool seq_connect = false;
+
PortAction port_action;
int port_action_node;
int port_action_output;
Vector2 port_action_pos;
int port_action_new_node;
void _port_action_menu(int p_option);
- void _selected_connect_node_method_or_setget(const String &p_text);
- void _cancel_connect_node_method_or_setget();
- void _selected_new_virtual_method(const String &p_text);
+
+ void new_node(Ref<VisualScriptNode> vnode, Vector2 ofs);
+
+ void connect_data(Ref<VisualScriptNode> vnode_old, Ref<VisualScriptNode> vnode, int new_id);
+
+ void _selected_connect_node(const String &p_text, const String &p_category);
+ void connect_seq(Ref<VisualScriptNode> vnode_old, Ref<VisualScriptNode> vnode_new, int new_id);
+
+ void _cancel_connect_node();
+ void _create_new_node(const String &p_text, const String &p_category, const Vector2 &p_point);
+ void _selected_new_virtual_method(const String &p_text, const String &p_category);
int error_line;
@@ -231,7 +232,7 @@ class VisualScriptEditor : public ScriptEditorBase {
void _comment_node_resized(const Vector2 &p_new_size, int p_node);
int selecting_method_id;
- void _selected_method(const String &p_method);
+ void _selected_method(const String &p_method, const String &p_type);
void _draw_color_over_button(Object *obj, Color p_color);
void _button_resource_previewed(const String &p_path, const Ref<Texture> &p_preview, Variant p_ud);
@@ -250,9 +251,9 @@ public:
virtual void set_syntax_highlighter(SyntaxHighlighter *p_highlighter);
virtual void apply_code();
- virtual Ref<Script> get_edited_script() const;
+ virtual RES get_edited_resource() const;
+ virtual void set_edited_resource(const RES &p_res);
virtual Vector<String> get_functions();
- virtual void set_edited_script(const Ref<Script> &p_script);
virtual void reload_text();
virtual String get_name();
virtual Ref<Texture> get_icon();
diff --git a/modules/visual_script/visual_script_flow_control.cpp b/modules/visual_script/visual_script_flow_control.cpp
index ea23ab1b2a..0f58a20c00 100644
--- a/modules/visual_script/visual_script_flow_control.cpp
+++ b/modules/visual_script/visual_script_flow_control.cpp
@@ -767,7 +767,7 @@ PropertyInfo VisualScriptTypeCast::get_input_value_port_info(int p_idx) const {
PropertyInfo VisualScriptTypeCast::get_output_value_port_info(int p_idx) const {
- return PropertyInfo(Variant::OBJECT, "");
+ return PropertyInfo(Variant::OBJECT, "", PROPERTY_HINT_TYPE_STRING, get_base_type());
}
String VisualScriptTypeCast::get_caption() const {
diff --git a/modules/visual_script/visual_script_func_nodes.cpp b/modules/visual_script/visual_script_func_nodes.cpp
index bdf5705ecd..ad886bc758 100644
--- a/modules/visual_script/visual_script_func_nodes.cpp
+++ b/modules/visual_script/visual_script_func_nodes.cpp
@@ -43,7 +43,7 @@
int VisualScriptFunctionCall::get_output_sequence_port_count() const {
- if (method_cache.flags & METHOD_FLAG_CONST || (call_mode == CALL_MODE_BASIC_TYPE && Variant::is_method_const(basic_type, function)))
+ if ((method_cache.flags & METHOD_FLAG_CONST && call_mode != CALL_MODE_INSTANCE) || (call_mode == CALL_MODE_BASIC_TYPE && Variant::is_method_const(basic_type, function)))
return 0;
else
return 1;
@@ -51,7 +51,7 @@ int VisualScriptFunctionCall::get_output_sequence_port_count() const {
bool VisualScriptFunctionCall::has_input_sequence_port() const {
- if (method_cache.flags & METHOD_FLAG_CONST || (call_mode == CALL_MODE_BASIC_TYPE && Variant::is_method_const(basic_type, function)))
+ if ((method_cache.flags & METHOD_FLAG_CONST && call_mode != CALL_MODE_INSTANCE) || (call_mode == CALL_MODE_BASIC_TYPE && Variant::is_method_const(basic_type, function)))
return false;
else
return true;
@@ -231,7 +231,7 @@ PropertyInfo VisualScriptFunctionCall::get_output_value_port_info(int p_idx) con
if (call_mode == CALL_MODE_INSTANCE) {
if (p_idx == 0) {
- return PropertyInfo(Variant::OBJECT, "pass");
+ return PropertyInfo(Variant::OBJECT, "pass", PROPERTY_HINT_TYPE_STRING, get_base_type());
} else {
p_idx--;
}
@@ -1055,7 +1055,7 @@ PropertyInfo VisualScriptPropertySet::get_output_value_port_info(int p_idx) cons
if (call_mode == CALL_MODE_BASIC_TYPE) {
return PropertyInfo(basic_type, "out");
} else if (call_mode == CALL_MODE_INSTANCE) {
- return PropertyInfo(Variant::OBJECT, "pass");
+ return PropertyInfo(Variant::OBJECT, "pass", PROPERTY_HINT_TYPE_STRING, get_base_type());
} else {
return PropertyInfo();
}
diff --git a/modules/visual_script/visual_script_nodes.cpp b/modules/visual_script/visual_script_nodes.cpp
index 8b7b809ec0..f174300a4c 100644
--- a/modules/visual_script/visual_script_nodes.cpp
+++ b/modules/visual_script/visual_script_nodes.cpp
@@ -2198,7 +2198,7 @@ PropertyInfo VisualScriptSceneTree::get_input_value_port_info(int p_idx) const {
PropertyInfo VisualScriptSceneTree::get_output_value_port_info(int p_idx) const {
- return PropertyInfo(Variant::OBJECT, "Scene Tree");
+ return PropertyInfo(Variant::OBJECT, "Scene Tree", PROPERTY_HINT_TYPE_STRING, "SceneTree");
}
String VisualScriptSceneTree::get_caption() const {
diff --git a/modules/visual_script/visual_script_property_selector.cpp b/modules/visual_script/visual_script_property_selector.cpp
new file mode 100644
index 0000000000..994ea1f791
--- /dev/null
+++ b/modules/visual_script/visual_script_property_selector.cpp
@@ -0,0 +1,705 @@
+/*************************************************************************/
+/* visual_script_property_selector.cpp */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md) */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+
+#include "visual_script_property_selector.h"
+
+#include "editor_scale.h"
+#include "modules/visual_script/visual_script.h"
+#include "modules/visual_script/visual_script_builtin_funcs.h"
+#include "modules/visual_script/visual_script_flow_control.h"
+#include "modules/visual_script/visual_script_func_nodes.h"
+#include "modules/visual_script/visual_script_nodes.h"
+#include "os/keyboard.h"
+#include "scene/main/node.h"
+#include "scene/main/viewport.h"
+
+void VisualScriptPropertySelector::_text_changed(const String &p_newtext) {
+ _update_search();
+}
+
+void VisualScriptPropertySelector::_sbox_input(const Ref<InputEvent> &p_ie) {
+
+ Ref<InputEventKey> k = p_ie;
+
+ if (k.is_valid()) {
+
+ switch (k->get_scancode()) {
+ case KEY_UP:
+ case KEY_DOWN:
+ case KEY_PAGEUP:
+ case KEY_PAGEDOWN: {
+
+ search_options->call("_gui_input", k);
+ search_box->accept_event();
+
+ TreeItem *root = search_options->get_root();
+ if (!root->get_children())
+ break;
+
+ TreeItem *current = search_options->get_selected();
+
+ TreeItem *item = search_options->get_next_selected(root);
+ while (item) {
+ item->deselect(0);
+ item = search_options->get_next_selected(item);
+ }
+
+ current->select(0);
+
+ } break;
+ }
+ }
+}
+
+void VisualScriptPropertySelector::_update_search() {
+ set_title(TTR("Search VisualScript"));
+
+ search_options->clear();
+ help_bit->set_text("");
+
+ TreeItem *root = search_options->create_item();
+ bool found = false;
+
+ if (properties) {
+
+ List<PropertyInfo> props;
+
+ if (instance) {
+ instance->get_property_list(&props, true);
+ } else if (type != Variant::NIL) {
+ Variant v;
+ Variant::CallError ce;
+ v = Variant::construct(type, NULL, 0, ce);
+
+ v.get_property_list(&props);
+ } else {
+
+ Object *obj = ObjectDB::get_instance(script);
+ if (Object::cast_to<Script>(obj)) {
+
+ props.push_back(PropertyInfo(Variant::NIL, "Script Variables", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_CATEGORY));
+ Object::cast_to<Script>(obj)->get_script_property_list(&props);
+ }
+
+ StringName base = base_type;
+ while (base) {
+ props.push_back(PropertyInfo(Variant::NIL, base, PROPERTY_HINT_NONE, "", PROPERTY_USAGE_CATEGORY));
+ ClassDB::get_property_list(base, &props, true);
+ base = ClassDB::get_parent_class_nocheck(base);
+ }
+ }
+
+ TreeItem *category = NULL;
+
+ Ref<Texture> type_icons[Variant::VARIANT_MAX] = {
+ Control::get_icon("Variant", "EditorIcons"),
+ Control::get_icon("bool", "EditorIcons"),
+ Control::get_icon("int", "EditorIcons"),
+ Control::get_icon("float", "EditorIcons"),
+ Control::get_icon("String", "EditorIcons"),
+ Control::get_icon("Vector2", "EditorIcons"),
+ Control::get_icon("Rect2", "EditorIcons"),
+ Control::get_icon("Vector3", "EditorIcons"),
+ Control::get_icon("Transform2D", "EditorIcons"),
+ Control::get_icon("Plane", "EditorIcons"),
+ Control::get_icon("Quat", "EditorIcons"),
+ Control::get_icon("AABB", "EditorIcons"),
+ Control::get_icon("Basis", "EditorIcons"),
+ Control::get_icon("Transform", "EditorIcons"),
+ Control::get_icon("Color", "EditorIcons"),
+ Control::get_icon("Path", "EditorIcons"),
+ Control::get_icon("RID", "EditorIcons"),
+ Control::get_icon("Object", "EditorIcons"),
+ Control::get_icon("Dictionary", "EditorIcons"),
+ Control::get_icon("Array", "EditorIcons"),
+ Control::get_icon("PoolByteArray", "EditorIcons"),
+ Control::get_icon("PoolIntArray", "EditorIcons"),
+ Control::get_icon("PoolRealArray", "EditorIcons"),
+ Control::get_icon("PoolStringArray", "EditorIcons"),
+ Control::get_icon("PoolVector2Array", "EditorIcons"),
+ Control::get_icon("PoolVector3Array", "EditorIcons"),
+ Control::get_icon("PoolColorArray", "EditorIcons")
+ };
+
+ if (!seq_connect && visual_script_generic == false) {
+ get_visual_node_names("flow_control/type_cast", Set<String>(), found, root, search_box);
+ get_visual_node_names("functions/built_in/print", Set<String>(), found, root, search_box);
+ get_visual_node_names("functions/by_type/" + Variant::get_type_name(type), Set<String>(), found, root, search_box);
+ get_visual_node_names("operators/compare/", Set<String>(), found, root, search_box);
+ if (type == Variant::INT) {
+ get_visual_node_names("operators/bitwise/", Set<String>(), found, root, search_box);
+ }
+ if (type == Variant::BOOL) {
+ get_visual_node_names("operators/logic/", Set<String>(), found, root, search_box);
+ }
+ if (type == Variant::BOOL || type == Variant::INT || type == Variant::REAL || type == Variant::VECTOR2 || type == Variant::VECTOR3) {
+ get_visual_node_names("operators/math/", Set<String>(), found, root, search_box);
+ }
+ }
+
+ for (List<PropertyInfo>::Element *E = props.front(); E; E = E->next()) {
+ if (E->get().usage == PROPERTY_USAGE_CATEGORY) {
+ if (category && category->get_children() == NULL) {
+ memdelete(category); //old category was unused
+ }
+ category = search_options->create_item(root);
+ category->set_text(0, E->get().name);
+ category->set_selectable(0, false);
+
+ Ref<Texture> icon;
+ if (E->get().name == "Script Variables") {
+ icon = get_icon("Script", "EditorIcons");
+ } else if (has_icon(E->get().name, "EditorIcons")) {
+ icon = get_icon(E->get().name, "EditorIcons");
+ } else {
+ icon = get_icon("Object", "EditorIcons");
+ }
+ category->set_icon(0, icon);
+ continue;
+ }
+
+ if (!(E->get().usage & PROPERTY_USAGE_EDITOR) && !(E->get().usage & PROPERTY_USAGE_SCRIPT_VARIABLE))
+ continue;
+
+ if (type_filter.size() && type_filter.find(E->get().type) == -1)
+ continue;
+
+ String get_text_raw = String(TTR("Get")) + String(" ") + E->get().name;
+ String get_text = get_text_raw.capitalize();
+
+ String set_text_raw = String(TTR("Set ")) + String(" ") + E->get().name;
+ String set_text = set_text_raw.capitalize();
+ String input = search_box->get_text().capitalize();
+ if (input == String() ||
+ get_text_raw.findn(input) != -1 ||
+ get_text.findn(input) != -1) {
+ TreeItem *item = search_options->create_item(category ? category : root);
+ item->set_text(0, get_text);
+ item->set_metadata(0, E->get().name);
+ item->set_icon(0, type_icons[E->get().type]);
+ item->set_metadata(1, "get");
+ item->set_collapsed(1);
+ item->set_selectable(1, false);
+ item->set_selectable(0, true);
+ }
+
+ if (input == String() ||
+ set_text_raw.findn(input) != -1 &&
+ set_text.findn(input) != -1) {
+ TreeItem *item = search_options->create_item(category ? category : root);
+ item->set_text(0, set_text);
+ item->set_metadata(0, E->get().name);
+ item->set_icon(0, type_icons[E->get().type]);
+ item->set_metadata(1, "set");
+ item->set_selectable(1, false);
+ item->set_selectable(0, true);
+ }
+ }
+ }
+
+ if (seq_connect == true && visual_script_generic == false) {
+ String text = search_box->get_text();
+ create_visualscript_item(String("VisualScriptCondition"), root, text, String("Condition"));
+ create_visualscript_item(String("VisualScriptSwitch"), root, text, String("Switch"));
+ create_visualscript_item(String("VisualScriptSequence"), root, text, String("Sequence"));
+ create_visualscript_item(String("VisualScriptIterator"), root, text, String("Iterator"));
+ create_visualscript_item(String("VisualScriptWhile"), root, text, String("While"));
+ create_visualscript_item(String("VisualScriptReturn"), root, text, String("Return"));
+ get_visual_node_names("flow_control/type_cast", Set<String>(), found, root, search_box);
+ get_visual_node_names("functions/built_in/print", Set<String>(), found, root, search_box);
+ }
+
+ if (visual_script_generic) {
+ get_visual_node_names("", Set<String>(), found, root, search_box);
+ }
+
+ List<MethodInfo> methods;
+
+ if (type != Variant::NIL) {
+ Variant v;
+ Variant::CallError ce;
+ v = Variant::construct(type, NULL, 0, ce);
+ v.get_method_list(&methods);
+ } else {
+
+ Object *obj = ObjectDB::get_instance(script);
+ if (Object::cast_to<Script>(obj)) {
+
+ methods.push_back(MethodInfo("*Script Methods"));
+ Object::cast_to<Script>(obj)->get_script_method_list(&methods);
+ }
+
+ StringName base = base_type;
+ while (base) {
+ methods.push_back(MethodInfo("*" + String(base)));
+ ClassDB::get_method_list(base, &methods, true, true);
+ base = ClassDB::get_parent_class_nocheck(base);
+ }
+ }
+ TreeItem *category = NULL;
+ bool script_methods = false;
+
+ for (List<MethodInfo>::Element *E = methods.front(); E; E = E->next()) {
+ if (E->get().name.begins_with("*")) {
+ if (category && category->get_children() == NULL) {
+ memdelete(category); //old category was unused
+ }
+ category = search_options->create_item(root);
+ category->set_text(0, E->get().name.replace_first("*", ""));
+ category->set_selectable(0, false);
+
+ Ref<Texture> icon;
+ script_methods = false;
+ print_line("name: " + E->get().name);
+ String rep = E->get().name.replace("*", "");
+ if (E->get().name == "*Script Methods") {
+ icon = get_icon("Script", "EditorIcons");
+ script_methods = true;
+ } else if (has_icon(rep, "EditorIcons")) {
+ icon = get_icon(rep, "EditorIcons");
+ } else {
+ icon = get_icon("Object", "EditorIcons");
+ }
+ category->set_icon(0, icon);
+
+ continue;
+ }
+
+ String name = E->get().name.get_slice(":", 0);
+ if (!script_methods && name.begins_with("_") && !(E->get().flags & METHOD_FLAG_VIRTUAL))
+ continue;
+
+ if (virtuals_only && !(E->get().flags & METHOD_FLAG_VIRTUAL))
+ continue;
+
+ if (!virtuals_only && (E->get().flags & METHOD_FLAG_VIRTUAL))
+ continue;
+
+ MethodInfo mi = E->get();
+ String desc = mi.name.capitalize() + " (";
+
+ if (search_box->get_text() != String() &&
+ name.findn(search_box->get_text()) == -1 &&
+ desc.findn(search_box->get_text()) == -1)
+ continue;
+
+ TreeItem *item = search_options->create_item(category ? category : root);
+
+ for (int i = 0; i < mi.arguments.size(); i++) {
+
+ if (i > 0)
+ desc += ", ";
+
+ if (mi.arguments[i].type == Variant::NIL)
+ desc += "var";
+ else if (mi.arguments[i].name.find(":") != -1) {
+ desc += mi.arguments[i].name.get_slice(":", 1);
+ mi.arguments[i].name = mi.arguments[i].name.get_slice(":", 0);
+ } else
+ desc += Variant::get_type_name(mi.arguments[i].type);
+ }
+
+ desc += ")";
+
+ item->set_text(0, desc);
+ item->set_icon(0, get_icon("MemberMethod", "EditorIcons"));
+ item->set_metadata(0, name);
+ item->set_selectable(0, true);
+
+ item->set_metadata(1, "method");
+ item->set_collapsed(1);
+ item->set_selectable(1, false);
+
+ if (category && category->get_children() == NULL) {
+ memdelete(category); //old category was unused
+ }
+ }
+
+ TreeItem *selected_item = search_options->search_item_text(search_box->get_text());
+ if (!found && selected_item != NULL) {
+ selected_item->select(0);
+ found = true;
+ }
+
+ get_ok()->set_disabled(root->get_children() == NULL);
+}
+
+void VisualScriptPropertySelector::create_visualscript_item(const String &name, TreeItem *const root, const String &search_input, const String &text) {
+ if (search_input == String() || text.findn(search_input) != -1) {
+ TreeItem *item = search_options->create_item(root);
+ item->set_text(0, text);
+ item->set_icon(0, get_icon("VisualScript", "EditorIcons"));
+ item->set_metadata(0, name);
+ item->set_metadata(1, "action");
+ item->set_selectable(0, true);
+ item->set_collapsed(1);
+ item->set_selectable(1, false);
+ }
+}
+
+void VisualScriptPropertySelector::get_visual_node_names(const String &root_filter, const Set<String> &filter, bool &found, TreeItem *const root, LineEdit *const search_box) {
+ Map<String, TreeItem *> path_cache;
+
+ List<String> fnodes;
+ VisualScriptLanguage::singleton->get_registered_node_names(&fnodes);
+
+ for (List<String>::Element *E = fnodes.front(); E; E = E->next()) {
+ if (!E->get().begins_with(root_filter)) {
+ continue;
+ }
+ Vector<String> path = E->get().split("/");
+ bool is_filter = false;
+ for (Set<String>::Element *E = filter.front(); E; E = E->next()) {
+ if (path.size() >= 2 && path[1].findn(E->get()) != -1) {
+ is_filter = true;
+ break;
+ }
+ }
+ if (is_filter == true) {
+ continue;
+ }
+
+ if (search_box->get_text() != String() && E->get().findn(search_box->get_text()) == -1) {
+ continue;
+ }
+ TreeItem *item = search_options->create_item(root);
+ VisualScriptOperator *vnode_operator = Object::cast_to<VisualScriptOperator>(*VisualScriptLanguage::singleton->create_node_from_name(E->get()));
+ String type_name;
+ if (vnode_operator != NULL) {
+ String type;
+ if (path.size() >= 2) {
+ type = path[1];
+ }
+ type_name = type.capitalize() + " ";
+ }
+ VisualScriptFunctionCall *vnode_function_call = Object::cast_to<VisualScriptFunctionCall>(*VisualScriptLanguage::singleton->create_node_from_name(E->get()));
+ if (vnode_function_call != NULL) {
+ String basic_type = Variant::get_type_name(vnode_function_call->get_basic_type());
+ type_name = basic_type.capitalize() + " ";
+ }
+ VisualScriptBuiltinFunc *vnode_builtin_function_call = Object::cast_to<VisualScriptBuiltinFunc>(*VisualScriptLanguage::singleton->create_node_from_name(E->get()));
+ if (vnode_builtin_function_call != NULL) {
+ type_name = "Builtin ";
+ }
+ item->set_text(0, type_name + path[path.size() - 1].capitalize());
+ item->set_icon(0, get_icon("VisualScript", "EditorIcons"));
+ item->set_selectable(0, true);
+ item->set_metadata(0, E->get());
+ item->set_selectable(0, true);
+ item->set_metadata(1, "visualscript");
+ item->set_selectable(1, false);
+ }
+}
+
+void VisualScriptPropertySelector::_confirmed() {
+
+ TreeItem *ti = search_options->get_selected();
+ if (!ti)
+ return;
+ emit_signal("selected", ti->get_metadata(0), ti->get_metadata(1));
+ hide();
+}
+
+void VisualScriptPropertySelector::_item_selected() {
+
+ help_bit->set_text("");
+
+ TreeItem *item = search_options->get_selected();
+ if (!item)
+ return;
+ String name = item->get_metadata(0);
+
+ String class_type;
+ if (type) {
+ class_type = Variant::get_type_name(type);
+
+ } else {
+ class_type = base_type;
+ }
+
+ DocData *dd = EditorHelp::get_doc_data();
+ String text;
+
+ String at_class = class_type;
+
+ while (at_class != String()) {
+
+ Map<String, DocData::ClassDoc>::Element *E = dd->class_list.find(at_class);
+ if (E) {
+ for (int i = 0; i < E->get().properties.size(); i++) {
+ if (E->get().properties[i].name == name) {
+ text = E->get().properties[i].description;
+ }
+ }
+ }
+
+ at_class = ClassDB::get_parent_class_nocheck(at_class);
+ }
+ at_class = class_type;
+
+ while (at_class != String()) {
+
+ Map<String, DocData::ClassDoc>::Element *E = dd->class_list.find(at_class);
+ if (E) {
+ for (int i = 0; i < E->get().methods.size(); i++) {
+ if (E->get().methods[i].name == name) {
+ text = E->get().methods[i].description;
+ }
+ }
+ }
+
+ at_class = ClassDB::get_parent_class_nocheck(at_class);
+ }
+ Map<String, DocData::ClassDoc>::Element *E = dd->class_list.find(class_type);
+ if (E) {
+ for (int i = 0; i < E->get().methods.size(); i++) {
+ Vector<String> functions = name.rsplit("/", false, 1);
+ if (E->get().methods[i].name == functions[functions.size() - 1]) {
+ text = E->get().methods[i].description;
+ }
+ }
+ }
+
+ List<String> *names = memnew(List<String>);
+ VisualScriptLanguage::singleton->get_registered_node_names(names);
+ if (names->find(name) != NULL) {
+ Ref<VisualScriptOperator> operator_node = VisualScriptLanguage::singleton->create_node_from_name(name);
+ if (operator_node.is_valid()) {
+ Map<String, DocData::ClassDoc>::Element *E = dd->class_list.find(operator_node->get_class_name());
+ if (E) {
+ text = Variant::get_operator_name(operator_node->get_operator());
+ }
+ }
+ Ref<VisualScriptTypeCast> typecast_node = VisualScriptLanguage::singleton->create_node_from_name(name);
+ if (typecast_node.is_valid()) {
+ Map<String, DocData::ClassDoc>::Element *E = dd->class_list.find(typecast_node->get_class_name());
+ if (E) {
+ text = E->get().description;
+ }
+ }
+
+ Ref<VisualScriptBuiltinFunc> builtin_node = VisualScriptLanguage::singleton->create_node_from_name(name);
+ if (builtin_node.is_valid()) {
+ Map<String, DocData::ClassDoc>::Element *E = dd->class_list.find(builtin_node->get_class_name());
+ if (E) {
+ for (int i = 0; i < E->get().constants.size(); i++) {
+ if (E->get().constants[i].value.to_int() == int(builtin_node->get_func())) {
+ text = E->get().constants[i].description;
+ }
+ }
+ }
+ }
+ }
+
+ memdelete(names);
+
+ if (text == String())
+ return;
+
+ help_bit->set_text(text);
+}
+
+void VisualScriptPropertySelector::_notification(int p_what) {
+
+ if (p_what == NOTIFICATION_ENTER_TREE) {
+
+ connect("confirmed", this, "_confirmed");
+ }
+}
+
+void VisualScriptPropertySelector::select_method_from_base_type(const String &p_base, const String &p_current, bool p_virtuals_only) {
+
+ base_type = p_base;
+ selected = p_current;
+ type = Variant::NIL;
+ script = 0;
+ properties = false;
+ instance = NULL;
+ virtuals_only = p_virtuals_only;
+
+ show_window(.5f);
+ search_box->set_text("");
+ search_box->grab_focus();
+ _update_search();
+}
+
+void VisualScriptPropertySelector::set_type_filter(const Vector<Variant::Type> &p_type_filter) {
+ type_filter = p_type_filter;
+}
+
+void VisualScriptPropertySelector::select_from_base_type(const String &p_base, const String &p_current /*= ""*/, bool p_virtuals_only /*= false*/, bool p_seq_connect /*= false*/) {
+
+ base_type = p_base;
+ selected = p_current;
+ type = Variant::NIL;
+ script = 0;
+ properties = true;
+ instance = NULL;
+ virtuals_only = p_virtuals_only;
+
+ show_window(.5f);
+ search_box->set_text("");
+ search_box->grab_focus();
+ seq_connect = p_seq_connect;
+
+ _update_search();
+}
+
+void VisualScriptPropertySelector::select_from_script(const Ref<Script> &p_script, const String &p_current /*= ""*/) {
+ ERR_FAIL_COND(p_script.is_null());
+
+ base_type = p_script->get_instance_base_type();
+ selected = p_current;
+ type = Variant::NIL;
+ script = p_script->get_instance_id();
+ properties = true;
+ instance = NULL;
+ virtuals_only = false;
+
+ show_window(.5f);
+ search_box->set_text("");
+ search_box->grab_focus();
+ seq_connect = false;
+
+ _update_search();
+}
+
+void VisualScriptPropertySelector::select_from_basic_type(Variant::Type p_type, const String &p_current /*= ""*/) {
+ ERR_FAIL_COND(p_type == Variant::NIL);
+ base_type = "";
+ selected = p_current;
+ type = p_type;
+ script = 0;
+ properties = true;
+ instance = NULL;
+ virtuals_only = false;
+
+ show_window(.5f);
+ search_box->set_text("");
+ search_box->grab_focus();
+ seq_connect = false;
+
+ _update_search();
+}
+
+void VisualScriptPropertySelector::select_from_action(const String &p_type, const String &p_current /*= ""*/) {
+ base_type = p_type;
+ selected = p_current;
+ type = Variant::NIL;
+ script = 0;
+ properties = false;
+ instance = NULL;
+ virtuals_only = false;
+
+ show_window(.5f);
+ search_box->set_text("");
+ search_box->grab_focus();
+ seq_connect = true;
+ _update_search();
+}
+
+void VisualScriptPropertySelector::select_from_instance(Object *p_instance, const String &p_current /*= ""*/) {
+ base_type = "";
+ selected = p_current;
+ type = Variant::NIL;
+ script = 0;
+ properties = true;
+ instance = p_instance;
+ virtuals_only = false;
+
+ show_window(.5f);
+ search_box->set_text("");
+ search_box->grab_focus();
+ seq_connect = false;
+
+ _update_search();
+}
+
+void VisualScriptPropertySelector::select_from_visual_script(const String &p_base) {
+ base_type = p_base;
+ selected = "";
+ type = Variant::NIL;
+ script = 0;
+ properties = true;
+ visual_script_generic = true;
+ instance = NULL;
+ virtuals_only = false;
+ show_window(.5f);
+ search_box->set_text("");
+ search_box->grab_focus();
+
+ _update_search();
+}
+
+void VisualScriptPropertySelector::show_window(float p_screen_ratio) {
+ Rect2 rect;
+ Point2 window_size = get_viewport_rect().size;
+ rect.size = (window_size * p_screen_ratio).floor();
+ rect.size.x = rect.size.x / 1.25f;
+ rect.position = ((window_size - rect.size) / 2.0f).floor();
+ popup(rect);
+}
+
+void VisualScriptPropertySelector::_bind_methods() {
+
+ ClassDB::bind_method(D_METHOD("_text_changed"), &VisualScriptPropertySelector::_text_changed);
+ ClassDB::bind_method(D_METHOD("_confirmed"), &VisualScriptPropertySelector::_confirmed);
+ ClassDB::bind_method(D_METHOD("_sbox_input"), &VisualScriptPropertySelector::_sbox_input);
+ ClassDB::bind_method(D_METHOD("_item_selected"), &VisualScriptPropertySelector::_item_selected);
+
+ ADD_SIGNAL(MethodInfo("selected", PropertyInfo(Variant::STRING, "name")));
+}
+
+VisualScriptPropertySelector::VisualScriptPropertySelector() {
+
+ VBoxContainer *vbc = memnew(VBoxContainer);
+ add_child(vbc);
+ //set_child_rect(vbc);
+ search_box = memnew(LineEdit);
+ vbc->add_margin_child(TTR("Search:"), search_box);
+ search_box->connect("text_changed", this, "_text_changed");
+ search_box->connect("gui_input", this, "_sbox_input");
+ search_options = memnew(Tree);
+ vbc->add_margin_child(TTR("Matches:"), search_options, true);
+ get_ok()->set_text(TTR("Open"));
+ get_ok()->set_disabled(true);
+ register_text_enter(search_box);
+ set_hide_on_ok(false);
+ search_options->connect("item_activated", this, "_confirmed");
+ search_options->connect("cell_selected", this, "_item_selected");
+ search_options->set_hide_root(true);
+ search_options->set_hide_folding(true);
+ virtuals_only = false;
+ help_bit = memnew(EditorHelpBit);
+ vbc->add_margin_child(TTR("Description:"), help_bit);
+ help_bit->connect("request_hide", this, "_closed");
+ search_options->set_columns(2);
+ search_options->set_column_expand(1, false);
+}
diff --git a/modules/visual_script/visual_script_property_selector.h b/modules/visual_script/visual_script_property_selector.h
new file mode 100644
index 0000000000..ec536f86a8
--- /dev/null
+++ b/modules/visual_script/visual_script_property_selector.h
@@ -0,0 +1,92 @@
+/*************************************************************************/
+/* visual_script_property_selector.h */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md) */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+
+#ifndef VISUALSCRIPT_PROPERTYSELECTOR_H
+#define VISUALSCRIPT_PROPERTYSELECTOR_H
+
+#include "editor/property_editor.h"
+#include "editor_help.h"
+#include "scene/gui/rich_text_label.h"
+
+class VisualScriptPropertySelector : public ConfirmationDialog {
+ GDCLASS(VisualScriptPropertySelector, ConfirmationDialog)
+
+ LineEdit *search_box;
+ Tree *search_options;
+
+ void _update_search();
+
+ void create_visualscript_item(const String &name, TreeItem *const root, const String &search_input, const String &text);
+
+ void get_visual_node_names(const String &root_filter, const Set<String> &filter, bool &found, TreeItem *const root, LineEdit *const search_box);
+
+ void _sbox_input(const Ref<InputEvent> &p_ie);
+
+ void _confirmed();
+ void _text_changed(const String &p_newtext);
+
+ EditorHelpBit *help_bit;
+
+ bool properties;
+ bool visual_script_generic;
+ String selected;
+ Variant::Type type;
+ String base_type;
+ ObjectID script;
+ Object *instance;
+ bool virtuals_only;
+
+ bool seq_connect = false;
+
+ void _item_selected();
+
+ Vector<Variant::Type> type_filter;
+
+protected:
+ void _notification(int p_what);
+ static void _bind_methods();
+
+public:
+ void select_method_from_base_type(const String &p_base, const String &p_current = "", bool p_virtuals_only = false);
+ void select_from_base_type(const String &p_base, const String &p_current = "", bool p_virtuals_only = false, bool p_seq_connect = false);
+ void select_from_script(const Ref<Script> &p_script, const String &p_current /*= ""*/);
+ void select_from_basic_type(Variant::Type p_type, const String &p_current = "");
+ void select_from_action(const String &p_type, const String &p_current = "");
+ void select_from_instance(Object *p_instance, const String &p_current = "");
+ void select_from_visual_script(const String &p_base);
+
+ void show_window(float p_screen_ratio);
+
+ void set_type_filter(const Vector<Variant::Type> &p_type_filter);
+
+ VisualScriptPropertySelector();
+};
+
+#endif // VISUALSCRIPT_PROPERTYSELECTOR_H
diff --git a/modules/websocket/emws_client.cpp b/modules/websocket/emws_client.cpp
index 1405fa98b0..00c36ebb47 100644
--- a/modules/websocket/emws_client.cpp
+++ b/modules/websocket/emws_client.cpp
@@ -64,7 +64,6 @@ Error EMWSClient::connect_to_host(String p_host, String p_path, uint16_t p_port,
String str = "ws://";
String proto_string = "";
- int i = 0;
if (p_ssl)
str = "wss://";