Ver código fonte

Add editor highlight for type-safe lines

The line number is hightlighted to indicate that the line contains only
type-safe code.
George Marques 7 anos atrás
pai
commit
03746da73f

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

+ 2 - 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);
@@ -593,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");

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

+ 1 - 1
modules/gdscript/gdscript.h

@@ -397,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;

+ 2 - 2
modules/gdscript/gdscript_editor.cpp

@@ -115,11 +115,11 @@ void GDScriptLanguage::make_template(const String &p_class_name, const String &p
 	p_script->set_source_code(src);
 }
 
-bool GDScriptLanguage::validate(const String &p_script, int &r_line_error, int &r_col_error, String &r_test_error, const String &p_path, List<String> *r_functions) const {
+bool GDScriptLanguage::validate(const String &p_script, int &r_line_error, int &r_col_error, String &r_test_error, const String &p_path, List<String> *r_functions, Set<int> *r_safe_lines) const {
 
 	GDScriptParser parser;
 
-	Error err = parser.parse(p_script, p_path.get_base_dir(), true, p_path);
+	Error err = parser.parse(p_script, p_path.get_base_dir(), true, p_path, false, r_safe_lines);
 	if (err) {
 		r_line_error = parser.get_error_line();
 		r_col_error = parser.get_error_column();

+ 175 - 99
modules/gdscript/gdscript_parser.cpp

@@ -2464,6 +2464,11 @@ void GDScriptParser::_transform_match_statment(MatchNode *p_match_statement) {
 	id->name = "#match_value";
 	id->line = p_match_statement->line;
 	id->datatype = _reduce_node_type(p_match_statement->val_to_match);
+	if (id->datatype.has_type) {
+		_mark_line_as_safe(id->line);
+	} else {
+		_mark_line_as_unsafe(id->line);
+	}
 
 	if (error_set) {
 		return;
@@ -2480,6 +2485,7 @@ void GDScriptParser::_transform_match_statment(MatchNode *p_match_statement) {
 
 		for (int j = 0; j < branch->patterns.size(); j++) {
 			PatternNode *pattern = branch->patterns[j];
+			_mark_line_as_safe(pattern->line);
 
 			Map<StringName, Node *> bindings;
 			Node *resulting_node = NULL;
@@ -2633,6 +2639,7 @@ void GDScriptParser::_parse_block(BlockNode *p_block, bool p_static) {
 					_set_error("Expected ';' or <NewLine>.");
 					return;
 				}
+				_mark_line_as_safe(tokenizer->get_token_line());
 				tokenizer->advance();
 				if (tokenizer->get_token() == GDScriptTokenizer::TK_SEMICOLON) {
 					// Ignore semicolon after 'pass'
@@ -3328,6 +3335,7 @@ void GDScriptParser::_parse_class(ClassNode *p_class) {
 			} break;
 			case GDScriptTokenizer::TK_PR_EXTENDS: {
 
+				_mark_line_as_safe(tokenizer->get_token_line());
 				_parse_extends(p_class);
 				if (error_set)
 					return;
@@ -5693,6 +5701,8 @@ GDScriptParser::DataType GDScriptParser::_reduce_node_type(Node *p_node) {
 						return DataType();
 					}
 				}
+			} else {
+				_mark_line_as_unsafe(cn->line);
 			}
 
 			node_type = cn->cast_type;
@@ -5804,6 +5814,7 @@ GDScriptParser::DataType GDScriptParser::_reduce_node_type(Node *p_node) {
 					DataType argument_a_type = _reduce_node_type(op->arguments[0]);
 					DataType argument_b_type = _reduce_node_type(op->arguments[1]);
 					if (!argument_a_type.has_type || !argument_b_type.has_type) {
+						_mark_line_as_unsafe(op->line);
 						break;
 					}
 
@@ -5902,6 +5913,8 @@ GDScriptParser::DataType GDScriptParser::_reduce_node_type(Node *p_node) {
 						} else {
 							node_type = _reduce_identifier_type(&base_type, member_id->name, op->line);
 						}
+					} else {
+						_mark_line_as_unsafe(op->line);
 					}
 					if (error_set) {
 						return DataType();
@@ -5928,6 +5941,7 @@ GDScriptParser::DataType GDScriptParser::_reduce_node_type(Node *p_node) {
 					DataType index_type = _reduce_node_type(op->arguments[1]);
 
 					if (!base_type.has_type) {
+						_mark_line_as_unsafe(op->line);
 						break;
 					}
 
@@ -6013,6 +6027,8 @@ GDScriptParser::DataType GDScriptParser::_reduce_node_type(Node *p_node) {
 										}
 									} break;
 								}
+							} else {
+								_mark_line_as_unsafe(op->line);
 							}
 						} else if (!for_completion && (index_type.kind != DataType::BUILTIN || index_type.builtin_type != Variant::STRING)) {
 							_set_error("Only strings can be used as index in the base type '" + base_type.to_string() + "'.", op->line);
@@ -6386,6 +6402,7 @@ GDScriptParser::DataType GDScriptParser::_reduce_function_call_type(const Operat
 			}
 
 			if (!base_type.has_type || (base_type.kind == DataType::BUILTIN && base_type.builtin_type == Variant::NIL)) {
+				_mark_line_as_unsafe(p_call->line);
 				return DataType();
 			}
 
@@ -6395,7 +6412,7 @@ GDScriptParser::DataType GDScriptParser::_reduce_function_call_type(const Operat
 
 				if (check_types) {
 					if (!tmp.has_method(callee_name)) {
-						_set_error("Method '" + String(callee_name) + "()' is not declared in base '" + base_type.to_string() + "'.", p_call->line);
+						_set_error("Method '" + callee_name + "' is not declared on base '" + base_type.to_string() + "'.", p_call->line);
 						return DataType();
 					}
 
@@ -6446,12 +6463,6 @@ GDScriptParser::DataType GDScriptParser::_reduce_function_call_type(const Operat
 						default_args_count, is_static, is_vararg);
 			}
 
-			String base_name;
-			if (p_call->arguments[0]->type == Node::TYPE_SELF) {
-				base_name = current_class->name == StringName() ? "self" : current_class->name;
-			} else {
-				base_name = original_type.to_string();
-			}
 			if (!valid) {
 				if (p_call->arguments[0]->type == Node::TYPE_SELF) {
 					_set_error("Method '" + callee_name + "' is not declared in the current class.", p_call->line);
@@ -6496,12 +6507,19 @@ GDScriptParser::DataType GDScriptParser::_reduce_function_call_type(const Operat
 			continue;
 		}
 
-		if (!_is_type_compatible(arg_types[i - arg_diff], par_type, true)) {
-			_set_error("At '" + callee_name + "()' call, argument " + itos(i - arg_diff + 1) + ". Assigned type (" +
-							   par_type.to_string() + ") doesn't match the function argument's type (" +
-							   arg_types[i - arg_diff].to_string() + ").",
-					p_call->line);
-			return DataType();
+		if (!par_type.has_type) {
+			_mark_line_as_unsafe(p_call->line);
+		} else if (!_is_type_compatible(arg_types[i - arg_diff], par_type, true)) {
+			// Supertypes are acceptable for dynamic compliance
+			if (!_is_type_compatible(par_type, arg_types[i - arg_diff])) {
+				_set_error("At '" + callee_name + "()' call, argument " + itos(i - arg_diff + 1) + ". Assigned type (" +
+								   par_type.to_string() + ") doesn't match the function argument's type (" +
+								   arg_types[i - arg_diff].to_string() + ").",
+						p_call->line);
+				return DataType();
+			} else {
+				_mark_line_as_unsafe(p_call->line);
+			}
 		}
 	}
 
@@ -6858,23 +6876,23 @@ GDScriptParser::DataType GDScriptParser::_reduce_identifier_type(const DataType
 		return member_type;
 	}
 
-	if (!check_types) {
-		return DataType();
-	}
-
 	if (!p_base_type) {
-		_set_error("Identifier '" + String(p_identifier) + "' is not declared in the current scope.", p_line);
-	} else {
-		_set_error("Identifier '" + String(p_identifier) + "' is not declared in base '" + p_base_type->to_string() + "'.", p_line);
+		// This means looking in the current class, which type is always known
+		_set_error("Identifier '" + p_identifier.operator String() + "' is not declared in the current scope.", p_line);
 	}
+
+	_mark_line_as_unsafe(p_line);
 	return DataType();
 }
 
 void GDScriptParser::_check_class_level_types(ClassNode *p_class) {
 
+	_mark_line_as_safe(p_class->line);
+
 	// Constants
 	for (Map<StringName, ClassNode::Constant>::Element *E = p_class->constant_expressions.front(); E; E = E->next()) {
 		ClassNode::Constant &c = E->get();
+		_mark_line_as_safe(c.expression->line);
 		DataType cont = _resolve_type(c.type, c.expression->line);
 		DataType expr = _resolve_type(c.expression->get_datatype(), c.expression->line);
 
@@ -6910,40 +6928,44 @@ void GDScriptParser::_check_class_level_types(ClassNode *p_class) {
 			return;
 		}
 
-		if (!v.data_type.has_type) continue;
-
+		_mark_line_as_safe(v.line);
 		v.data_type = _resolve_type(v.data_type, v.line);
 
 		if (v.expression) {
 			DataType expr_type = _reduce_node_type(v.expression);
 
 			if (!_is_type_compatible(v.data_type, expr_type)) {
-				// Try with implicit conversion
-				if (v.data_type.kind != DataType::BUILTIN || !_is_type_compatible(v.data_type, expr_type, true)) {
-					_set_error("Assigned expression type (" + expr_type.to_string() + ") doesn't match the variable's type (" +
-									   v.data_type.to_string() + ").",
-							v.line);
-					return;
-				}
+				// Try supertype test
+				if (_is_type_compatible(expr_type, v.data_type)) {
+					_mark_line_as_unsafe(v.line);
+				} else {
+					// Try with implicit conversion
+					if (v.data_type.kind != DataType::BUILTIN || !_is_type_compatible(v.data_type, expr_type, true)) {
+						_set_error("Assigned expression type (" + expr_type.to_string() + ") doesn't match the variable's type (" +
+										   v.data_type.to_string() + ").",
+								v.line);
+						return;
+					}
 
-				// Replace assigment with implict conversion
-				BuiltInFunctionNode *convert = alloc_node<BuiltInFunctionNode>();
-				convert->line = v.line;
-				convert->function = GDScriptFunctions::TYPE_CONVERT;
+					// Replace assigment with implict conversion
+					BuiltInFunctionNode *convert = alloc_node<BuiltInFunctionNode>();
+					convert->line = v.line;
+					convert->function = GDScriptFunctions::TYPE_CONVERT;
 
-				ConstantNode *tgt_type = alloc_node<ConstantNode>();
-				tgt_type->line = v.line;
-				tgt_type->value = (int)v.data_type.builtin_type;
+					ConstantNode *tgt_type = alloc_node<ConstantNode>();
+					tgt_type->line = v.line;
+					tgt_type->value = (int)v.data_type.builtin_type;
 
-				OperatorNode *convert_call = alloc_node<OperatorNode>();
-				convert_call->line = v.line;
-				convert_call->op = OperatorNode::OP_CALL;
-				convert_call->arguments.push_back(convert);
-				convert_call->arguments.push_back(v.expression);
-				convert_call->arguments.push_back(tgt_type);
+					OperatorNode *convert_call = alloc_node<OperatorNode>();
+					convert_call->line = v.line;
+					convert_call->op = OperatorNode::OP_CALL;
+					convert_call->arguments.push_back(convert);
+					convert_call->arguments.push_back(v.expression);
+					convert_call->arguments.push_back(tgt_type);
 
-				v.expression = convert_call;
-				v.initial_assignment->arguments[1] = convert_call;
+					v.expression = convert_call;
+					v.initial_assignment->arguments[1] = convert_call;
+				}
 			}
 		} else if (v.data_type.has_type && v.data_type.kind == DataType::BUILTIN) {
 			// Create default value based on the type
@@ -6972,7 +6994,7 @@ void GDScriptParser::_check_class_level_types(ClassNode *p_class) {
 		}
 
 		// Check export hint
-		if (v._export.type != Variant::NIL) {
+		if (v.data_type.has_type && v._export.type != Variant::NIL) {
 			DataType export_type = _type_from_property(v._export);
 			if (!_is_type_compatible(v.data_type, export_type, true)) {
 				_set_error("Export hint type (" + export_type.to_string() + ") doesn't match the variable's type (" +
@@ -7149,6 +7171,7 @@ void GDScriptParser::_check_class_blocks_types(ClassNode *p_class) {
 	for (int i = 0; i < p_class->static_functions.size(); i++) {
 		current_function = p_class->static_functions[i];
 		current_block = current_function->body;
+		_mark_line_as_safe(current_function->line);
 		_check_block_types(current_block);
 		current_block = NULL;
 		current_function = NULL;
@@ -7158,6 +7181,7 @@ void GDScriptParser::_check_class_blocks_types(ClassNode *p_class) {
 	for (int i = 0; i < p_class->functions.size(); i++) {
 		current_function = p_class->functions[i];
 		current_block = current_function->body;
+		_mark_line_as_safe(current_function->line);
 		_check_block_types(current_block);
 		current_block = NULL;
 		current_function = NULL;
@@ -7175,6 +7199,8 @@ void GDScriptParser::_check_class_blocks_types(ClassNode *p_class) {
 
 void GDScriptParser::_check_block_types(BlockNode *p_block) {
 
+	Node *last_var_assign = NULL;
+
 	// Check each statement
 	for (List<Node *>::Element *E = p_block->statements.front(); E; E = E->next()) {
 		Node *statement = E->get();
@@ -7187,37 +7213,47 @@ void GDScriptParser::_check_block_types(BlockNode *p_block) {
 			case Node::TYPE_LOCAL_VAR: {
 				LocalVarNode *lv = static_cast<LocalVarNode *>(statement);
 				lv->datatype = _resolve_type(lv->datatype, lv->line);
+				_mark_line_as_safe(lv->line);
 
 				if (lv->assign) {
 					DataType assign_type = _reduce_node_type(lv->assign);
 					if (!_is_type_compatible(lv->datatype, assign_type)) {
-						// Try implict conversion
-						if (lv->datatype.kind != DataType::BUILTIN || !_is_type_compatible(lv->datatype, assign_type, true)) {
-							_set_error("Assigned value type (" + assign_type.to_string() + ") doesn't match the variable's type (" +
-											   lv->datatype.to_string() + ").",
-									lv->line);
-							return;
-						}
-						// Replace assigment with implict conversion
-						BuiltInFunctionNode *convert = alloc_node<BuiltInFunctionNode>();
-						convert->line = lv->line;
-						convert->function = GDScriptFunctions::TYPE_CONVERT;
+						// Try supertype test
+						if (_is_type_compatible(assign_type, lv->datatype)) {
+							_mark_line_as_unsafe(lv->line);
+						} else {
+							// Try implict conversion
+							if (lv->datatype.kind != DataType::BUILTIN || !_is_type_compatible(lv->datatype, assign_type, true)) {
+								_set_error("Assigned value type (" + assign_type.to_string() + ") doesn't match the variable's type (" +
+												   lv->datatype.to_string() + ").",
+										lv->line);
+								return;
+							}
+							// Replace assigment with implict conversion
+							BuiltInFunctionNode *convert = alloc_node<BuiltInFunctionNode>();
+							convert->line = lv->line;
+							convert->function = GDScriptFunctions::TYPE_CONVERT;
 
-						ConstantNode *tgt_type = alloc_node<ConstantNode>();
-						tgt_type->line = lv->line;
-						tgt_type->value = (int)lv->datatype.builtin_type;
+							ConstantNode *tgt_type = alloc_node<ConstantNode>();
+							tgt_type->line = lv->line;
+							tgt_type->value = (int)lv->datatype.builtin_type;
 
-						OperatorNode *convert_call = alloc_node<OperatorNode>();
-						convert_call->line = lv->line;
-						convert_call->op = OperatorNode::OP_CALL;
-						convert_call->arguments.push_back(convert);
-						convert_call->arguments.push_back(lv->assign);
-						convert_call->arguments.push_back(tgt_type);
+							OperatorNode *convert_call = alloc_node<OperatorNode>();
+							convert_call->line = lv->line;
+							convert_call->op = OperatorNode::OP_CALL;
+							convert_call->arguments.push_back(convert);
+							convert_call->arguments.push_back(lv->assign);
+							convert_call->arguments.push_back(tgt_type);
 
-						lv->assign = convert_call;
-						lv->assign_op->arguments[1] = convert_call;
+							lv->assign = convert_call;
+							lv->assign_op->arguments[1] = convert_call;
+						}
+					}
+					if (lv->datatype.has_type && !assign_type.has_type) {
+						_mark_line_as_unsafe(lv->line);
 					}
 				}
+				last_var_assign = lv->assign;
 
 				// TODO: Make a warning
 				/*
@@ -7247,6 +7283,13 @@ void GDScriptParser::_check_block_types(BlockNode *p_block) {
 							return;
 						}
 
+						if (op->arguments[1] == last_var_assign) {
+							// Assignment was already checked
+							break;
+						}
+
+						_mark_line_as_safe(op->line);
+
 						DataType lh_type = _reduce_node_type(op->arguments[0]);
 
 						if (error_set) {
@@ -7254,10 +7297,10 @@ void GDScriptParser::_check_block_types(BlockNode *p_block) {
 						}
 
 						if (!lh_type.has_type) {
-							break;
-						}
-
-						if (lh_type.is_constant) {
+							if (op->arguments[0]->type == Node::TYPE_OPERATOR) {
+								_mark_line_as_unsafe(op->line);
+							}
+						} else if (lh_type.is_constant) {
 							_set_error("Cannot assign a new value to a constant.", op->line);
 							return;
 						}
@@ -7267,6 +7310,7 @@ void GDScriptParser::_check_block_types(BlockNode *p_block) {
 							// Validate operation
 							DataType arg_type = _reduce_node_type(op->arguments[1]);
 							if (!arg_type.has_type) {
+								_mark_line_as_unsafe(op->line);
 								break;
 							}
 
@@ -7285,38 +7329,49 @@ void GDScriptParser::_check_block_types(BlockNode *p_block) {
 						}
 
 						if (!_is_type_compatible(lh_type, rh_type)) {
-							// Try implict conversion
-							if (lh_type.kind != DataType::BUILTIN || !_is_type_compatible(lh_type, rh_type, true)) {
-								_set_error("Assigned value type (" + rh_type.to_string() + ") doesn't match the variable's type (" +
-												   lh_type.to_string() + ").",
-										op->line);
-								return;
+							// Try supertype test
+							if (_is_type_compatible(rh_type, lh_type)) {
+								_mark_line_as_unsafe(op->line);
+							} else {
+								// Try implict conversion
+								if (lh_type.kind != DataType::BUILTIN || !_is_type_compatible(lh_type, rh_type, true)) {
+									_set_error("Assigned value type (" + rh_type.to_string() + ") doesn't match the variable's type (" +
+													   lh_type.to_string() + ").",
+											op->line);
+									return;
+								}
+								// Replace assigment with implict conversion
+								BuiltInFunctionNode *convert = alloc_node<BuiltInFunctionNode>();
+								convert->line = op->line;
+								convert->function = GDScriptFunctions::TYPE_CONVERT;
+
+								ConstantNode *tgt_type = alloc_node<ConstantNode>();
+								tgt_type->line = op->line;
+								tgt_type->value = (int)lh_type.builtin_type;
+
+								OperatorNode *convert_call = alloc_node<OperatorNode>();
+								convert_call->line = op->line;
+								convert_call->op = OperatorNode::OP_CALL;
+								convert_call->arguments.push_back(convert);
+								convert_call->arguments.push_back(op->arguments[1]);
+								convert_call->arguments.push_back(tgt_type);
+
+								op->arguments[1] = convert_call;
 							}
-							// Replace assigment with implict conversion
-							BuiltInFunctionNode *convert = alloc_node<BuiltInFunctionNode>();
-							convert->line = op->line;
-							convert->function = GDScriptFunctions::TYPE_CONVERT;
-
-							ConstantNode *tgt_type = alloc_node<ConstantNode>();
-							tgt_type->line = op->line;
-							tgt_type->value = (int)lh_type.builtin_type;
-
-							OperatorNode *convert_call = alloc_node<OperatorNode>();
-							convert_call->line = op->line;
-							convert_call->op = OperatorNode::OP_CALL;
-							convert_call->arguments.push_back(convert);
-							convert_call->arguments.push_back(op->arguments[1]);
-							convert_call->arguments.push_back(tgt_type);
-
-							op->arguments[1] = convert_call;
+						}
+						if (!rh_type.has_type && (op->op != OperatorNode::OP_ASSIGN || lh_type.has_type || op->arguments[0]->type == Node::TYPE_OPERATOR)) {
+							_mark_line_as_unsafe(op->line);
 						}
 					} break;
 					case OperatorNode::OP_CALL:
 					case OperatorNode::OP_PARENT_CALL: {
+						_mark_line_as_safe(op->line);
 						_reduce_function_call_type(op);
 						if (error_set) return;
 					} break;
 					default: {
+						_mark_line_as_safe(op->line);
+						_reduce_node_type(op); // Test for safety anyway
 						// TODO: Make this a warning
 						/*_set_error("Standalone expression, nothing is done in this line.", statement->line);
 						return; */
@@ -7325,11 +7380,20 @@ void GDScriptParser::_check_block_types(BlockNode *p_block) {
 			} break;
 			case Node::TYPE_CONTROL_FLOW: {
 				ControlFlowNode *cf = static_cast<ControlFlowNode *>(statement);
+				_mark_line_as_safe(cf->line);
 
 				switch (cf->cf_type) {
 					case ControlFlowNode::CF_RETURN: {
 						DataType function_type = current_function->get_datatype();
 
+						DataType ret_type;
+						if (cf->arguments.size() > 0) {
+							ret_type = _reduce_node_type(cf->arguments[0]);
+							if (error_set) {
+								return;
+							}
+						}
+
 						if (!function_type.has_type) break;
 
 						if (function_type.kind == DataType::BUILTIN && function_type.builtin_type == Variant::NIL) {
@@ -7345,10 +7409,6 @@ void GDScriptParser::_check_block_types(BlockNode *p_block) {
 								return;
 							}
 
-							DataType ret_type = _reduce_node_type(cf->arguments[0]);
-							if (error_set) {
-								return;
-							}
 							if (!_is_type_compatible(function_type, ret_type)) {
 								_set_error("Returned value type (" + ret_type.to_string() + ") doesn't match the function return type (" +
 												   function_type.to_string() + ").",
@@ -7361,6 +7421,14 @@ void GDScriptParser::_check_block_types(BlockNode *p_block) {
 						MatchNode *match_node = cf->match;
 						_transform_match_statment(match_node);
 					} break;
+					default: {
+						if (cf->body_else) {
+							_mark_line_as_safe(cf->body_else->line);
+						}
+						for (int i = 0; i < cf->arguments.size(); i++) {
+							_reduce_node_type(cf->arguments[i]);
+						}
+					} break;
 				}
 			} break;
 			case Node::TYPE_CONSTANT: {
@@ -7371,6 +7439,8 @@ void GDScriptParser::_check_block_types(BlockNode *p_block) {
 				}
 			} // falthrough
 			default: {
+				_mark_line_as_safe(statement->line);
+				_reduce_node_type(statement); // Test for safety anyway
 				// TODO: Make this a warning
 				/* _set_error("Standalone expression, nothing is done in this line.", statement->line);
 				return; */
@@ -7479,7 +7549,7 @@ Error GDScriptParser::parse_bytecode(const Vector<uint8_t> &p_bytecode, const St
 	return ret;
 }
 
-Error GDScriptParser::parse(const String &p_code, const String &p_base_path, bool p_just_validate, const String &p_self_path, bool p_for_completion) {
+Error GDScriptParser::parse(const String &p_code, const String &p_base_path, bool p_just_validate, const String &p_self_path, bool p_for_completion, Set<int> *r_safe_lines) {
 
 	clear();
 
@@ -7489,6 +7559,9 @@ Error GDScriptParser::parse(const String &p_code, const String &p_base_path, boo
 
 	validating = p_just_validate;
 	for_completion = p_for_completion;
+#ifdef DEBUG_ENABLED
+	safe_lines = r_safe_lines;
+#endif // DEBUG_ENABLED
 	tokenizer = tt;
 	Error ret = _parse(p_base_path);
 	memdelete(tt);
@@ -7543,6 +7616,9 @@ void GDScriptParser::clear() {
 	current_export.type = Variant::NIL;
 	check_types = true;
 	error = "";
+#ifdef DEBUG_ENABLED
+	safe_lines = NULL;
+#endif // DEBUG_ENABLED
 }
 
 GDScriptParser::CompletionType GDScriptParser::get_completion_type() {

+ 14 - 1
modules/gdscript/gdscript_parser.h

@@ -512,6 +512,9 @@ private:
 	int error_line;
 	int error_column;
 	bool check_types;
+#ifdef DEBUG_ENABLED
+	Set<int> *safe_lines;
+#endif // DEBUG_ENABLED
 
 	int pending_newline;
 
@@ -583,6 +586,16 @@ private:
 	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);
 
@@ -590,7 +603,7 @@ 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;

+ 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"));