Browse Source

GDScript: Warn when enum variable has no default

The default will always be set to `0`, so if it's not a valid value in
the enum, the warning is shown.
George Marques 1 year ago
parent
commit
f9048fcd7d

+ 3 - 0
doc/classes/ProjectSettings.xml

@@ -488,6 +488,9 @@
 		<member name="debug/gdscript/warnings/enable" type="bool" setter="" getter="" default="true">
 		<member name="debug/gdscript/warnings/enable" type="bool" setter="" getter="" default="true">
 			If [code]true[/code], enables specific GDScript warnings (see [code]debug/gdscript/warnings/*[/code] settings). If [code]false[/code], disables all GDScript warnings.
 			If [code]true[/code], enables specific GDScript warnings (see [code]debug/gdscript/warnings/*[/code] settings). If [code]false[/code], disables all GDScript warnings.
 		</member>
 		</member>
+		<member name="debug/gdscript/warnings/enum_variable_without_default" type="int" setter="" getter="" default="1">
+			When set to [code]warn[/code] or [code]error[/code], produces a warning or an error respectively when a variable has an enum type but no explicit default value, but only if the enum does not contain [code]0[/code] as a valid value.
+		</member>
 		<member name="debug/gdscript/warnings/exclude_addons" type="bool" setter="" getter="" default="true">
 		<member name="debug/gdscript/warnings/exclude_addons" type="bool" setter="" getter="" default="true">
 			If [code]true[/code], scripts in the [code]res://addons[/code] folder will not generate warnings.
 			If [code]true[/code], scripts in the [code]res://addons[/code] folder will not generate warnings.
 		</member>
 		</member>

+ 12 - 0
modules/gdscript/gdscript_analyzer.cpp

@@ -1957,6 +1957,18 @@ void GDScriptAnalyzer::resolve_assignable(GDScriptParser::AssignableNode *p_assi
 		} else {
 		} else {
 			parser->push_warning(p_assignable, GDScriptWarning::UNTYPED_DECLARATION, declaration_type, p_assignable->identifier->name);
 			parser->push_warning(p_assignable, GDScriptWarning::UNTYPED_DECLARATION, declaration_type, p_assignable->identifier->name);
 		}
 		}
+	} else if (specified_type.kind == GDScriptParser::DataType::ENUM && p_assignable->initializer == nullptr) {
+		// Warn about enum variables without default value. Unless the enum defines the "0" value, then it's fine.
+		bool has_zero_value = false;
+		for (const KeyValue<StringName, int64_t> &kv : specified_type.enum_values) {
+			if (kv.value == 0) {
+				has_zero_value = true;
+				break;
+			}
+		}
+		if (!has_zero_value) {
+			parser->push_warning(p_assignable, GDScriptWarning::ENUM_VARIABLE_WITHOUT_DEFAULT, p_assignable->identifier->name);
+		}
 	}
 	}
 #endif
 #endif
 
 

+ 4 - 0
modules/gdscript/gdscript_warning.cpp

@@ -126,6 +126,9 @@ String GDScriptWarning::get_message() const {
 		case INT_AS_ENUM_WITHOUT_MATCH:
 		case INT_AS_ENUM_WITHOUT_MATCH:
 			CHECK_SYMBOLS(3);
 			CHECK_SYMBOLS(3);
 			return vformat(R"(Cannot %s %s as Enum "%s": no enum member has matching value.)", symbols[0], symbols[1], symbols[2]);
 			return vformat(R"(Cannot %s %s as Enum "%s": no enum member has matching value.)", symbols[0], symbols[1], symbols[2]);
+		case ENUM_VARIABLE_WITHOUT_DEFAULT:
+			CHECK_SYMBOLS(1);
+			return vformat(R"(The variable "%s" has an enum type and does not set an explicit default value. The default will be set to "0".)", symbols[0]);
 		case EMPTY_FILE:
 		case EMPTY_FILE:
 			return "Empty script file.";
 			return "Empty script file.";
 		case DEPRECATED_KEYWORD:
 		case DEPRECATED_KEYWORD:
@@ -221,6 +224,7 @@ String GDScriptWarning::get_name_from_code(Code p_code) {
 		"NARROWING_CONVERSION",
 		"NARROWING_CONVERSION",
 		"INT_AS_ENUM_WITHOUT_CAST",
 		"INT_AS_ENUM_WITHOUT_CAST",
 		"INT_AS_ENUM_WITHOUT_MATCH",
 		"INT_AS_ENUM_WITHOUT_MATCH",
+		"ENUM_VARIABLE_WITHOUT_DEFAULT",
 		"EMPTY_FILE",
 		"EMPTY_FILE",
 		"DEPRECATED_KEYWORD",
 		"DEPRECATED_KEYWORD",
 		"RENAMED_IN_GODOT_4_HINT",
 		"RENAMED_IN_GODOT_4_HINT",

+ 2 - 0
modules/gdscript/gdscript_warning.h

@@ -78,6 +78,7 @@ public:
 		NARROWING_CONVERSION, // Float value into an integer slot, precision is lost.
 		NARROWING_CONVERSION, // Float value into an integer slot, precision is lost.
 		INT_AS_ENUM_WITHOUT_CAST, // An integer value was used as an enum value without casting.
 		INT_AS_ENUM_WITHOUT_CAST, // An integer value was used as an enum value without casting.
 		INT_AS_ENUM_WITHOUT_MATCH, // An integer value was used as an enum value without matching enum member.
 		INT_AS_ENUM_WITHOUT_MATCH, // An integer value was used as an enum value without matching enum member.
+		ENUM_VARIABLE_WITHOUT_DEFAULT, // A variable with an enum type does not have a default value. The default will be set to `0` instead of the first enum value.
 		EMPTY_FILE, // A script file is empty.
 		EMPTY_FILE, // A script file is empty.
 		DEPRECATED_KEYWORD, // The keyword is deprecated and should be replaced.
 		DEPRECATED_KEYWORD, // The keyword is deprecated and should be replaced.
 		RENAMED_IN_GODOT_4_HINT, // A variable or function that could not be found has been renamed in Godot 4.
 		RENAMED_IN_GODOT_4_HINT, // A variable or function that could not be found has been renamed in Godot 4.
@@ -129,6 +130,7 @@ public:
 		WARN, // NARROWING_CONVERSION
 		WARN, // NARROWING_CONVERSION
 		WARN, // INT_AS_ENUM_WITHOUT_CAST
 		WARN, // INT_AS_ENUM_WITHOUT_CAST
 		WARN, // INT_AS_ENUM_WITHOUT_MATCH
 		WARN, // INT_AS_ENUM_WITHOUT_MATCH
+		WARN, // ENUM_VARIABLE_WITHOUT_DEFAULT
 		WARN, // EMPTY_FILE
 		WARN, // EMPTY_FILE
 		WARN, // DEPRECATED_KEYWORD
 		WARN, // DEPRECATED_KEYWORD
 		WARN, // RENAMED_IN_GODOT_4_HINT
 		WARN, // RENAMED_IN_GODOT_4_HINT

+ 9 - 0
modules/gdscript/tests/scripts/analyzer/warnings/enum_without_default_value.gd

@@ -0,0 +1,9 @@
+enum HasZero { A = 0, B = 1 }
+enum HasNoZero { A = 1, B = 2 }
+var has_zero: HasZero # No warning, because the default `0` is valid.
+var has_no_zero: HasNoZero # Warning, because there is no `0` in the enum.
+
+
+func test():
+	print(has_zero)
+	print(has_no_zero)

+ 7 - 0
modules/gdscript/tests/scripts/analyzer/warnings/enum_without_default_value.out

@@ -0,0 +1,7 @@
+GDTEST_OK
+>> WARNING
+>> Line: 4
+>> ENUM_VARIABLE_WITHOUT_DEFAULT
+>> The variable "has_no_zero" has an enum type and does not set an explicit default value. The default will be set to "0".
+0
+0

+ 1 - 0
modules/gdscript/tests/scripts/runtime/features/member_info.gd

@@ -23,6 +23,7 @@ var test_var_hard_int: int
 var test_var_hard_variant_type: Variant.Type
 var test_var_hard_variant_type: Variant.Type
 @export var test_var_hard_variant_type_exported: Variant.Type
 @export var test_var_hard_variant_type_exported: Variant.Type
 var test_var_hard_node_process_mode: Node.ProcessMode
 var test_var_hard_node_process_mode: Node.ProcessMode
+@warning_ignore("enum_variable_without_default")
 var test_var_hard_my_enum: MyEnum
 var test_var_hard_my_enum: MyEnum
 var test_var_hard_array: Array
 var test_var_hard_array: Array
 var test_var_hard_array_int: Array[int]
 var test_var_hard_array_int: Array[int]