2
0
Эх сурвалжийг харах

GDScript: Fix constant conversions

Dmitrii Maganov 2 жил өмнө
parent
commit
31e0ae2012
29 өөрчлөгдсөн 204 нэмэгдсэн , 105 устгасан
  1. 5 2
      doc/classes/ProjectSettings.xml
  2. 111 79
      modules/gdscript/gdscript_analyzer.cpp
  3. 3 2
      modules/gdscript/gdscript_analyzer.h
  4. 7 2
      modules/gdscript/gdscript_warning.cpp
  5. 2 1
      modules/gdscript/gdscript_warning.h
  6. 3 0
      modules/gdscript/tests/scripts/analyzer/errors/assymetric_assignment_bad.gd
  7. 2 0
      modules/gdscript/tests/scripts/analyzer/errors/assymetric_assignment_bad.out
  8. 0 2
      modules/gdscript/tests/scripts/analyzer/errors/cast_enum_bad_enum.out
  9. 0 2
      modules/gdscript/tests/scripts/analyzer/errors/cast_enum_bad_int.out
  10. 1 1
      modules/gdscript/tests/scripts/analyzer/errors/enum_class_var_assign_with_wrong_enum_type.out
  11. 1 1
      modules/gdscript/tests/scripts/analyzer/errors/enum_class_var_init_with_wrong_enum_type.out
  12. 1 1
      modules/gdscript/tests/scripts/analyzer/errors/enum_function_parameter_wrong_type.out
  13. 1 1
      modules/gdscript/tests/scripts/analyzer/errors/enum_function_return_wrong_type.out
  14. 1 1
      modules/gdscript/tests/scripts/analyzer/errors/enum_local_var_assign_outer_with_wrong_enum_type.out
  15. 1 1
      modules/gdscript/tests/scripts/analyzer/errors/enum_local_var_assign_with_wrong_enum_type.out
  16. 1 1
      modules/gdscript/tests/scripts/analyzer/errors/enum_local_var_init_with_wrong_enum_type.out
  17. 1 1
      modules/gdscript/tests/scripts/analyzer/errors/enum_preload_unnamed_assign_to_named.out
  18. 1 1
      modules/gdscript/tests/scripts/analyzer/errors/enum_unnamed_assign_to_named.out
  19. 1 1
      modules/gdscript/tests/scripts/analyzer/errors/lambda_wrong_return.out
  20. 1 1
      modules/gdscript/tests/scripts/analyzer/errors/preload_enum_error.out
  21. 16 0
      modules/gdscript/tests/scripts/analyzer/features/assymetric_assignment_good.gd
  22. 2 0
      modules/gdscript/tests/scripts/analyzer/features/assymetric_assignment_good.out
  23. 24 0
      modules/gdscript/tests/scripts/analyzer/features/const_conversions.gd
  24. 2 0
      modules/gdscript/tests/scripts/analyzer/features/const_conversions.out
  25. 0 0
      modules/gdscript/tests/scripts/analyzer/warnings/cast_enum_bad_enum.gd
  26. 6 0
      modules/gdscript/tests/scripts/analyzer/warnings/cast_enum_bad_enum.out
  27. 0 0
      modules/gdscript/tests/scripts/analyzer/warnings/cast_enum_bad_int.gd
  28. 6 0
      modules/gdscript/tests/scripts/analyzer/warnings/cast_enum_bad_int.out
  29. 4 4
      modules/gdscript/tests/scripts/parser/warnings/enum_assign_int_without_casting.out

+ 5 - 2
doc/classes/ProjectSettings.xml

@@ -408,8 +408,11 @@
 		<member name="debug/gdscript/warnings/incompatible_ternary" 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 ternary operator may emit values with incompatible types.
 		</member>
-		<member name="debug/gdscript/warnings/int_assigned_to_enum" type="int" setter="" getter="" default="1">
-			When set to [code]warn[/code] or [code]error[/code], produces a warning or an error respectively when trying to assign an integer to a variable that expects an enum value.
+		<member name="debug/gdscript/warnings/int_as_enum_without_cast" type="int" setter="" getter="" default="1">
+			When set to [code]warn[/code] or [code]error[/code], produces a warning or an error respectively when trying to use an integer as an enum without an explicit cast.
+		</member>
+		<member name="debug/gdscript/warnings/int_as_enum_without_match" type="int" setter="" getter="" default="1">
+			When set to [code]warn[/code] or [code]error[/code], produces a warning or an error respectively when trying to use an integer as an enum when there is no matching enum member for that numeric value.
 		</member>
 		<member name="debug/gdscript/warnings/integer_division" type="int" setter="" getter="" default="1">
 			When set to [code]warn[/code] or [code]error[/code], produces a warning or an error respectively when dividing an integer by another integer (the decimal part will be discarded).

+ 111 - 79
modules/gdscript/gdscript_analyzer.cpp

@@ -160,19 +160,6 @@ static GDScriptParser::DataType make_builtin_meta_type(Variant::Type p_type) {
 	return type;
 }
 
-static StringName enum_get_value_name(const GDScriptParser::DataType p_type, int64_t p_val) {
-	// Check that an enum has a given value, not key.
-	// Make sure that implicit conversion to int64_t is sensible before calling!
-	HashMap<StringName, int64_t>::ConstIterator i = p_type.enum_values.begin();
-	while (i) {
-		if (i->value == p_val) {
-			return i->key;
-		}
-		++i;
-	}
-	return StringName();
-}
-
 bool GDScriptAnalyzer::has_member_name_conflict_in_script_class(const StringName &p_member_name, const GDScriptParser::ClassNode *p_class, const GDScriptParser::Node *p_member) {
 	if (p_class->members_indices.has(p_member_name)) {
 		int index = p_class->members_indices[p_member_name];
@@ -1596,6 +1583,9 @@ void GDScriptAnalyzer::resolve_assignable(GDScriptParser::AssignableNode *p_assi
 			}
 		}
 
+		if (has_specified_type && p_assignable->initializer->is_constant) {
+			update_const_expression_builtin_type(p_assignable->initializer, specified_type, "assign");
+		}
 		GDScriptParser::DataType initializer_type = p_assignable->initializer->get_datatype();
 
 		if (p_assignable->infer_datatype) {
@@ -1630,7 +1620,7 @@ void GDScriptAnalyzer::resolve_assignable(GDScriptParser::AssignableNode *p_assi
 					downgrade_node_type_source(p_assignable->initializer);
 				}
 			} else if (!is_type_compatible(specified_type, initializer_type, true, p_assignable->initializer)) {
-				if (!is_constant && is_type_compatible(initializer_type, specified_type, true, p_assignable->initializer)) {
+				if (!is_constant && is_type_compatible(initializer_type, specified_type)) {
 					mark_node_unsafe(p_assignable->initializer);
 					p_assignable->use_conversion_assign = true;
 				} else {
@@ -1976,6 +1966,9 @@ void GDScriptAnalyzer::resolve_return(GDScriptParser::ReturnNode *p_return) {
 		if (has_expected_type && expected_type.is_hard_type() && expected_type.kind == GDScriptParser::DataType::BUILTIN && expected_type.builtin_type == Variant::NIL) {
 			push_error("A void function cannot return a value.", p_return);
 		}
+		if (has_expected_type && expected_type.is_hard_type() && p_return->return_value->is_constant) {
+			update_const_expression_builtin_type(p_return->return_value, expected_type, "return");
+		}
 		result = p_return->return_value->get_datatype();
 	} else {
 		// Return type is null by default.
@@ -2116,6 +2109,68 @@ void GDScriptAnalyzer::reduce_array(GDScriptParser::ArrayNode *p_array) {
 	p_array->set_datatype(arr_type);
 }
 
+#ifdef DEBUG_ENABLED
+static bool enum_has_value(const GDScriptParser::DataType p_type, int64_t p_value) {
+	for (const KeyValue<StringName, int64_t> &E : p_type.enum_values) {
+		if (E.value == p_value) {
+			return true;
+		}
+	}
+	return false;
+}
+#endif
+
+void GDScriptAnalyzer::update_const_expression_builtin_type(GDScriptParser::ExpressionNode *p_expression, const GDScriptParser::DataType &p_type, const char *p_usage, bool p_is_cast) {
+	if (p_expression->get_datatype() == p_type) {
+		return;
+	}
+	if (p_type.kind != GDScriptParser::DataType::BUILTIN && p_type.kind != GDScriptParser::DataType::ENUM) {
+		return;
+	}
+
+	GDScriptParser::DataType expression_type = p_expression->get_datatype();
+	bool is_enum_cast = p_is_cast && p_type.kind == GDScriptParser::DataType::ENUM && p_type.is_meta_type == false && expression_type.builtin_type == Variant::INT;
+	if (!is_enum_cast && !is_type_compatible(p_type, expression_type, true, p_expression)) {
+		push_error(vformat(R"(Cannot %s a value of type "%s" as "%s".)", p_usage, expression_type.to_string(), p_type.to_string()), p_expression);
+		return;
+	}
+
+	GDScriptParser::DataType value_type = type_from_variant(p_expression->reduced_value, p_expression);
+	if (expression_type.is_variant() && !is_enum_cast && !is_type_compatible(p_type, value_type, true, p_expression)) {
+		push_error(vformat(R"(Cannot %s a value of type "%s" as "%s".)", p_usage, value_type.to_string(), p_type.to_string()), p_expression);
+		return;
+	}
+
+#ifdef DEBUG_ENABLED
+	if (p_type.kind == GDScriptParser::DataType::ENUM && value_type.builtin_type == Variant::INT && !enum_has_value(p_type, p_expression->reduced_value)) {
+		parser->push_warning(p_expression, GDScriptWarning::INT_AS_ENUM_WITHOUT_MATCH, p_usage, p_expression->reduced_value.stringify(), p_type.to_string());
+	}
+#endif
+
+	if (value_type.builtin_type == p_type.builtin_type) {
+		p_expression->set_datatype(p_type);
+		return;
+	}
+
+	Variant converted_to;
+	const Variant *converted_from = &p_expression->reduced_value;
+	Callable::CallError call_error;
+	Variant::construct(p_type.builtin_type, converted_to, &converted_from, 1, call_error);
+	if (call_error.error) {
+		push_error(vformat(R"(Failed to convert a value of type "%s" to "%s".)", value_type.to_string(), p_type.to_string()), p_expression);
+		return;
+	}
+
+#ifdef DEBUG_ENABLED
+	if (p_type.builtin_type == Variant::INT && value_type.builtin_type == Variant::FLOAT) {
+		parser->push_warning(p_expression, GDScriptWarning::NARROWING_CONVERSION);
+	}
+#endif
+
+	p_expression->reduced_value = converted_to;
+	p_expression->set_datatype(p_type);
+}
+
 // When an array literal is stored (or passed as function argument) to a typed context, we then assume the array is typed.
 // This function determines which type is that (if any).
 void GDScriptAnalyzer::update_array_literal_element_type(const GDScriptParser::DataType &p_base_type, GDScriptParser::ArrayNode *p_array_literal) {
@@ -2182,6 +2237,10 @@ void GDScriptAnalyzer::reduce_assignment(GDScriptParser::AssignmentNode *p_assig
 		update_array_literal_element_type(assignee_type, static_cast<GDScriptParser::ArrayNode *>(p_assignment->assigned_value));
 	}
 
+	if (p_assignment->operation == GDScriptParser::AssignmentNode::OP_NONE && assignee_type.is_hard_type() && p_assignment->assigned_value->is_constant) {
+		update_const_expression_builtin_type(p_assignment->assigned_value, assignee_type, "assign");
+	}
+
 	GDScriptParser::DataType assigned_value_type = p_assignment->assigned_value->get_datatype();
 
 	bool assignee_is_variant = assignee_type.is_variant();
@@ -2242,7 +2301,7 @@ void GDScriptAnalyzer::reduce_assignment(GDScriptParser::AssignmentNode *p_assig
 				// non-variant assignee and incompatible result
 				mark_node_unsafe(p_assignment);
 				if (assignee_is_hard) {
-					if (is_type_compatible(op_type, assignee_type, true, p_assignment->assigned_value)) {
+					if (is_type_compatible(op_type, assignee_type)) {
 						// hard non-variant assignee and maybe compatible result
 						p_assignment->use_conversion_assign = true;
 					} else {
@@ -2358,7 +2417,7 @@ void GDScriptAnalyzer::reduce_binary_op(GDScriptParser::BinaryOpNode *p_binary_o
 				GDScriptParser::DataType test_type = right_type;
 				test_type.is_meta_type = false;
 
-				if (!is_type_compatible(test_type, left_type, false)) {
+				if (!is_type_compatible(test_type, left_type)) {
 					push_error(vformat(R"(Expression is of type "%s" so it can't be of type "%s".)"), p_binary_op->left_operand);
 					p_binary_op->reduced_value = false;
 				} else {
@@ -2379,7 +2438,7 @@ void GDScriptAnalyzer::reduce_binary_op(GDScriptParser::BinaryOpNode *p_binary_o
 		GDScriptParser::DataType test_type = right_type;
 		test_type.is_meta_type = false;
 
-		if (!is_type_compatible(test_type, left_type, false) && !is_type_compatible(left_type, test_type, false)) {
+		if (!is_type_compatible(test_type, left_type) && !is_type_compatible(left_type, test_type)) {
 			if (left_type.is_hard_type()) {
 				push_error(vformat(R"(Expression is of type "%s" so it can't be of type "%s".)", left_type.to_string(), test_type.to_string()), p_binary_op->left_operand);
 			} else {
@@ -2555,6 +2614,11 @@ void GDScriptAnalyzer::reduce_call(GDScriptParser::CallNode *p_call, bool p_is_a
 					}
 
 					if (types_match) {
+						for (int i = 0; i < p_call->arguments.size(); i++) {
+							if (p_call->arguments[i]->is_constant) {
+								update_const_expression_builtin_type(p_call->arguments[i], type_from_property(info.arguments[i], true), "pass");
+							}
+						}
 						match = true;
 						call_type = type_from_property(info.return_val);
 						break;
@@ -2851,67 +2915,39 @@ void GDScriptAnalyzer::reduce_cast(GDScriptParser::CastNode *p_cast) {
 	}
 
 	p_cast->set_datatype(cast_type);
+	if (p_cast->operand->is_constant) {
+		update_const_expression_builtin_type(p_cast->operand, cast_type, "cast", true);
+		if (cast_type.is_variant() || p_cast->operand->get_datatype() == cast_type) {
+			p_cast->is_constant = true;
+			p_cast->reduced_value = p_cast->operand->reduced_value;
+		}
+	}
 
 	if (!cast_type.is_variant()) {
 		GDScriptParser::DataType op_type = p_cast->operand->get_datatype();
-		if (!op_type.is_variant()) {
+		if (op_type.is_variant() || !op_type.is_hard_type()) {
+			mark_node_unsafe(p_cast);
+#ifdef DEBUG_ENABLED
+			if (op_type.is_variant() && !op_type.is_hard_type()) {
+				parser->push_warning(p_cast, GDScriptWarning::UNSAFE_CAST, cast_type.to_string());
+			}
+#endif
+		} else {
 			bool valid = false;
-			bool more_informative_error = false;
-			if (op_type.kind == GDScriptParser::DataType::ENUM && cast_type.kind == GDScriptParser::DataType::ENUM) {
-				// Enum casts are compatible when value from operand exists in target enum
-				if (p_cast->operand->is_constant && p_cast->operand->reduced) {
-					if (enum_get_value_name(cast_type, p_cast->operand->reduced_value) != StringName()) {
-						valid = true;
-					} else {
-						valid = false;
-						more_informative_error = true;
-						push_error(vformat(R"(Invalid cast. Enum "%s" does not have value corresponding to "%s.%s" (%d).)",
-										   cast_type.to_string(), op_type.enum_type,
-										   enum_get_value_name(op_type, p_cast->operand->reduced_value), // Can never be null
-										   p_cast->operand->reduced_value.operator uint64_t()),
-								p_cast->cast_type);
-					}
-				} else {
-					// Can't statically tell whether int has a corresponding enum value. Valid but dangerous!
-					mark_node_unsafe(p_cast);
-					valid = true;
-				}
-			} else if (op_type.kind == GDScriptParser::DataType::BUILTIN && op_type.builtin_type == Variant::INT && cast_type.kind == GDScriptParser::DataType::ENUM) {
-				// Int assignment to enum not valid when exact int assigned is known but is not an enum value
-				if (p_cast->operand->is_constant && p_cast->operand->reduced) {
-					if (enum_get_value_name(cast_type, p_cast->operand->reduced_value) != StringName()) {
-						valid = true;
-					} else {
-						valid = false;
-						more_informative_error = true;
-						push_error(vformat(R"(Invalid cast. Enum "%s" does not have enum value %d.)", cast_type.to_string(), p_cast->operand->reduced_value.operator uint64_t()), p_cast->cast_type);
-					}
-				} else {
-					// Can't statically tell whether int has a corresponding enum value. Valid but dangerous!
-					mark_node_unsafe(p_cast);
-					valid = true;
-				}
+			if (op_type.builtin_type == Variant::INT && cast_type.kind == GDScriptParser::DataType::ENUM) {
+				mark_node_unsafe(p_cast);
+				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);
 			} 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);
 			}
 
-			if (!valid && !more_informative_error) {
+			if (!valid) {
 				push_error(vformat(R"(Invalid cast. Cannot convert from "%s" to "%s".)", op_type.to_string(), cast_type.to_string()), p_cast->cast_type);
 			}
 		}
-	} else {
-		mark_node_unsafe(p_cast);
-	}
-#ifdef DEBUG_ENABLED
-	if (p_cast->operand->get_datatype().is_variant()) {
-		parser->push_warning(p_cast, GDScriptWarning::UNSAFE_CAST, cast_type.to_string());
-		mark_node_unsafe(p_cast);
 	}
-#endif
-
-	// TODO: Perform cast on constants.
 }
 
 void GDScriptAnalyzer::reduce_dictionary(GDScriptParser::DictionaryNode *p_dictionary) {
@@ -4210,26 +4246,22 @@ bool GDScriptAnalyzer::function_signature_from_info(const MethodInfo &p_info, GD
 	return true;
 }
 
-bool GDScriptAnalyzer::validate_call_arg(const MethodInfo &p_method, const GDScriptParser::CallNode *p_call) {
+void GDScriptAnalyzer::validate_call_arg(const MethodInfo &p_method, const GDScriptParser::CallNode *p_call) {
 	List<GDScriptParser::DataType> arg_types;
 
 	for (const PropertyInfo &E : p_method.arguments) {
 		arg_types.push_back(type_from_property(E, true));
 	}
 
-	return validate_call_arg(arg_types, p_method.default_arguments.size(), (p_method.flags & METHOD_FLAG_VARARG) != 0, p_call);
+	validate_call_arg(arg_types, p_method.default_arguments.size(), (p_method.flags & METHOD_FLAG_VARARG) != 0, p_call);
 }
 
-bool GDScriptAnalyzer::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 valid = true;
-
+void GDScriptAnalyzer::validate_call_arg(const List<GDScriptParser::DataType> &p_par_types, int p_default_args_count, bool p_is_vararg, const GDScriptParser::CallNode *p_call) {
 	if (p_call->arguments.size() < p_par_types.size() - p_default_args_count) {
 		push_error(vformat(R"*(Too few arguments for "%s()" call. Expected at least %d but received %d.)*", p_call->function_name, p_par_types.size() - p_default_args_count, p_call->arguments.size()), p_call);
-		valid = false;
 	}
 	if (!p_is_vararg && p_call->arguments.size() > p_par_types.size()) {
 		push_error(vformat(R"*(Too many arguments for "%s()" call. Expected at most %d but received %d.)*", p_call->function_name, p_par_types.size(), p_call->arguments.size()), p_call->arguments[p_par_types.size()]);
-		valid = false;
 	}
 
 	for (int i = 0; i < p_call->arguments.size(); i++) {
@@ -4238,9 +4270,13 @@ bool GDScriptAnalyzer::validate_call_arg(const List<GDScriptParser::DataType> &p
 			break;
 		}
 		GDScriptParser::DataType par_type = p_par_types[i];
+
+		if (par_type.is_hard_type() && p_call->arguments[i]->is_constant) {
+			update_const_expression_builtin_type(p_call->arguments[i], par_type, "pass");
+		}
 		GDScriptParser::DataType arg_type = p_call->arguments[i]->get_datatype();
 
-		if (arg_type.is_variant() && !(par_type.is_hard_type() && par_type.is_variant())) {
+		if ((arg_type.is_variant() || !arg_type.is_hard_type()) && !(par_type.is_hard_type() && par_type.is_variant())) {
 			// Argument can be anything, so this is unsafe.
 			mark_node_unsafe(p_call->arguments[i]);
 		} else if (par_type.is_hard_type() && !is_type_compatible(par_type, arg_type, true)) {
@@ -4250,17 +4286,13 @@ bool GDScriptAnalyzer::validate_call_arg(const List<GDScriptParser::DataType> &p
 				push_error(vformat(R"*(Invalid argument for "%s()" function: argument %d should be "%s" but is "%s".)*",
 								   p_call->function_name, i + 1, par_type.to_string(), arg_type.to_string()),
 						p_call->arguments[i]);
-				valid = false;
 			}
 #ifdef DEBUG_ENABLED
-		} else {
-			if (par_type.kind == GDScriptParser::DataType::BUILTIN && par_type.builtin_type == Variant::INT && arg_type.kind == GDScriptParser::DataType::BUILTIN && arg_type.builtin_type == Variant::FLOAT) {
-				parser->push_warning(p_call, GDScriptWarning::NARROWING_CONVERSION, p_call->function_name);
-			}
+		} else if (par_type.kind == GDScriptParser::DataType::BUILTIN && par_type.builtin_type == Variant::INT && arg_type.kind == GDScriptParser::DataType::BUILTIN && arg_type.builtin_type == Variant::FLOAT) {
+			parser->push_warning(p_call, GDScriptWarning::NARROWING_CONVERSION, p_call->function_name);
 #endif
 		}
 	}
-	return valid;
 }
 
 #ifdef DEBUG_ENABLED
@@ -4412,7 +4444,7 @@ bool GDScriptAnalyzer::is_type_compatible(const GDScriptParser::DataType &p_targ
 		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);
+				parser->push_warning(p_source_node, GDScriptWarning::INT_AS_ENUM_WITHOUT_CAST);
 			}
 #endif
 			return true;

+ 3 - 2
modules/gdscript/gdscript_analyzer.h

@@ -112,10 +112,11 @@ class GDScriptAnalyzer {
 	GDScriptParser::DataType make_global_class_meta_type(const StringName &p_class_name, const GDScriptParser::Node *p_source);
 	bool get_function_signature(GDScriptParser::Node *p_source, bool p_is_constructor, GDScriptParser::DataType base_type, const StringName &p_function, GDScriptParser::DataType &r_return_type, List<GDScriptParser::DataType> &r_par_types, int &r_default_arg_count, bool &r_static, bool &r_vararg);
 	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);
+	void validate_call_arg(const List<GDScriptParser::DataType> &p_par_types, int p_default_args_count, bool p_is_vararg, const GDScriptParser::CallNode *p_call);
+	void validate_call_arg(const MethodInfo &p_method, const GDScriptParser::CallNode *p_call);
 	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);
+	void update_const_expression_builtin_type(GDScriptParser::ExpressionNode *p_expression, const GDScriptParser::DataType &p_type, const char *p_usage, bool p_is_cast = false);
 	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 GDScriptParser::Node *p_source_node = nullptr);
 	void push_error(const String &p_message, const GDScriptParser::Node *p_origin = nullptr);

+ 7 - 2
modules/gdscript/gdscript_warning.cpp

@@ -148,9 +148,13 @@ String GDScriptWarning::get_message() const {
 			CHECK_SYMBOLS(3);
 			return vformat(R"(The %s '%s' has the same name as a %s.)", symbols[0], symbols[1], symbols[2]);
 		}
-		case INT_ASSIGNED_TO_ENUM: {
+		case INT_AS_ENUM_WITHOUT_CAST: {
 			return "Integer used when an enum value is expected. If this is intended cast the integer to the enum type.";
 		}
+		case INT_AS_ENUM_WITHOUT_MATCH: {
+			CHECK_SYMBOLS(3);
+			return vformat(R"(Cannot %s %s as Enum "%s": no enum member has matching value.)", symbols[0], symbols[1], symbols[2]);
+		} break;
 		case STATIC_CALLED_ON_INSTANCE: {
 			CHECK_SYMBOLS(2);
 			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]);
@@ -221,7 +225,8 @@ String GDScriptWarning::get_name_from_code(Code p_code) {
 		"REDUNDANT_AWAIT",
 		"EMPTY_FILE",
 		"SHADOWED_GLOBAL_IDENTIFIER",
-		"INT_ASSIGNED_TO_ENUM",
+		"INT_AS_ENUM_WITHOUT_CAST",
+		"INT_AS_ENUM_WITHOUT_MATCH",
 		"STATIC_CALLED_ON_INSTANCE",
 		"CONFUSABLE_IDENTIFIER",
 	};

+ 2 - 1
modules/gdscript/gdscript_warning.h

@@ -76,7 +76,8 @@ public:
 		REDUNDANT_AWAIT, // await is used but expression is synchronous (not a signal nor a coroutine).
 		EMPTY_FILE, // A script file is empty.
 		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.
+		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.
 		STATIC_CALLED_ON_INSTANCE, // A static method was called on an instance of a class instead of on the class itself.
 		CONFUSABLE_IDENTIFIER, // The identifier contains misleading characters that can be confused. E.g. "usеr" (has Cyrillic "е" instead of Latin "e").
 		WARNING_MAX,

+ 3 - 0
modules/gdscript/tests/scripts/analyzer/errors/assymetric_assignment_bad.gd

@@ -0,0 +1,3 @@
+func test():
+	var var_color: String = Color.RED
+	print('not ok')

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

@@ -0,0 +1,2 @@
+GDTEST_ANALYZER_ERROR
+Cannot assign a value of type "Color" as "String".

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

@@ -1,2 +0,0 @@
-GDTEST_ANALYZER_ERROR
-Invalid cast. Enum "cast_enum_bad_enum.gd::MyEnum" does not have value corresponding to "MyOtherEnum.OTHER_ENUM_VALUE_3" (2).

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

@@ -1,2 +0,0 @@
-GDTEST_ANALYZER_ERROR
-Invalid cast. Enum "cast_enum_bad_int.gd::MyEnum" does not have enum value 2.

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

@@ -1,2 +1,2 @@
 GDTEST_ANALYZER_ERROR
-Value of type "enum_class_var_assign_with_wrong_enum_type.gd::MyOtherEnum" cannot be assigned to a variable of type "enum_class_var_assign_with_wrong_enum_type.gd::MyEnum".
+Cannot assign a value of type "enum_class_var_assign_with_wrong_enum_type.gd::MyOtherEnum" as "enum_class_var_assign_with_wrong_enum_type.gd::MyEnum".

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

@@ -1,2 +1,2 @@
 GDTEST_ANALYZER_ERROR
-Cannot assign a value of type enum_class_var_init_with_wrong_enum_type.gd::MyOtherEnum to variable "class_var" with specified type enum_class_var_init_with_wrong_enum_type.gd::MyEnum.
+Cannot assign a value of type "enum_class_var_init_with_wrong_enum_type.gd::MyOtherEnum" as "enum_class_var_init_with_wrong_enum_type.gd::MyEnum".

+ 1 - 1
modules/gdscript/tests/scripts/analyzer/errors/enum_function_parameter_wrong_type.out

@@ -1,2 +1,2 @@
 GDTEST_ANALYZER_ERROR
-Invalid argument for "enum_func()" function: argument 1 should be "enum_function_parameter_wrong_type.gd::MyEnum" but is "enum_function_parameter_wrong_type.gd::MyOtherEnum".
+Cannot pass a value of type "enum_function_parameter_wrong_type.gd::MyOtherEnum" as "enum_function_parameter_wrong_type.gd::MyEnum".

+ 1 - 1
modules/gdscript/tests/scripts/analyzer/errors/enum_function_return_wrong_type.out

@@ -1,2 +1,2 @@
 GDTEST_ANALYZER_ERROR
-Cannot return value of type "enum_function_return_wrong_type.gd::MyOtherEnum" because the function return type is "enum_function_return_wrong_type.gd::MyEnum".
+Cannot return a value of type "enum_function_return_wrong_type.gd::MyOtherEnum" as "enum_function_return_wrong_type.gd::MyEnum".

+ 1 - 1
modules/gdscript/tests/scripts/analyzer/errors/enum_local_var_assign_outer_with_wrong_enum_type.out

@@ -1,2 +1,2 @@
 GDTEST_ANALYZER_ERROR
-Value of type "enum_local_var_assign_outer_with_wrong_enum_type.gd::InnerClass::MyEnum" cannot be assigned to a variable of type "enum_local_var_assign_outer_with_wrong_enum_type.gd::MyEnum".
+Cannot assign a value of type "enum_local_var_assign_outer_with_wrong_enum_type.gd::InnerClass::MyEnum" as "enum_local_var_assign_outer_with_wrong_enum_type.gd::MyEnum".

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

@@ -1,2 +1,2 @@
 GDTEST_ANALYZER_ERROR
-Value of type "enum_local_var_assign_with_wrong_enum_type.gd::MyOtherEnum" cannot be assigned to a variable of type "enum_local_var_assign_with_wrong_enum_type.gd::MyEnum".
+Cannot assign a value of type "enum_local_var_assign_with_wrong_enum_type.gd::MyOtherEnum" as "enum_local_var_assign_with_wrong_enum_type.gd::MyEnum".

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

@@ -1,2 +1,2 @@
 GDTEST_ANALYZER_ERROR
-Cannot assign a value of type enum_local_var_init_with_wrong_enum_type.gd::MyOtherEnum to variable "local_var" with specified type enum_local_var_init_with_wrong_enum_type.gd::MyEnum.
+Cannot assign a value of type "enum_local_var_init_with_wrong_enum_type.gd::MyOtherEnum" as "enum_local_var_init_with_wrong_enum_type.gd::MyEnum".

+ 1 - 1
modules/gdscript/tests/scripts/analyzer/errors/enum_preload_unnamed_assign_to_named.out

@@ -1,2 +1,2 @@
 GDTEST_ANALYZER_ERROR
-Value of type "enum_value_from_parent.gd::<anonymous enum>" cannot be assigned to a variable of type "enum_preload_unnamed_assign_to_named.gd::MyEnum".
+Cannot assign a value of type "enum_value_from_parent.gd::<anonymous enum>" as "enum_preload_unnamed_assign_to_named.gd::MyEnum".

+ 1 - 1
modules/gdscript/tests/scripts/analyzer/errors/enum_unnamed_assign_to_named.out

@@ -1,2 +1,2 @@
 GDTEST_ANALYZER_ERROR
-Cannot assign a value of type enum_unnamed_assign_to_named.gd::<anonymous enum> to variable "local_var" with specified type enum_unnamed_assign_to_named.gd::MyEnum.
+Cannot assign a value of type "enum_unnamed_assign_to_named.gd::<anonymous enum>" as "enum_unnamed_assign_to_named.gd::MyEnum".

+ 1 - 1
modules/gdscript/tests/scripts/analyzer/errors/lambda_wrong_return.out

@@ -1,2 +1,2 @@
 GDTEST_ANALYZER_ERROR
-Cannot return value of type "String" because the function return type is "int".
+Cannot return a value of type "String" as "int".

+ 1 - 1
modules/gdscript/tests/scripts/analyzer/errors/preload_enum_error.out

@@ -1,2 +1,2 @@
 GDTEST_ANALYZER_ERROR
-Value of type "enum_from_outer.gd::Named" cannot be assigned to a variable of type "preload_enum_error.gd::LocalNamed".
+Cannot assign a value of type "enum_from_outer.gd::Named" as "preload_enum_error.gd::LocalNamed".

+ 16 - 0
modules/gdscript/tests/scripts/analyzer/features/assymetric_assignment_good.gd

@@ -0,0 +1,16 @@
+const const_color: Color = 'red'
+
+func func_color(arg_color: Color = 'blue') -> bool:
+	return arg_color == Color.BLUE
+
+@warning_ignore("assert_always_true")
+func test():
+	assert(const_color == Color.RED)
+
+	assert(func_color() == true)
+	assert(func_color('blue') == true)
+
+	var var_color: Color = 'green'
+	assert(var_color == Color.GREEN)
+
+	print('ok')

+ 2 - 0
modules/gdscript/tests/scripts/analyzer/features/assymetric_assignment_good.out

@@ -0,0 +1,2 @@
+GDTEST_OK
+ok

+ 24 - 0
modules/gdscript/tests/scripts/analyzer/features/const_conversions.gd

@@ -0,0 +1,24 @@
+const const_float_int: float = 19
+const const_float_plus: float = 12 + 22
+const const_float_cast: float = 76 as float
+
+const const_packed_empty: PackedFloat64Array = []
+const const_packed_ints: PackedFloat64Array = [52]
+
+@warning_ignore("assert_always_true")
+func test():
+	assert(typeof(const_float_int) == TYPE_FLOAT)
+	assert(str(const_float_int) == '19')
+	assert(typeof(const_float_plus) == TYPE_FLOAT)
+	assert(str(const_float_plus) == '34')
+	assert(typeof(const_float_cast) == TYPE_FLOAT)
+	assert(str(const_float_cast) == '76')
+
+	assert(typeof(const_packed_empty) == TYPE_PACKED_FLOAT64_ARRAY)
+	assert(str(const_packed_empty) == '[]')
+	assert(typeof(const_packed_ints) == TYPE_PACKED_FLOAT64_ARRAY)
+	assert(str(const_packed_ints) == '[52]')
+	assert(typeof(const_packed_ints[0]) == TYPE_FLOAT)
+	assert(str(const_packed_ints[0]) == '52')
+
+	print('ok')

+ 2 - 0
modules/gdscript/tests/scripts/analyzer/features/const_conversions.out

@@ -0,0 +1,2 @@
+GDTEST_OK
+ok

+ 0 - 0
modules/gdscript/tests/scripts/analyzer/errors/cast_enum_bad_enum.gd → modules/gdscript/tests/scripts/analyzer/warnings/cast_enum_bad_enum.gd


+ 6 - 0
modules/gdscript/tests/scripts/analyzer/warnings/cast_enum_bad_enum.out

@@ -0,0 +1,6 @@
+GDTEST_OK
+>> WARNING
+>> Line: 5
+>> INT_AS_ENUM_WITHOUT_MATCH
+>> Cannot cast 2 as Enum "cast_enum_bad_enum.gd::MyEnum": no enum member has matching value.
+2

+ 0 - 0
modules/gdscript/tests/scripts/analyzer/errors/cast_enum_bad_int.gd → modules/gdscript/tests/scripts/analyzer/warnings/cast_enum_bad_int.gd


+ 6 - 0
modules/gdscript/tests/scripts/analyzer/warnings/cast_enum_bad_int.out

@@ -0,0 +1,6 @@
+GDTEST_OK
+>> WARNING
+>> Line: 4
+>> INT_AS_ENUM_WITHOUT_MATCH
+>> Cannot cast 2 as Enum "cast_enum_bad_int.gd::MyEnum": no enum member has matching value.
+2

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

@@ -1,19 +1,19 @@
 GDTEST_OK
 >> WARNING
 >> Line: 5
->> INT_ASSIGNED_TO_ENUM
+>> INT_AS_ENUM_WITHOUT_CAST
 >> 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
+>> INT_AS_ENUM_WITHOUT_CAST
 >> 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
+>> INT_AS_ENUM_WITHOUT_CAST
 >> 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
+>> INT_AS_ENUM_WITHOUT_CAST
 >> Integer used when an enum value is expected. If this is intended cast the integer to the enum type.
 0
 1