summaryrefslogtreecommitdiff
path: root/modules/gdscript
diff options
context:
space:
mode:
Diffstat (limited to 'modules/gdscript')
-rw-r--r--modules/gdscript/gdscript.cpp180
-rw-r--r--modules/gdscript/gdscript.h51
-rw-r--r--modules/gdscript/gdscript_compiler.cpp44
-rw-r--r--modules/gdscript/gdscript_editor.cpp21
-rw-r--r--modules/gdscript/gdscript_function.cpp38
-rw-r--r--modules/gdscript/gdscript_functions.cpp4
-rw-r--r--modules/gdscript/gdscript_parser.cpp578
-rw-r--r--modules/gdscript/gdscript_parser.h24
-rw-r--r--modules/gdscript/gdscript_tokenizer.cpp53
-rw-r--r--modules/gdscript/gdscript_tokenizer.h22
10 files changed, 866 insertions, 149 deletions
diff --git a/modules/gdscript/gdscript.cpp b/modules/gdscript/gdscript.cpp
index 8bd29ffc55..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;
@@ -730,7 +737,7 @@ Error GDScript::load_byte_code(const String &p_path) {
Vector<uint8_t> key;
key.resize(32);
for (int i = 0; i < key.size(); i++) {
- key[i] = script_encryption_key[i];
+ key.write[i] = script_encryption_key[i];
}
Error err = fae->open_and_parse(fa, key, FileAccessEncrypted::MODE_READ);
ERR_FAIL_COND_V(err, err);
@@ -941,7 +948,7 @@ bool GDScriptInstance::set(const StringName &p_name, const Variant &p_value) {
if (!E->get().data_type.is_type(p_value)) {
return false; // Type mismatch
}
- members[E->get().index] = p_value;
+ members.write[E->get().index] = p_value;
}
return true;
}
@@ -1270,7 +1277,7 @@ void GDScriptInstance::reload_members() {
if (member_indices_cache.has(E->key())) {
Variant value = members[member_indices_cache[E->key()]];
- new_members[E->get().index] = value;
+ new_members.write[E->get().index] = value;
}
}
@@ -1320,7 +1327,7 @@ void GDScriptLanguage::_add_global(const StringName &p_name, const Variant &p_va
if (globals.has(p_name)) {
//overwrite existing
- global_array[globals[p_name]] = p_value;
+ global_array.write[globals[p_name]] = p_value;
return;
}
globals[p_name] = global_array.size();
@@ -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 d5fe7a000b..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;
@@ -355,10 +398,10 @@ public:
Vector<StackInfo> csi;
csi.resize(_debug_call_stack_pos);
for (int i = 0; i < _debug_call_stack_pos; i++) {
- csi[_debug_call_stack_pos - i - 1].line = _call_stack[i].line ? *_call_stack[i].line : 0;
+ csi.write[_debug_call_stack_pos - i - 1].line = _call_stack[i].line ? *_call_stack[i].line : 0;
if (_call_stack[i].function)
- csi[_debug_call_stack_pos - i - 1].func = _call_stack[i].function->get_name();
- csi[_debug_call_stack_pos - i - 1].file = _call_stack[i].function->get_script()->get_path();
+ csi.write[_debug_call_stack_pos - i - 1].func = _call_stack[i].function->get_name();
+ csi.write[_debug_call_stack_pos - i - 1].file = _call_stack[i].function->get_script()->get_path();
}
return csi;
}
@@ -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_compiler.cpp b/modules/gdscript/gdscript_compiler.cpp
index a428ccd306..fe393957db 100644
--- a/modules/gdscript/gdscript_compiler.cpp
+++ b/modules/gdscript/gdscript_compiler.cpp
@@ -140,7 +140,7 @@ GDScriptDataType GDScriptCompiler::_gdtype_from_datatype(const GDScriptParser::D
} break;
case GDScriptParser::DataType::CLASS: {
result.kind = GDScriptDataType::GDSCRIPT;
- if (p_datatype.class_type->name == StringName()) {
+ if (!p_datatype.class_type->owner) {
result.script_type = Ref<GDScript>(main_script);
} else {
result.script_type = class_map[p_datatype.class_type->name];
@@ -482,7 +482,7 @@ int GDScriptCompiler::_parse_expression(CodeGen &codegen, const GDScriptParser::
Variant script;
int idx = -1;
- if (cn->cast_type.class_type->name == StringName()) {
+ if (!cn->cast_type.class_type->owner) {
script = codegen.script;
} else {
StringName name = cn->cast_type.class_type->name;
@@ -785,8 +785,8 @@ int GDScriptCompiler::_parse_expression(CodeGen &codegen, const GDScriptParser::
codegen.opcodes.push_back(p_stack_level | GDScriptFunction::ADDR_TYPE_STACK << GDScriptFunction::ADDR_BITS);
codegen.opcodes.push_back(GDScriptFunction::OPCODE_JUMP);
codegen.opcodes.push_back(codegen.opcodes.size() + 3);
- codegen.opcodes[jump_fail_pos] = codegen.opcodes.size();
- codegen.opcodes[jump_fail_pos2] = codegen.opcodes.size();
+ codegen.opcodes.write[jump_fail_pos] = codegen.opcodes.size();
+ codegen.opcodes.write[jump_fail_pos2] = codegen.opcodes.size();
codegen.opcodes.push_back(GDScriptFunction::OPCODE_ASSIGN_FALSE);
codegen.opcodes.push_back(p_stack_level | GDScriptFunction::ADDR_TYPE_STACK << GDScriptFunction::ADDR_BITS);
return p_stack_level | GDScriptFunction::ADDR_TYPE_STACK << GDScriptFunction::ADDR_BITS;
@@ -818,8 +818,8 @@ int GDScriptCompiler::_parse_expression(CodeGen &codegen, const GDScriptParser::
codegen.opcodes.push_back(p_stack_level | GDScriptFunction::ADDR_TYPE_STACK << GDScriptFunction::ADDR_BITS);
codegen.opcodes.push_back(GDScriptFunction::OPCODE_JUMP);
codegen.opcodes.push_back(codegen.opcodes.size() + 3);
- codegen.opcodes[jump_success_pos] = codegen.opcodes.size();
- codegen.opcodes[jump_success_pos2] = codegen.opcodes.size();
+ codegen.opcodes.write[jump_success_pos] = codegen.opcodes.size();
+ codegen.opcodes.write[jump_success_pos2] = codegen.opcodes.size();
codegen.opcodes.push_back(GDScriptFunction::OPCODE_ASSIGN_TRUE);
codegen.opcodes.push_back(p_stack_level | GDScriptFunction::ADDR_TYPE_STACK << GDScriptFunction::ADDR_BITS);
return p_stack_level | GDScriptFunction::ADDR_TYPE_STACK << GDScriptFunction::ADDR_BITS;
@@ -850,7 +850,7 @@ int GDScriptCompiler::_parse_expression(CodeGen &codegen, const GDScriptParser::
int jump_past_pos = codegen.opcodes.size();
codegen.opcodes.push_back(0);
- codegen.opcodes[jump_fail_pos] = codegen.opcodes.size();
+ codegen.opcodes.write[jump_fail_pos] = codegen.opcodes.size();
res = _parse_expression(codegen, on->arguments[2], p_stack_level);
if (res < 0)
return res;
@@ -859,7 +859,7 @@ int GDScriptCompiler::_parse_expression(CodeGen &codegen, const GDScriptParser::
codegen.opcodes.push_back(p_stack_level | GDScriptFunction::ADDR_TYPE_STACK << GDScriptFunction::ADDR_BITS);
codegen.opcodes.push_back(res);
- codegen.opcodes[jump_past_pos] = codegen.opcodes.size();
+ codegen.opcodes.write[jump_past_pos] = codegen.opcodes.size();
return p_stack_level | GDScriptFunction::ADDR_TYPE_STACK << GDScriptFunction::ADDR_BITS;
@@ -1181,7 +1181,7 @@ int GDScriptCompiler::_parse_expression(CodeGen &codegen, const GDScriptParser::
Variant script;
int idx = -1;
- if (assign_type.class_type->name == StringName()) {
+ if (!assign_type.class_type->owner) {
script = codegen.script;
} else {
StringName name = assign_type.class_type->name;
@@ -1361,10 +1361,10 @@ Error GDScriptCompiler::_parse_block(CodeGen &codegen, const GDScriptParser::Blo
codegen.opcodes.push_back(GDScriptFunction::OPCODE_JUMP);
codegen.opcodes.push_back(break_addr);
- codegen.opcodes[continue_addr + 1] = codegen.opcodes.size();
+ codegen.opcodes.write[continue_addr + 1] = codegen.opcodes.size();
}
- codegen.opcodes[break_addr + 1] = codegen.opcodes.size();
+ codegen.opcodes.write[break_addr + 1] = codegen.opcodes.size();
} break;
@@ -1393,16 +1393,16 @@ Error GDScriptCompiler::_parse_block(CodeGen &codegen, const GDScriptParser::Blo
codegen.opcodes.push_back(GDScriptFunction::OPCODE_JUMP);
int end_addr = codegen.opcodes.size();
codegen.opcodes.push_back(0);
- codegen.opcodes[else_addr] = codegen.opcodes.size();
+ codegen.opcodes.write[else_addr] = codegen.opcodes.size();
Error err = _parse_block(codegen, cf->body_else, p_stack_level, p_break_addr, p_continue_addr);
if (err)
return err;
- codegen.opcodes[end_addr] = codegen.opcodes.size();
+ codegen.opcodes.write[end_addr] = codegen.opcodes.size();
} else {
//end without else
- codegen.opcodes[else_addr] = codegen.opcodes.size();
+ codegen.opcodes.write[else_addr] = codegen.opcodes.size();
}
} break;
@@ -1453,7 +1453,7 @@ Error GDScriptCompiler::_parse_block(CodeGen &codegen, const GDScriptParser::Blo
codegen.opcodes.push_back(GDScriptFunction::OPCODE_JUMP);
codegen.opcodes.push_back(continue_pos);
- codegen.opcodes[break_pos + 1] = codegen.opcodes.size();
+ codegen.opcodes.write[break_pos + 1] = codegen.opcodes.size();
codegen.pop_stack_identifiers();
@@ -1479,7 +1479,7 @@ Error GDScriptCompiler::_parse_block(CodeGen &codegen, const GDScriptParser::Blo
codegen.opcodes.push_back(GDScriptFunction::OPCODE_JUMP);
codegen.opcodes.push_back(continue_addr);
- codegen.opcodes[break_addr + 1] = codegen.opcodes.size();
+ codegen.opcodes.write[break_addr + 1] = codegen.opcodes.size();
} break;
case GDScriptParser::ControlFlowNode::CF_SWITCH: {
@@ -1689,7 +1689,7 @@ Error GDScriptCompiler::_parse_function(GDScript *p_script, const GDScriptParser
gdfunc->rpc_mode = p_func->rpc_mode;
gdfunc->argument_types.resize(p_func->argument_types.size());
for (int i = 0; i < p_func->argument_types.size(); i++) {
- gdfunc->argument_types[i] = _gdtype_from_datatype(p_func->argument_types[i]);
+ gdfunc->argument_types.write[i] = _gdtype_from_datatype(p_func->argument_types[i]);
}
gdfunc->return_type = _gdtype_from_datatype(p_func->return_type);
} else {
@@ -1708,11 +1708,11 @@ Error GDScriptCompiler::_parse_function(GDScript *p_script, const GDScriptParser
if (codegen.constant_map.size()) {
gdfunc->_constant_count = codegen.constant_map.size();
gdfunc->constants.resize(codegen.constant_map.size());
- gdfunc->_constants_ptr = &gdfunc->constants[0];
+ gdfunc->_constants_ptr = gdfunc->constants.ptrw();
const Variant *K = NULL;
while ((K = codegen.constant_map.next(K))) {
int idx = codegen.constant_map[*K];
- gdfunc->constants[idx] = *K;
+ gdfunc->constants.write[idx] = *K;
}
} else {
@@ -1726,7 +1726,7 @@ Error GDScriptCompiler::_parse_function(GDScript *p_script, const GDScriptParser
gdfunc->_global_names_ptr = &gdfunc->global_names[0];
for (Map<StringName, int>::Element *E = codegen.name_map.front(); E; E = E->next()) {
- gdfunc->global_names[E->get()] = E->key();
+ gdfunc->global_names.write[E->get()] = E->key();
}
gdfunc->_global_names_count = gdfunc->global_names.size();
@@ -1741,7 +1741,7 @@ Error GDScriptCompiler::_parse_function(GDScript *p_script, const GDScriptParser
gdfunc->named_globals.resize(codegen.named_globals.size());
gdfunc->_named_globals_ptr = gdfunc->named_globals.ptr();
for (int i = 0; i < codegen.named_globals.size(); i++) {
- gdfunc->named_globals[i] = codegen.named_globals[i];
+ gdfunc->named_globals.write[i] = codegen.named_globals[i];
}
gdfunc->_named_globals_count = gdfunc->named_globals.size();
}
@@ -1994,7 +1994,7 @@ Error GDScriptCompiler::_parse_class_level(GDScript *p_script, GDScript *p_owner
p_script->_signals[name] = p_class->_signals[i].arguments;
}
- if (p_class->name != StringName()) {
+ if (!p_class->owner) {
parsed_classes.insert(p_class->name);
}
diff --git a/modules/gdscript/gdscript_editor.cpp b/modules/gdscript/gdscript_editor.cpp
index 2e4a4c40dd..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());
}
@@ -2918,7 +2931,7 @@ void GDScriptLanguage::auto_indent_code(String &p_code, int p_from_line, int p_t
}
//print_line(itos(indent_stack.size())+","+itos(tc)+": "+l);
- lines[i] = l;
+ lines.write[i] = l;
}
p_code = "";
@@ -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_function.cpp b/modules/gdscript/gdscript_function.cpp
index 6a08d86904..bae3f48923 100644
--- a/modules/gdscript/gdscript_function.cpp
+++ b/modules/gdscript/gdscript_function.cpp
@@ -62,7 +62,7 @@ Variant *GDScriptFunction::_get_variant(int p_address, GDScriptInstance *p_insta
}
#endif
//member indexing is O(1)
- return &p_instance->members[address];
+ return &p_instance->members.write[address];
} break;
case ADDR_TYPE_CLASS_CONSTANT: {
@@ -742,13 +742,22 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a
GD_ERR_BREAK(var_type < 0 || var_type >= Variant::VARIANT_MAX);
+#ifdef DEBUG_ENABLED
if (src->get_type() != var_type) {
- err_text = "Trying to assign value of type '" + Variant::get_type_name(src->get_type()) +
- "' to a variable of type '" + Variant::get_type_name(var_type) + "'.";
- OPCODE_BREAK;
+ if (Variant::can_convert_strict(src->get_type(), var_type)) {
+ Variant::CallError ce;
+ *dst = Variant::construct(var_type, const_cast<const Variant **>(&src), 1, ce);
+ } else {
+ err_text = "Trying to assign value of type '" + Variant::get_type_name(src->get_type()) +
+ "' to a variable of type '" + Variant::get_type_name(var_type) + "'.";
+ OPCODE_BREAK;
+ }
+ } else {
+#endif // DEBUG_ENABLED
+ *dst = *src;
+#ifdef DEBUG_ENABLED
}
-
- *dst = *src;
+#endif // DEBUG_ENABLED
ip += 4;
}
@@ -761,17 +770,22 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a
GET_VARIANT_PTR(dst, 2);
GET_VARIANT_PTR(src, 3);
+#ifdef DEBUG_ENABLED
GDScriptNativeClass *nc = Object::cast_to<GDScriptNativeClass>(type->operator Object *());
GD_ERR_BREAK(!nc);
+ if (!src->get_type() != Variant::OBJECT && !src->get_type() != Variant::NIL) {
+ err_text = "Trying to assign value of type '" + Variant::get_type_name(src->get_type()) +
+ "' to a variable of type '" + nc->get_name() + "'.";
+ OPCODE_BREAK;
+ }
Object *src_obj = src->operator Object *();
- GD_ERR_BREAK(!src_obj);
- if (!ClassDB::is_parent_class(src_obj->get_class_name(), nc->get_name())) {
+ if (src_obj && !ClassDB::is_parent_class(src_obj->get_class_name(), nc->get_name())) {
err_text = "Trying to assign value of type '" + src_obj->get_class_name() +
"' to a variable of type '" + nc->get_name() + "'.";
OPCODE_BREAK;
}
-
+#endif // DEBUG_ENABLED
*dst = *src;
ip += 4;
@@ -785,6 +799,7 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a
GET_VARIANT_PTR(dst, 2);
GET_VARIANT_PTR(src, 3);
+#ifdef DEBUG_ENABLED
Script *base_type = Object::cast_to<Script>(type->operator Object *());
GD_ERR_BREAK(!base_type);
@@ -820,6 +835,7 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a
OPCODE_BREAK;
}
}
+#endif // DEBUG_ENABLED
*dst = *src;
@@ -1218,7 +1234,7 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a
gdfs->state.stack.resize(alloca_size);
//copy variant stack
for (int i = 0; i < _stack_size; i++) {
- memnew_placement(&gdfs->state.stack[sizeof(Variant) * i], Variant(stack[i]));
+ memnew_placement(&gdfs->state.stack.write[sizeof(Variant) * i], Variant(stack[i]));
}
gdfs->state.stack_size = _stack_size;
gdfs->state.self = self;
@@ -1579,7 +1595,7 @@ StringName GDScriptFunction::get_global_name(int p_idx) const {
int GDScriptFunction::get_default_argument_count() const {
- return default_arguments.size();
+ return _default_arg_count;
}
int GDScriptFunction::get_default_argument_addr(int p_idx) const {
diff --git a/modules/gdscript/gdscript_functions.cpp b/modules/gdscript/gdscript_functions.cpp
index 7e98b6ced9..f2e52d48dd 100644
--- a/modules/gdscript/gdscript_functions.cpp
+++ b/modules/gdscript/gdscript_functions.cpp
@@ -1098,7 +1098,7 @@ void GDScriptFunctions::call(Function p_func, const Variant **p_args, int p_arg_
for (Map<StringName, GDScript::MemberInfo>::Element *E = gd_ref->member_indices.front(); E; E = E->next()) {
if (d.has(E->key())) {
- ins->members[E->get().index] = d[E->key()];
+ ins->members.write[E->get().index] = d[E->key()];
}
}
@@ -1412,7 +1412,7 @@ bool GDScriptFunctions::is_deterministic(Function p_func) {
MethodInfo GDScriptFunctions::get_info(Function p_func) {
-#ifdef TOOLS_ENABLED
+#ifdef DEBUG_ENABLED
//using a switch, so the compiler generates a jumptable
switch (p_func) {
diff --git a/modules/gdscript/gdscript_parser.cpp b/modules/gdscript/gdscript_parser.cpp
index ac53f33e9e..e0ed2b332b 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;
@@ -1411,8 +1449,8 @@ GDScriptParser::Node *GDScriptParser::_parse_expression(Node *p_parent, bool p_s
op->op = expression[i].op;
op->arguments.push_back(expression[i + 1].node);
op->line = op_line; //line might have been changed from a \n
- expression[i].is_op = false;
- expression[i].node = op;
+ expression.write[i].is_op = false;
+ expression.write[i].node = op;
expression.remove(i + 1);
}
@@ -1466,7 +1504,7 @@ GDScriptParser::Node *GDScriptParser::_parse_expression(Node *p_parent, bool p_s
op->arguments.push_back(expression[next_op + 3].node); //expression after next goes as when-false
//replace all 3 nodes by this operator and make it an expression
- expression[next_op - 1].node = op;
+ expression.write[next_op - 1].node = op;
expression.remove(next_op);
expression.remove(next_op);
expression.remove(next_op);
@@ -1502,7 +1540,7 @@ GDScriptParser::Node *GDScriptParser::_parse_expression(Node *p_parent, bool p_s
op->arguments.push_back(expression[next_op + 1].node); //next expression goes as right
//replace all 3 nodes by this operator and make it an expression
- expression[next_op - 1].node = op;
+ expression.write[next_op - 1].node = op;
expression.remove(next_op);
expression.remove(next_op);
}
@@ -1526,7 +1564,7 @@ GDScriptParser::Node *GDScriptParser::_reduce_expression(Node *p_node, bool p_to
for (int i = 0; i < an->elements.size(); i++) {
- an->elements[i] = _reduce_expression(an->elements[i], p_to_const);
+ an->elements.write[i] = _reduce_expression(an->elements[i], p_to_const);
if (an->elements[i]->type != Node::TYPE_CONSTANT)
all_constants = false;
}
@@ -1556,10 +1594,10 @@ GDScriptParser::Node *GDScriptParser::_reduce_expression(Node *p_node, bool p_to
for (int i = 0; i < dn->elements.size(); i++) {
- dn->elements[i].key = _reduce_expression(dn->elements[i].key, p_to_const);
+ dn->elements.write[i].key = _reduce_expression(dn->elements[i].key, p_to_const);
if (dn->elements[i].key->type != Node::TYPE_CONSTANT)
all_constants = false;
- dn->elements[i].value = _reduce_expression(dn->elements[i].value, p_to_const);
+ dn->elements.write[i].value = _reduce_expression(dn->elements[i].value, p_to_const);
if (dn->elements[i].value->type != Node::TYPE_CONSTANT)
all_constants = false;
}
@@ -1592,7 +1630,7 @@ GDScriptParser::Node *GDScriptParser::_reduce_expression(Node *p_node, bool p_to
for (int i = 0; i < op->arguments.size(); i++) {
- op->arguments[i] = _reduce_expression(op->arguments[i], p_to_const);
+ op->arguments.write[i] = _reduce_expression(op->arguments[i], p_to_const);
if (op->arguments[i]->type != Node::TYPE_CONSTANT) {
all_constants = false;
last_not_constant = i;
@@ -1620,7 +1658,7 @@ GDScriptParser::Node *GDScriptParser::_reduce_expression(Node *p_node, bool p_to
for (int i = 0; i < ptrs.size(); i++) {
ConstantNode *cn = static_cast<ConstantNode *>(op->arguments[i + 1]);
- ptrs[i] = &cn->value;
+ ptrs.write[i] = &cn->value;
}
vptr = (const Variant **)&ptrs[0];
@@ -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;
@@ -3332,6 +3374,9 @@ void GDScriptParser::_parse_class(ClassNode *p_class) {
switch (token) {
+ case GDScriptTokenizer::TK_CURSOR: {
+ tokenizer->advance();
+ } break;
case GDScriptTokenizer::TK_EOF:
p_class->end_line = tokenizer->get_token_line();
case GDScriptTokenizer::TK_ERROR: {
@@ -3510,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]):' ).");
@@ -3521,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();
@@ -3547,12 +3606,18 @@ 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();
DataType argtype;
if (tokenizer->get_token() == GDScriptTokenizer::TK_COLON) {
- if (!_parse_type(argtype)) {
+ if (tokenizer->get_token(1) == GDScriptTokenizer::TK_OP_ASSIGN) {
+ argtype.infer_type = true;
+ tokenizer->advance();
+ } else if (!_parse_type(argtype)) {
_set_error("Expected type for argument.");
return;
}
@@ -3697,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;
@@ -3724,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) {
@@ -4187,6 +4256,7 @@ void GDScriptParser::_parse_class(ClassNode *p_class) {
current_export.usage |= PROPERTY_USAGE_SCRIPT_VARIABLE;
current_export.hint_string = native_class->get_name();
+ current_export.class_name = native_class->get_name();
} else {
current_export = PropertyInfo();
@@ -4406,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)) {
@@ -4421,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;
@@ -4546,6 +4630,7 @@ void GDScriptParser::_parse_class(ClassNode *p_class) {
member._export.type = Variant::OBJECT;
member._export.hint = PROPERTY_HINT_RESOURCE_TYPE;
member._export.usage |= PROPERTY_USAGE_SCRIPT_VARIABLE;
+ member._export.hint_string = member.data_type.native_type;
member._export.class_name = member.data_type.native_type;
} else {
_set_error("Invalid export type. Only built-in and native resource types can be exported.", member.line);
@@ -5433,6 +5518,9 @@ GDScriptParser::DataType GDScriptParser::_get_operation_type(const Variant::Oper
if (b_type == Variant::INT || b_type == Variant::REAL) {
Variant::evaluate(Variant::OP_ADD, b, 1, b, r_valid);
}
+ if (a_type == Variant::STRING && b_type != Variant::ARRAY) {
+ a = "%s"; // Work around for formatting operator (%)
+ }
Variant ret;
Variant::evaluate(p_op, a, b, ret, r_valid);
@@ -5678,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;
@@ -5693,6 +5796,7 @@ 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;
} else if (id->name == "#match_value") {
// It's a special id just for the match statetement, ignore
break;
@@ -5727,6 +5831,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);
}
@@ -5853,6 +5960,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
@@ -5871,10 +5984,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:
@@ -5937,6 +6051,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);
@@ -5956,7 +6075,7 @@ GDScriptParser::DataType GDScriptParser::_reduce_node_type(Node *p_node) {
id->name = cn->value.operator StringName();
op->op = OperatorNode::OP_INDEX_NAMED;
- op->arguments[1] = id;
+ op->arguments.write[1] = id;
return _reduce_node_type(op);
}
@@ -6323,7 +6442,7 @@ GDScriptParser::DataType GDScriptParser::_reduce_function_call_type(const Operat
Vector<DataType> par_types;
par_types.resize(p_call->arguments.size() - 1);
for (int i = 1; i < p_call->arguments.size(); i++) {
- par_types[i - 1] = _reduce_node_type(p_call->arguments[i]);
+ par_types.write[i - 1] = _reduce_node_type(p_call->arguments[i]);
}
if (error_set) return DataType();
@@ -6356,6 +6475,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
}
}
@@ -6389,6 +6517,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;
@@ -6415,6 +6550,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;
@@ -6494,8 +6636,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();
}
@@ -6511,7 +6663,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;
}
@@ -6536,8 +6700,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])) {
@@ -6549,6 +6720,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
}
}
@@ -6770,7 +6947,35 @@ GDScriptParser::DataType GDScriptParser::_reduce_identifier_type(const DataType
// Check classes in current file
ClassNode *base = NULL;
if (!p_base_type) {
- // Possibly this is a global, check first
+ base = current_class;
+ base_type.has_type = true;
+ base_type.is_constant = true;
+ base_type.kind = DataType::CLASS;
+ base_type.class_type = base;
+ } else {
+ base_type = DataType(*p_base_type);
+ if (base_type.kind == DataType::CLASS) {
+ base = base_type.class_type;
+ }
+ }
+
+ 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;
+ }
+
+ if (!p_base_type) {
+ // Possibly this is a global, check before failing
if (ClassDB::class_exists(p_identifier) || ClassDB::class_exists("_" + p_identifier.operator String())) {
DataType result;
@@ -6796,6 +7001,9 @@ GDScriptParser::DataType GDScriptParser::_reduce_identifier_type(const DataType
result.class_type = outer_class;
return result;
}
+ if (outer_class->constant_expressions.has(p_identifier)) {
+ return outer_class->constant_expressions[p_identifier].type;
+ }
for (int i = 0; i < outer_class->subclasses.size(); i++) {
if (outer_class->subclasses[i] == current_class) {
continue;
@@ -6885,31 +7093,23 @@ GDScriptParser::DataType GDScriptParser::_reduce_identifier_type(const DataType
}
}
- // Nothing found, keep looking in local scope
-
- base = current_class;
- base_type.has_type = true;
- base_type.is_constant = true;
- base_type.kind = DataType::CLASS;
- base_type.class_type = base;
- } else {
- base_type = *p_base_type;
- if (base_type.kind == DataType::CLASS) {
- base = base_type.class_type;
- }
- }
-
- DataType member_type;
-
- if (_get_member_type(base_type, p_identifier, member_type)) {
- return member_type;
- }
-
- if (!p_base_type) {
// This means looking in the current class, which type is always known
_set_error("Identifier '" + p_identifier.operator String() + "' is not declared in the current scope.", p_line);
}
+#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();
}
@@ -6949,7 +7149,7 @@ void GDScriptParser::_check_class_level_types(ClassNode *p_class) {
// Class variables
for (int i = 0; i < p_class->variables.size(); i++) {
- ClassNode::Member &v = p_class->variables[i];
+ ClassNode::Member &v = p_class->variables.write[i];
DataType tmp;
if (_get_member_type(p_class->base_type, v.identifier, tmp)) {
@@ -6993,7 +7193,7 @@ void GDScriptParser::_check_class_level_types(ClassNode *p_class) {
convert_call->arguments.push_back(tgt_type);
v.expression = convert_call;
- v.initial_assignment->arguments[1] = convert_call;
+ v.initial_assignment->arguments.write[1] = convert_call;
}
}
@@ -7131,11 +7331,9 @@ void GDScriptParser::_check_function_types(FunctionNode *p_function) {
// Arguments
int defaults_ofs = p_function->arguments.size() - p_function->default_values.size();
for (int i = 0; i < p_function->arguments.size(); i++) {
-
- // Resolve types
- p_function->argument_types[i] = _resolve_type(p_function->argument_types[i], p_function->line);
-
- if (i >= defaults_ofs) {
+ if (i < defaults_ofs) {
+ p_function->argument_types.write[i] = _resolve_type(p_function->argument_types[i], p_function->line);
+ } else {
if (p_function->default_values[i - defaults_ofs]->type != Node::TYPE_OPERATOR) {
_set_error("Parser bug: invalid argument default value.", p_function->line, p_function->column);
return;
@@ -7150,17 +7348,30 @@ void GDScriptParser::_check_function_types(FunctionNode *p_function) {
DataType def_type = _reduce_node_type(op->arguments[1]);
- if (!_is_type_compatible(p_function->argument_types[i], def_type, true)) {
- String arg_name = p_function->arguments[i];
- _set_error("Value type (" + def_type.to_string() + ") doesn't match the type of argument '" +
- arg_name + "' (" + p_function->arguments[i] + ")",
- p_function->line);
+ if (p_function->argument_types[i].infer_type) {
+ def_type.is_constant = false;
+ p_function->argument_types.write[i] = def_type;
+ } else {
+ p_function->return_type = _resolve_type(p_function->return_type, p_function->line);
+
+ if (!_is_type_compatible(p_function->argument_types[i], def_type, true)) {
+ String arg_name = p_function->arguments[i];
+ _set_error("Value type (" + def_type.to_string() + ") doesn't match the type of argument '" +
+ arg_name + "' (" + p_function->arguments[i] + ")",
+ p_function->line);
+ }
}
}
+#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")) {
// Signature for the initializer may vary
+#ifdef DEBUG_ENABLED
DataType return_type;
List<DataType> arg_types;
int default_arg_count = 0;
@@ -7171,18 +7382,44 @@ void GDScriptParser::_check_function_types(FunctionNode *p_function) {
if (_get_function_signature(base_type, p_function->name, return_type, arg_types, default_arg_count, _static, vararg)) {
bool valid = _static == p_function->_static;
valid = valid && return_type == p_function->return_type;
- valid = valid && p_function->default_values.size() >= default_arg_count;
- valid = valid && arg_types.size() == p_function->arguments.size();
+ int argsize_diff = p_function->arguments.size() - arg_types.size();
+ valid = valid && argsize_diff >= 0;
+ valid = valid && p_function->default_values.size() >= default_arg_count + argsize_diff;
int i = 0;
for (List<DataType>::Element *E = arg_types.front(); valid && E; E = E->next()) {
valid = valid && E->get() == p_function->argument_types[i++];
}
if (!valid) {
- _set_error("Function signature doesn't match the parent.", p_function->line);
+ String parent_signature = return_type.has_type ? return_type.to_string() : "Variant";
+ if (parent_signature == "null") {
+ parent_signature = "void";
+ }
+ parent_signature += " " + p_function->name + "(";
+ if (arg_types.size()) {
+ int i = 0;
+ for (List<DataType>::Element *E = arg_types.front(); E; E = E->next()) {
+ if (E != arg_types.front()) {
+ parent_signature += ", ";
+ }
+ String arg = E->get().to_string();
+ if (arg == "null" || arg == "var") {
+ arg = "Variant";
+ }
+ parent_signature += arg;
+ if (i == arg_types.size() - default_arg_count) {
+ parent_signature += "=default";
+ }
+
+ i++;
+ }
+ }
+ parent_signature += ")";
+ _set_error("Function signature doesn't match the parent. Parent signature is: '" + parent_signature + "'.", p_function->line);
return;
}
}
+#endif // DEBUG_ENABLED
} else {
if (p_function->return_type.has_type && (p_function->return_type.kind != DataType::BUILTIN || p_function->return_type.builtin_type != Variant::NIL)) {
_set_error("Constructor cannot return a value.", p_function->line);
@@ -7200,6 +7437,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;
}
}
@@ -7226,6 +7464,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];
@@ -7235,6 +7487,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;
@@ -7253,8 +7525,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)) {
@@ -7284,7 +7571,12 @@ void GDScriptParser::_check_block_types(BlockNode *p_block) {
convert_call->arguments.push_back(tgt_type);
lv->assign = convert_call;
- lv->assign_op->arguments[1] = 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) {
@@ -7299,15 +7591,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);
@@ -7373,6 +7656,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
@@ -7402,7 +7698,12 @@ void GDScriptParser::_check_block_types(BlockNode *p_block) {
convert_call->arguments.push_back(op->arguments[1]);
convert_call->arguments.push_back(tgt_type);
- op->arguments[1] = convert_call;
+ 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)) {
@@ -7412,15 +7713,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;
@@ -7487,9 +7802,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
}
}
}
@@ -7501,6 +7816,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) {
@@ -7514,6 +7841,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;
@@ -7580,6 +7957,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 940bdcbc8d..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();
@@ -1172,15 +1191,15 @@ Error GDScriptTokenizerBuffer::set_code_buffer(const Vector<uint8_t> &p_buffer)
Vector<uint8_t> cs;
cs.resize(len);
for (int j = 0; j < len; j++) {
- cs[j] = b[j] ^ 0xb6;
+ cs.write[j] = b[j] ^ 0xb6;
}
- cs[cs.size() - 1] = 0;
+ cs.write[cs.size() - 1] = 0;
String s;
s.parse_utf8((const char *)cs.ptr());
b += len;
total_len -= len + 4;
- identifiers[i] = s;
+ identifiers.write[i] = s;
}
constants.resize(constant_count);
@@ -1193,7 +1212,7 @@ Error GDScriptTokenizerBuffer::set_code_buffer(const Vector<uint8_t> &p_buffer)
return err;
b += len;
total_len -= len;
- constants[i] = v;
+ constants.write[i] = v;
}
ERR_FAIL_COND_V(line_count * 8 > total_len, ERR_INVALID_DATA);
@@ -1218,10 +1237,10 @@ Error GDScriptTokenizerBuffer::set_code_buffer(const Vector<uint8_t> &p_buffer)
if ((*b) & TOKEN_BYTE_MASK) { //little endian always
ERR_FAIL_COND_V(total_len < 4, ERR_INVALID_DATA);
- tokens[i] = decode_uint32(b) & ~TOKEN_BYTE_MASK;
+ tokens.write[i] = decode_uint32(b) & ~TOKEN_BYTE_MASK;
b += 4;
} else {
- tokens[i] = *b;
+ tokens.write[i] = *b;
b += 1;
total_len--;
}
@@ -1320,15 +1339,15 @@ Vector<uint8_t> GDScriptTokenizerBuffer::parse_code_string(const String &p_code)
//save header
buf.resize(24);
- buf[0] = 'G';
- buf[1] = 'D';
- buf[2] = 'S';
- buf[3] = 'C';
- encode_uint32(BYTECODE_VERSION, &buf[4]);
- encode_uint32(identifier_map.size(), &buf[8]);
- encode_uint32(constant_map.size(), &buf[12]);
- encode_uint32(line_map.size(), &buf[16]);
- encode_uint32(token_array.size(), &buf[20]);
+ buf.write[0] = 'G';
+ buf.write[1] = 'D';
+ buf.write[2] = 'S';
+ buf.write[3] = 'C';
+ encode_uint32(BYTECODE_VERSION, &buf.write[4]);
+ encode_uint32(identifier_map.size(), &buf.write[8]);
+ encode_uint32(constant_map.size(), &buf.write[12]);
+ encode_uint32(line_map.size(), &buf.write[16]);
+ encode_uint32(token_array.size(), &buf.write[20]);
//save identifiers
@@ -1360,7 +1379,7 @@ Vector<uint8_t> GDScriptTokenizerBuffer::parse_code_string(const String &p_code)
ERR_FAIL_COND_V(err != OK, Vector<uint8_t>());
int pos = buf.size();
buf.resize(pos + len);
- encode_variant(E->get(), &buf[pos], len);
+ encode_variant(E->get(), &buf.write[pos], len);
}
for (Map<int, uint32_t>::Element *E = rev_line_map.front(); E; E = E->next()) {
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();
};