Browse Source

Add typed instructions to GDScript

- Typed assignment (built-in, native, and script).
- Cast (built-in conversion; native and script checks).
- Check type of functions arguments on call.
- Check type of members on set.
George Marques 7 years ago
parent
commit
4b18c4e448

+ 5 - 1
modules/gdscript/gdscript.cpp

@@ -941,8 +941,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;
 		}
 	}

+ 165 - 6
modules/gdscript/gdscript_compiler.cpp

@@ -423,7 +423,80 @@ int GDScriptCompiler::_parse_expression(CodeGen &codegen, const GDScriptParser::
 		} break;
 		case GDScriptParser::Node::TYPE_CAST: {
 			const GDScriptParser::CastNode *cn = static_cast<const GDScriptParser::CastNode *>(p_expression);
-			return _parse_expression(codegen, cn->source_node, p_stack_level);
+
+			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
@@ -1051,12 +1124,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: {
 
@@ -1513,6 +1661,17 @@ Error GDScriptCompiler::_parse_function(GDScript *p_script, const GDScriptParser
 		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
@@ -1843,7 +2002,6 @@ Error GDScriptCompiler::_parse_class_level(GDScript *p_script, GDScript *p_owner
 		p_script->subclasses.insert(name, subclass);
 	}
 
-	p_script->valid = true;
 	return OK;
 }
 
@@ -1950,6 +2108,7 @@ Error GDScriptCompiler::_parse_class_blocks(GDScript *p_script, const GDScriptPa
 		}
 	}
 
+	p_script->valid = true;
 	return OK;
 }
 

+ 220 - 3
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);

+ 79 - 0
modules/gdscript/gdscript_function.h

@@ -54,6 +54,79 @@ struct GDScriptDataType {
 	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) {}
 };
@@ -72,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,