Browse Source

Merge pull request #57591 from vnen/gdscript-enum-fixes

Rémi Verschelde 3 years ago
parent
commit
89eb6d372d
34 changed files with 352 additions and 57 deletions
  1. 2 0
      doc/classes/ProjectSettings.xml
  2. 88 45
      modules/gdscript/gdscript_analyzer.cpp
  3. 1 1
      modules/gdscript/gdscript_analyzer.h
  4. 13 3
      modules/gdscript/gdscript_compiler.cpp
  5. 1 1
      modules/gdscript/gdscript_editor.cpp
  6. 0 2
      modules/gdscript/gdscript_parser.cpp
  7. 1 4
      modules/gdscript/gdscript_parser.h
  8. 4 0
      modules/gdscript/gdscript_warning.cpp
  9. 1 0
      modules/gdscript/gdscript_warning.h
  10. 42 0
      modules/gdscript/tests/gdscript_test_runner.cpp
  11. 10 0
      modules/gdscript/tests/scripts/analyzer/errors/enum_class_var_assign_with_wrong_enum_type.gd
  12. 2 0
      modules/gdscript/tests/scripts/analyzer/errors/enum_class_var_assign_with_wrong_enum_type.out
  13. 8 0
      modules/gdscript/tests/scripts/analyzer/errors/enum_class_var_init_with_wrong_enum_type.gd
  14. 2 0
      modules/gdscript/tests/scripts/analyzer/errors/enum_class_var_init_with_wrong_enum_type.out
  15. 8 0
      modules/gdscript/tests/scripts/analyzer/errors/enum_local_var_assign_with_wrong_enum_type.gd
  16. 2 0
      modules/gdscript/tests/scripts/analyzer/errors/enum_local_var_assign_with_wrong_enum_type.out
  17. 6 0
      modules/gdscript/tests/scripts/analyzer/errors/enum_local_var_init_with_wrong_enum_type.gd
  18. 2 0
      modules/gdscript/tests/scripts/analyzer/errors/enum_local_var_init_with_wrong_enum_type.out
  19. 13 0
      modules/gdscript/tests/scripts/analyzer/features/enum_assign_enum_to_int_typed_var.gd
  20. 5 0
      modules/gdscript/tests/scripts/analyzer/features/enum_assign_enum_to_int_typed_var.out
  21. 13 0
      modules/gdscript/tests/scripts/analyzer/features/enum_assign_int_cast_to_same_enum.gd
  22. 5 0
      modules/gdscript/tests/scripts/analyzer/features/enum_assign_int_cast_to_same_enum.out
  23. 14 0
      modules/gdscript/tests/scripts/analyzer/features/enum_assign_other_enum_cast_to_same_enum.gd
  24. 5 0
      modules/gdscript/tests/scripts/analyzer/features/enum_assign_other_enum_cast_to_same_enum.out
  25. 13 0
      modules/gdscript/tests/scripts/analyzer/features/enum_assign_same_enum.gd
  26. 5 0
      modules/gdscript/tests/scripts/analyzer/features/enum_assign_same_enum.out
  27. 21 0
      modules/gdscript/tests/scripts/analyzer/features/enum_is_treated_as_int.gd
  28. 7 0
      modules/gdscript/tests/scripts/analyzer/features/enum_is_treated_as_int.out
  29. 13 0
      modules/gdscript/tests/scripts/analyzer/features/enum_type_is_treated_as_dictionary.gd
  30. 7 0
      modules/gdscript/tests/scripts/analyzer/features/enum_type_is_treated_as_dictionary.out
  31. 15 0
      modules/gdscript/tests/scripts/parser/warnings/enum_assign_int_without_casting.gd
  32. 21 0
      modules/gdscript/tests/scripts/parser/warnings/enum_assign_int_without_casting.out
  33. 1 0
      modules/gdscript/tests/scripts/runtime/errors/callable_call_after_free_object.gd
  34. 1 1
      modules/gdscript/tests/scripts/runtime/errors/callable_call_after_free_object.out

+ 2 - 0
doc/classes/ProjectSettings.xml

@@ -356,6 +356,8 @@
 		<member name="debug/gdscript/warnings/incompatible_ternary" type="bool" setter="" getter="" default="true">
 		<member name="debug/gdscript/warnings/incompatible_ternary" type="bool" setter="" getter="" default="true">
 			If [code]true[/code], enables warnings when a ternary operator may emit values with incompatible types.
 			If [code]true[/code], enables warnings when a ternary operator may emit values with incompatible types.
 		</member>
 		</member>
+		<member name="debug/gdscript/warnings/int_assigned_to_enum" type="bool" setter="" getter="" default="true">
+		</member>
 		<member name="debug/gdscript/warnings/integer_division" type="bool" setter="" getter="" default="true">
 		<member name="debug/gdscript/warnings/integer_division" type="bool" setter="" getter="" default="true">
 			If [code]true[/code], enables warnings when dividing an integer by another integer (the decimal part will be discarded).
 			If [code]true[/code], enables warnings when dividing an integer by another integer (the decimal part will be discarded).
 		</member>
 		</member>

+ 88 - 45
modules/gdscript/gdscript_analyzer.cpp

@@ -108,7 +108,7 @@ static GDScriptParser::DataType make_native_enum_type(const StringName &p_native
 	GDScriptParser::DataType type;
 	GDScriptParser::DataType type;
 	type.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT;
 	type.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT;
 	type.kind = GDScriptParser::DataType::ENUM;
 	type.kind = GDScriptParser::DataType::ENUM;
-	type.builtin_type = Variant::OBJECT;
+	type.builtin_type = Variant::INT;
 	type.is_constant = true;
 	type.is_constant = true;
 	type.is_meta_type = true;
 	type.is_meta_type = true;
 
 
@@ -650,9 +650,9 @@ void GDScriptAnalyzer::resolve_class_interface(GDScriptParser::ClassNode *p_clas
 					datatype = specified_type;
 					datatype = specified_type;
 
 
 					if (member.variable->initializer != nullptr) {
 					if (member.variable->initializer != nullptr) {
-						if (!is_type_compatible(datatype, member.variable->initializer->get_datatype(), true)) {
+						if (!is_type_compatible(datatype, member.variable->initializer->get_datatype(), true, member.variable->initializer)) {
 							// Try reverse test since it can be a masked subtype.
 							// Try reverse test since it can be a masked subtype.
-							if (!is_type_compatible(member.variable->initializer->get_datatype(), datatype, true)) {
+							if (!is_type_compatible(member.variable->initializer->get_datatype(), datatype, true, member.variable->initializer)) {
 								push_error(vformat(R"(Value of type "%s" cannot be assigned to a variable of type "%s".)", member.variable->initializer->get_datatype().to_string(), datatype.to_string()), member.variable->initializer);
 								push_error(vformat(R"(Value of type "%s" cannot be assigned to a variable of type "%s".)", member.variable->initializer->get_datatype().to_string(), datatype.to_string()), member.variable->initializer);
 							} else {
 							} else {
 								// TODO: Add warning.
 								// TODO: Add warning.
@@ -1400,9 +1400,9 @@ void GDScriptAnalyzer::resolve_variable(GDScriptParser::VariableNode *p_variable
 		type.is_meta_type = false;
 		type.is_meta_type = false;
 
 
 		if (p_variable->initializer != nullptr) {
 		if (p_variable->initializer != nullptr) {
-			if (!is_type_compatible(type, p_variable->initializer->get_datatype(), true)) {
+			if (!is_type_compatible(type, p_variable->initializer->get_datatype(), true, p_variable->initializer)) {
 				// Try reverse test since it can be a masked subtype.
 				// Try reverse test since it can be a masked subtype.
-				if (!is_type_compatible(p_variable->initializer->get_datatype(), type, true)) {
+				if (!is_type_compatible(p_variable->initializer->get_datatype(), type, true, p_variable->initializer)) {
 					push_error(vformat(R"(Value of type "%s" cannot be assigned to a variable of type "%s".)", p_variable->initializer->get_datatype().to_string(), type.to_string()), p_variable->initializer);
 					push_error(vformat(R"(Value of type "%s" cannot be assigned to a variable of type "%s".)", p_variable->initializer->get_datatype().to_string(), type.to_string()), p_variable->initializer);
 				} else {
 				} else {
 					// TODO: Add warning.
 					// TODO: Add warning.
@@ -1877,11 +1877,11 @@ void GDScriptAnalyzer::reduce_assignment(GDScriptParser::AssignmentNode *p_assig
 
 
 	if (!assignee_type.is_variant() && assigned_value_type.is_hard_type()) {
 	if (!assignee_type.is_variant() && assigned_value_type.is_hard_type()) {
 		if (compatible) {
 		if (compatible) {
-			compatible = is_type_compatible(assignee_type, op_type, true);
+			compatible = is_type_compatible(assignee_type, op_type, true, p_assignment->assigned_value);
 			if (!compatible) {
 			if (!compatible) {
 				if (assignee_type.is_hard_type()) {
 				if (assignee_type.is_hard_type()) {
 					// Try reverse test since it can be a masked subtype.
 					// Try reverse test since it can be a masked subtype.
-					if (!is_type_compatible(op_type, assignee_type, true)) {
+					if (!is_type_compatible(op_type, assignee_type, true, p_assignment->assigned_value)) {
 						push_error(vformat(R"(Cannot assign a value of type "%s" to a target of type "%s".)", assigned_value_type.to_string(), assignee_type.to_string()), p_assignment->assigned_value);
 						push_error(vformat(R"(Cannot assign a value of type "%s" to a target of type "%s".)", assigned_value_type.to_string(), assignee_type.to_string()), p_assignment->assigned_value);
 					} else {
 					} else {
 						// TODO: Add warning.
 						// TODO: Add warning.
@@ -2416,6 +2416,11 @@ void GDScriptAnalyzer::reduce_call(GDScriptParser::CallNode *p_call, bool p_is_a
 		}
 		}
 		validate_call_arg(par_types, default_arg_count, is_vararg, p_call);
 		validate_call_arg(par_types, default_arg_count, is_vararg, p_call);
 
 
+		if (base_type.kind == GDScriptParser::DataType::ENUM && base_type.is_meta_type) {
+			// Enum type is treated as a dictionary value for function calls.
+			base_type.is_meta_type = false;
+		}
+
 		if (is_self && parser->current_function != nullptr && parser->current_function->is_static && !is_static) {
 		if (is_self && parser->current_function != nullptr && parser->current_function->is_static && !is_static) {
 			push_error(vformat(R"*(Cannot call non-static function "%s()" from static function "%s()".)*", p_call->function_name, parser->current_function->identifier->name), p_call->callee);
 			push_error(vformat(R"*(Cannot call non-static function "%s()" from static function "%s()".)*", p_call->function_name, parser->current_function->identifier->name), p_call->callee);
 		} else if (!is_self && base_type.is_meta_type && !is_static) {
 		} else if (!is_self && base_type.is_meta_type && !is_static) {
@@ -2474,17 +2479,24 @@ void GDScriptAnalyzer::reduce_cast(GDScriptParser::CastNode *p_cast) {
 	GDScriptParser::DataType cast_type = resolve_datatype(p_cast->cast_type);
 	GDScriptParser::DataType cast_type = resolve_datatype(p_cast->cast_type);
 
 
 	if (!cast_type.is_set()) {
 	if (!cast_type.is_set()) {
+		mark_node_unsafe(p_cast);
 		return;
 		return;
 	}
 	}
 
 
-	cast_type.is_meta_type = false; // The casted value won't be a type name.
+	cast_type = type_from_metatype(cast_type); // The casted value won't be a type name.
 	p_cast->set_datatype(cast_type);
 	p_cast->set_datatype(cast_type);
 
 
 	if (!cast_type.is_variant()) {
 	if (!cast_type.is_variant()) {
 		GDScriptParser::DataType op_type = p_cast->operand->get_datatype();
 		GDScriptParser::DataType op_type = p_cast->operand->get_datatype();
 		if (!op_type.is_variant()) {
 		if (!op_type.is_variant()) {
 			bool valid = false;
 			bool valid = false;
-			if (op_type.kind == GDScriptParser::DataType::BUILTIN && cast_type.kind == GDScriptParser::DataType::BUILTIN) {
+			if (op_type.kind == GDScriptParser::DataType::ENUM && cast_type.kind == GDScriptParser::DataType::ENUM) {
+				// Enum types are compatible between each other, so it's a safe cast.
+				valid = true;
+			} else if (op_type.kind == GDScriptParser::DataType::BUILTIN && op_type.builtin_type == Variant::INT && cast_type.kind == GDScriptParser::DataType::ENUM) {
+				// Convertint int to enum is always valid.
+				valid = true;
+			} else if (op_type.kind == GDScriptParser::DataType::BUILTIN && cast_type.kind == GDScriptParser::DataType::BUILTIN) {
 				valid = Variant::can_convert(op_type.builtin_type, cast_type.builtin_type);
 				valid = Variant::can_convert(op_type.builtin_type, cast_type.builtin_type);
 			} else if (op_type.kind != GDScriptParser::DataType::BUILTIN && cast_type.kind != GDScriptParser::DataType::BUILTIN) {
 			} else if (op_type.kind != GDScriptParser::DataType::BUILTIN && cast_type.kind != GDScriptParser::DataType::BUILTIN) {
 				valid = is_type_compatible(cast_type, op_type) || is_type_compatible(op_type, cast_type);
 				valid = is_type_compatible(cast_type, op_type) || is_type_compatible(op_type, cast_type);
@@ -2586,6 +2598,34 @@ void GDScriptAnalyzer::reduce_identifier_from_base(GDScriptParser::IdentifierNod
 
 
 	const StringName &name = p_identifier->name;
 	const StringName &name = p_identifier->name;
 
 
+	if (base.kind == GDScriptParser::DataType::ENUM) {
+		if (base.is_meta_type) {
+			if (base.enum_values.has(name)) {
+				p_identifier->is_constant = true;
+				p_identifier->reduced_value = base.enum_values[name];
+
+				GDScriptParser::DataType result;
+				result.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT;
+				result.kind = GDScriptParser::DataType::ENUM;
+				result.is_constant = true;
+				result.builtin_type = Variant::INT;
+				result.native_type = base.native_type;
+				result.enum_type = base.enum_type;
+				p_identifier->set_datatype(result);
+				return;
+			} else {
+				// Consider as a Dictionary, so it can be anything.
+				// This will be evaluated in the next if block.
+				base.kind = GDScriptParser::DataType::BUILTIN;
+				base.builtin_type = Variant::DICTIONARY;
+				base.is_meta_type = false;
+			}
+		} else {
+			push_error(R"(Cannot get property from enum value.)", p_identifier);
+			return;
+		}
+	}
+
 	if (base.kind == GDScriptParser::DataType::BUILTIN) {
 	if (base.kind == GDScriptParser::DataType::BUILTIN) {
 		if (base.is_meta_type) {
 		if (base.is_meta_type) {
 			bool valid = true;
 			bool valid = true;
@@ -2632,32 +2672,6 @@ void GDScriptAnalyzer::reduce_identifier_from_base(GDScriptParser::IdentifierNod
 		return;
 		return;
 	}
 	}
 
 
-	if (base.kind == GDScriptParser::DataType::ENUM) {
-		if (base.is_meta_type) {
-			if (base.enum_values.has(name)) {
-				p_identifier->is_constant = true;
-				p_identifier->reduced_value = base.enum_values[name];
-
-				GDScriptParser::DataType result;
-				result.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT;
-				result.kind = GDScriptParser::DataType::ENUM_VALUE;
-				result.is_constant = true;
-				result.builtin_type = Variant::INT;
-				result.native_type = base.native_type;
-				result.enum_type = name;
-				p_identifier->set_datatype(result);
-			} else {
-				// Consider as a Dictionary
-				GDScriptParser::DataType dummy;
-				dummy.kind = GDScriptParser::DataType::VARIANT;
-				p_identifier->set_datatype(dummy);
-			}
-		} else {
-			push_error(R"(Cannot get property from enum value.)", p_identifier);
-		}
-		return;
-	}
-
 	GDScriptParser::ClassNode *base_class = base.class_type;
 	GDScriptParser::ClassNode *base_class = base.class_type;
 
 
 	// TODO: Switch current class/function/suite here to avoid misrepresenting identifiers (in recursive reduce calls).
 	// TODO: Switch current class/function/suite here to avoid misrepresenting identifiers (in recursive reduce calls).
@@ -2793,7 +2807,7 @@ void GDScriptAnalyzer::reduce_identifier(GDScriptParser::IdentifierNode *p_ident
 			if (element.identifier->name == p_identifier->name) {
 			if (element.identifier->name == p_identifier->name) {
 				GDScriptParser::DataType type;
 				GDScriptParser::DataType type;
 				type.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT;
 				type.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT;
-				type.kind = element.parent_enum->identifier ? GDScriptParser::DataType::ENUM_VALUE : GDScriptParser::DataType::BUILTIN;
+				type.kind = element.parent_enum->identifier ? GDScriptParser::DataType::ENUM : GDScriptParser::DataType::BUILTIN;
 				type.builtin_type = Variant::INT;
 				type.builtin_type = Variant::INT;
 				type.is_constant = true;
 				type.is_constant = true;
 				if (element.parent_enum->identifier) {
 				if (element.parent_enum->identifier) {
@@ -3493,6 +3507,9 @@ GDScriptParser::DataType GDScriptAnalyzer::type_from_metatype(const GDScriptPars
 	GDScriptParser::DataType result = p_meta_type;
 	GDScriptParser::DataType result = p_meta_type;
 	result.is_meta_type = false;
 	result.is_meta_type = false;
 	result.is_constant = false;
 	result.is_constant = false;
+	if (p_meta_type.kind == GDScriptParser::DataType::ENUM) {
+		result.builtin_type = Variant::INT;
+	}
 	return result;
 	return result;
 }
 }
 
 
@@ -3549,6 +3566,18 @@ bool GDScriptAnalyzer::get_function_signature(GDScriptParser::CallNode *p_source
 	r_default_arg_count = 0;
 	r_default_arg_count = 0;
 	StringName function_name = p_function;
 	StringName function_name = p_function;
 
 
+	if (p_base_type.kind == GDScriptParser::DataType::ENUM) {
+		if (p_base_type.is_meta_type) {
+			// Enum type can be treated as a dictionary value.
+			p_base_type.kind = GDScriptParser::DataType::BUILTIN;
+			p_base_type.builtin_type = Variant::DICTIONARY;
+			p_base_type.is_meta_type = false;
+		} else {
+			push_error("Cannot call function on enum value.", p_source);
+			return false;
+		}
+	}
+
 	if (p_base_type.kind == GDScriptParser::DataType::BUILTIN) {
 	if (p_base_type.kind == GDScriptParser::DataType::BUILTIN) {
 		// Construct a base type to get methods.
 		// Construct a base type to get methods.
 		Callable::CallError err;
 		Callable::CallError err;
@@ -3799,6 +3828,22 @@ GDScriptParser::DataType GDScriptAnalyzer::get_operation_type(Variant::Operator
 
 
 	Variant::Type a_type = p_a.builtin_type;
 	Variant::Type a_type = p_a.builtin_type;
 	Variant::Type b_type = p_b.builtin_type;
 	Variant::Type b_type = p_b.builtin_type;
+
+	if (p_a.kind == GDScriptParser::DataType::ENUM) {
+		if (p_a.is_meta_type) {
+			a_type = Variant::DICTIONARY;
+		} else {
+			a_type = Variant::INT;
+		}
+	}
+	if (p_b.kind == GDScriptParser::DataType::ENUM) {
+		if (p_b.is_meta_type) {
+			b_type = Variant::DICTIONARY;
+		} else {
+			b_type = Variant::INT;
+		}
+	}
+
 	Variant::ValidatedOperatorEvaluator op_eval = Variant::get_validated_operator_evaluator(p_operation, a_type, b_type);
 	Variant::ValidatedOperatorEvaluator op_eval = Variant::get_validated_operator_evaluator(p_operation, a_type, b_type);
 
 
 	bool hard_operation = p_a.is_hard_type() && p_b.is_hard_type();
 	bool hard_operation = p_a.is_hard_type() && p_b.is_hard_type();
@@ -3828,7 +3873,7 @@ GDScriptParser::DataType GDScriptAnalyzer::get_operation_type(Variant::Operator
 }
 }
 
 
 // TODO: Add safe/unsafe return variable (for variant cases)
 // TODO: Add safe/unsafe return variable (for variant cases)
-bool GDScriptAnalyzer::is_type_compatible(const GDScriptParser::DataType &p_target, const GDScriptParser::DataType &p_source, bool p_allow_implicit_conversion) const {
+bool GDScriptAnalyzer::is_type_compatible(const GDScriptParser::DataType &p_target, const GDScriptParser::DataType &p_source, bool p_allow_implicit_conversion, const GDScriptParser::Node *p_source_node) {
 	// These return "true" so it doesn't affect users negatively.
 	// These return "true" so it doesn't affect users negatively.
 	ERR_FAIL_COND_V_MSG(!p_target.is_set(), true, "Parser bug (please report): Trying to check compatibility of unset target type");
 	ERR_FAIL_COND_V_MSG(!p_target.is_set(), true, "Parser bug (please report): Trying to check compatibility of unset target type");
 	ERR_FAIL_COND_V_MSG(!p_source.is_set(), true, "Parser bug (please report): Trying to check compatibility of unset value type");
 	ERR_FAIL_COND_V_MSG(!p_source.is_set(), true, "Parser bug (please report): Trying to check compatibility of unset value type");
@@ -3848,7 +3893,7 @@ bool GDScriptAnalyzer::is_type_compatible(const GDScriptParser::DataType &p_targ
 		if (!valid && p_allow_implicit_conversion) {
 		if (!valid && p_allow_implicit_conversion) {
 			valid = Variant::can_convert_strict(p_source.builtin_type, p_target.builtin_type);
 			valid = Variant::can_convert_strict(p_source.builtin_type, p_target.builtin_type);
 		}
 		}
-		if (!valid && p_target.builtin_type == Variant::INT && p_source.kind == GDScriptParser::DataType::ENUM_VALUE) {
+		if (!valid && p_target.builtin_type == Variant::INT && p_source.kind == GDScriptParser::DataType::ENUM && !p_source.is_meta_type) {
 			// Enum value is also integer.
 			// Enum value is also integer.
 			valid = true;
 			valid = true;
 		}
 		}
@@ -3869,6 +3914,11 @@ bool GDScriptAnalyzer::is_type_compatible(const GDScriptParser::DataType &p_targ
 
 
 	if (p_target.kind == GDScriptParser::DataType::ENUM) {
 	if (p_target.kind == GDScriptParser::DataType::ENUM) {
 		if (p_source.kind == GDScriptParser::DataType::BUILTIN && p_source.builtin_type == Variant::INT) {
 		if (p_source.kind == GDScriptParser::DataType::BUILTIN && p_source.builtin_type == Variant::INT) {
+#ifdef DEBUG_ENABLED
+			if (p_source_node) {
+				parser->push_warning(p_source_node, GDScriptWarning::INT_ASSIGNED_TO_ENUM);
+			}
+#endif
 			return true;
 			return true;
 		}
 		}
 		if (p_source.kind == GDScriptParser::DataType::ENUM) {
 		if (p_source.kind == GDScriptParser::DataType::ENUM) {
@@ -3876,11 +3926,6 @@ bool GDScriptAnalyzer::is_type_compatible(const GDScriptParser::DataType &p_targ
 				return true;
 				return true;
 			}
 			}
 		}
 		}
-		if (p_source.kind == GDScriptParser::DataType::ENUM_VALUE) {
-			if (p_source.native_type == p_target.native_type && p_target.enum_values.has(p_source.enum_type)) {
-				return true;
-			}
-		}
 		return false;
 		return false;
 	}
 	}
 
 
@@ -3935,7 +3980,6 @@ bool GDScriptAnalyzer::is_type_compatible(const GDScriptParser::DataType &p_targ
 		case GDScriptParser::DataType::VARIANT:
 		case GDScriptParser::DataType::VARIANT:
 		case GDScriptParser::DataType::BUILTIN:
 		case GDScriptParser::DataType::BUILTIN:
 		case GDScriptParser::DataType::ENUM:
 		case GDScriptParser::DataType::ENUM:
-		case GDScriptParser::DataType::ENUM_VALUE:
 		case GDScriptParser::DataType::UNRESOLVED:
 		case GDScriptParser::DataType::UNRESOLVED:
 			break; // Already solved before.
 			break; // Already solved before.
 	}
 	}
@@ -3972,7 +4016,6 @@ bool GDScriptAnalyzer::is_type_compatible(const GDScriptParser::DataType &p_targ
 		case GDScriptParser::DataType::VARIANT:
 		case GDScriptParser::DataType::VARIANT:
 		case GDScriptParser::DataType::BUILTIN:
 		case GDScriptParser::DataType::BUILTIN:
 		case GDScriptParser::DataType::ENUM:
 		case GDScriptParser::DataType::ENUM:
-		case GDScriptParser::DataType::ENUM_VALUE:
 		case GDScriptParser::DataType::UNRESOLVED:
 		case GDScriptParser::DataType::UNRESOLVED:
 			break; // Already solved before.
 			break; // Already solved before.
 	}
 	}

+ 1 - 1
modules/gdscript/gdscript_analyzer.h

@@ -112,7 +112,7 @@ class GDScriptAnalyzer {
 	GDScriptParser::DataType get_operation_type(Variant::Operator p_operation, const GDScriptParser::DataType &p_a, const GDScriptParser::DataType &p_b, bool &r_valid, const GDScriptParser::Node *p_source);
 	GDScriptParser::DataType get_operation_type(Variant::Operator p_operation, const GDScriptParser::DataType &p_a, const GDScriptParser::DataType &p_b, bool &r_valid, const GDScriptParser::Node *p_source);
 	GDScriptParser::DataType get_operation_type(Variant::Operator p_operation, const GDScriptParser::DataType &p_a, bool &r_valid, const GDScriptParser::Node *p_source);
 	GDScriptParser::DataType get_operation_type(Variant::Operator p_operation, const GDScriptParser::DataType &p_a, bool &r_valid, const GDScriptParser::Node *p_source);
 	void update_array_literal_element_type(const GDScriptParser::DataType &p_base_type, GDScriptParser::ArrayNode *p_array_literal);
 	void update_array_literal_element_type(const GDScriptParser::DataType &p_base_type, GDScriptParser::ArrayNode *p_array_literal);
-	bool is_type_compatible(const GDScriptParser::DataType &p_target, const GDScriptParser::DataType &p_source, bool p_allow_implicit_conversion = false) const;
+	bool is_type_compatible(const GDScriptParser::DataType &p_target, const GDScriptParser::DataType &p_source, bool p_allow_implicit_conversion = false, const GDScriptParser::Node *p_source_node = nullptr);
 	void push_error(const String &p_message, const GDScriptParser::Node *p_origin);
 	void push_error(const String &p_message, const GDScriptParser::Node *p_origin);
 	void mark_node_unsafe(const GDScriptParser::Node *p_node);
 	void mark_node_unsafe(const GDScriptParser::Node *p_node);
 	bool class_exists(const StringName &p_class) const;
 	bool class_exists(const StringName &p_class) const;

+ 13 - 3
modules/gdscript/gdscript_compiler.cpp

@@ -141,10 +141,13 @@ GDScriptDataType GDScriptCompiler::_gdtype_from_datatype(const GDScriptParser::D
 			}
 			}
 		} break;
 		} break;
 		case GDScriptParser::DataType::ENUM:
 		case GDScriptParser::DataType::ENUM:
-		case GDScriptParser::DataType::ENUM_VALUE:
 			result.has_type = true;
 			result.has_type = true;
 			result.kind = GDScriptDataType::BUILTIN;
 			result.kind = GDScriptDataType::BUILTIN;
-			result.builtin_type = Variant::INT;
+			if (p_datatype.is_meta_type) {
+				result.builtin_type = Variant::DICTIONARY;
+			} else {
+				result.builtin_type = Variant::INT;
+			}
 			break;
 			break;
 		case GDScriptParser::DataType::UNRESOLVED: {
 		case GDScriptParser::DataType::UNRESOLVED: {
 			ERR_PRINT("Parser bug: converting unresolved type.");
 			ERR_PRINT("Parser bug: converting unresolved type.");
@@ -469,7 +472,14 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &code
 		} break;
 		} break;
 		case GDScriptParser::Node::CAST: {
 		case GDScriptParser::Node::CAST: {
 			const GDScriptParser::CastNode *cn = static_cast<const GDScriptParser::CastNode *>(p_expression);
 			const GDScriptParser::CastNode *cn = static_cast<const GDScriptParser::CastNode *>(p_expression);
-			GDScriptDataType cast_type = _gdtype_from_datatype(cn->cast_type->get_datatype());
+			GDScriptParser::DataType og_cast_type = cn->cast_type->get_datatype();
+			GDScriptDataType cast_type = _gdtype_from_datatype(og_cast_type);
+
+			if (og_cast_type.kind == GDScriptParser::DataType::ENUM) {
+				// Enum types are usually treated as dictionaries, but in this case we want to cast to an integer.
+				cast_type.kind = GDScriptDataType::BUILTIN;
+				cast_type.builtin_type = Variant::INT;
+			}
 
 
 			// Create temporary for result first since it will be deleted last.
 			// Create temporary for result first since it will be deleted last.
 			GDScriptCodeGenerator::Address result = codegen.add_temporary(cast_type);
 			GDScriptCodeGenerator::Address result = codegen.add_temporary(cast_type);

+ 1 - 1
modules/gdscript/gdscript_editor.cpp

@@ -610,7 +610,7 @@ static String _make_arguments_hint(const GDScriptParser::FunctionNode *p_functio
 				case GDScriptParser::Node::SUBSCRIPT: {
 				case GDScriptParser::Node::SUBSCRIPT: {
 					const GDScriptParser::SubscriptNode *sub = static_cast<const GDScriptParser::SubscriptNode *>(par->default_value);
 					const GDScriptParser::SubscriptNode *sub = static_cast<const GDScriptParser::SubscriptNode *>(par->default_value);
 					if (sub->is_constant) {
 					if (sub->is_constant) {
-						if (sub->datatype.kind == GDScriptParser::DataType::ENUM_VALUE) {
+						if (sub->datatype.kind == GDScriptParser::DataType::ENUM) {
 							def_val = sub->get_datatype().to_string();
 							def_val = sub->get_datatype().to_string();
 						} else if (sub->reduced) {
 						} else if (sub->reduced) {
 							const Variant::Type vt = sub->reduced_value.get_type();
 							const Variant::Type vt = sub->reduced_value.get_type();

+ 0 - 2
modules/gdscript/gdscript_parser.cpp

@@ -3740,8 +3740,6 @@ String GDScriptParser::DataType::to_string() const {
 		}
 		}
 		case ENUM:
 		case ENUM:
 			return enum_type.operator String() + " (enum)";
 			return enum_type.operator String() + " (enum)";
-		case ENUM_VALUE:
-			return enum_type.operator String() + " (enum value)";
 		case UNRESOLVED:
 		case UNRESOLVED:
 			return "<unresolved type>";
 			return "<unresolved type>";
 	}
 	}

+ 1 - 4
modules/gdscript/gdscript_parser.h

@@ -106,8 +106,7 @@ public:
 			NATIVE,
 			NATIVE,
 			SCRIPT,
 			SCRIPT,
 			CLASS, // GDScript.
 			CLASS, // GDScript.
-			ENUM, // Full enumeration.
-			ENUM_VALUE, // Value from enumeration.
+			ENUM, // Enumeration.
 			VARIANT, // Can be any type.
 			VARIANT, // Can be any type.
 			UNRESOLVED,
 			UNRESOLVED,
 		};
 		};
@@ -185,8 +184,6 @@ public:
 					return builtin_type == p_other.builtin_type;
 					return builtin_type == p_other.builtin_type;
 				case NATIVE:
 				case NATIVE:
 				case ENUM:
 				case ENUM:
-					return native_type == p_other.native_type;
-				case ENUM_VALUE:
 					return native_type == p_other.native_type && enum_type == p_other.enum_type;
 					return native_type == p_other.native_type && enum_type == p_other.enum_type;
 				case SCRIPT:
 				case SCRIPT:
 					return script_type == p_other.script_type;
 					return script_type == p_other.script_type;

+ 4 - 0
modules/gdscript/gdscript_warning.cpp

@@ -152,6 +152,9 @@ String GDScriptWarning::get_message() const {
 			CHECK_SYMBOLS(3);
 			CHECK_SYMBOLS(3);
 			return vformat(R"(The %s '%s' has the same name as a %s.)", symbols[0], symbols[1], symbols[2]);
 			return vformat(R"(The %s '%s' has the same name as a %s.)", symbols[0], symbols[1], symbols[2]);
 		}
 		}
+		case INT_ASSIGNED_TO_ENUM: {
+			return "Integer used when an enum value is expected. If this is intended cast the integer to the enum type.";
+		}
 		case WARNING_MAX:
 		case WARNING_MAX:
 			break; // Can't happen, but silences warning
 			break; // Can't happen, but silences warning
 	}
 	}
@@ -199,6 +202,7 @@ String GDScriptWarning::get_name_from_code(Code p_code) {
 		"REDUNDANT_AWAIT",
 		"REDUNDANT_AWAIT",
 		"EMPTY_FILE",
 		"EMPTY_FILE",
 		"SHADOWED_GLOBAL_IDENTIFIER",
 		"SHADOWED_GLOBAL_IDENTIFIER",
+		"INT_ASSIGNED_TO_ENUM",
 	};
 	};
 
 
 	static_assert((sizeof(names) / sizeof(*names)) == WARNING_MAX, "Amount of warning types don't match the amount of warning names.");
 	static_assert((sizeof(names) / sizeof(*names)) == WARNING_MAX, "Amount of warning types don't match the amount of warning names.");

+ 1 - 0
modules/gdscript/gdscript_warning.h

@@ -70,6 +70,7 @@ public:
 		REDUNDANT_AWAIT, // await is used but expression is synchronous (not a signal nor a coroutine).
 		REDUNDANT_AWAIT, // await is used but expression is synchronous (not a signal nor a coroutine).
 		EMPTY_FILE, // A script file is empty.
 		EMPTY_FILE, // A script file is empty.
 		SHADOWED_GLOBAL_IDENTIFIER, // A global class or function has the same name as variable.
 		SHADOWED_GLOBAL_IDENTIFIER, // A global class or function has the same name as variable.
+		INT_ASSIGNED_TO_ENUM, // An integer value was assigned to an enum-typed variable without casting.
 		WARNING_MAX,
 		WARNING_MAX,
 	};
 	};
 
 

+ 42 - 0
modules/gdscript/tests/gdscript_test_runner.cpp

@@ -134,12 +134,14 @@ GDScriptTestRunner::GDScriptTestRunner(const String &p_source_dir, bool p_init_l
 	if (do_init_languages) {
 	if (do_init_languages) {
 		init_language(p_source_dir);
 		init_language(p_source_dir);
 	}
 	}
+#ifdef DEBUG_ENABLED
 	// Enable all warnings for GDScript, so we can test them.
 	// Enable all warnings for GDScript, so we can test them.
 	ProjectSettings::get_singleton()->set_setting("debug/gdscript/warnings/enable", true);
 	ProjectSettings::get_singleton()->set_setting("debug/gdscript/warnings/enable", true);
 	for (int i = 0; i < (int)GDScriptWarning::WARNING_MAX; i++) {
 	for (int i = 0; i < (int)GDScriptWarning::WARNING_MAX; i++) {
 		String warning = GDScriptWarning::get_name_from_code((GDScriptWarning::Code)i).to_lower();
 		String warning = GDScriptWarning::get_name_from_code((GDScriptWarning::Code)i).to_lower();
 		ProjectSettings::get_singleton()->set_setting("debug/gdscript/warnings/" + warning, true);
 		ProjectSettings::get_singleton()->set_setting("debug/gdscript/warnings/" + warning, true);
 	}
 	}
+#endif
 
 
 	// Enable printing to show results
 	// Enable printing to show results
 	_print_line_enabled = true;
 	_print_line_enabled = true;
@@ -153,6 +155,21 @@ GDScriptTestRunner::~GDScriptTestRunner() {
 	}
 	}
 }
 }
 
 
+#ifndef DEBUG_ENABLED
+static String strip_warnings(const String &p_expected) {
+	// On release builds we don't have warnings. Here we remove them from the output before comparison
+	// so it doesn't fail just because of difference in warnings.
+	String expected_no_warnings;
+	for (String line : p_expected.split("\n")) {
+		if (line.begins_with(">> ")) {
+			continue;
+		}
+		expected_no_warnings += line + "\n";
+	}
+	return expected_no_warnings.strip_edges() + "\n";
+}
+#endif
+
 int GDScriptTestRunner::run_tests() {
 int GDScriptTestRunner::run_tests() {
 	if (!make_tests()) {
 	if (!make_tests()) {
 		FAIL("An error occurred while making the tests.");
 		FAIL("An error occurred while making the tests.");
@@ -170,6 +187,9 @@ int GDScriptTestRunner::run_tests() {
 		GDScriptTest::TestResult result = test.run_test();
 		GDScriptTest::TestResult result = test.run_test();
 
 
 		String expected = FileAccess::get_file_as_string(test.get_output_file());
 		String expected = FileAccess::get_file_as_string(test.get_output_file());
+#ifndef DEBUG_ENABLED
+		expected = strip_warnings(expected);
+#endif
 		INFO(test.get_source_file());
 		INFO(test.get_source_file());
 		if (!result.passed) {
 		if (!result.passed) {
 			INFO(expected);
 			INFO(expected);
@@ -233,6 +253,22 @@ bool GDScriptTestRunner::make_tests_for_dir(const String &p_dir) {
 			}
 			}
 		} else {
 		} else {
 			if (next.get_extension().to_lower() == "gd") {
 			if (next.get_extension().to_lower() == "gd") {
+#ifndef DEBUG_ENABLED
+				// On release builds, skip tests marked as debug only.
+				Error open_err = OK;
+				FileAccessRef script_file(FileAccess::open(current_dir.plus_file(next), FileAccess::READ, &open_err));
+				if (open_err != OK) {
+					ERR_PRINT(vformat(R"(Couldn't open test file "%s".)", next));
+					next = dir->get_next();
+					continue;
+				} else {
+					if (script_file->get_line() == "#debug-only") {
+						next = dir->get_next();
+						continue;
+					}
+				}
+#endif
+
 				String out_file = next.get_basename() + ".out";
 				String out_file = next.get_basename() + ".out";
 				if (!is_generating && !dir->file_exists(out_file)) {
 				if (!is_generating && !dir->file_exists(out_file)) {
 					ERR_FAIL_V_MSG(false, "Could not find output file for " + next);
 					ERR_FAIL_V_MSG(false, "Could not find output file for " + next);
@@ -387,6 +423,10 @@ bool GDScriptTest::check_output(const String &p_output) const {
 	String got = p_output.strip_edges(); // TODO: may be hacky.
 	String got = p_output.strip_edges(); // TODO: may be hacky.
 	got += "\n"; // Make sure to insert newline for CI static checks.
 	got += "\n"; // Make sure to insert newline for CI static checks.
 
 
+#ifndef DEBUG_ENABLED
+	expected = strip_warnings(expected);
+#endif
+
 	return got == expected;
 	return got == expected;
 }
 }
 
 
@@ -469,6 +509,7 @@ GDScriptTest::TestResult GDScriptTest::execute_test_code(bool p_is_generating) {
 		return result;
 		return result;
 	}
 	}
 
 
+#ifdef DEBUG_ENABLED
 	StringBuilder warning_string;
 	StringBuilder warning_string;
 	for (const GDScriptWarning &E : parser.get_warnings()) {
 	for (const GDScriptWarning &E : parser.get_warnings()) {
 		const GDScriptWarning warning = E;
 		const GDScriptWarning warning = E;
@@ -482,6 +523,7 @@ GDScriptTest::TestResult GDScriptTest::execute_test_code(bool p_is_generating) {
 		warning_string.append("\n");
 		warning_string.append("\n");
 	}
 	}
 	result.output += warning_string.as_string();
 	result.output += warning_string.as_string();
+#endif
 
 
 	// Test compiling.
 	// Test compiling.
 	GDScriptCompiler compiler;
 	GDScriptCompiler compiler;

+ 10 - 0
modules/gdscript/tests/scripts/analyzer/errors/enum_class_var_assign_with_wrong_enum_type.gd

@@ -0,0 +1,10 @@
+enum MyEnum { ENUM_VALUE_1, ENUM_VALUE_2 }
+enum MyOtherEnum { OTHER_ENUM_VALUE_1, OTHER_ENUM_VALUE_2 }
+
+# Different enum types can't be assigned without casting.
+var class_var: MyEnum = MyEnum.ENUM_VALUE_1
+
+func test():
+	print(class_var)
+	class_var = MyOtherEnum.OTHER_ENUM_VALUE_2
+	print(class_var)

+ 2 - 0
modules/gdscript/tests/scripts/analyzer/errors/enum_class_var_assign_with_wrong_enum_type.out

@@ -0,0 +1,2 @@
+GDTEST_ANALYZER_ERROR
+Cannot assign a value of type "MyOtherEnum (enum)" to a target of type "MyEnum (enum)".

+ 8 - 0
modules/gdscript/tests/scripts/analyzer/errors/enum_class_var_init_with_wrong_enum_type.gd

@@ -0,0 +1,8 @@
+enum MyEnum { ENUM_VALUE_1, ENUM_VALUE_2 }
+enum MyOtherEnum { OTHER_ENUM_VALUE_1, OTHER_ENUM_VALUE_2 }
+
+# Different enum types can't be assigned without casting.
+var class_var: MyEnum = MyOtherEnum.OTHER_ENUM_VALUE_1
+
+func test():
+	print(class_var)

+ 2 - 0
modules/gdscript/tests/scripts/analyzer/errors/enum_class_var_init_with_wrong_enum_type.out

@@ -0,0 +1,2 @@
+GDTEST_ANALYZER_ERROR
+Value of type "MyOtherEnum (enum)" cannot be assigned to a variable of type "MyEnum (enum)".

+ 8 - 0
modules/gdscript/tests/scripts/analyzer/errors/enum_local_var_assign_with_wrong_enum_type.gd

@@ -0,0 +1,8 @@
+enum MyEnum { ENUM_VALUE_1, ENUM_VALUE_2 }
+enum MyOtherEnum { OTHER_ENUM_VALUE_1, OTHER_ENUM_VALUE_2 }
+
+func test():
+	var local_var: MyEnum = MyEnum.ENUM_VALUE_1
+	print(local_var)
+	local_var = MyOtherEnum.OTHER_ENUM_VALUE_2
+	print(local_var)

+ 2 - 0
modules/gdscript/tests/scripts/analyzer/errors/enum_local_var_assign_with_wrong_enum_type.out

@@ -0,0 +1,2 @@
+GDTEST_ANALYZER_ERROR
+Cannot assign a value of type "MyOtherEnum (enum)" to a target of type "MyEnum (enum)".

+ 6 - 0
modules/gdscript/tests/scripts/analyzer/errors/enum_local_var_init_with_wrong_enum_type.gd

@@ -0,0 +1,6 @@
+enum MyEnum { ENUM_VALUE_1, ENUM_VALUE_2 }
+enum MyOtherEnum { OTHER_ENUM_VALUE_1, OTHER_ENUM_VALUE_2 }
+
+func test():
+	var local_var: MyEnum = MyOtherEnum.OTHER_ENUM_VALUE_1
+	print(local_var)

+ 2 - 0
modules/gdscript/tests/scripts/analyzer/errors/enum_local_var_init_with_wrong_enum_type.out

@@ -0,0 +1,2 @@
+GDTEST_ANALYZER_ERROR
+Value of type "MyOtherEnum (enum)" cannot be assigned to a variable of type "MyEnum (enum)".

+ 13 - 0
modules/gdscript/tests/scripts/analyzer/features/enum_assign_enum_to_int_typed_var.gd

@@ -0,0 +1,13 @@
+enum MyEnum { ENUM_VALUE_1, ENUM_VALUE_2 }
+
+var class_var: int = MyEnum.ENUM_VALUE_1
+
+func test():
+	print(class_var)
+	class_var = MyEnum.ENUM_VALUE_2
+	print(class_var)
+
+	var local_var: int = MyEnum.ENUM_VALUE_1
+	print(local_var)
+	local_var = MyEnum.ENUM_VALUE_2
+	print(local_var)

+ 5 - 0
modules/gdscript/tests/scripts/analyzer/features/enum_assign_enum_to_int_typed_var.out

@@ -0,0 +1,5 @@
+GDTEST_OK
+0
+1
+0
+1

+ 13 - 0
modules/gdscript/tests/scripts/analyzer/features/enum_assign_int_cast_to_same_enum.gd

@@ -0,0 +1,13 @@
+enum MyEnum { ENUM_VALUE_1, ENUM_VALUE_2 }
+
+var class_var: MyEnum = 0 as MyEnum
+
+func test():
+	print(class_var)
+	class_var = 1 as MyEnum
+	print(class_var)
+
+	var local_var: MyEnum = 0 as MyEnum
+	print(local_var)
+	local_var = 1 as MyEnum
+	print(local_var)

+ 5 - 0
modules/gdscript/tests/scripts/analyzer/features/enum_assign_int_cast_to_same_enum.out

@@ -0,0 +1,5 @@
+GDTEST_OK
+0
+1
+0
+1

+ 14 - 0
modules/gdscript/tests/scripts/analyzer/features/enum_assign_other_enum_cast_to_same_enum.gd

@@ -0,0 +1,14 @@
+enum MyEnum { ENUM_VALUE_1, ENUM_VALUE_2 }
+enum MyOtherEnum { OTHER_ENUM_VALUE_1, OTHER_ENUM_VALUE_2 }
+
+var class_var: MyEnum = MyOtherEnum.OTHER_ENUM_VALUE_1 as MyEnum
+
+func test():
+	print(class_var)
+	class_var = MyOtherEnum.OTHER_ENUM_VALUE_2 as MyEnum
+	print(class_var)
+
+	var local_var: MyEnum = MyOtherEnum.OTHER_ENUM_VALUE_1 as MyEnum
+	print(local_var)
+	local_var = MyOtherEnum.OTHER_ENUM_VALUE_2 as MyEnum
+	print(local_var)

+ 5 - 0
modules/gdscript/tests/scripts/analyzer/features/enum_assign_other_enum_cast_to_same_enum.out

@@ -0,0 +1,5 @@
+GDTEST_OK
+0
+1
+0
+1

+ 13 - 0
modules/gdscript/tests/scripts/analyzer/features/enum_assign_same_enum.gd

@@ -0,0 +1,13 @@
+enum MyEnum { ENUM_VALUE_1, ENUM_VALUE_2 }
+
+var class_var: MyEnum = MyEnum.ENUM_VALUE_1
+
+func test():
+	print(class_var)
+	class_var = MyEnum.ENUM_VALUE_2
+	print(class_var)
+
+	var local_var: MyEnum = MyEnum.ENUM_VALUE_1
+	print(local_var)
+	local_var = MyEnum.ENUM_VALUE_2
+	print(local_var)

+ 5 - 0
modules/gdscript/tests/scripts/analyzer/features/enum_assign_same_enum.out

@@ -0,0 +1,5 @@
+GDTEST_OK
+0
+1
+0
+1

+ 21 - 0
modules/gdscript/tests/scripts/analyzer/features/enum_is_treated_as_int.gd

@@ -0,0 +1,21 @@
+# Enum is equivalent to int for comparisons and operations.
+enum MyEnum {
+	ZERO,
+	ONE,
+	TWO,
+}
+
+enum OtherEnum {
+	ZERO,
+	ONE,
+	TWO,
+}
+
+func test():
+	print(MyEnum.ZERO == OtherEnum.ZERO)
+	print(MyEnum.ZERO == 1)
+	print(MyEnum.ZERO != OtherEnum.ONE)
+	print(MyEnum.ZERO != 0)
+
+	print(MyEnum.ONE + OtherEnum.TWO)
+	print(2 - MyEnum.ONE)

+ 7 - 0
modules/gdscript/tests/scripts/analyzer/features/enum_is_treated_as_int.out

@@ -0,0 +1,7 @@
+GDTEST_OK
+true
+false
+true
+false
+3
+1

+ 13 - 0
modules/gdscript/tests/scripts/analyzer/features/enum_type_is_treated_as_dictionary.gd

@@ -0,0 +1,13 @@
+enum MyEnum {
+	ZERO,
+	ONE,
+	TWO,
+}
+
+func test():
+	for key in MyEnum.keys():
+		prints(key, MyEnum[key])
+
+	# https://github.com/godotengine/godot/issues/55491
+	for key in MyEnum:
+		prints(key, MyEnum[key])

+ 7 - 0
modules/gdscript/tests/scripts/analyzer/features/enum_type_is_treated_as_dictionary.out

@@ -0,0 +1,7 @@
+GDTEST_OK
+ZERO 0
+ONE 1
+TWO 2
+ZERO 0
+ONE 1
+TWO 2

+ 15 - 0
modules/gdscript/tests/scripts/parser/warnings/enum_assign_int_without_casting.gd

@@ -0,0 +1,15 @@
+enum MyEnum { ENUM_VALUE_1, ENUM_VALUE_2 }
+
+# Assigning int value to enum-typed variable without explicit cast causes a warning.
+# While it is valid it may be a mistake in the assignment.
+var class_var: MyEnum = 0
+
+func test():
+	print(class_var)
+	class_var = 1
+	print(class_var)
+
+	var local_var: MyEnum = 0
+	print(local_var)
+	local_var = 1
+	print(local_var)

+ 21 - 0
modules/gdscript/tests/scripts/parser/warnings/enum_assign_int_without_casting.out

@@ -0,0 +1,21 @@
+GDTEST_OK
+>> WARNING
+>> Line: 5
+>> INT_ASSIGNED_TO_ENUM
+>> Integer used when an enum value is expected. If this is intended cast the integer to the enum type.
+>> WARNING
+>> Line: 9
+>> INT_ASSIGNED_TO_ENUM
+>> Integer used when an enum value is expected. If this is intended cast the integer to the enum type.
+>> WARNING
+>> Line: 12
+>> INT_ASSIGNED_TO_ENUM
+>> Integer used when an enum value is expected. If this is intended cast the integer to the enum type.
+>> WARNING
+>> Line: 14
+>> INT_ASSIGNED_TO_ENUM
+>> Integer used when an enum value is expected. If this is intended cast the integer to the enum type.
+0
+1
+0
+1

+ 1 - 0
modules/gdscript/tests/scripts/runtime/errors/callable_call_after_free_object.gd

@@ -1,3 +1,4 @@
+#debug-only
 func test():
 func test():
 	var node := Node.new()
 	var node := Node.new()
 	var inside_tree = node.is_inside_tree
 	var inside_tree = node.is_inside_tree

+ 1 - 1
modules/gdscript/tests/scripts/runtime/errors/callable_call_after_free_object.out

@@ -2,5 +2,5 @@ GDTEST_RUNTIME_ERROR
 >> SCRIPT ERROR
 >> SCRIPT ERROR
 >> on function: test()
 >> on function: test()
 >> runtime/errors/callable_call_after_free_object.gd
 >> runtime/errors/callable_call_after_free_object.gd
->> 5
+>> 6
 >> Attempt to call function 'null::is_inside_tree (Callable)' on a null instance.
 >> Attempt to call function 'null::is_inside_tree (Callable)' on a null instance.