diff options
Diffstat (limited to 'modules/gdscript')
-rw-r--r-- | modules/gdscript/gdscript.cpp | 172 | ||||
-rw-r--r-- | modules/gdscript/gdscript.h | 45 | ||||
-rw-r--r-- | modules/gdscript/gdscript_editor.cpp | 19 | ||||
-rw-r--r-- | modules/gdscript/gdscript_parser.cpp | 433 | ||||
-rw-r--r-- | modules/gdscript/gdscript_parser.h | 24 | ||||
-rw-r--r-- | modules/gdscript/gdscript_tokenizer.cpp | 21 | ||||
-rw-r--r-- | modules/gdscript/gdscript_tokenizer.h | 22 |
7 files changed, 697 insertions, 39 deletions
diff --git a/modules/gdscript/gdscript.cpp b/modules/gdscript/gdscript.cpp index cff3be76ae..ef6a42f145 100644 --- a/modules/gdscript/gdscript.cpp +++ b/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() { diff --git a/modules/gdscript/gdscript.h b/modules/gdscript/gdscript.h index 79ac9ed413..edad12f1f3 100644 --- a/modules/gdscript/gdscript.h +++ b/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; diff --git a/modules/gdscript/gdscript_editor.cpp b/modules/gdscript/gdscript_editor.cpp index 2a42524ba7..abd56d2757 100644 --- a/modules/gdscript/gdscript_editor.cpp +++ b/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(); @@ -2442,7 +2455,7 @@ Error GDScriptLanguage::complete_code(const String &p_code, const String &p_base } break; case GDScriptParser::COMPLETION_BUILT_IN_TYPE_CONSTANT: { List<StringName> constants; - Variant::get_numeric_constants_for_type(parser.get_completion_built_in_constant(), &constants); + Variant::get_constants_for_type(parser.get_completion_built_in_constant(), &constants); for (List<StringName>::Element *E = constants.front(); E; E = E->next()) { options.insert(E->get().operator String()); } @@ -3065,7 +3078,7 @@ static Error _lookup_symbol_from_base(const GDScriptParser::DataType &p_base, co case GDScriptParser::DataType::BUILTIN: { base_type.has_type = false; - if (Variant::has_numeric_constant(base_type.builtin_type, p_symbol)) { + if (Variant::has_constant(base_type.builtin_type, p_symbol)) { r_result.type = ScriptLanguage::LookupResult::RESULT_CLASS_CONSTANT; r_result.class_name = Variant::get_type_name(base_type.builtin_type); r_result.class_member = p_symbol; diff --git a/modules/gdscript/gdscript_parser.cpp b/modules/gdscript/gdscript_parser.cpp index 852d465206..177e245986 100644 --- a/modules/gdscript/gdscript_parser.cpp +++ b/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) { @@ -607,7 +610,7 @@ GDScriptParser::Node *GDScriptParser::_parse_expression(Node *p_parent, bool p_s _set_error("Built-in type constant or static function expected after '.'"); return NULL; } - if (!Variant::has_numeric_constant(bi_type, identifier)) { + if (!Variant::has_constant(bi_type, identifier)) { if (tokenizer->get_token() == GDScriptTokenizer::TK_PARENTHESIS_OPEN && Variant::is_method_const(bi_type, identifier) && @@ -642,7 +645,7 @@ GDScriptParser::Node *GDScriptParser::_parse_expression(Node *p_parent, bool p_s } else { ConstantNode *cn = alloc_node<ConstantNode>(); - cn->value = Variant::get_numeric_constant_value(bi_type, identifier); + cn->value = Variant::get_constant_value(bi_type, identifier); cn->datatype = _type_from_variant(cn->value); expr = cn; } @@ -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; } diff --git a/modules/gdscript/gdscript_parser.h b/modules/gdscript/gdscript_parser.h index 48f256b4c6..d8ee4e8159 100644 --- a/modules/gdscript/gdscript_parser.h +++ b/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 = ""); diff --git a/modules/gdscript/gdscript_tokenizer.cpp b/modules/gdscript/gdscript_tokenizer.cpp index 7ae7c72ed3..537a0c5eaf 100644 --- a/modules/gdscript/gdscript_tokenizer.cpp +++ b/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(); diff --git a/modules/gdscript/gdscript_tokenizer.h b/modules/gdscript/gdscript_tokenizer.h index 5bd303224c..28a08bfaf8 100644 --- a/modules/gdscript/gdscript_tokenizer.h +++ b/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(); }; |