Add warning checks in GDScript analyzer

Reenable checking those when validating code.
This commit is contained in:
George Marques
2020-06-11 19:31:28 -03:00
parent 9a76ab8b6a
commit 95c0909290
11 changed files with 618 additions and 185 deletions
+2 -1
View File
@@ -294,7 +294,8 @@ public:
/* EDITOR FUNCTIONS */
struct Warning {
int line;
int start_line = -1, end_line = -1;
int leftmost_column = -1, rightmost_column = -1;
int code;
String string_code;
String message;
+3 -3
View File
@@ -494,7 +494,7 @@ void ScriptTextEditor::_validate_script() {
ScriptLanguage::Warning w = E->get();
Dictionary ignore_meta;
ignore_meta["line"] = w.line;
ignore_meta["line"] = w.start_line;
ignore_meta["code"] = w.string_code.to_lower();
warnings_panel->push_cell();
warnings_panel->push_meta(ignore_meta);
@@ -506,9 +506,9 @@ void ScriptTextEditor::_validate_script() {
warnings_panel->pop(); // Cell.
warnings_panel->push_cell();
warnings_panel->push_meta(w.line - 1);
warnings_panel->push_meta(w.start_line - 1);
warnings_panel->push_color(warnings_panel->get_theme_color("warning_color", "Editor"));
warnings_panel->add_text(TTR("Line") + " " + itos(w.line));
warnings_panel->add_text(TTR("Line") + " " + itos(w.start_line));
warnings_panel->add_text(" (" + w.string_code + "):");
warnings_panel->pop(); // Color.
warnings_panel->pop(); // Meta goto.
+11 -12
View File
@@ -43,6 +43,7 @@
#include "gdscript_cache.h"
#include "gdscript_compiler.h"
#include "gdscript_parser.h"
#include "gdscript_warning.h"
///////////////////////////
@@ -469,10 +470,9 @@ bool GDScript::_update_exports(bool *r_err, bool p_recursive_call) {
}
members_cache.push_back(member.variable->export_info);
// FIXME: Get variable's default value in non-literal cases.
Variant default_value;
if (member.variable->initializer != nullptr && member.variable->initializer->type == GDScriptParser::Node::LITERAL) {
default_value = static_cast<const GDScriptParser::LiteralNode *>(member.variable->initializer)->value;
if (member.variable->initializer->is_constant) {
default_value = member.variable->initializer->reduced_value;
}
member_default_values_cache[member.variable->identifier->name] = default_value;
} break;
@@ -637,14 +637,13 @@ Error GDScript::reload(bool p_keep_state) {
}
}
#ifdef DEBUG_ENABLED
// FIXME: Add warnings.
// for (const List<GDScriptWarning>::Element *E = parser.get_warnings().front(); E; E = E->next()) {
// const GDScriptWarning &warning = E->get();
// if (EngineDebugger::is_active()) {
// Vector<ScriptLanguage::StackInfo> si;
// EngineDebugger::get_script_debugger()->send_error("", get_path(), warning.line, warning.get_name(), warning.get_message(), ERR_HANDLER_WARNING, si);
// }
// }
for (const List<GDScriptWarning>::Element *E = parser.get_warnings().front(); E; E = E->next()) {
const GDScriptWarning &warning = E->get();
if (EngineDebugger::is_active()) {
Vector<ScriptLanguage::StackInfo> si;
EngineDebugger::get_script_debugger()->send_error("", get_path(), warning.start_line, warning.get_name(), warning.get_message(), ERR_HANDLER_WARNING, si);
}
}
#endif
valid = true;
@@ -2044,7 +2043,7 @@ GDScriptLanguage::GDScriptLanguage() {
GLOBAL_DEF("debug/gdscript/completion/autocomplete_setters_and_getters", false);
for (int i = 0; i < (int)GDScriptWarning::WARNING_MAX; i++) {
String warning = GDScriptWarning::get_name_from_code((GDScriptWarning::Code)i).to_lower();
bool default_enabled = !warning.begins_with("unsafe_") && i != GDScriptWarning::UNUSED_CLASS_VARIABLE;
bool default_enabled = !warning.begins_with("unsafe_");
GLOBAL_DEF("debug/gdscript/warnings/" + warning, default_enabled);
}
#endif // DEBUG_ENABLED
-46
View File
@@ -303,52 +303,6 @@ 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
SHADOWED_VARIABLE, // Variable name shadowed by other variable
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, // Function 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
DEPRECATED_KEYWORD, // The keyword is deprecated and should be replaced
STANDALONE_TERNARY, // Return value of ternary expression is discarded
WARNING_MAX,
};
Code code = WARNING_MAX;
Vector<String> symbols;
int line = -1;
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() {}
};
#endif // DEBUG_ENABLED
class GDScriptLanguage : public ScriptLanguage {
friend class GDScriptFunctionState;
File diff suppressed because it is too large Load Diff
+3 -2
View File
@@ -42,7 +42,7 @@ class GDScriptAnalyzer {
HashMap<String, Ref<GDScriptParserRef>> depended_parsers;
Error resolve_inheritance(GDScriptParser::ClassNode *p_class, bool p_recursive = true);
GDScriptParser::DataType resolve_datatype(const GDScriptParser::TypeNode *p_type);
GDScriptParser::DataType resolve_datatype(GDScriptParser::TypeNode *p_type);
void decide_suite_type(GDScriptParser::Node *p_suite, GDScriptParser::Node *p_statement);
@@ -74,7 +74,7 @@ class GDScriptAnalyzer {
void reduce_assignment(GDScriptParser::AssignmentNode *p_assignment);
void reduce_await(GDScriptParser::AwaitNode *p_await);
void reduce_binary_op(GDScriptParser::BinaryOpNode *p_binary_op);
void reduce_call(GDScriptParser::CallNode *p_call);
void reduce_call(GDScriptParser::CallNode *p_call, bool is_await = false);
void reduce_cast(GDScriptParser::CastNode *p_cast);
void reduce_dictionary(GDScriptParser::DictionaryNode *p_dictionary);
void reduce_get_node(GDScriptParser::GetNodeNode *p_get_node);
@@ -96,6 +96,7 @@ class GDScriptAnalyzer {
bool function_signature_from_info(const MethodInfo &p_info, GDScriptParser::DataType &r_return_type, List<GDScriptParser::DataType> &r_par_types, int &r_default_arg_count, bool &r_static, bool &r_vararg);
bool validate_call_arg(const List<GDScriptParser::DataType> &p_par_types, int p_default_args_count, bool p_is_vararg, const GDScriptParser::CallNode *p_call);
bool validate_call_arg(const MethodInfo &p_method, const GDScriptParser::CallNode *p_call);
bool is_shadowing(GDScriptParser::IdentifierNode *p_local, const String &p_context);
GDScriptParser::DataType get_operation_type(Variant::Operator p_operation, const GDScriptParser::DataType &p_a, const GDScriptParser::DataType &p_b, bool &r_valid);
bool is_type_compatible(const GDScriptParser::DataType &p_target, const GDScriptParser::DataType &p_source, bool p_allow_implicit_conversion = false) const;
void push_error(const String &p_message, const GDScriptParser::Node *p_origin);
+16 -14
View File
@@ -134,23 +134,25 @@ bool GDScriptLanguage::validate(const String &p_script, int &r_line_error, int &
GDScriptAnalyzer analyzer(&parser);
Error err = parser.parse(p_script, p_path, false);
#ifdef DEBUG_ENABLED
// FIXME: Warnings.
// 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 == OK) {
err = analyzer.analyze();
}
#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.start_line = warn.start_line;
w.end_line = warn.end_line;
w.leftmost_column = warn.leftmost_column;
w.rightmost_column = warn.rightmost_column;
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) {
GDScriptParser::ParserError parse_error = parser.get_errors().front()->get();
r_line_error = parse_error.line;
+157 -1
View File
@@ -33,6 +33,7 @@
#include "core/io/resource_loader.h"
#include "core/math/math_defs.h"
#include "core/os/file_access.h"
#include "core/project_settings.h"
#include "gdscript.h"
#ifdef DEBUG_ENABLED
@@ -181,6 +182,61 @@ void GDScriptParser::push_error(const String &p_message, const Node *p_origin) {
}
}
void GDScriptParser::push_warning(const Node *p_source, GDScriptWarning::Code p_code, 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);
}
push_warning(p_source, p_code, symbols);
}
void GDScriptParser::push_warning(const Node *p_source, GDScriptWarning::Code p_code, const Vector<String> &p_symbols) {
if (is_ignoring_warnings) {
return;
}
if (GLOBAL_GET("debug/gdscript/warnings/exclude_addons").booleanize() && script_path.begins_with("res://addons/")) {
return;
}
String warn_name = GDScriptWarning::get_name_from_code((GDScriptWarning::Code)p_code).to_lower();
if (ignored_warnings.has(warn_name)) {
return;
}
if (!GLOBAL_GET("debug/gdscript/warnings/" + warn_name)) {
return;
}
GDScriptWarning warning;
warning.code = p_code;
warning.symbols = p_symbols;
warning.start_line = p_source->start_line;
warning.end_line = p_source->end_line;
warning.leftmost_column = p_source->leftmost_column;
warning.rightmost_column = p_source->rightmost_column;
List<GDScriptWarning>::Element *before = nullptr;
for (List<GDScriptWarning>::Element *E = warnings.front(); E != nullptr; E = E->next()) {
if (E->get().start_line > warning.start_line) {
break;
}
before = E;
}
if (before) {
warnings.insert_after(before, warning);
} else {
warnings.push_front(warning);
}
}
Error GDScriptParser::parse(const String &p_source_code, const String &p_script_path, bool p_for_completion) {
clear();
tokenizer.set_source_code(p_source_code);
@@ -601,6 +657,7 @@ GDScriptParser::VariableNode *GDScriptParser::parse_variable(bool p_allow_proper
if (match(GDScriptTokenizer::Token::EQUAL)) {
// Initializer.
variable->initializer = parse_expression(false);
variable->assignments++;
}
if (p_allow_property && match(GDScriptTokenizer::Token::COLON)) {
@@ -1106,6 +1163,8 @@ GDScriptParser::SuiteNode *GDScriptParser::parse_suite(const String &p_context,
GDScriptParser::Node *GDScriptParser::parse_statement() {
Node *result = nullptr;
bool unreachable = current_suite->has_return && !current_suite->has_unreachable_code;
switch (current.type) {
case GDScriptTokenizer::Token::PASS:
advance();
@@ -1151,6 +1210,9 @@ GDScriptParser::Node *GDScriptParser::parse_statement() {
n_return->return_value = parse_expression(false);
}
result = n_return;
current_suite->has_return = true;
end_statement("return statement");
break;
}
@@ -1179,10 +1241,27 @@ GDScriptParser::Node *GDScriptParser::parse_statement() {
}
end_statement("expression");
result = expression;
if (expression != nullptr) {
switch (expression->type) {
case Node::CALL:
case Node::ASSIGNMENT:
case Node::AWAIT:
// Fine.
break;
default:
push_warning(expression, GDScriptWarning::STANDALONE_EXPRESSION);
}
}
break;
}
}
if (unreachable) {
current_suite->has_unreachable_code = true;
push_warning(result, GDScriptWarning::UNREACHABLE_CODE, current_function->identifier->name);
}
if (panic_mode) {
synchronize();
}
@@ -1232,6 +1311,7 @@ GDScriptParser::ContinueNode *GDScriptParser::parse_continue() {
if (!can_continue) {
push_error(R"(Cannot use "continue" outside of a loop or pattern matching block.)");
}
current_suite->has_continue = true;
end_statement(R"("continue")");
return alloc_node<ContinueNode>();
}
@@ -1281,6 +1361,10 @@ GDScriptParser::IfNode *GDScriptParser::parse_if(const String &p_token) {
n_if->true_block = parse_suite(vformat(R"("%s" block)", p_token));
if (n_if->true_block->has_continue) {
current_suite->has_continue = true;
}
if (match(GDScriptTokenizer::Token::ELIF)) {
IfNode *elif = parse_if("elif");
@@ -1292,6 +1376,13 @@ GDScriptParser::IfNode *GDScriptParser::parse_if(const String &p_token) {
n_if->false_block = parse_suite(R"("else" block)");
}
if (n_if->false_block != nullptr && n_if->false_block->has_return && n_if->true_block->has_return) {
current_suite->has_return = true;
}
if (n_if->false_block != nullptr && n_if->false_block->has_continue) {
current_suite->has_continue = true;
}
return n_if;
}
@@ -1310,16 +1401,43 @@ GDScriptParser::MatchNode *GDScriptParser::parse_match() {
return match;
}
bool all_have_return = true;
bool have_wildcard = false;
bool wildcard_has_return = false;
bool have_wildcard_without_continue = false;
bool have_unreachable_pattern = false;
while (!check(GDScriptTokenizer::Token::DEDENT) && !is_at_end()) {
MatchBranchNode *branch = parse_match_branch();
if (branch == nullptr) {
continue;
}
if (have_wildcard_without_continue && !have_unreachable_pattern) {
push_warning(branch->patterns[0], GDScriptWarning::UNREACHABLE_PATTERN);
}
if (branch->has_wildcard) {
have_wildcard = true;
if (branch->block->has_return) {
wildcard_has_return = true;
}
if (!branch->block->has_continue) {
have_wildcard_without_continue = true;
}
}
if (!branch->block->has_return) {
all_have_return = false;
}
match->branches.push_back(branch);
}
consume(GDScriptTokenizer::Token::DEDENT, R"(Expected an indented block after "match" statement.)");
if (wildcard_has_return || (all_have_return && have_wildcard)) {
current_suite->has_return = true;
}
return match;
}
@@ -1341,6 +1459,8 @@ GDScriptParser::MatchBranchNode *GDScriptParser::parse_match_branch() {
}
if (pattern->pattern_type == PatternNode::PT_REST) {
push_error(R"(Rest pattern can only be used inside array and dictionary patterns.)");
} else if (pattern->pattern_type == PatternNode::PT_BIND || pattern->pattern_type == PatternNode::PT_WILDCARD) {
branch->has_wildcard = true;
}
branch->patterns.push_back(pattern);
} while (match(GDScriptTokenizer::Token::COMMA));
@@ -1605,22 +1725,27 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_identifier(ExpressionNode
case SuiteNode::Local::CONSTANT:
identifier->source = IdentifierNode::LOCAL_CONSTANT;
identifier->constant_source = declaration.constant;
declaration.constant->usages++;
break;
case SuiteNode::Local::VARIABLE:
identifier->source = IdentifierNode::LOCAL_VARIABLE;
identifier->variable_source = declaration.variable;
declaration.variable->usages++;
break;
case SuiteNode::Local::PARAMETER:
identifier->source = IdentifierNode::FUNCTION_PARAMETER;
identifier->parameter_source = declaration.parameter;
declaration.parameter->usages++;
break;
case SuiteNode::Local::FOR_VARIABLE:
identifier->source = IdentifierNode::LOCAL_ITERATOR;
identifier->bind_source = declaration.bind;
declaration.bind->usages++;
break;
case SuiteNode::Local::PATTERN_BIND:
identifier->source = IdentifierNode::LOCAL_BIND;
identifier->bind_source = declaration.bind;
declaration.bind->usages++;
break;
case SuiteNode::Local::UNDEFINED:
ERR_FAIL_V_MSG(nullptr, "Undefined local found.");
@@ -1836,8 +1961,33 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_assignment(ExpressionNode
return parse_expression(false); // Return the following expression.
}
VariableNode *source_variable = nullptr;
switch (p_previous_operand->type) {
case Node::IDENTIFIER:
case Node::IDENTIFIER: {
// Get source to store assignment count.
// Also remove one usage since assignment isn't usage.
IdentifierNode *id = static_cast<IdentifierNode *>(p_previous_operand);
switch (id->source) {
case IdentifierNode::LOCAL_VARIABLE:
source_variable = id->variable_source;
id->variable_source->usages--;
break;
case IdentifierNode::LOCAL_CONSTANT:
id->constant_source->usages--;
break;
case IdentifierNode::FUNCTION_PARAMETER:
id->parameter_source->usages--;
break;
case IdentifierNode::LOCAL_ITERATOR:
case IdentifierNode::LOCAL_BIND:
id->bind_source->usages--;
break;
default:
break;
}
break;
}
case Node::SUBSCRIPT:
// Okay.
break;
@@ -1847,9 +1997,11 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_assignment(ExpressionNode
}
AssignmentNode *assignment = alloc_node<AssignmentNode>();
bool has_operator = true;
switch (previous.type) {
case GDScriptTokenizer::Token::EQUAL:
assignment->operation = AssignmentNode::OP_NONE;
has_operator = false;
break;
case GDScriptTokenizer::Token::PLUS_EQUAL:
assignment->operation = AssignmentNode::OP_ADDITION;
@@ -1887,6 +2039,10 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_assignment(ExpressionNode
assignment->assignee = p_previous_operand;
assignment->assigned_value = parse_expression(false);
if (has_operator && source_variable != nullptr && source_variable->assignments == 0) {
push_warning(assignment, GDScriptWarning::UNASSIGNED_VARIABLE_OP_ASSIGN, source_variable->identifier->name);
}
return assignment;
}
+56 -6
View File
@@ -44,6 +44,7 @@
#include "core/vector.h"
#include "gdscript_functions.h"
#include "gdscript_tokenizer.h"
#include "gdscript_warning.h"
#ifdef DEBUG_ENABLED
#include "core/string_builder.h"
@@ -101,7 +102,7 @@ public:
CLASS, // GDScript.
VARIANT, // Can be any type.
UNRESOLVED,
// TODO: Enum, Signal, Callable
// TODO: Enum
};
Kind kind = UNRESOLVED;
@@ -366,7 +367,7 @@ public:
struct CallNode : public ExpressionNode {
ExpressionNode *callee = nullptr;
Vector<ExpressionNode *> arguments;
StringName function_name; // TODO: Set this.
StringName function_name;
bool is_super = false;
CallNode() {
@@ -388,6 +389,9 @@ public:
IdentifierNode *identifier = nullptr;
LiteralNode *custom_value = nullptr;
int value = 0;
int line = 0;
int leftmost_column = 0;
int rightmost_column = 0;
};
IdentifierNode *identifier = nullptr;
Vector<Value> values;
@@ -444,6 +448,28 @@ public:
return "";
}
int get_line() const {
switch (type) {
case CLASS:
return m_class->start_line;
case CONSTANT:
return constant->start_line;
case FUNCTION:
return function->start_line;
case VARIABLE:
return variable->start_line;
case ENUM_VALUE:
return enum_value.line;
case ENUM:
return m_enum->start_line;
case SIGNAL:
return signal->start_line;
case UNDEFINED:
ERR_FAIL_V_MSG(-1, "Reaching undefined member type.");
}
ERR_FAIL_V_MSG(-1, "Reaching unhandled type.");
}
DataType get_datatype() const {
switch (type) {
case CLASS:
@@ -462,9 +488,16 @@ public:
type.builtin_type = Variant::INT;
return type;
}
case SIGNAL: {
DataType type;
type.type_source = DataType::ANNOTATED_EXPLICIT;
type.kind = DataType::BUILTIN;
type.builtin_type = Variant::SIGNAL;
// TODO: Add parameter info.
return type;
}
case ENUM:
case SIGNAL:
// TODO: Use special datatype kinds for these.
// TODO: Use special datatype kinds for this.
return DataType();
case UNDEFINED:
return DataType();
@@ -545,6 +578,7 @@ public:
ExpressionNode *initializer = nullptr;
TypeNode *datatype_specifier = nullptr;
bool infer_datatype = false;
int usages = 0;
ConstantNode() {
type = CONSTANT;
@@ -594,6 +628,7 @@ public:
bool is_static = false;
bool is_coroutine = false;
MultiplayerAPI::RPCMode rpc_mode = MultiplayerAPI::RPC_MODE_DISABLED;
MethodInfo info;
bool resolved_signature = false;
bool resolved_body = false;
@@ -634,6 +669,8 @@ public:
IdentifierNode *bind_source;
};
int usages = 0; // Useful for binds/iterator variable.
IdentifierNode() {
type = IDENTIFIER;
}
@@ -669,6 +706,7 @@ public:
struct MatchBranchNode : public Node {
Vector<PatternNode *> patterns;
SuiteNode *block;
bool has_wildcard = false;
MatchBranchNode() {
type = MATCH_BRANCH;
@@ -680,6 +718,7 @@ public:
ExpressionNode *default_value = nullptr;
TypeNode *datatype_specifier = nullptr;
bool infer_datatype = false;
int usages = 0;
ParameterNode() {
type = PARAMETER;
@@ -836,6 +875,10 @@ public:
IfNode *parent_if = nullptr;
PatternNode *parent_pattern = nullptr;
bool has_return = false;
bool has_continue = false;
bool has_unreachable_code = false; // Just so warnings aren't given more than once per block.
bool has_local(const StringName &p_name) const;
const Local &get_local(const StringName &p_name) const;
template <class T>
@@ -916,6 +959,8 @@ public:
bool onready = false;
PropertyInfo export_info;
MultiplayerAPI::RPCMode rpc_mode = MultiplayerAPI::RPC_MODE_DISABLED;
int assignments = 0;
int usages = 0;
VariableNode() {
type = VARIABLE;
@@ -940,11 +985,15 @@ private:
bool panic_mode = false;
bool can_break = false;
bool can_continue = false;
bool is_ignoring_warnings = false;
List<bool> multiline_stack;
ClassNode *head = nullptr;
Node *list = nullptr;
List<ParserError> errors;
List<GDScriptWarning> warnings;
Set<String> ignored_warnings;
Set<int> unsafe_lines;
GDScriptTokenizer tokenizer;
GDScriptTokenizer::Token previous;
@@ -974,8 +1023,6 @@ private:
HashMap<StringName, AnnotationInfo> valid_annotations;
List<AnnotationNode *> annotation_stack;
Set<int> unsafe_lines;
typedef ExpressionNode *(GDScriptParser::*ParseFunction)(ExpressionNode *p_previous_operand, bool p_can_assign);
// Higher value means higher precedence (i.e. is evaluated first).
enum Precedence {
@@ -1015,6 +1062,8 @@ private:
T *alloc_node();
void clear();
void push_error(const String &p_message, const Node *p_origin = nullptr);
void push_warning(const Node *p_source, GDScriptWarning::Code p_code, const String &p_symbol1 = String(), const String &p_symbol2 = String(), const String &p_symbol3 = String(), const String &p_symbol4 = String());
void push_warning(const Node *p_source, GDScriptWarning::Code p_code, const Vector<String> &p_symbols);
GDScriptTokenizer::Token advance();
bool match(GDScriptTokenizer::Token::Type p_token_type);
@@ -1104,6 +1153,7 @@ public:
static GDScriptFunctions::Function get_builtin_function(const StringName &p_name);
const List<ParserError> &get_errors() const { return errors; }
const List<GDScriptWarning> &get_warnings() const { return warnings; }
const List<String> get_dependencies() const {
// TODO: Keep track of deps.
return List<String>();
+38 -29
View File
@@ -30,6 +30,8 @@
#include "gdscript_warning.h"
#include "core/variant.h"
#ifdef DEBUG_ENABLED
String GDScriptWarning::get_message() const {
@@ -48,22 +50,33 @@ String GDScriptWarning::get_message() const {
CHECK_SYMBOLS(1);
return "The local variable '" + symbols[0] + "' is declared but never used in the block. If this is intended, prefix it with an underscore: '_" + symbols[0] + "'";
} break;
case SHADOWED_VARIABLE: {
CHECK_SYMBOLS(2);
return "The local variable '" + symbols[0] + "' is shadowing an already-defined variable at line " + symbols[1] + ".";
case UNUSED_LOCAL_CONSTANT: {
CHECK_SYMBOLS(1);
return "The local constant '" + symbols[0] + "' is declared but never used in the block. If this is intended, prefix it with an underscore: '_" + symbols[0] + "'";
} break;
case UNUSED_CLASS_VARIABLE: {
case SHADOWED_VARIABLE: {
CHECK_SYMBOLS(4);
return vformat(R"(The local %s "%s" is shadowing an already-declared %s at line %s.)", symbols[0], symbols[1], symbols[2], symbols[3]);
} break;
case SHADOWED_VARIABLE_BASE_CLASS: {
CHECK_SYMBOLS(4);
return vformat(R"(The local %s "%s" is shadowing an already-declared %s at the base class "%s".)", symbols[0], symbols[1], symbols[2], symbols[3]);
} break;
case UNUSED_PRIVATE_CLASS_VARIABLE: {
CHECK_SYMBOLS(1);
return "The class variable '" + symbols[0] + "' is declared but never used in the script.";
} break;
case UNUSED_ARGUMENT: {
case UNUSED_PARAMETER: {
CHECK_SYMBOLS(2);
return "The argument '" + symbols[1] + "' is never used in the function '" + symbols[0] + "'. If this is intended, prefix it with an underscore: '_" + symbols[1] + "'";
return "The parameter '" + symbols[1] + "' is never used in the function '" + symbols[0] + "'. If this is intended, prefix it with an underscore: '_" + symbols[1] + "'";
} break;
case UNREACHABLE_CODE: {
CHECK_SYMBOLS(1);
return "Unreachable code (statement after return) in function '" + symbols[0] + "()'.";
} break;
case UNREACHABLE_PATTERN: {
return "Unreachable pattern (pattern after wildcard or bind).";
} break;
case STANDALONE_EXPRESSION: {
return "Standalone expression (the line has no effect).";
} break;
@@ -74,22 +87,6 @@ String GDScriptWarning::get_message() const {
case NARROWING_CONVERSION: {
return "Narrowing conversion (float is converted to int and loses 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;
@@ -139,6 +136,15 @@ String GDScriptWarning::get_message() const {
case STANDALONE_TERNARY: {
return "Standalone ternary conditional operator: the return value is being discarded.";
}
case ASSERT_ALWAYS_TRUE: {
return "Assert statement is redundant because the expression is always true.";
}
case ASSERT_ALWAYS_FALSE: {
return "Assert statement will raise an error because the expression is always false.";
}
case REDUNDANT_AWAIT: {
return R"("await" keyword not needed in this case, because the expression isn't a coroutine nor a signal.)";
}
case WARNING_MAX:
break; // Can't happen, but silences warning
}
@@ -158,17 +164,16 @@ String GDScriptWarning::get_name_from_code(Code p_code) {
"UNASSIGNED_VARIABLE",
"UNASSIGNED_VARIABLE_OP_ASSIGN",
"UNUSED_VARIABLE",
"UNUSED_LOCAL_CONSTANT",
"SHADOWED_VARIABLE",
"UNUSED_CLASS_VARIABLE",
"UNUSED_ARGUMENT",
"SHADOWED_VARIABLE_BASE_CLASS",
"UNUSED_PRIVATE_CLASS_VARIABLE",
"UNUSED_PARAMETER",
"UNREACHABLE_CODE",
"UNREACHABLE_PATTERN",
"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",
@@ -182,9 +187,13 @@ String GDScriptWarning::get_name_from_code(Code p_code) {
"UNSAFE_CALL_ARGUMENT",
"DEPRECATED_KEYWORD",
"STANDALONE_TERNARY",
nullptr
"ASSERT_ALWAYS_TRUE",
"ASSERT_ALWAYS_FALSE",
"REDUNDANT_AWAIT",
};
static_assert((sizeof(names) / sizeof(*names)) == WARNING_MAX, "Amount of warning types don't match the amount of warning names.");
return names[(int)p_code];
}
+31 -28
View File
@@ -39,38 +39,41 @@
class GDScriptWarning {
public:
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
SHADOWED_VARIABLE, // Variable name shadowed by other variable
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, // Function 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
DEPRECATED_KEYWORD, // The keyword is deprecated and should be replaced
STANDALONE_TERNARY, // Return value of ternary expression is discarded
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_LOCAL_CONSTANT, // Local constant is declared but never used.
SHADOWED_VARIABLE, // Variable name shadowed by other variable in same class.
SHADOWED_VARIABLE_BASE_CLASS, // Variable name shadowed by other variable in some base class.
UNUSED_PRIVATE_CLASS_VARIABLE, // Class variable is declared private ("_" prefix) but never used in the file.
UNUSED_PARAMETER, // Function parameter is never used.
UNREACHABLE_CODE, // Code after a return statement.
UNREACHABLE_PATTERN, // Pattern in a match statement after a catch all pattern (wildcard or bind).
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.
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, // Function 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.
DEPRECATED_KEYWORD, // The keyword is deprecated and should be replaced.
STANDALONE_TERNARY, // Return value of ternary expression is discarded.
ASSERT_ALWAYS_TRUE, // Expression for assert argument is always true.
ASSERT_ALWAYS_FALSE, // Expression for assert argument is always false.
REDUNDANT_AWAIT, // await is used but expression is synchronous (not a signal nor a coroutine).
WARNING_MAX,
};
Code code = WARNING_MAX;
int line = -1;
int start_line = -1, end_line = -1;
int leftmost_column = -1, rightmost_column = -1;
Vector<String> symbols;
String get_name() const;