Jelajahi Sumber

Merge pull request #19264 from vnen/typed-gdscript-final

Typed GDScript
Rémi Verschelde 7 tahun lalu
induk
melakukan
92415365c8

+ 1 - 1
core/script_language.h

@@ -213,7 +213,7 @@ public:
 	virtual Ref<Script> get_template(const String &p_class_name, const String &p_base_class_name) const = 0;
 	virtual void make_template(const String &p_class_name, const String &p_base_class_name, Ref<Script> &p_script) {}
 	virtual bool is_using_templates() { return false; }
-	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 = 0;
+	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 = 0;
 	virtual String validate_path(const String &p_path) const { return ""; }
 	virtual Script *create_script() const = 0;
 	virtual bool has_named_classes() const = 0;

+ 3 - 1
editor/connections_dialog.cpp

@@ -820,7 +820,9 @@ void ConnectionsDock::update_tree() {
 					if (i > 0)
 						signaldesc += ", ";
 					String tname = "var";
-					if (pi.type != Variant::NIL) {
+					if (pi.type == Variant::OBJECT && pi.class_name != StringName()) {
+						tname = pi.class_name.operator String();
+					} else if (pi.type != Variant::NIL) {
 						tname = Variant::get_type_name(pi.type);
 					}
 					signaldesc += tname + " " + (pi.name == "" ? String("arg " + itos(i)) : pi.name);

+ 3 - 0
editor/editor_settings.cpp

@@ -364,6 +364,7 @@ void EditorSettings::_load_defaults(Ref<ConfigFile> p_extra_config) {
 
 	_initial_set("text_editor/highlighting/highlight_all_occurrences", true);
 	_initial_set("text_editor/highlighting/highlight_current_line", true);
+	_initial_set("text_editor/highlighting/highlight_type_safe_lines", true);
 	_initial_set("text_editor/cursor/scroll_past_end_of_file", false);
 
 	_initial_set("text_editor/indent/type", 0);
@@ -404,6 +405,7 @@ void EditorSettings::_load_defaults(Ref<ConfigFile> p_extra_config) {
 	_initial_set("text_editor/completion/callhint_tooltip_offset", Vector2());
 	_initial_set("text_editor/files/restore_scripts_on_load", true);
 	_initial_set("text_editor/completion/complete_file_paths", true);
+	_initial_set("text_editor/completion/add_type_hints", false);
 
 	_initial_set("docks/scene_tree/start_create_dialog_fully_expanded", false);
 	_initial_set("docks/scene_tree/draw_relationship_lines", false);
@@ -592,6 +594,7 @@ void EditorSettings::_load_default_text_editor_theme() {
 	_initial_set("text_editor/highlighting/completion_font_color", Color::html("aaaaaa"));
 	_initial_set("text_editor/highlighting/text_color", Color::html("aaaaaa"));
 	_initial_set("text_editor/highlighting/line_number_color", Color::html("66aaaaaa"));
+	_initial_set("text_editor/highlighting/safe_line_number_color", Color::html("99aac8aa"));
 	_initial_set("text_editor/highlighting/caret_color", Color::html("aaaaaa"));
 	_initial_set("text_editor/highlighting/caret_background_color", Color::html("000000"));
 	_initial_set("text_editor/highlighting/text_selected_color", Color::html("000000"));

+ 2 - 0
editor/editor_themes.cpp

@@ -1089,6 +1089,7 @@ Ref<Theme> create_editor_theme(const Ref<Theme> p_theme) {
 	const Color completion_font_color = font_color;
 	const Color text_color = font_color;
 	const Color line_number_color = dim_color;
+	const Color safe_line_number_color = dim_color * Color(1, 1.2, 1, 1.5);
 	const Color caret_color = mono_color;
 	const Color caret_background_color = mono_color.inverted();
 	const Color text_selected_color = dark_color_3;
@@ -1123,6 +1124,7 @@ Ref<Theme> create_editor_theme(const Ref<Theme> p_theme) {
 		setting->set_initial_value("text_editor/highlighting/completion_font_color", completion_font_color, true);
 		setting->set_initial_value("text_editor/highlighting/text_color", text_color, true);
 		setting->set_initial_value("text_editor/highlighting/line_number_color", line_number_color, true);
+		setting->set_initial_value("text_editor/highlighting/safe_line_number_color", safe_line_number_color, true);
 		setting->set_initial_value("text_editor/highlighting/caret_color", caret_color, true);
 		setting->set_initial_value("text_editor/highlighting/caret_background_color", caret_background_color, true);
 		setting->set_initial_value("text_editor/highlighting/text_selected_color", text_selected_color, true);

+ 20 - 1
editor/plugins/script_text_editor.cpp

@@ -116,6 +116,7 @@ void ScriptTextEditor::_load_theme_settings() {
 	Color completion_font_color = EDITOR_GET("text_editor/highlighting/completion_font_color");
 	Color text_color = EDITOR_GET("text_editor/highlighting/text_color");
 	Color line_number_color = EDITOR_GET("text_editor/highlighting/line_number_color");
+	Color safe_line_number_color = EDITOR_GET("text_editor/highlighting/safe_line_number_color");
 	Color caret_color = EDITOR_GET("text_editor/highlighting/caret_color");
 	Color caret_background_color = EDITOR_GET("text_editor/highlighting/caret_background_color");
 	Color text_selected_color = EDITOR_GET("text_editor/highlighting/text_selected_color");
@@ -147,6 +148,7 @@ void ScriptTextEditor::_load_theme_settings() {
 	text_edit->add_color_override("completion_font_color", completion_font_color);
 	text_edit->add_color_override("font_color", text_color);
 	text_edit->add_color_override("line_number_color", line_number_color);
+	text_edit->add_color_override("safe_line_number_color", safe_line_number_color);
 	text_edit->add_color_override("caret_color", caret_color);
 	text_edit->add_color_override("caret_background_color", caret_background_color);
 	text_edit->add_color_override("font_selected_color", text_selected_color);
@@ -589,6 +591,7 @@ void ScriptTextEditor::set_edited_script(const Ref<Script> &p_script) {
 
 	emit_signal("name_changed");
 	code_editor->update_line_and_column();
+	call_deferred("_validate_script");
 }
 
 void ScriptTextEditor::_validate_script() {
@@ -599,8 +602,9 @@ void ScriptTextEditor::_validate_script() {
 
 	String text = te->get_text();
 	List<String> fnc;
+	Set<int> safe_lines;
 
-	if (!script->get_language()->validate(text, line, col, errortxt, script->get_path(), &fnc)) {
+	if (!script->get_language()->validate(text, line, col, errortxt, script->get_path(), &fnc, &safe_lines)) {
 		String error_text = "error(" + itos(line) + "," + itos(col) + "): " + errortxt;
 		code_editor->set_error(error_text);
 	} else {
@@ -621,8 +625,23 @@ void ScriptTextEditor::_validate_script() {
 	}
 
 	line--;
+	bool highlight_safe = EDITOR_DEF("text_editor/highlighting/highlight_type_safe_lines", true);
+	bool last_is_safe = false;
 	for (int i = 0; i < te->get_line_count(); i++) {
 		te->set_line_as_marked(i, line == i);
+		if (highlight_safe) {
+			if (safe_lines.has(i + 1)) {
+				te->set_line_as_safe(i, true);
+				last_is_safe = true;
+			} else if (last_is_safe && (te->is_line_comment(i) || te->get_line(i).strip_edges().empty())) {
+				te->set_line_as_safe(i, true);
+			} else {
+				te->set_line_as_safe(i, false);
+				last_is_safe = false;
+			}
+		} else {
+			te->set_line_as_safe(i, false);
+		}
 	}
 
 	emit_signal("name_changed");

+ 3 - 12
main/tests/test_gdscript.cpp

@@ -193,14 +193,6 @@ static String _parser_expr(const GDScriptParser::Node *p_expr) {
 				case GDScriptParser::OperatorNode::OP_BIT_INVERT: {
 					txt = "~" + _parser_expr(c_node->arguments[0]);
 				} 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;
 				case GDScriptParser::OperatorNode::OP_IN: {
 					txt = _parser_expr(c_node->arguments[0]) + " in " + _parser_expr(c_node->arguments[1]);
 				} break;
@@ -455,10 +447,9 @@ static void _parser_show_class(const GDScriptParser::ClassNode *p_class, int p_i
 		print_line("\n");
 	}
 
-	for (int i = 0; i < p_class->constant_expressions.size(); i++) {
-
-		const GDScriptParser::ClassNode::Constant &constant = p_class->constant_expressions[i];
-		_print_indent(p_indent, "const " + String(constant.identifier) + "=" + _parser_expr(constant.expression));
+	for (Map<StringName, GDScriptParser::ClassNode::Constant>::Element *E = p_class->constant_expressions.front(); E; E = E->next()) {
+		const GDScriptParser::ClassNode::Constant &constant = E->get();
+		_print_indent(p_indent, "const " + String(E->key()) + "=" + _parser_expr(constant.expression));
 	}
 
 	for (int i = 0; i < p_class->variables.size(); i++) {

+ 1 - 1
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;
 }
 

+ 1 - 1
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;

+ 1 - 1
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(

+ 1 - 1
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;

+ 30 - 0
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() {

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

+ 49 - 15
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;
 	}

+ 7 - 1
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;

+ 345 - 281
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];
@@ -429,6 +447,83 @@ int GDScriptCompiler::_parse_expression(CodeGen &codegen, const GDScriptParser::
 			codegen.alloc_stack(p_stack_level);
 			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;

+ 10 - 1
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;

File diff ditekan karena terlalu besar
+ 1401 - 836
modules/gdscript/gdscript_editor.cpp


+ 229 - 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);
@@ -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;

+ 99 - 0
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; }
 

+ 12 - 11
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;

File diff ditekan karena terlalu besar
+ 387 - 136
modules/gdscript/gdscript_parser.cpp


+ 168 - 84
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;

+ 7 - 4
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);
 				}

+ 3 - 0
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,

+ 1 - 1
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;

+ 1 - 1
modules/visual_script/visual_script.cpp

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

+ 1 - 1
modules/visual_script/visual_script.h

@@ -563,7 +563,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;

+ 14 - 1
scene/gui/text_edit.cpp

@@ -290,6 +290,7 @@ void TextEdit::Text::insert(int p_at, const String &p_text) {
 
 	Line line;
 	line.marked = false;
+	line.safe = false;
 	line.breakpoint = false;
 	line.hidden = false;
 	line.width_cache = -1;
@@ -972,7 +973,7 @@ void TextEdit::_notification(int p_what) {
 								fc = line_num_padding + fc;
 							}
 
-							cache.font->draw(ci, Point2(cache.style_normal->get_margin(MARGIN_LEFT) + cache.breakpoint_gutter_width + ofs_x, yofs + cache.font->get_ascent()), fc, cache.line_number_color);
+							cache.font->draw(ci, Point2(cache.style_normal->get_margin(MARGIN_LEFT) + cache.breakpoint_gutter_width + ofs_x, yofs + cache.font->get_ascent()), fc, text.is_safe(line) ? cache.safe_line_number_color : cache.line_number_color);
 						}
 					}
 
@@ -4314,6 +4315,7 @@ void TextEdit::_update_caches() {
 	cache.caret_color = get_color("caret_color");
 	cache.caret_background_color = get_color("caret_background_color");
 	cache.line_number_color = get_color("line_number_color");
+	cache.safe_line_number_color = get_color("safe_line_number_color");
 	cache.font_color = get_color("font_color");
 	cache.font_selected_color = get_color("font_selected_color");
 	cache.keyword_color = get_color("keyword_color");
@@ -4885,6 +4887,17 @@ void TextEdit::set_line_as_marked(int p_line, bool p_marked) {
 	update();
 }
 
+void TextEdit::set_line_as_safe(int p_line, bool p_safe) {
+	ERR_FAIL_INDEX(p_line, text.size());
+	text.set_safe(p_line, p_safe);
+	update();
+}
+
+bool TextEdit::is_line_set_as_safe(int p_line) const {
+	ERR_FAIL_INDEX_V(p_line, text.size(), false);
+	return text.is_safe(p_line);
+}
+
 bool TextEdit::is_line_set_as_breakpoint(int p_line) const {
 
 	ERR_FAIL_INDEX_V(p_line, text.size(), false);

+ 6 - 0
scene/gui/text_edit.h

@@ -76,6 +76,7 @@ public:
 			bool marked : 1;
 			bool breakpoint : 1;
 			bool hidden : 1;
+			bool safe : 1;
 			int wrap_amount_cache : 24;
 			Map<int, ColorRegionInfo> region_info;
 			String data;
@@ -106,6 +107,8 @@ public:
 		bool is_breakpoint(int p_line) const { return text[p_line].breakpoint; }
 		void set_hidden(int p_line, bool p_hidden) { text[p_line].hidden = p_hidden; }
 		bool is_hidden(int p_line) const { return text[p_line].hidden; }
+		void set_safe(int p_line, bool p_safe) { text[p_line].safe = p_safe; }
+		bool is_safe(int p_line) const { return text[p_line].safe; }
 		void insert(int p_at, const String &p_text);
 		void remove(int p_at);
 		int size() const { return text.size(); }
@@ -165,6 +168,7 @@ private:
 		Color caret_color;
 		Color caret_background_color;
 		Color line_number_color;
+		Color safe_line_number_color;
 		Color font_color;
 		Color font_selected_color;
 		Color keyword_color;
@@ -472,6 +476,8 @@ public:
 	void set_line_as_marked(int p_line, bool p_marked);
 	void set_line_as_breakpoint(int p_line, bool p_breakpoint);
 	bool is_line_set_as_breakpoint(int p_line) const;
+	void set_line_as_safe(int p_line, bool p_safe);
+	bool is_line_set_as_safe(int p_line) const;
 	void get_breakpoints(List<int> *p_breakpoints) const;
 	Array get_breakpoints_array() const;
 	void remove_breakpoints();

+ 1 - 0
scene/resources/default_theme/default_theme.cpp

@@ -476,6 +476,7 @@ void fill_default_theme(Ref<Theme> &theme, const Ref<Font> &default_font, const
 	theme->set_color("symbol_color", "TextEdit", control_font_color_hover);
 	theme->set_color("brace_mismatch_color", "TextEdit", Color(1, 0.2, 0.2));
 	theme->set_color("line_number_color", "TextEdit", Color::html("66aaaaaa"));
+	theme->set_color("safe_line_number_color", "TextEdit", Color::html("99aac8aa"));
 	theme->set_color("function_color", "TextEdit", Color::html("66a2ce"));
 	theme->set_color("member_variable_color", "TextEdit", Color::html("e64e59"));
 	theme->set_color("number_color", "TextEdit", Color::html("EB9532"));

Beberapa file tidak ditampilkan karena terlalu banyak file yang berubah dalam diff ini