| 1 | /**************************************************************************/ |
| 2 | /* gdscript_warning.cpp */ |
| 3 | /**************************************************************************/ |
| 4 | /* This file is part of: */ |
| 5 | /* GODOT ENGINE */ |
| 6 | /* https://godotengine.org */ |
| 7 | /**************************************************************************/ |
| 8 | /* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ |
| 9 | /* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ |
| 10 | /* */ |
| 11 | /* Permission is hereby granted, free of charge, to any person obtaining */ |
| 12 | /* a copy of this software and associated documentation files (the */ |
| 13 | /* "Software"), to deal in the Software without restriction, including */ |
| 14 | /* without limitation the rights to use, copy, modify, merge, publish, */ |
| 15 | /* distribute, sublicense, and/or sell copies of the Software, and to */ |
| 16 | /* permit persons to whom the Software is furnished to do so, subject to */ |
| 17 | /* the following conditions: */ |
| 18 | /* */ |
| 19 | /* The above copyright notice and this permission notice shall be */ |
| 20 | /* included in all copies or substantial portions of the Software. */ |
| 21 | /* */ |
| 22 | /* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ |
| 23 | /* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ |
| 24 | /* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ |
| 25 | /* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ |
| 26 | /* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ |
| 27 | /* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ |
| 28 | /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ |
| 29 | /**************************************************************************/ |
| 30 | |
| 31 | #include "gdscript_warning.h" |
| 32 | |
| 33 | #include "core/variant/variant.h" |
| 34 | |
| 35 | #ifdef DEBUG_ENABLED |
| 36 | |
| 37 | String GDScriptWarning::get_message() const { |
| 38 | #define CHECK_SYMBOLS(m_amount) ERR_FAIL_COND_V(symbols.size() < m_amount, String()); |
| 39 | |
| 40 | switch (code) { |
| 41 | case UNASSIGNED_VARIABLE: |
| 42 | CHECK_SYMBOLS(1); |
| 43 | return vformat(R"(The variable "%s" was used but never assigned a value.)" , symbols[0]); |
| 44 | case UNASSIGNED_VARIABLE_OP_ASSIGN: |
| 45 | CHECK_SYMBOLS(1); |
| 46 | return vformat(R"(Using assignment with operation but the variable "%s" was not previously assigned a value.)" , symbols[0]); |
| 47 | case UNUSED_VARIABLE: |
| 48 | CHECK_SYMBOLS(1); |
| 49 | return vformat(R"(The local variable "%s" is declared but never used in the block. If this is intended, prefix it with an underscore: "_%s".)" , symbols[0], symbols[0]); |
| 50 | case UNUSED_LOCAL_CONSTANT: |
| 51 | CHECK_SYMBOLS(1); |
| 52 | return vformat(R"(The local constant "%s" is declared but never used in the block. If this is intended, prefix it with an underscore: "_%s".)" , symbols[0], symbols[0]); |
| 53 | case UNUSED_PRIVATE_CLASS_VARIABLE: |
| 54 | CHECK_SYMBOLS(1); |
| 55 | return vformat(R"(The class variable "%s" is declared but never used in the script.)" , symbols[0]); |
| 56 | case UNUSED_PARAMETER: |
| 57 | CHECK_SYMBOLS(2); |
| 58 | return vformat(R"*(The parameter "%s" is never used in the function "%s()". If this is intended, prefix it with an underscore: "_%s".)*" , symbols[1], symbols[0], symbols[1]); |
| 59 | case UNUSED_SIGNAL: |
| 60 | CHECK_SYMBOLS(1); |
| 61 | return vformat(R"(The signal "%s" is declared but never emitted.)" , symbols[0]); |
| 62 | case SHADOWED_VARIABLE: |
| 63 | CHECK_SYMBOLS(4); |
| 64 | return vformat(R"(The local %s "%s" is shadowing an already-declared %s at line %s.)" , symbols[0], symbols[1], symbols[2], symbols[3]); |
| 65 | case SHADOWED_VARIABLE_BASE_CLASS: |
| 66 | CHECK_SYMBOLS(4); |
| 67 | 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]); |
| 68 | case SHADOWED_GLOBAL_IDENTIFIER: |
| 69 | CHECK_SYMBOLS(3); |
| 70 | return vformat(R"(The %s "%s" has the same name as a %s.)" , symbols[0], symbols[1], symbols[2]); |
| 71 | case UNREACHABLE_CODE: |
| 72 | CHECK_SYMBOLS(1); |
| 73 | return vformat(R"*(Unreachable code (statement after return) in function "%s()".)*" , symbols[0]); |
| 74 | case UNREACHABLE_PATTERN: |
| 75 | return "Unreachable pattern (pattern after wildcard or bind)." ; |
| 76 | case STANDALONE_EXPRESSION: |
| 77 | return "Standalone expression (the line has no effect)." ; |
| 78 | case STANDALONE_TERNARY: |
| 79 | return "Standalone ternary conditional operator: the return value is being discarded." ; |
| 80 | case INCOMPATIBLE_TERNARY: |
| 81 | return "Values of the ternary conditional are not mutually compatible." ; |
| 82 | case PROPERTY_USED_AS_FUNCTION: |
| 83 | CHECK_SYMBOLS(2); |
| 84 | return vformat(R"*(The method "%s()" was not found in base "%s" but there's a property with the same name. Did you mean to access it?)*" , symbols[0], symbols[1]); |
| 85 | case CONSTANT_USED_AS_FUNCTION: |
| 86 | CHECK_SYMBOLS(2); |
| 87 | return vformat(R"*(The method "%s()" was not found in base "%s" but there's a constant with the same name. Did you mean to access it?)*" , symbols[0], symbols[1]); |
| 88 | case FUNCTION_USED_AS_PROPERTY: |
| 89 | CHECK_SYMBOLS(2); |
| 90 | return vformat(R"(The property "%s" was not found in base "%s" but there's a method with the same name. Did you mean to call it?)" , symbols[0], symbols[1]); |
| 91 | case UNTYPED_DECLARATION: |
| 92 | CHECK_SYMBOLS(2); |
| 93 | if (symbols[0] == "Function" ) { |
| 94 | return vformat(R"*(%s "%s()" has no static return type.)*" , symbols[0], symbols[1]); |
| 95 | } |
| 96 | return vformat(R"(%s "%s" has no static type.)" , symbols[0], symbols[1]); |
| 97 | case UNSAFE_PROPERTY_ACCESS: |
| 98 | CHECK_SYMBOLS(2); |
| 99 | return vformat(R"(The property "%s" is not present on the inferred type "%s" (but may be present on a subtype).)" , symbols[0], symbols[1]); |
| 100 | case UNSAFE_METHOD_ACCESS: |
| 101 | CHECK_SYMBOLS(2); |
| 102 | return vformat(R"*(The method "%s()" is not present on the inferred type "%s" (but may be present on a subtype).)*" , symbols[0], symbols[1]); |
| 103 | case UNSAFE_CAST: |
| 104 | CHECK_SYMBOLS(1); |
| 105 | return vformat(R"(The value is cast to "%s" but has an unknown type.)" , symbols[0]); |
| 106 | case UNSAFE_CALL_ARGUMENT: |
| 107 | CHECK_SYMBOLS(4); |
| 108 | return vformat(R"*(The argument %s of the function "%s()" requires a the subtype "%s" but the supertype "%s" was provided.)*" , symbols[0], symbols[1], symbols[2], symbols[3]); |
| 109 | case UNSAFE_VOID_RETURN: |
| 110 | CHECK_SYMBOLS(2); |
| 111 | return vformat(R"*(The method "%s()" returns "void" but it's trying to return a call to "%s()" that can't be ensured to also be "void".)*" , symbols[0], symbols[1]); |
| 112 | case RETURN_VALUE_DISCARDED: |
| 113 | CHECK_SYMBOLS(1); |
| 114 | return vformat(R"*(The function "%s()" returns a value that will be discarded if not used.)*" , symbols[0]); |
| 115 | case STATIC_CALLED_ON_INSTANCE: |
| 116 | CHECK_SYMBOLS(2); |
| 117 | return vformat(R"*(The function "%s()" is a static function but was called from an instance. Instead, it should be directly called from the type: "%s.%s()".)*" , symbols[0], symbols[1], symbols[0]); |
| 118 | case REDUNDANT_STATIC_UNLOAD: |
| 119 | return R"(The "@static_unload" annotation is redundant because the file does not have a class with static variables.)" ; |
| 120 | case REDUNDANT_AWAIT: |
| 121 | return R"("await" keyword not needed in this case, because the expression isn't a coroutine nor a signal.)" ; |
| 122 | case ASSERT_ALWAYS_TRUE: |
| 123 | return "Assert statement is redundant because the expression is always true." ; |
| 124 | case ASSERT_ALWAYS_FALSE: |
| 125 | return "Assert statement will raise an error because the expression is always false." ; |
| 126 | case INTEGER_DIVISION: |
| 127 | return "Integer division, decimal part will be discarded." ; |
| 128 | case NARROWING_CONVERSION: |
| 129 | return "Narrowing conversion (float is converted to int and loses precision)." ; |
| 130 | case INT_AS_ENUM_WITHOUT_CAST: |
| 131 | return "Integer used when an enum value is expected. If this is intended cast the integer to the enum type." ; |
| 132 | case INT_AS_ENUM_WITHOUT_MATCH: |
| 133 | CHECK_SYMBOLS(3); |
| 134 | return vformat(R"(Cannot %s %s as Enum "%s": no enum member has matching value.)" , symbols[0], symbols[1], symbols[2]); |
| 135 | case EMPTY_FILE: |
| 136 | return "Empty script file." ; |
| 137 | case DEPRECATED_KEYWORD: |
| 138 | CHECK_SYMBOLS(2); |
| 139 | return vformat(R"(The "%s" keyword is deprecated and will be removed in a future release, please replace its uses by "%s".)" , symbols[0], symbols[1]); |
| 140 | case RENAMED_IN_GODOT_4_HINT: |
| 141 | break; // Renamed identifier hint is taken care of by the GDScriptAnalyzer. No message needed here. |
| 142 | case CONFUSABLE_IDENTIFIER: |
| 143 | CHECK_SYMBOLS(1); |
| 144 | return vformat(R"(The identifier "%s" has misleading characters and might be confused with something else.)" , symbols[0]); |
| 145 | case CONFUSABLE_LOCAL_DECLARATION: |
| 146 | CHECK_SYMBOLS(2); |
| 147 | return vformat(R"(The %s "%s" is declared below in the parent block.)" , symbols[0], symbols[1]); |
| 148 | case CONFUSABLE_LOCAL_USAGE: |
| 149 | CHECK_SYMBOLS(1); |
| 150 | return vformat(R"(The identifier "%s" will be shadowed below in the block.)" , symbols[0]); |
| 151 | case INFERENCE_ON_VARIANT: |
| 152 | CHECK_SYMBOLS(1); |
| 153 | return vformat("The %s type is being inferred from a Variant value, so it will be typed as Variant." , symbols[0]); |
| 154 | case NATIVE_METHOD_OVERRIDE: |
| 155 | CHECK_SYMBOLS(2); |
| 156 | return vformat(R"*(The method "%s()" overrides a method from native class "%s". This won't be called by the engine and may not work as expected.)*" , symbols[0], symbols[1]); |
| 157 | case GET_NODE_DEFAULT_WITHOUT_ONREADY: |
| 158 | CHECK_SYMBOLS(1); |
| 159 | return vformat(R"*(The default value is using "%s" which won't return nodes in the scene tree before "_ready()" is called. Use the "@onready" annotation to solve this.)*" , symbols[0]); |
| 160 | case ONREADY_WITH_EXPORT: |
| 161 | return R"("@onready" will set the default value after "@export" takes effect and will override it.)" ; |
| 162 | case WARNING_MAX: |
| 163 | break; // Can't happen, but silences warning |
| 164 | } |
| 165 | ERR_FAIL_V_MSG(String(), "Invalid GDScript warning code: " + get_name_from_code(code) + "." ); |
| 166 | |
| 167 | #undef CHECK_SYMBOLS |
| 168 | } |
| 169 | |
| 170 | int GDScriptWarning::get_default_value(Code p_code) { |
| 171 | ERR_FAIL_INDEX_V_MSG(p_code, WARNING_MAX, WarnLevel::IGNORE, "Getting default value of invalid warning code." ); |
| 172 | return default_warning_levels[p_code]; |
| 173 | } |
| 174 | |
| 175 | PropertyInfo GDScriptWarning::get_property_info(Code p_code) { |
| 176 | // Making this a separate function in case a warning needs different PropertyInfo in the future. |
| 177 | if (p_code == Code::RENAMED_IN_GODOT_4_HINT) { |
| 178 | return PropertyInfo(Variant::BOOL, get_settings_path_from_code(p_code)); |
| 179 | } |
| 180 | return PropertyInfo(Variant::INT, get_settings_path_from_code(p_code), PROPERTY_HINT_ENUM, "Ignore,Warn,Error" ); |
| 181 | } |
| 182 | |
| 183 | String GDScriptWarning::get_name() const { |
| 184 | return get_name_from_code(code); |
| 185 | } |
| 186 | |
| 187 | String GDScriptWarning::get_name_from_code(Code p_code) { |
| 188 | ERR_FAIL_COND_V(p_code < 0 || p_code >= WARNING_MAX, String()); |
| 189 | |
| 190 | static const char *names[] = { |
| 191 | "UNASSIGNED_VARIABLE" , |
| 192 | "UNASSIGNED_VARIABLE_OP_ASSIGN" , |
| 193 | "UNUSED_VARIABLE" , |
| 194 | "UNUSED_LOCAL_CONSTANT" , |
| 195 | "UNUSED_PRIVATE_CLASS_VARIABLE" , |
| 196 | "UNUSED_PARAMETER" , |
| 197 | "UNUSED_SIGNAL" , |
| 198 | "SHADOWED_VARIABLE" , |
| 199 | "SHADOWED_VARIABLE_BASE_CLASS" , |
| 200 | "SHADOWED_GLOBAL_IDENTIFIER" , |
| 201 | "UNREACHABLE_CODE" , |
| 202 | "UNREACHABLE_PATTERN" , |
| 203 | "STANDALONE_EXPRESSION" , |
| 204 | "STANDALONE_TERNARY" , |
| 205 | "INCOMPATIBLE_TERNARY" , |
| 206 | "PROPERTY_USED_AS_FUNCTION" , |
| 207 | "CONSTANT_USED_AS_FUNCTION" , |
| 208 | "FUNCTION_USED_AS_PROPERTY" , |
| 209 | "UNTYPED_DECLARATION" , |
| 210 | "UNSAFE_PROPERTY_ACCESS" , |
| 211 | "UNSAFE_METHOD_ACCESS" , |
| 212 | "UNSAFE_CAST" , |
| 213 | "UNSAFE_CALL_ARGUMENT" , |
| 214 | "UNSAFE_VOID_RETURN" , |
| 215 | "RETURN_VALUE_DISCARDED" , |
| 216 | "STATIC_CALLED_ON_INSTANCE" , |
| 217 | "REDUNDANT_STATIC_UNLOAD" , |
| 218 | "REDUNDANT_AWAIT" , |
| 219 | "ASSERT_ALWAYS_TRUE" , |
| 220 | "ASSERT_ALWAYS_FALSE" , |
| 221 | "INTEGER_DIVISION" , |
| 222 | "NARROWING_CONVERSION" , |
| 223 | "INT_AS_ENUM_WITHOUT_CAST" , |
| 224 | "INT_AS_ENUM_WITHOUT_MATCH" , |
| 225 | "EMPTY_FILE" , |
| 226 | "DEPRECATED_KEYWORD" , |
| 227 | "RENAMED_IN_GODOT_4_HINT" , |
| 228 | "CONFUSABLE_IDENTIFIER" , |
| 229 | "CONFUSABLE_LOCAL_DECLARATION" , |
| 230 | "CONFUSABLE_LOCAL_USAGE" , |
| 231 | "INFERENCE_ON_VARIANT" , |
| 232 | "NATIVE_METHOD_OVERRIDE" , |
| 233 | "GET_NODE_DEFAULT_WITHOUT_ONREADY" , |
| 234 | "ONREADY_WITH_EXPORT" , |
| 235 | }; |
| 236 | |
| 237 | static_assert((sizeof(names) / sizeof(*names)) == WARNING_MAX, "Amount of warning types don't match the amount of warning names." ); |
| 238 | |
| 239 | return names[(int)p_code]; |
| 240 | } |
| 241 | |
| 242 | String GDScriptWarning::get_settings_path_from_code(Code p_code) { |
| 243 | return "debug/gdscript/warnings/" + get_name_from_code(p_code).to_lower(); |
| 244 | } |
| 245 | |
| 246 | GDScriptWarning::Code GDScriptWarning::get_code_from_name(const String &p_name) { |
| 247 | for (int i = 0; i < WARNING_MAX; i++) { |
| 248 | if (get_name_from_code((Code)i) == p_name) { |
| 249 | return (Code)i; |
| 250 | } |
| 251 | } |
| 252 | |
| 253 | return WARNING_MAX; |
| 254 | } |
| 255 | |
| 256 | #endif // DEBUG_ENABLED |
| 257 | |