Przeglądaj źródła

Merge pull request #19993 from vnen/gdscript-warnings

System for GDScript warnings
Juan Linietsky 7 lat temu
rodzic
commit
783fd23dea

+ 8 - 1
core/script_language.h

@@ -207,13 +207,20 @@ public:
 	virtual void finish() = 0;
 
 	/* EDITOR FUNCTIONS */
+	struct Warning {
+		int line;
+		int code;
+		String string_code;
+		String message;
+	};
+
 	virtual void get_reserved_words(List<String> *p_words) const = 0;
 	virtual void get_comment_delimiters(List<String> *p_delimiters) const = 0;
 	virtual void get_string_delimiters(List<String> *p_delimiters) const = 0;
 	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, Set<int> *r_safe_lines = 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, List<Warning> *r_warnings = 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;

+ 23 - 0
editor/code_editor.cpp

@@ -1246,6 +1246,29 @@ CodeTextEditor::CodeTextEditor() {
 
 	status_bar->add_child(memnew(Label)); //to keep the height if the other labels are not visible
 
+	warning_label = memnew(Label);
+	status_bar->add_child(warning_label);
+	warning_label->set_align(Label::ALIGN_RIGHT);
+	warning_label->set_valign(Label::VALIGN_CENTER);
+	warning_label->set_v_size_flags(SIZE_FILL);
+	warning_label->set_default_cursor_shape(CURSOR_POINTING_HAND);
+	warning_label->set_mouse_filter(MOUSE_FILTER_STOP);
+	warning_label->set_text(TTR("Warnings:"));
+	warning_label->add_font_override("font", EditorNode::get_singleton()->get_gui_base()->get_font("status_source", "EditorFonts"));
+
+	warning_count_label = memnew(Label);
+	status_bar->add_child(warning_count_label);
+	warning_count_label->set_valign(Label::VALIGN_CENTER);
+	warning_count_label->set_v_size_flags(SIZE_FILL);
+	warning_count_label->set_autowrap(true); // workaround to prevent resizing the label on each change, do not touch
+	warning_count_label->set_clip_text(true); // workaround to prevent resizing the label on each change, do not touch
+	warning_count_label->set_custom_minimum_size(Size2(40, 1) * EDSCALE);
+	warning_count_label->set_align(Label::ALIGN_RIGHT);
+	warning_count_label->set_default_cursor_shape(CURSOR_POINTING_HAND);
+	warning_count_label->set_mouse_filter(MOUSE_FILTER_STOP);
+	warning_count_label->add_font_override("font", EditorNode::get_singleton()->get_gui_base()->get_font("status_source", "EditorFonts"));
+	warning_count_label->set_text("0");
+
 	Label *zoom_txt = memnew(Label);
 	status_bar->add_child(zoom_txt);
 	zoom_txt->set_align(Label::ALIGN_RIGHT);

+ 4 - 0
editor/code_editor.h

@@ -142,6 +142,8 @@ class CodeTextEditor : public VBoxContainer {
 	TextEdit *text_editor;
 	FindReplaceBar *find_replace_bar;
 	HBoxContainer *status_bar;
+	Label *warning_label;
+	Label *warning_count_label;
 
 	Label *line_nb;
 	Label *col_nb;
@@ -214,6 +216,8 @@ public:
 	void update_line_and_column() { _line_col_changed(); }
 	TextEdit *get_text_edit() { return text_editor; }
 	FindReplaceBar *get_find_replace_bar() { return find_replace_bar; }
+	Label *get_warning_label() const { return warning_label; }
+	Label *get_warning_count_label() const { return warning_count_label; }
 	virtual void apply_code() {}
 
 	void set_code_complete_func(CodeTextEditorCodeCompleteFunc p_code_complete_func, void *p_ud);

+ 72 - 3
editor/plugins/script_text_editor.cpp

@@ -274,6 +274,23 @@ void ScriptTextEditor::_set_theme_for_script() {
 	}
 }
 
+void ScriptTextEditor::_toggle_warning_pannel(const Ref<InputEvent> &p_event) {
+	Ref<InputEventMouseButton> mb = p_event;
+	if (mb.is_valid() && mb->is_pressed() && mb->get_button_index() == BUTTON_LEFT) {
+		warnings_panel->set_visible(!warnings_panel->is_visible());
+	}
+}
+
+void ScriptTextEditor::_warning_clicked(Variant p_line) {
+	if (p_line.get_type() == Variant::INT) {
+		code_editor->get_text_edit()->cursor_set_line(p_line.operator int64_t());
+	} else if (p_line.get_type() == Variant::DICTIONARY) {
+		Dictionary meta = p_line.operator Dictionary();
+		code_editor->get_text_edit()->insert_at("#warning-ignore:" + meta["code"].operator String(), meta["line"].operator int64_t() - 1);
+		_validate_script();
+	}
+}
+
 void ScriptTextEditor::reload_text() {
 
 	ERR_FAIL_COND(script.is_null());
@@ -421,8 +438,9 @@ void ScriptTextEditor::_validate_script() {
 	String text = te->get_text();
 	List<String> fnc;
 	Set<int> safe_lines;
+	List<ScriptLanguage::Warning> warnings;
 
-	if (!script->get_language()->validate(text, line, col, errortxt, script->get_path(), &fnc, &safe_lines)) {
+	if (!script->get_language()->validate(text, line, col, errortxt, script->get_path(), &fnc, &warnings, &safe_lines)) {
 		String error_text = "error(" + itos(line) + "," + itos(col) + "): " + errortxt;
 		code_editor->set_error(error_text);
 	} else {
@@ -442,6 +460,37 @@ void ScriptTextEditor::_validate_script() {
 		}
 	}
 
+	code_editor->get_warning_count_label()->set_text(itos(warnings.size()));
+	warnings_panel->clear();
+	warnings_panel->push_table(3);
+	for (List<ScriptLanguage::Warning>::Element *E = warnings.front(); E; E = E->next()) {
+		ScriptLanguage::Warning w = E->get();
+
+		warnings_panel->push_cell();
+		warnings_panel->push_meta(w.line - 1);
+		warnings_panel->push_color(warnings_panel->get_color("warning_color", "Editor"));
+		warnings_panel->add_text(TTR("Line") + " " + itos(w.line));
+		warnings_panel->add_text(" (" + w.string_code + "):");
+		warnings_panel->pop(); // Color
+		warnings_panel->pop(); // Meta goto
+		warnings_panel->pop(); // Cell
+
+		warnings_panel->push_cell();
+		warnings_panel->add_text(w.message);
+		warnings_panel->pop(); // Cell
+
+		Dictionary ignore_meta;
+		ignore_meta["line"] = w.line;
+		ignore_meta["code"] = w.string_code.to_lower();
+		warnings_panel->push_cell();
+		warnings_panel->push_meta(ignore_meta);
+		warnings_panel->add_text(TTR("(ignore)"));
+		warnings_panel->pop(); // Meta ignore
+		warnings_panel->pop(); // Cell
+		//warnings_panel->add_newline();
+	}
+	warnings_panel->pop(); // Table
+
 	line--;
 	bool highlight_safe = EDITOR_DEF("text_editor/highlighting/highlight_type_safe_lines", true);
 	bool last_is_safe = false;
@@ -1022,6 +1071,8 @@ void ScriptTextEditor::_bind_methods() {
 	ClassDB::bind_method("_goto_line", &ScriptTextEditor::_goto_line);
 	ClassDB::bind_method("_lookup_symbol", &ScriptTextEditor::_lookup_symbol);
 	ClassDB::bind_method("_text_edit_gui_input", &ScriptTextEditor::_text_edit_gui_input);
+	ClassDB::bind_method("_toggle_warning_pannel", &ScriptTextEditor::_toggle_warning_pannel);
+	ClassDB::bind_method("_warning_clicked", &ScriptTextEditor::_warning_clicked);
 	ClassDB::bind_method("_color_changed", &ScriptTextEditor::_color_changed);
 
 	ClassDB::bind_method("get_drag_data_fw", &ScriptTextEditor::get_drag_data_fw);
@@ -1333,8 +1384,13 @@ ScriptTextEditor::ScriptTextEditor() {
 
 	theme_loaded = false;
 
+	VSplitContainer *editor_box = memnew(VSplitContainer);
+	add_child(editor_box);
+	editor_box->set_anchors_and_margins_preset(Control::PRESET_WIDE);
+	editor_box->set_v_size_flags(SIZE_EXPAND_FILL);
+
 	code_editor = memnew(CodeTextEditor);
-	add_child(code_editor);
+	editor_box->add_child(code_editor);
 	code_editor->add_constant_override("separation", 0);
 	code_editor->set_anchors_and_margins_preset(Control::PRESET_WIDE);
 	code_editor->connect("validate_script", this, "_validate_script");
@@ -1342,7 +1398,20 @@ ScriptTextEditor::ScriptTextEditor() {
 	code_editor->set_code_complete_func(_code_complete_scripts, this);
 	code_editor->get_text_edit()->connect("breakpoint_toggled", this, "_breakpoint_toggled");
 	code_editor->get_text_edit()->connect("symbol_lookup", this, "_lookup_symbol");
-	code_editor->set_v_size_flags(Control::SIZE_EXPAND_FILL);
+	code_editor->set_v_size_flags(SIZE_EXPAND_FILL);
+
+	warnings_panel = memnew(RichTextLabel);
+	editor_box->add_child(warnings_panel);
+	warnings_panel->set_custom_minimum_size(Size2(0, 100 * EDSCALE));
+	warnings_panel->set_h_size_flags(SIZE_EXPAND_FILL);
+	warnings_panel->set_meta_underline(true);
+	warnings_panel->set_selection_enabled(true);
+	warnings_panel->set_focus_mode(FOCUS_CLICK);
+	warnings_panel->hide();
+
+	code_editor->get_warning_label()->connect("gui_input", this, "_toggle_warning_pannel");
+	code_editor->get_warning_count_label()->connect("gui_input", this, "_toggle_warning_pannel");
+	warnings_panel->connect("meta_clicked", this, "_warning_clicked");
 
 	update_settings();
 

+ 3 - 0
editor/plugins/script_text_editor.h

@@ -39,6 +39,7 @@ class ScriptTextEditor : public ScriptEditorBase {
 	GDCLASS(ScriptTextEditor, ScriptEditorBase);
 
 	CodeTextEditor *code_editor;
+	RichTextLabel *warnings_panel;
 
 	Ref<Script> script;
 
@@ -124,6 +125,8 @@ protected:
 	void _code_complete_script(const String &p_code, List<String> *r_options, bool &r_force);
 	void _load_theme_settings();
 	void _set_theme_for_script();
+	void _toggle_warning_pannel(const Ref<InputEvent> &p_event);
+	void _warning_clicked(Variant p_line);
 
 	void _notification(int p_what);
 	static void _bind_methods();

+ 1 - 1
modules/gdnative/nativescript/nativescript.cpp

@@ -1060,7 +1060,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, Set<int> *r_safe_lines) 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, List<ScriptLanguage::Warning> *r_warnings, 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, Set<int> *r_safe_lines = 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, List<ScriptLanguage::Warning> *r_warnings = 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/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, Set<int> *r_safe_lines) 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, List<ScriptLanguage::Warning> *r_warnings, 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, Set<int> *r_safe_lines = 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, List<ScriptLanguage::Warning> *r_warnings = 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;

+ 172 - 0
modules/gdscript/gdscript.cpp

@@ -596,6 +596,13 @@ Error GDScript::reload(bool p_keep_state) {
 			return err;
 		}
 	}
+#if DEBUG_ENABLED
+	for (const List<GDScriptWarning>::Element *E = parser.get_warnings().front(); E; E = E->next()) {
+		String msg = "Script warning: " + E->get().get_name() + " (" + path + ") line " + itos(E->get().line) + ": ";
+		msg += E->get().get_message();
+		WARN_PRINTS(msg);
+	}
+#endif
 
 	valid = true;
 
@@ -1867,6 +1874,162 @@ String GDScriptLanguage::get_global_class_name(const String &p_path, String *r_b
 	return String();
 }
 
+#ifdef DEBUG_ENABLED
+String GDScriptWarning::get_message() const {
+
+#define CHECK_SYMBOLS(m_amount) ERR_FAIL_COND_V(symbols.size() < m_amount, String());
+
+	switch (code) {
+		case UNASSIGNED_VARIABLE_OP_ASSIGN: {
+			CHECK_SYMBOLS(1);
+			return "Using assignment with operation but the variable '" + symbols[0] + "' was not previously assigned a value.";
+		} break;
+		case UNASSIGNED_VARIABLE: {
+			CHECK_SYMBOLS(1);
+			return "The variable '" + symbols[0] + "' was used but never assigned a value.";
+		} break;
+		case UNUSED_VARIABLE: {
+			CHECK_SYMBOLS(1);
+			return "The local variable '" + symbols[0] + "' is declared but never used in the block.";
+		} break;
+		case UNUSED_CLASS_VARIABLE: {
+			CHECK_SYMBOLS(1);
+			return "The class variable '" + symbols[0] + "' is declared but never used in the script.";
+		} break;
+		case UNUSED_ARGUMENT: {
+			CHECK_SYMBOLS(2);
+			return "The argument '" + symbols[1] + "' is never used in the function '" + symbols[0] + "'.";
+		} break;
+		case UNREACHABLE_CODE: {
+			CHECK_SYMBOLS(1);
+			return "Unreachable code (statement after return) in function '" + symbols[0] + "()'.";
+		} break;
+		case STANDALONE_EXPRESSION: {
+			return "Standalone expression (the line has no effect).";
+		} break;
+		case VOID_ASSIGNMENT: {
+			CHECK_SYMBOLS(1);
+			return "Assignment operation, but the function '" + symbols[0] + "()' returns void.";
+		} break;
+		case NARROWING_CONVERSION: {
+			return "Narrowing coversion (float is converted to int and lose precision).";
+		} break;
+		case FUNCTION_MAY_YIELD: {
+			CHECK_SYMBOLS(1);
+			return "Assigned variable is typed but the function '" + symbols[0] + "()' may yield and return a GDScriptFunctionState instead.";
+		} break;
+		case VARIABLE_CONFLICTS_FUNCTION: {
+			CHECK_SYMBOLS(1);
+			return "Variable declaration of '" + symbols[0] + "' conflicts with a function of the same name.";
+		} break;
+		case FUNCTION_CONFLICTS_VARIABLE: {
+			CHECK_SYMBOLS(1);
+			return "Function declaration of '" + symbols[0] + "()' conflicts with a variable of the same name.";
+		} break;
+		case FUNCTION_CONFLICTS_CONSTANT: {
+			CHECK_SYMBOLS(1);
+			return "Function declaration of '" + symbols[0] + "()' conflicts with a constant of the same name.";
+		} break;
+		case INCOMPATIBLE_TERNARY: {
+			return "Values of the ternary conditional are not mutually compatible.";
+		} break;
+		case UNUSED_SIGNAL: {
+			CHECK_SYMBOLS(1);
+			return "The signal '" + symbols[0] + "' is declared but never emitted.";
+		} break;
+		case RETURN_VALUE_DISCARDED: {
+			CHECK_SYMBOLS(1);
+			return "The function '" + symbols[0] + "()' returns a value, but this value is never used.";
+		} break;
+		case PROPERTY_USED_AS_FUNCTION: {
+			CHECK_SYMBOLS(2);
+			return "The method '" + symbols[0] + "()' was not found in base '" + symbols[1] + "' but there's a property with the same name. Did you mean to access it?";
+		} break;
+		case CONSTANT_USED_AS_FUNCTION: {
+			CHECK_SYMBOLS(2);
+			return "The method '" + symbols[0] + "()' was not found in base '" + symbols[1] + "' but there's a constant with the same name. Did you mean to access it?";
+		} break;
+		case FUNCTION_USED_AS_PROPERTY: {
+			CHECK_SYMBOLS(2);
+			return "The property '" + symbols[0] + "' was not found in base '" + symbols[1] + "' but there's a method with the same name. Did you mean to call it?";
+		} break;
+		case INTEGER_DIVISION: {
+			return "Integer division, decimal part will be discarded.";
+		} break;
+		case UNSAFE_PROPERTY_ACCESS: {
+			CHECK_SYMBOLS(2);
+			return "The property '" + symbols[0] + "' is not present on the inferred type '" + symbols[1] + "' (but may be present on a subtype).";
+		} break;
+		case UNSAFE_METHOD_ACCESS: {
+			CHECK_SYMBOLS(2);
+			return "The method '" + symbols[0] + "' is not present on the inferred type '" + symbols[1] + "' (but may be present on a subtype).";
+		} break;
+		case UNSAFE_CAST: {
+			CHECK_SYMBOLS(1);
+			return "The value is cast to '" + symbols[0] + "' but has an unkown type.";
+		} break;
+		case UNSAFE_CALL_ARGUMENT: {
+			CHECK_SYMBOLS(4);
+			return "The argument '" + symbols[0] + "' of the function '" + symbols[1] + "' requires a the subtype '" + symbols[2] + "' but the supertype '" + symbols[3] + "' was provided";
+		} break;
+	}
+	ERR_EXPLAIN("Invalid GDScript waring code: " + get_name_from_code(code));
+	ERR_FAIL_V(String());
+
+#undef CHECK_SYMBOLS
+}
+
+String GDScriptWarning::get_name() const {
+	return get_name_from_code(code);
+}
+
+String GDScriptWarning::get_name_from_code(Code p_code) {
+	ERR_FAIL_COND_V(p_code < 0 || p_code >= WARNING_MAX, String());
+
+	static const char *names[] = {
+		"UNASSIGNED_VARIABLE",
+		"UNASSIGNED_VARIABLE_OP_ASSIGN",
+		"UNUSED_VARIABLE",
+		"UNUSED_CLASS_VARIABLE",
+		"UNUSED_ARGUMENT",
+		"UNREACHABLE_CODE",
+		"STANDALONE_EXPRESSION",
+		"VOID_ASSIGNMENT",
+		"NARROWING_CONVERSION",
+		"FUNCTION_MAY_YIELD",
+		"VARIABLE_CONFLICTS_FUNCTION",
+		"FUNCTION_CONFLICTS_VARIABLE",
+		"FUNCTION_CONFLICTS_CONSTANT",
+		"INCOMPATIBLE_TERNARY",
+		"UNUSED_SIGNAL",
+		"RETURN_VALUE_DISCARDED",
+		"PROPERTY_USED_AS_FUNCTION",
+		"CONSTANT_USED_AS_FUNCTION",
+		"FUNCTION_USED_AS_PROPERTY",
+		"INTEGER_DIVISION",
+		"UNSAFE_PROPERTY_ACCESS",
+		"UNSAFE_METHOD_ACCESS",
+		"UNSAFE_CAST",
+		"UNSAFE_CALL_ARGUMENT",
+		NULL
+	};
+
+	return names[(int)p_code];
+}
+
+GDScriptWarning::Code GDScriptWarning::get_code_from_name(const String &p_name) {
+	for (int i = 0; i < WARNING_MAX; i++) {
+		if (get_name_from_code((Code)i) == p_name) {
+			return (Code)i;
+		}
+	}
+
+	ERR_EXPLAIN("Invalid GDScript waring name: " + p_name);
+	ERR_FAIL_V(WARNING_MAX);
+}
+
+#endif // DEBUG_ENABLED
+
 GDScriptLanguage::GDScriptLanguage() {
 
 	calls = 0;
@@ -1903,6 +2066,15 @@ GDScriptLanguage::GDScriptLanguage() {
 		_debug_max_call_stack = 0;
 		_call_stack = NULL;
 	}
+
+#ifdef DEBUG_ENABLED
+	GLOBAL_DEF("debug/gdscript/warnings/enable", true);
+	GLOBAL_DEF("debug/gdscript/warnings/treat_warnings_as_errors", false);
+	for (int i = 0; i < (int)GDScriptWarning::WARNING_MAX; i++) {
+		String warning = GDScriptWarning::get_name_from_code((GDScriptWarning::Code)i).to_lower();
+		GLOBAL_DEF("debug/gdscript/warnings/" + warning, !warning.begins_with("unsafe_"));
+	}
+#endif // DEBUG_ENABLED
 }
 
 GDScriptLanguage::~GDScriptLanguage() {

+ 44 - 1
modules/gdscript/gdscript.h

@@ -261,6 +261,49 @@ public:
 	~GDScriptInstance();
 };
 
+#ifdef DEBUG_ENABLED
+struct GDScriptWarning {
+	enum Code {
+		UNASSIGNED_VARIABLE, // Variable used but never assigned
+		UNASSIGNED_VARIABLE_OP_ASSIGN, // Variable never assigned but used in an assignment operation (+=, *=, etc)
+		UNUSED_VARIABLE, // Local variable is declared but never used
+		UNUSED_CLASS_VARIABLE, // Class variable is declared but never used in the file
+		UNUSED_ARGUMENT, // Function argument is never used
+		UNREACHABLE_CODE, // Code after a return statement
+		STANDALONE_EXPRESSION, // Expression not assigned to a variable
+		VOID_ASSIGNMENT, // Function returns void but it's assigned to a variable
+		NARROWING_CONVERSION, // Float value into an integer slot, precision is lost
+		FUNCTION_MAY_YIELD, // Typed assign of function call that yields (it may return a function state)
+		VARIABLE_CONFLICTS_FUNCTION, // Variable has the same name of a function
+		FUNCTION_CONFLICTS_VARIABLE, // Function has the same name of a variable
+		FUNCTION_CONFLICTS_CONSTANT, // Function has the same name of a constant
+		INCOMPATIBLE_TERNARY, // Possible values of a ternary if are not mutually compatible
+		UNUSED_SIGNAL, // Signal is defined but never emitted
+		RETURN_VALUE_DISCARDED, // Function call returns something but the value isn't used
+		PROPERTY_USED_AS_FUNCTION, // Function not found, but there's a property with the same name
+		CONSTANT_USED_AS_FUNCTION, // Function not found, but there's a constant with the same name
+		FUNCTION_USED_AS_PROPERTY, // Property not found, but there's a function with the same name
+		INTEGER_DIVISION, // Integer divide by integer, decimal part is discarded
+		UNSAFE_PROPERTY_ACCESS, // Property not found in the detected type (but can be in subtypes)
+		UNSAFE_METHOD_ACCESS, // Fucntion not found in the detected type (but can be in subtypes)
+		UNSAFE_CAST, // Cast used in an unknown type
+		UNSAFE_CALL_ARGUMENT, // Function call argument is of a supertype of the require argument
+		WARNING_MAX,
+	} code;
+	Vector<String> symbols;
+	int line;
+
+	String get_name() const;
+	String get_message() const;
+	static String get_name_from_code(Code p_code);
+	static Code get_code_from_name(const String &p_name);
+
+	GDScriptWarning() :
+			line(-1),
+			code(WARNING_MAX) {}
+};
+#endif // DEBUG_ENABLED
+
 class GDScriptLanguage : public ScriptLanguage {
 
 	static GDScriptLanguage *singleton;
@@ -397,7 +440,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, Set<int> *r_safe_lines = 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, List<ScriptLanguage::Warning> *r_warnings = 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
modules/gdscript/gdscript_editor.cpp

@@ -116,11 +116,24 @@ 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, Set<int> *r_safe_lines) 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, List<ScriptLanguage::Warning> *r_warnings, Set<int> *r_safe_lines) const {
 
 	GDScriptParser parser;
 
 	Error err = parser.parse(p_script, p_path.get_base_dir(), true, p_path, false, r_safe_lines);
+#ifdef DEBUG_ENABLED
+	if (r_warnings) {
+		for (const List<GDScriptWarning>::Element *E = parser.get_warnings().front(); E; E = E->next()) {
+			const GDScriptWarning &warn = E->get();
+			ScriptLanguage::Warning w;
+			w.line = warn.line;
+			w.code = (int)warn.code;
+			w.string_code = GDScriptWarning::get_name_from_code(warn.code);
+			w.message = warn.get_message();
+			r_warnings->push_back(w);
+		}
+	}
+#endif
 	if (err) {
 		r_line_error = parser.get_error_line();
 		r_col_error = parser.get_error_column();

+ 397 - 32
modules/gdscript/gdscript_parser.cpp

@@ -38,6 +38,7 @@
 #include "io/resource_loader.h"
 #include "os/file_access.h"
 #include "print_string.h"
+#include "project_settings.h"
 #include "script_language.h"
 
 template <class T>
@@ -56,6 +57,8 @@ T *GDScriptParser::alloc_node() {
 	return t;
 }
 
+static String _find_function_name(const GDScriptParser::OperatorNode *p_call);
+
 bool GDScriptParser::_end_statement() {
 
 	if (tokenizer->get_token() == GDScriptTokenizer::TK_SEMICOLON) {
@@ -726,7 +729,7 @@ GDScriptParser::Node *GDScriptParser::_parse_expression(Node *p_parent, bool p_s
 			}
 
 			BlockNode *b = current_block;
-			while (b) {
+			while (!bfn && b) {
 				if (b->variables.has(identifier)) {
 					IdentifierNode *id = alloc_node<IdentifierNode>();
 					LocalVarNode *lv = b->variables[identifier];
@@ -736,6 +739,7 @@ GDScriptParser::Node *GDScriptParser::_parse_expression(Node *p_parent, bool p_s
 					expr = id;
 					bfn = true;
 
+#ifdef DEBUG_ENABLED
 					switch (tokenizer->get_token()) {
 						case GDScriptTokenizer::TK_OP_ASSIGN_ADD:
 						case GDScriptTokenizer::TK_OP_ASSIGN_BIT_AND:
@@ -747,15 +751,23 @@ GDScriptParser::Node *GDScriptParser::_parse_expression(Node *p_parent, bool p_s
 						case GDScriptTokenizer::TK_OP_ASSIGN_SHIFT_LEFT:
 						case GDScriptTokenizer::TK_OP_ASSIGN_SHIFT_RIGHT:
 						case GDScriptTokenizer::TK_OP_ASSIGN_SUB: {
-							if (lv->assignments == 0 && !lv->datatype.has_type) {
-								_set_error("Using assignment with operation on a variable that was never assigned.");
-								return NULL;
+							if (lv->assignments == 0) {
+								if (!lv->datatype.has_type) {
+									_set_error("Using assignment with operation on a variable that was never assigned.");
+									return NULL;
+								}
+								_add_warning(GDScriptWarning::UNASSIGNED_VARIABLE_OP_ASSIGN, -1, identifier.operator String());
 							}
 						} // fallthrough
 						case GDScriptTokenizer::TK_OP_ASSIGN: {
 							lv->assignments += 1;
+							lv->usages--; // Assignment is not really usage
+						} break;
+						default: {
+							lv->usages++;
 						}
 					}
+#endif // DEBUG_ENABLED
 					break;
 				}
 				b = b->parent_block;
@@ -785,6 +797,32 @@ GDScriptParser::Node *GDScriptParser::_parse_expression(Node *p_parent, bool p_s
 			}
 
 			if (!bfn) {
+#ifdef DEBUG_ENABLED
+				if (current_function) {
+					int arg_idx = current_function->arguments.find(identifier);
+					if (arg_idx != -1) {
+						switch (tokenizer->get_token()) {
+							case GDScriptTokenizer::TK_OP_ASSIGN_ADD:
+							case GDScriptTokenizer::TK_OP_ASSIGN_BIT_AND:
+							case GDScriptTokenizer::TK_OP_ASSIGN_BIT_OR:
+							case GDScriptTokenizer::TK_OP_ASSIGN_BIT_XOR:
+							case GDScriptTokenizer::TK_OP_ASSIGN_DIV:
+							case GDScriptTokenizer::TK_OP_ASSIGN_MOD:
+							case GDScriptTokenizer::TK_OP_ASSIGN_MUL:
+							case GDScriptTokenizer::TK_OP_ASSIGN_SHIFT_LEFT:
+							case GDScriptTokenizer::TK_OP_ASSIGN_SHIFT_RIGHT:
+							case GDScriptTokenizer::TK_OP_ASSIGN_SUB:
+							case GDScriptTokenizer::TK_OP_ASSIGN: {
+								// Assignment is not really usage
+								current_function->arguments_usage.write[arg_idx] = current_function->arguments_usage[arg_idx] - 1;
+							} break;
+							default: {
+								current_function->arguments_usage.write[arg_idx] = current_function->arguments_usage[arg_idx] + 1;
+							}
+						}
+					}
+				}
+#endif // DEBUG_ENABLED
 				IdentifierNode *id = alloc_node<IdentifierNode>();
 				id->name = identifier;
 				id->line = id_line;
@@ -2601,6 +2639,7 @@ void GDScriptParser::_parse_block(BlockNode *p_block, bool p_static) {
 			pending_newline = -1;
 		}
 
+#ifdef DEBUG_ENABLED
 		switch (token) {
 			case GDScriptTokenizer::TK_EOF:
 			case GDScriptTokenizer::TK_ERROR:
@@ -2609,13 +2648,13 @@ void GDScriptParser::_parse_block(BlockNode *p_block, bool p_static) {
 				// will check later
 			} break;
 			default: {
-				// TODO: Make this a warning
-				/*if (p_block->has_return) {
-					_set_error("Unreacheable code.");
-					return;
-				}*/
+				if (p_block->has_return && !current_function->has_unreachable_code) {
+					_add_warning(GDScriptWarning::UNREACHABLE_CODE, -1, current_function->name.operator String());
+					current_function->has_unreachable_code = true;
+				}
 			} break;
 		}
+#endif // DEBUG_ENABLED
 		switch (token) {
 			case GDScriptTokenizer::TK_EOF:
 				p_block->end_line = tokenizer->get_token_line();
@@ -2728,6 +2767,7 @@ void GDScriptParser::_parse_block(BlockNode *p_block, bool p_static) {
 					c->line = var_line;
 					assigned = c;
 				}
+				lv->assign = assigned;
 				//must be added later, to avoid self-referencing.
 				p_block->variables.insert(n, lv);
 
@@ -2745,6 +2785,8 @@ void GDScriptParser::_parse_block(BlockNode *p_block, bool p_static) {
 				lv->assign_op = op;
 				lv->assign = assigned;
 
+				lv->assign_op = op;
+
 				if (!_end_statement()) {
 					_set_error("Expected end of statement (var)");
 					return;
@@ -3513,6 +3555,17 @@ void GDScriptParser::_parse_class(ClassNode *p_class) {
 					}
 				}
 
+#ifdef DEBUG_ENABLED
+				if (p_class->constant_expressions.has(name)) {
+					_add_warning(GDScriptWarning::FUNCTION_CONFLICTS_CONSTANT, -1, name);
+				}
+				for (int i = 0; i < p_class->variables.size(); i++) {
+					if (p_class->variables[i].identifier == name) {
+						_add_warning(GDScriptWarning::FUNCTION_CONFLICTS_VARIABLE, -1, name);
+					}
+				}
+#endif // DEBUG_ENABLED
+
 				if (tokenizer->get_token() != GDScriptTokenizer::TK_PARENTHESIS_OPEN) {
 
 					_set_error("Expected '(' after identifier (syntax: 'func <identifier>([arguments]):' ).");
@@ -3524,6 +3577,9 @@ void GDScriptParser::_parse_class(ClassNode *p_class) {
 				Vector<StringName> arguments;
 				Vector<DataType> argument_types;
 				Vector<Node *> default_values;
+#ifdef DEBUG_ENABLED
+				Vector<int> arguments_usage;
+#endif // DEBUG_ENABLED
 
 				int fnline = tokenizer->get_token_line();
 
@@ -3550,6 +3606,9 @@ void GDScriptParser::_parse_class(ClassNode *p_class) {
 
 						StringName argname = tokenizer->get_token_identifier();
 						arguments.push_back(argname);
+#ifdef DEBUG_ENABLED
+						arguments_usage.push_back(0);
+#endif // DEBUG_ENABLED
 
 						tokenizer->advance();
 
@@ -3703,7 +3762,9 @@ void GDScriptParser::_parse_class(ClassNode *p_class) {
 				function->default_values = default_values;
 				function->_static = _static;
 				function->line = fnline;
-
+#ifdef DEBUG_ENABLED
+				function->arguments_usage = arguments_usage;
+#endif // DEBUG_ENABLED
 				function->rpc_mode = rpc_mode;
 				rpc_mode = MultiplayerAPI::RPC_MODE_DISABLED;
 
@@ -3730,6 +3791,8 @@ void GDScriptParser::_parse_class(ClassNode *p_class) {
 
 				ClassNode::Signal sig;
 				sig.name = tokenizer->get_token_identifier();
+				sig.emissions = 0;
+				sig.line = tokenizer->get_token_line();
 				tokenizer->advance();
 
 				if (tokenizer->get_token() == GDScriptTokenizer::TK_PARENTHESIS_OPEN) {
@@ -4413,6 +4476,7 @@ void GDScriptParser::_parse_class(ClassNode *p_class) {
 				member.expression = NULL;
 				member._export.name = member.identifier;
 				member.line = tokenizer->get_token_line();
+				member.usages = 0;
 				member.rpc_mode = rpc_mode;
 
 				if (current_class->constant_expressions.has(member.identifier)) {
@@ -4428,7 +4492,20 @@ void GDScriptParser::_parse_class(ClassNode *p_class) {
 						return;
 					}
 				}
-
+#ifdef DEBUG_ENABLED
+				for (int i = 0; i < current_class->functions.size(); i++) {
+					if (current_class->functions[i]->name == member.identifier) {
+						_add_warning(GDScriptWarning::VARIABLE_CONFLICTS_FUNCTION, member.line, member.identifier);
+						break;
+					}
+				}
+				for (int i = 0; i < current_class->static_functions.size(); i++) {
+					if (current_class->static_functions[i]->name == member.identifier) {
+						_add_warning(GDScriptWarning::VARIABLE_CONFLICTS_FUNCTION, member.line, member.identifier);
+						break;
+					}
+				}
+#endif // DEBUG_ENABLED
 				tokenizer->advance();
 
 				rpc_mode = MultiplayerAPI::RPC_MODE_DISABLED;
@@ -5689,11 +5766,26 @@ GDScriptParser::DataType GDScriptParser::_reduce_node_type(Node *p_node) {
 			node_type.has_type = true;
 			node_type.kind = DataType::BUILTIN;
 			node_type.builtin_type = Variant::ARRAY;
+#ifdef DEBUG_ENABLED
+			// Check stuff inside the array
+			ArrayNode *an = static_cast<ArrayNode *>(p_node);
+			for (int i = 0; i < an->elements.size(); i++) {
+				_reduce_node_type(an->elements[i]);
+			}
+#endif // DEBUG_ENABLED
 		} break;
 		case Node::TYPE_DICTIONARY: {
 			node_type.has_type = true;
 			node_type.kind = DataType::BUILTIN;
 			node_type.builtin_type = Variant::DICTIONARY;
+#ifdef DEBUG_ENABLED
+			// Check stuff inside the dictionarty
+			DictionaryNode *dn = static_cast<DictionaryNode *>(p_node);
+			for (int i = 0; i < dn->elements.size(); i++) {
+				_reduce_node_type(dn->elements[i].key);
+				_reduce_node_type(dn->elements[i].value);
+			}
+#endif // DEBUG_ENABLED
 		} break;
 		case Node::TYPE_SELF: {
 			node_type.has_type = true;
@@ -5704,6 +5796,8 @@ GDScriptParser::DataType GDScriptParser::_reduce_node_type(Node *p_node) {
 			IdentifierNode *id = static_cast<IdentifierNode *>(p_node);
 			if (id->declared_block) {
 				node_type = id->declared_block->variables[id->name]->get_datatype();
+				id->declared_block->variables[id->name]->usages += 1;
+				print_line("var " + id->name + " line " + itos(id->line) + " usages " + itos(id->declared_block->variables[id->name]->usages));
 			} else if (id->name == "#match_value") {
 				// It's a special id just for the match statetement, ignore
 				break;
@@ -5738,6 +5832,9 @@ GDScriptParser::DataType GDScriptParser::_reduce_node_type(Node *p_node) {
 					}
 				}
 			} else {
+#ifdef DEBUG_ENABLED
+				_add_warning(GDScriptWarning::UNSAFE_CAST, cn->line, cn->cast_type.to_string());
+#endif // DEBUG_ENABLED
 				_mark_line_as_unsafe(cn->line);
 			}
 
@@ -5864,6 +5961,12 @@ GDScriptParser::DataType GDScriptParser::_reduce_node_type(Node *p_node) {
 								op->line, op->column);
 						return DataType();
 					}
+#ifdef DEBUG_ENABLED
+					if (var_op == Variant::OP_DIVIDE && argument_a_type.has_type && argument_a_type.kind == DataType::BUILTIN && argument_a_type.builtin_type == Variant::INT &&
+							argument_b_type.has_type && argument_b_type.kind == DataType::BUILTIN && argument_b_type.builtin_type == Variant::INT) {
+						_add_warning(GDScriptWarning::INTEGER_DIVISION, op->line);
+					}
+#endif // DEBUG_ENABLED
 
 				} break;
 				// Ternary operators
@@ -5882,10 +5985,11 @@ GDScriptParser::DataType GDScriptParser::_reduce_node_type(Node *p_node) {
 						node_type = true_type;
 					} else if (_is_type_compatible(false_type, true_type)) {
 						node_type = false_type;
+					} else {
+#ifdef DEBUG_ENABLED
+						_add_warning(GDScriptWarning::INCOMPATIBLE_TERNARY, op->line);
+#endif // DEBUG_ENABLED
 					}
-
-					// TODO: Warn if types aren't compatible
-
 				} break;
 				// Assignment should never happen within an expression
 				case OperatorNode::OP_ASSIGN:
@@ -5948,6 +6052,11 @@ GDScriptParser::DataType GDScriptParser::_reduce_node_type(Node *p_node) {
 							node_type = result;
 						} else {
 							node_type = _reduce_identifier_type(&base_type, member_id->name, op->line);
+#ifdef DEBUG_ENABLED
+							if (!node_type.has_type) {
+								_add_warning(GDScriptWarning::UNSAFE_PROPERTY_ACCESS, op->line, member_id->name.operator String(), base_type.to_string());
+							}
+#endif // DEBUG_ENABLED
 						}
 					} else {
 						_mark_line_as_unsafe(op->line);
@@ -6367,6 +6476,15 @@ GDScriptParser::DataType GDScriptParser::_reduce_function_call_type(const Operat
 					if (!_is_type_compatible(arg_type, par_types[i], true)) {
 						types_match = false;
 						break;
+					} else {
+#ifdef DEBUG_ENABLED
+						if (arg_type.kind == DataType::BUILTIN && arg_type.builtin_type == Variant::INT && par_types[i].kind == DataType::BUILTIN && par_types[i].builtin_type == Variant::REAL) {
+							_add_warning(GDScriptWarning::NARROWING_CONVERSION, p_call->line, Variant::get_type_name(tn->vtype));
+						}
+						if (par_types[i].may_yield && p_call->arguments[i + 1]->type == Node::TYPE_OPERATOR) {
+							_add_warning(GDScriptWarning::FUNCTION_MAY_YIELD, p_call->line, _find_function_name(static_cast<OperatorNode *>(p_call->arguments[i + 1])));
+						}
+#endif // DEBUG_ENABLED
 					}
 				}
 
@@ -6400,6 +6518,13 @@ GDScriptParser::DataType GDScriptParser::_reduce_function_call_type(const Operat
 
 			return_type = _type_from_property(mi.return_val, false);
 
+#ifdef DEBUG_ENABLED
+			// Check all arguments beforehand to solve warnings
+			for (int i = 1; i < p_call->arguments.size(); i++) {
+				_reduce_node_type(p_call->arguments[i]);
+			}
+#endif // DEBUG_ENABLED
+
 			// Check arguments
 
 			is_vararg = mi.flags & METHOD_FLAG_VARARG;
@@ -6426,6 +6551,13 @@ GDScriptParser::DataType GDScriptParser::_reduce_function_call_type(const Operat
 				ERR_FAIL_V(DataType());
 			}
 
+#ifdef DEBUG_ENABLED
+			// Check all arguments beforehand to solve warnings
+			for (int i = arg_id + 1; i < p_call->arguments.size(); i++) {
+				_reduce_node_type(p_call->arguments[i]);
+			}
+#endif // DEBUG_ENABLED
+
 			IdentifierNode *func_id = static_cast<IdentifierNode *>(p_call->arguments[arg_id]);
 			callee_name = func_id->name;
 			arg_count -= 1 + arg_id;
@@ -6505,8 +6637,18 @@ GDScriptParser::DataType GDScriptParser::_reduce_function_call_type(const Operat
 					_set_error("Method '" + callee_name + "' is not declared in the current class.", p_call->line);
 					return DataType();
 				}
+				DataType tmp_type;
+				valid = _get_member_type(original_type, func_id->name, tmp_type);
+				if (valid) {
+					if (tmp_type.is_constant) {
+						_add_warning(GDScriptWarning::CONSTANT_USED_AS_FUNCTION, p_call->line, callee_name, original_type.to_string());
+					} else {
+						_add_warning(GDScriptWarning::PROPERTY_USED_AS_FUNCTION, p_call->line, callee_name, original_type.to_string());
+					}
+				}
+				_add_warning(GDScriptWarning::UNSAFE_METHOD_ACCESS, p_call->line, callee_name, original_type.to_string());
 				_mark_line_as_unsafe(p_call->line);
-#endif
+#endif // DEBUG_ENABLED
 				return DataType();
 			}
 
@@ -6522,7 +6664,19 @@ GDScriptParser::DataType GDScriptParser::_reduce_function_call_type(const Operat
 				_set_error("Non-static function '" + String(callee_name) + "' can only be called from an instance.", p_call->line);
 				return DataType();
 			}
-#endif
+
+			// Check signal emission for warnings
+			if (callee_name == "emit_signal" && p_call->op == OperatorNode::OP_CALL && p_call->arguments[0]->type == Node::TYPE_SELF && p_call->arguments.size() >= 3 && p_call->arguments[2]->type == Node::TYPE_CONSTANT) {
+				ConstantNode *sig = static_cast<ConstantNode *>(p_call->arguments[2]);
+				String emitted = sig->value.get_type() == Variant::STRING ? sig->value.operator String() : "";
+				for (int i = 0; i < current_class->_signals.size(); i++) {
+					if (current_class->_signals[i].name == emitted) {
+						current_class->_signals.write[i].emissions += 1;
+						break;
+					}
+				}
+			}
+#endif // DEBUG_ENABLED
 		} break;
 	}
 
@@ -6547,8 +6701,15 @@ GDScriptParser::DataType GDScriptParser::_reduce_function_call_type(const Operat
 			continue;
 		}
 
+		DataType arg_type = arg_types[i - arg_diff];
+
 		if (!par_type.has_type) {
 			_mark_line_as_unsafe(p_call->line);
+#ifdef DEBUG_ENABLED
+			if (par_type.may_yield && p_call->arguments[i]->type == Node::TYPE_OPERATOR) {
+				_add_warning(GDScriptWarning::FUNCTION_MAY_YIELD, p_call->line, _find_function_name(static_cast<OperatorNode *>(p_call->arguments[i])));
+			}
+#endif // DEBUG_ENABLED
 		} 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])) {
@@ -6560,6 +6721,12 @@ GDScriptParser::DataType GDScriptParser::_reduce_function_call_type(const Operat
 			} else {
 				_mark_line_as_unsafe(p_call->line);
 			}
+		} else {
+#ifdef DEBUG_ENABLED
+			if (arg_type.kind == DataType::BUILTIN && arg_type.builtin_type == Variant::INT && par_type.kind == DataType::BUILTIN && par_type.builtin_type == Variant::REAL) {
+				_add_warning(GDScriptWarning::NARROWING_CONVERSION, p_call->line, callee_name);
+			}
+#endif // DEBUG_ENABLED
 		}
 	}
 
@@ -6795,6 +6962,15 @@ GDScriptParser::DataType GDScriptParser::_reduce_identifier_type(const DataType
 
 	DataType member_type;
 
+	for (int i = 0; i < current_class->variables.size(); i++) {
+		ClassNode::Member m = current_class->variables[i];
+		if (current_class->variables[i].identifier == p_identifier) {
+			member_type = current_class->variables[i].data_type;
+			current_class->variables.write[i].usages += 1;
+			return member_type;
+		}
+	}
+
 	if (_get_member_type(base_type, p_identifier, member_type)) {
 		return member_type;
 	}
@@ -6922,6 +7098,19 @@ GDScriptParser::DataType GDScriptParser::_reduce_identifier_type(const DataType
 		_set_error("Identifier '" + p_identifier.operator String() + "' is not declared in the current scope.", p_line);
 	}
 
+#ifdef DEBUG_ENABLED
+	{
+		DataType tmp_type;
+		List<DataType> arg_types;
+		int argcount;
+		bool _static;
+		bool vararg;
+		if (_get_function_signature(base_type, p_identifier, tmp_type, arg_types, argcount, _static, vararg)) {
+			_add_warning(GDScriptWarning::FUNCTION_USED_AS_PROPERTY, p_line, p_identifier.operator String(), base_type.to_string());
+		}
+	}
+#endif // DEBUG_ENABLED
+
 	_mark_line_as_unsafe(p_line);
 	return DataType();
 }
@@ -7174,6 +7363,11 @@ void GDScriptParser::_check_function_types(FunctionNode *p_function) {
 				}
 			}
 		}
+#ifdef DEBUG_ENABLED
+		if (p_function->arguments_usage[i] == 0) {
+			_add_warning(GDScriptWarning::UNUSED_ARGUMENT, p_function->line, p_function->name, p_function->arguments[i].operator String());
+		}
+#endif // DEBUG_ENABLED
 	}
 
 	if (!(p_function->name == "_init")) {
@@ -7244,6 +7438,7 @@ void GDScriptParser::_check_function_types(FunctionNode *p_function) {
 	if (p_function->has_yield) {
 		// yield() will make the function return a GDScriptFunctionState, so the type is ambiguous
 		p_function->return_type.has_type = false;
+		p_function->return_type.may_yield = true;
 	}
 }
 
@@ -7270,6 +7465,20 @@ void GDScriptParser::_check_class_blocks_types(ClassNode *p_class) {
 		if (error_set) return;
 	}
 
+#ifdef DEBUG_ENABLED
+	// Warnings
+	for (int i = 0; i < p_class->variables.size(); i++) {
+		if (p_class->variables[i].usages == 0) {
+			_add_warning(GDScriptWarning::UNUSED_CLASS_VARIABLE, p_class->variables[i].line, p_class->variables[i].identifier);
+		}
+	}
+	for (int i = 0; i < p_class->_signals.size(); i++) {
+		if (p_class->_signals[i].emissions == 0) {
+			_add_warning(GDScriptWarning::UNUSED_SIGNAL, p_class->_signals[i].line, p_class->_signals[i].name);
+		}
+	}
+#endif // DEBUG_ENABLED
+
 	// Inner classes
 	for (int i = 0; i < p_class->subclasses.size(); i++) {
 		current_class = p_class->subclasses[i];
@@ -7279,6 +7488,26 @@ void GDScriptParser::_check_class_blocks_types(ClassNode *p_class) {
 	}
 }
 
+#ifdef DEBUG_ENABLED
+static String _find_function_name(const GDScriptParser::OperatorNode *p_call) {
+	switch (p_call->arguments[0]->type) {
+		case GDScriptParser::Node::TYPE_TYPE: {
+			return Variant::get_type_name(static_cast<GDScriptParser::TypeNode *>(p_call->arguments[0])->vtype);
+		} break;
+		case GDScriptParser::Node::TYPE_BUILT_IN_FUNCTION: {
+			return GDScriptFunctions::get_func_name(static_cast<GDScriptParser::BuiltInFunctionNode *>(p_call->arguments[0])->function);
+		} break;
+		default: {
+			int id_index = p_call->op == GDScriptParser::OperatorNode::OP_PARENT_CALL ? 0 : 1;
+			if (p_call->arguments.size() > id_index && p_call->arguments[id_index]->type == GDScriptParser::Node::TYPE_IDENTIFIER) {
+				return static_cast<GDScriptParser::IdentifierNode *>(p_call->arguments[id_index])->name;
+			}
+		} break;
+	}
+	return String();
+}
+#endif // DEBUG_ENABLED
+
 void GDScriptParser::_check_block_types(BlockNode *p_block) {
 
 	Node *last_var_assign = NULL;
@@ -7297,8 +7526,23 @@ void GDScriptParser::_check_block_types(BlockNode *p_block) {
 				lv->datatype = _resolve_type(lv->datatype, lv->line);
 				_mark_line_as_safe(lv->line);
 
+				last_var_assign = lv->assign;
 				if (lv->assign) {
 					DataType assign_type = _reduce_node_type(lv->assign);
+#ifdef DEBUG_ENABLED
+					if (assign_type.has_type && assign_type.kind == DataType::BUILTIN && assign_type.builtin_type == Variant::NIL) {
+						if (lv->assign->type == Node::TYPE_OPERATOR) {
+							OperatorNode *call = static_cast<OperatorNode *>(lv->assign);
+							if (call->op == OperatorNode::OP_CALL || call->op == OperatorNode::OP_PARENT_CALL) {
+								_add_warning(GDScriptWarning::VOID_ASSIGNMENT, lv->line, _find_function_name(call));
+							}
+						}
+					}
+					if (lv->datatype.has_type && assign_type.may_yield && lv->assign->type == Node::TYPE_OPERATOR) {
+						_add_warning(GDScriptWarning::FUNCTION_MAY_YIELD, lv->line, _find_function_name(static_cast<OperatorNode *>(lv->assign)));
+					}
+#endif // DEBUG_ENABLED
+
 					if (!_is_type_compatible(lv->datatype, assign_type)) {
 						// Try supertype test
 						if (_is_type_compatible(assign_type, lv->datatype)) {
@@ -7329,6 +7573,11 @@ void GDScriptParser::_check_block_types(BlockNode *p_block) {
 
 							lv->assign = convert_call;
 							lv->assign_op->arguments.write[1] = convert_call;
+#ifdef DEBUG_ENABLED
+							if (lv->datatype.builtin_type == Variant::INT && assign_type.builtin_type == Variant::REAL) {
+								_add_warning(GDScriptWarning::NARROWING_CONVERSION, lv->line);
+							}
+#endif // DEBUG_ENABLED
 						}
 					}
 					if (lv->datatype.infer_type) {
@@ -7343,15 +7592,6 @@ void GDScriptParser::_check_block_types(BlockNode *p_block) {
 						_mark_line_as_unsafe(lv->line);
 					}
 				}
-				last_var_assign = lv->assign;
-
-				// TODO: Make a warning
-				/*
-				if (lv->assignments == 0) {
-					_set_error("Variable '" + String(lv->name) + "' is never assigned.", lv->line);
-					return;
-				}
-				*/
 			} break;
 			case Node::TYPE_OPERATOR: {
 				OperatorNode *op = static_cast<OperatorNode *>(statement);
@@ -7417,6 +7657,19 @@ void GDScriptParser::_check_block_types(BlockNode *p_block) {
 						} else {
 							rh_type = _reduce_node_type(op->arguments[1]);
 						}
+#ifdef DEBUG_ENABLED
+						if (rh_type.has_type && rh_type.kind == DataType::BUILTIN && rh_type.builtin_type == Variant::NIL) {
+							if (op->arguments[1]->type == Node::TYPE_OPERATOR) {
+								OperatorNode *call = static_cast<OperatorNode *>(op->arguments[1]);
+								if (call->op == OperatorNode::OP_CALL || call->op == OperatorNode::OP_PARENT_CALL) {
+									_add_warning(GDScriptWarning::VOID_ASSIGNMENT, op->line, _find_function_name(call));
+								}
+							}
+						}
+						if (lh_type.has_type && rh_type.may_yield && op->arguments[1]->type == Node::TYPE_OPERATOR) {
+							_add_warning(GDScriptWarning::FUNCTION_MAY_YIELD, op->line, _find_function_name(static_cast<OperatorNode *>(op->arguments[1])));
+						}
+#endif // DEBUG_ENABLED
 
 						if (!_is_type_compatible(lh_type, rh_type)) {
 							// Try supertype test
@@ -7447,6 +7700,11 @@ void GDScriptParser::_check_block_types(BlockNode *p_block) {
 								convert_call->arguments.push_back(tgt_type);
 
 								op->arguments.write[1] = convert_call;
+#ifdef DEBUG_ENABLED
+								if (lh_type.builtin_type == Variant::INT && rh_type.builtin_type == Variant::REAL) {
+									_add_warning(GDScriptWarning::NARROWING_CONVERSION, op->line);
+								}
+#endif // DEBUG_ENABLED
 							}
 						}
 						if (!rh_type.has_type && (op->op != OperatorNode::OP_ASSIGN || lh_type.has_type || op->arguments[0]->type == Node::TYPE_OPERATOR)) {
@@ -7456,15 +7714,29 @@ void GDScriptParser::_check_block_types(BlockNode *p_block) {
 					case OperatorNode::OP_CALL:
 					case OperatorNode::OP_PARENT_CALL: {
 						_mark_line_as_safe(op->line);
-						_reduce_function_call_type(op);
+						DataType func_type = _reduce_function_call_type(op);
+#ifdef DEBUG_ENABLED
+						if (func_type.has_type && (func_type.kind != DataType::BUILTIN || func_type.builtin_type != Variant::NIL)) {
+							// Figure out function name for warning
+							String func_name = _find_function_name(op);
+							if (func_name.empty()) {
+								func_name == "<undetected name>";
+							}
+							_add_warning(GDScriptWarning::RETURN_VALUE_DISCARDED, op->line, func_name);
+						}
+#endif // DEBUG_ENABLED
 						if (error_set) return;
 					} break;
+					case OperatorNode::OP_YIELD: {
+						_mark_line_as_safe(op->line);
+						_reduce_node_type(op);
+					} 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; */
+#ifdef DEBUG_ENABLED
+						_add_warning(GDScriptWarning::STANDALONE_EXPRESSION, statement->line);
+#endif // DEBUG_ENABLED
 					}
 				}
 			} break;
@@ -7531,9 +7803,9 @@ void GDScriptParser::_check_block_types(BlockNode *p_block) {
 			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; */
+#ifdef DEBUG_ENABLED
+				_add_warning(GDScriptWarning::STANDALONE_EXPRESSION, statement->line);
+#endif // DEBUG_ENABLED
 			}
 		}
 	}
@@ -7545,6 +7817,18 @@ void GDScriptParser::_check_block_types(BlockNode *p_block) {
 		current_block = p_block;
 		if (error_set) return;
 	}
+
+#ifdef DEBUG_ENABLED
+	// Warnings check
+	for (Map<StringName, LocalVarNode *>::Element *E = p_block->variables.front(); E; E = E->next()) {
+		LocalVarNode *lv = E->get();
+		if (lv->usages == 0) {
+			_add_warning(GDScriptWarning::UNUSED_VARIABLE, lv->line, lv->name);
+		} else if (lv->assignments == 0) {
+			_add_warning(GDScriptWarning::UNASSIGNED_VARIABLE, lv->line, lv->name);
+		}
+	}
+#endif // DEBUG_ENABLED
 }
 
 void GDScriptParser::_set_error(const String &p_error, int p_line, int p_column) {
@@ -7558,6 +7842,56 @@ void GDScriptParser::_set_error(const String &p_error, int p_line, int p_column)
 	error_set = true;
 }
 
+#ifdef DEBUG_ENABLED
+void GDScriptParser::_add_warning(int p_code, int p_line, const String &p_symbol1, const String &p_symbol2, const String &p_symbol3, const String &p_symbol4) {
+	Vector<String> symbols;
+	if (!p_symbol1.empty()) {
+		symbols.push_back(p_symbol1);
+	}
+	if (!p_symbol2.empty()) {
+		symbols.push_back(p_symbol2);
+	}
+	if (!p_symbol3.empty()) {
+		symbols.push_back(p_symbol3);
+	}
+	if (!p_symbol4.empty()) {
+		symbols.push_back(p_symbol4);
+	}
+	_add_warning(p_code, p_line, symbols);
+}
+
+void GDScriptParser::_add_warning(int p_code, int p_line, const Vector<String> &p_symbols) {
+	if (tokenizer->is_ignoring_warnings() || !GLOBAL_GET("debug/gdscript/warnings/enable").booleanize()) {
+		return;
+	}
+	String warn_name = GDScriptWarning::get_name_from_code((GDScriptWarning::Code)p_code).to_lower();
+	if (tokenizer->get_warning_global_skips().has(warn_name)) {
+		return;
+	}
+	if (!GLOBAL_GET("debug/gdscript/warnings/" + warn_name)) {
+		return;
+	}
+
+	GDScriptWarning warn;
+	warn.code = (GDScriptWarning::Code)p_code;
+	warn.symbols = p_symbols;
+	warn.line = p_line == -1 ? tokenizer->get_token_line() : p_line;
+
+	List<GDScriptWarning>::Element *before = NULL;
+	for (List<GDScriptWarning>::Element *E = warnings.front(); E; E = E->next()) {
+		if (E->get().line > warn.line) {
+			break;
+		}
+		before = E;
+	}
+	if (before) {
+		warnings.insert_after(before, warn);
+	} else {
+		warnings.push_front(warn);
+	}
+}
+#endif // DEBUG_ENABLED
+
 String GDScriptParser::get_error() const {
 
 	return error;
@@ -7624,6 +7958,37 @@ Error GDScriptParser::_parse(const String &p_base_path) {
 		return ERR_PARSE_ERROR;
 	}
 
+#ifdef DEBUG_ENABLED
+	// Resolve warning ignores
+	Vector<Pair<int, String> > warning_skips = tokenizer->get_warning_skips();
+	bool warning_is_error = GLOBAL_GET("debug/gdscript/warnings/treat_warnings_as_errors").booleanize();
+	for (List<GDScriptWarning>::Element *E = warnings.front(); E;) {
+		GDScriptWarning &w = E->get();
+		int skip_index = -1;
+		for (int i = 0; i < warning_skips.size(); i++) {
+			if (warning_skips[i].first >= w.line) {
+				break;
+			}
+			skip_index = i;
+		}
+		List<GDScriptWarning>::Element *next = E->next();
+		bool erase = false;
+		if (skip_index != -1) {
+			if (warning_skips[skip_index].second == GDScriptWarning::get_name_from_code(w.code).to_lower()) {
+				erase = true;
+			}
+			warning_skips.remove(skip_index);
+		}
+		if (erase) {
+			warnings.erase(E);
+		} else if (warning_is_error) {
+			_set_error(w.get_message() + " (warning treated as error)", w.line);
+			return ERR_PARSE_ERROR;
+		}
+		E = next;
+	}
+#endif // DEBUG_ENABLED
+
 	return OK;
 }
 

+ 24 - 0
modules/gdscript/gdscript_parser.h

@@ -38,6 +38,7 @@
 #include "script_language.h"
 
 struct GDScriptDataType;
+struct GDScriptWarning;
 
 class GDScriptParser {
 public:
@@ -57,6 +58,7 @@ public:
 		bool is_constant;
 		bool is_meta_type; // Whether the value can be used as a type
 		bool infer_type;
+		bool may_yield; // For function calls
 
 		Variant::Type builtin_type;
 		StringName native_type;
@@ -95,6 +97,7 @@ public:
 				is_constant(false),
 				is_meta_type(false),
 				infer_type(false),
+				may_yield(false),
 				builtin_type(Variant::NIL),
 				class_type(NULL) {}
 	};
@@ -160,6 +163,7 @@ public:
 			Node *expression;
 			OperatorNode *initial_assignment;
 			MultiplayerAPI::RPCMode rpc_mode;
+			int usages;
 		};
 		struct Constant {
 			Node *expression;
@@ -169,6 +173,8 @@ public:
 		struct Signal {
 			StringName name;
 			Vector<StringName> arguments;
+			int emissions;
+			int line;
 		};
 
 		Vector<ClassNode *> subclasses;
@@ -197,12 +203,16 @@ public:
 		bool _static;
 		MultiplayerAPI::RPCMode rpc_mode;
 		bool has_yield;
+		bool has_unreachable_code;
 		StringName name;
 		DataType return_type;
 		Vector<StringName> arguments;
 		Vector<DataType> argument_types;
 		Vector<Node *> default_values;
 		BlockNode *body;
+#ifdef DEBUG_ENABLED
+		Vector<int> arguments_usage;
+#endif // DEBUG_ENABLED
 
 		virtual DataType get_datatype() const { return return_type; }
 		virtual void set_datatype(const DataType &p_datatype) { return_type = p_datatype; }
@@ -212,6 +222,7 @@ public:
 			_static = false;
 			rpc_mode = MultiplayerAPI::RPC_MODE_DISABLED;
 			has_yield = false;
+			has_unreachable_code = false;
 		}
 	};
 
@@ -267,6 +278,7 @@ public:
 		Node *assign;
 		OperatorNode *assign_op;
 		int assignments;
+		int usages;
 		DataType datatype;
 		virtual DataType get_datatype() const { return datatype; }
 		virtual void set_datatype(const DataType &p_datatype) { datatype = p_datatype; }
@@ -275,6 +287,7 @@ public:
 			assign = NULL;
 			assign_op = NULL;
 			assignments = 0;
+			usages = 0;
 		}
 	};
 
@@ -518,6 +531,10 @@ private:
 	Set<int> *safe_lines;
 #endif // DEBUG_ENABLED
 
+#ifdef DEBUG_ENABLED
+	List<GDScriptWarning> warnings;
+#endif // DEBUG_ENABLED
+
 	int pending_newline;
 
 	List<int> tab_level;
@@ -550,6 +567,10 @@ private:
 	MultiplayerAPI::RPCMode rpc_mode;
 
 	void _set_error(const String &p_error, int p_line = -1, int p_column = -1);
+#ifdef DEBUG_ENABLED
+	void _add_warning(int p_code, int p_line = -1, const String &p_symbol1 = String(), const String &p_symbol2 = String(), const String &p_symbol3 = String(), const String &p_symbol4 = String());
+	void _add_warning(int p_code, int p_line, const Vector<String> &p_symbols);
+#endif // DEBUG_ENABLED
 	bool _recover_from_completion();
 
 	bool _parse_arguments(Node *p_parent, Vector<Node *> &p_args, bool p_static, bool p_can_codecomplete = false);
@@ -605,6 +626,9 @@ public:
 	String get_error() const;
 	int get_error_line() const;
 	int get_error_column() const;
+#ifdef DEBUG_ENABLED
+	const List<GDScriptWarning> &get_warnings() const { return warnings; }
+#endif // DEBUG_ENABLED
 	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 = "");
 

+ 20 - 1
modules/gdscript/gdscript_tokenizer.cpp

@@ -526,8 +526,13 @@ void GDScriptTokenizerText::_advance() {
 				return;
 			}
 			case '#': { // line comment skip
-
+#ifdef DEBUG_ENABLED
+				String comment;
+#endif // DEBUG_ENABLED
 				while (GETCHAR(0) != '\n') {
+#ifdef DEBUG_ENABLED
+					comment += GETCHAR(0);
+#endif // DEBUG_ENABLED
 					code_pos++;
 					if (GETCHAR(0) == 0) { //end of file
 						//_make_error("Unterminated Comment");
@@ -535,6 +540,17 @@ void GDScriptTokenizerText::_advance() {
 						return;
 					}
 				}
+#ifdef DEBUG_ENABLED
+				if (comment.begins_with("#warning-ignore:")) {
+					String code = comment.get_slice(":", 1);
+					warning_skips.push_back(Pair<int, String>(line, code.strip_edges().to_lower()));
+				} else if (comment.begins_with("#warning-ignore-all:")) {
+					String code = comment.get_slice(":", 1);
+					warning_global_skips.insert(code.strip_edges().to_lower());
+				} else if (comment.strip_edges() == "#warnings-disable") {
+					ignore_warnings = true;
+				}
+#endif // DEBUG_ENABLED
 				INCPOS(1);
 				column = 1;
 				line++;
@@ -1045,6 +1061,9 @@ void GDScriptTokenizerText::set_code(const String &p_code) {
 	column = 1; //the same holds for columns
 	tk_rb_pos = 0;
 	error_flag = false;
+#ifdef DEBUG_ENABLED
+	ignore_warnings = false;
+#endif // DEBUG_ENABLED
 	last_error = "";
 	for (int i = 0; i < MAX_LOOKAHEAD + 1; i++)
 		_advance();

+ 22 - 0
modules/gdscript/gdscript_tokenizer.h

@@ -31,6 +31,7 @@
 #ifndef GDSCRIPT_TOKENIZER_H
 #define GDSCRIPT_TOKENIZER_H
 
+#include "core/pair.h"
 #include "gdscript_functions.h"
 #include "string_db.h"
 #include "ustring.h"
@@ -171,6 +172,11 @@ public:
 	virtual int get_token_line_indent(int p_offset = 0) const = 0;
 	virtual String get_token_error(int p_offset = 0) const = 0;
 	virtual void advance(int p_amount = 1) = 0;
+#ifdef DEBUG_ENABLED
+	virtual const Vector<Pair<int, String> > &get_warning_skips() const = 0;
+	virtual const Set<String> &get_warning_global_skips() const = 0;
+	virtual const bool is_ignoring_warnings() const = 0;
+#endif // DEBUG_ENABLED
 
 	virtual ~GDScriptTokenizer(){};
 };
@@ -190,6 +196,7 @@ class GDScriptTokenizerText : public GDScriptTokenizer {
 		union {
 			Variant::Type vtype; //for type types
 			GDScriptFunctions::Function func; //function for built in functions
+			int warning_code; //for warning skip
 		};
 		int line, col;
 		TokenData() {
@@ -217,6 +224,11 @@ class GDScriptTokenizerText : public GDScriptTokenizer {
 	int tk_rb_pos;
 	String last_error;
 	bool error_flag;
+#ifdef DEBUG_ENABLED
+	Vector<Pair<int, String> > warning_skips;
+	Set<String> warning_global_skips;
+	bool ignore_warnings;
+#endif // DEBUG_ENABLED
 
 	void _advance();
 
@@ -232,6 +244,11 @@ public:
 	virtual const Variant &get_token_constant(int p_offset = 0) const;
 	virtual String get_token_error(int p_offset = 0) const;
 	virtual void advance(int p_amount = 1);
+#ifdef DEBUG_ENABLED
+	virtual const Vector<Pair<int, String> > &get_warning_skips() const { return warning_skips; }
+	virtual const Set<String> &get_warning_global_skips() const { return warning_global_skips; }
+	virtual const bool is_ignoring_warnings() const { return ignore_warnings; }
+#endif // DEBUG_ENABLED
 };
 
 class GDScriptTokenizerBuffer : public GDScriptTokenizer {
@@ -265,6 +282,11 @@ public:
 	virtual const Variant &get_token_constant(int p_offset = 0) const;
 	virtual String get_token_error(int p_offset = 0) const;
 	virtual void advance(int p_amount = 1);
+#ifdef DEBUG_ENABLED
+	virtual const Vector<Pair<int, String> > &get_warning_skips() const { return Vector<Pair<int, String> >(); }
+	virtual const Set<String> &get_warning_global_skips() const { return Set<String>(); }
+	virtual const bool is_ignoring_warnings() const { return true; }
+#endif // DEBUG_ENABLED
 	GDScriptTokenizerBuffer();
 };
 

+ 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, Set<int> *r_safe_lines = NULL) 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, List<ScriptLanguage::Warning> *r_warnings = NULL, 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

@@ -2415,7 +2415,7 @@ void VisualScriptLanguage::make_template(const String &p_class_name, const Strin
 	script->set_instance_base_type(p_base_class_name);
 }
 
-bool VisualScriptLanguage::validate(const String &p_script, int &r_line_error, int &r_col_error, String &r_test_error, const String &p_path, List<String> *r_functions, Set<int> *r_safe_lines) 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, List<ScriptLanguage::Warning> *r_warnings, Set<int> *r_safe_lines) const {
 
 	return false;
 }

+ 1 - 1
modules/visual_script/visual_script.h

@@ -564,7 +564,7 @@ public:
 	virtual Ref<Script> get_template(const String &p_class_name, const String &p_base_class_name) const;
 	virtual bool is_using_templates();
 	virtual void make_template(const String &p_class_name, const String &p_base_class_name, Ref<Script> &p_script);
-	virtual bool validate(const String &p_script, int &r_line_error, int &r_col_error, String &r_test_error, const String &p_path = "", List<String> *r_functions = NULL, Set<int> *r_safe_lines = 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, List<ScriptLanguage::Warning> *r_warnings = 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;