You've already forked godot
mirror of
https://github.com/godotengine/godot.git
synced 2025-11-05 12:10:55 +00:00
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:
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user