Added system for GDScript warnings
- Count and panel per script. - Ability to disable warnings per script using special comments. - Ability to disable warnings globally using Project Settings. - Option to treat enabled warnings as errors.
This commit is contained in:
parent
767fb2fa0b
commit
eb48119821
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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();
|
||||
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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 = "");
|
||||
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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();
|
||||
};
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
Loading…
Reference in New Issue