Procházet zdrojové kódy

Assorted enum and native type fixes

ocean (they/them) před 2 roky
rodič
revize
366ec895b1
61 změnil soubory, kde provedl 698 přidání a 96 odebrání
  1. 137 70
      modules/gdscript/gdscript_analyzer.cpp
  2. 1 1
      modules/gdscript/gdscript_analyzer.h
  3. 1 1
      modules/gdscript/gdscript_compiler.cpp
  4. 13 6
      modules/gdscript/gdscript_editor.cpp
  5. 14 9
      modules/gdscript/gdscript_parser.cpp
  6. 2 2
      modules/gdscript/gdscript_parser.h
  7. 3 0
      modules/gdscript/tests/scripts/analyzer/errors/assign_enum.gd
  8. 2 0
      modules/gdscript/tests/scripts/analyzer/errors/assign_enum.out
  9. 3 0
      modules/gdscript/tests/scripts/analyzer/errors/assign_named_enum.gd
  10. 2 0
      modules/gdscript/tests/scripts/analyzer/errors/assign_named_enum.out
  11. 5 0
      modules/gdscript/tests/scripts/analyzer/errors/cast_enum_bad_enum.gd
  12. 2 0
      modules/gdscript/tests/scripts/analyzer/errors/cast_enum_bad_enum.out
  13. 4 0
      modules/gdscript/tests/scripts/analyzer/errors/cast_enum_bad_int.gd
  14. 2 0
      modules/gdscript/tests/scripts/analyzer/errors/cast_enum_bad_int.out
  15. 4 0
      modules/gdscript/tests/scripts/analyzer/errors/enum_bad_method.gd
  16. 2 0
      modules/gdscript/tests/scripts/analyzer/errors/enum_bad_method.out
  17. 4 0
      modules/gdscript/tests/scripts/analyzer/errors/enum_bad_value.gd
  18. 2 0
      modules/gdscript/tests/scripts/analyzer/errors/enum_bad_value.out
  19. 1 1
      modules/gdscript/tests/scripts/analyzer/errors/enum_class_var_assign_with_wrong_enum_type.out
  20. 1 1
      modules/gdscript/tests/scripts/analyzer/errors/enum_class_var_init_with_wrong_enum_type.out
  21. 5 0
      modules/gdscript/tests/scripts/analyzer/errors/enum_duplicate_bad_method.gd
  22. 2 0
      modules/gdscript/tests/scripts/analyzer/errors/enum_duplicate_bad_method.out
  23. 8 0
      modules/gdscript/tests/scripts/analyzer/errors/enum_function_parameter_wrong_type.gd
  24. 2 0
      modules/gdscript/tests/scripts/analyzer/errors/enum_function_parameter_wrong_type.out
  25. 8 0
      modules/gdscript/tests/scripts/analyzer/errors/enum_function_return_wrong_type.gd
  26. 2 0
      modules/gdscript/tests/scripts/analyzer/errors/enum_function_return_wrong_type.out
  27. 10 0
      modules/gdscript/tests/scripts/analyzer/errors/enum_local_var_assign_outer_with_wrong_enum_type.gd
  28. 2 0
      modules/gdscript/tests/scripts/analyzer/errors/enum_local_var_assign_outer_with_wrong_enum_type.out
  29. 1 1
      modules/gdscript/tests/scripts/analyzer/errors/enum_local_var_assign_with_wrong_enum_type.out
  30. 1 1
      modules/gdscript/tests/scripts/analyzer/errors/enum_local_var_init_with_wrong_enum_type.out
  31. 2 0
      modules/gdscript/tests/scripts/analyzer/errors/enum_native_bad_value.gd
  32. 2 0
      modules/gdscript/tests/scripts/analyzer/errors/enum_native_bad_value.out
  33. 6 0
      modules/gdscript/tests/scripts/analyzer/errors/enum_preload_unnamed_assign_to_named.gd
  34. 2 0
      modules/gdscript/tests/scripts/analyzer/errors/enum_preload_unnamed_assign_to_named.out
  35. 8 0
      modules/gdscript/tests/scripts/analyzer/errors/enum_shadows_base_enum.gd
  36. 2 0
      modules/gdscript/tests/scripts/analyzer/errors/enum_shadows_base_enum.out
  37. 7 0
      modules/gdscript/tests/scripts/analyzer/errors/enum_shadows_outer_enum.gd
  38. 2 0
      modules/gdscript/tests/scripts/analyzer/errors/enum_shadows_outer_enum.out
  39. 7 0
      modules/gdscript/tests/scripts/analyzer/errors/enum_unnamed_assign_to_named.gd
  40. 2 0
      modules/gdscript/tests/scripts/analyzer/errors/enum_unnamed_assign_to_named.out
  41. 2 0
      modules/gdscript/tests/scripts/analyzer/errors/native_type_errors.gd
  42. 2 0
      modules/gdscript/tests/scripts/analyzer/errors/native_type_errors.out
  43. 6 0
      modules/gdscript/tests/scripts/analyzer/errors/preload_enum_error.gd
  44. 2 0
      modules/gdscript/tests/scripts/analyzer/errors/preload_enum_error.out
  45. 1 1
      modules/gdscript/tests/scripts/analyzer/errors/property_inline_set_type_error.out
  46. 29 0
      modules/gdscript/tests/scripts/analyzer/features/enum_access_types.gd
  47. 13 0
      modules/gdscript/tests/scripts/analyzer/features/enum_access_types.out
  48. 13 0
      modules/gdscript/tests/scripts/analyzer/features/enum_duplicate_into_dict.gd
  49. 7 0
      modules/gdscript/tests/scripts/analyzer/features/enum_duplicate_into_dict.out
  50. 13 0
      modules/gdscript/tests/scripts/analyzer/features/enum_from_base.gd
  51. 0 0
      modules/gdscript/tests/scripts/analyzer/features/enum_from_base.out
  52. 0 2
      modules/gdscript/tests/scripts/analyzer/features/enum_from_outer.gd
  53. 4 0
      modules/gdscript/tests/scripts/analyzer/features/enum_from_outer.out
  54. 112 0
      modules/gdscript/tests/scripts/analyzer/features/enum_function_typecheck.gd
  55. 55 0
      modules/gdscript/tests/scripts/analyzer/features/enum_function_typecheck.out
  56. 16 0
      modules/gdscript/tests/scripts/analyzer/features/enum_named_no_shadow.gd
  57. 4 0
      modules/gdscript/tests/scripts/analyzer/features/enum_named_no_shadow.out
  58. 19 0
      modules/gdscript/tests/scripts/analyzer/features/enum_native_access_types.gd
  59. 9 0
      modules/gdscript/tests/scripts/analyzer/features/enum_native_access_types.out
  60. 86 0
      modules/gdscript/tests/scripts/analyzer/features/enum_typecheck_inner_class.gd
  61. 19 0
      modules/gdscript/tests/scripts/analyzer/features/enum_typecheck_inner_class.out

+ 137 - 70
modules/gdscript/gdscript_analyzer.cpp

@@ -42,6 +42,9 @@
 #include "gdscript_utility_functions.h"
 #include "scene/resources/packed_scene.h"
 
+#define UNNAMED_ENUM "<anonymous enum>"
+#define ENUM_SEPARATOR "::"
+
 static MethodInfo info_from_utility_func(const StringName &p_function) {
 	ERR_FAIL_COND_V(!Variant::has_utility_function(p_function), MethodInfo());
 
@@ -106,13 +109,26 @@ static GDScriptParser::DataType make_native_meta_type(const StringName &p_class_
 	return type;
 }
 
-static GDScriptParser::DataType make_native_enum_type(const StringName &p_native_class, const StringName &p_enum_name) {
+// In enum types, native_type is used to store the class (native or otherwise) that the enum belongs to.
+// This disambiguates between similarly named enums in base classes or outer classes
+static GDScriptParser::DataType make_enum_type(const StringName &p_enum_name, const String &p_base_name, const bool p_meta = false) {
 	GDScriptParser::DataType type;
 	type.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT;
 	type.kind = GDScriptParser::DataType::ENUM;
-	type.builtin_type = Variant::INT;
+	type.builtin_type = p_meta ? Variant::DICTIONARY : Variant::INT;
+	type.enum_type = p_enum_name;
 	type.is_constant = true;
-	type.is_meta_type = true;
+	type.is_meta_type = p_meta;
+
+	// For enums, native_type is only used to check compatibility in is_type_compatible()
+	// We can set anything readable here for error messages, as long as it uniquely identifies the type of the enum
+	type.native_type = p_base_name + ENUM_SEPARATOR + p_enum_name;
+
+	return type;
+}
+
+static GDScriptParser::DataType make_native_enum_type(const StringName &p_enum_name, const StringName &p_native_class, const bool p_meta = true) {
+	GDScriptParser::DataType type = make_enum_type(p_enum_name, p_native_class, p_meta);
 
 	List<StringName> enum_values;
 	ClassDB::get_enum_constants(p_native_class, p_enum_name, &enum_values);
@@ -134,6 +150,19 @@ 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];
@@ -192,6 +221,7 @@ Error GDScriptAnalyzer::check_native_member_name_conflict(const StringName &p_me
 }
 
 Error GDScriptAnalyzer::check_class_member_name_conflict(const GDScriptParser::ClassNode *p_class_node, const StringName &p_member_name, const GDScriptParser::Node *p_member_node) {
+	// TODO check outer classes for static members only
 	const GDScriptParser::DataType *current_data_type = &p_class_node->base_type;
 	while (current_data_type && current_data_type->kind == GDScriptParser::DataType::Kind::CLASS) {
 		GDScriptParser::ClassNode *current_class_node = current_data_type->class_type;
@@ -591,12 +621,17 @@ GDScriptParser::DataType GDScriptAnalyzer::resolve_datatype(GDScriptParser::Type
 		result = ref->get_parser()->head->get_datatype();
 	} else if (ClassDB::has_enum(parser->current_class->base_type.native_type, first)) {
 		// Native enum in current class.
-		result = make_native_enum_type(parser->current_class->base_type.native_type, first);
+		result = make_native_enum_type(first, parser->current_class->base_type.native_type);
 	} else {
 		// Classes in current scope.
 		List<GDScriptParser::ClassNode *> script_classes;
+		bool found = false;
 		get_class_node_current_scope_classes(parser->current_class, &script_classes);
 		for (GDScriptParser::ClassNode *script_class : script_classes) {
+			if (found) {
+				break;
+			}
+
 			if (script_class->identifier && script_class->identifier->name == first) {
 				result = script_class->get_datatype();
 				break;
@@ -608,14 +643,17 @@ GDScriptParser::DataType GDScriptAnalyzer::resolve_datatype(GDScriptParser::Type
 				switch (member.type) {
 					case GDScriptParser::ClassNode::Member::CLASS:
 						result = member.get_datatype();
+						found = true;
 						break;
 					case GDScriptParser::ClassNode::Member::ENUM:
 						result = member.get_datatype();
+						found = true;
 						break;
 					case GDScriptParser::ClassNode::Member::CONSTANT:
 						if (member.get_datatype().is_meta_type) {
 							result = member.get_datatype();
 							result.is_meta_type = false;
+							found = true;
 							break;
 						} else if (Ref<Script>(member.constant->initializer->reduced_value).is_valid()) {
 							Ref<GDScript> gdscript = member.constant->initializer->reduced_value;
@@ -636,6 +674,7 @@ GDScriptParser::DataType GDScriptAnalyzer::resolve_datatype(GDScriptParser::Type
 								result.native_type = script->get_instance_base_type();
 								result.is_meta_type = false;
 							}
+							found = true;
 							break;
 						}
 						[[fallthrough]];
@@ -667,15 +706,17 @@ GDScriptParser::DataType GDScriptAnalyzer::resolve_datatype(GDScriptParser::Type
 			}
 		} else if (result.kind == GDScriptParser::DataType::NATIVE) {
 			// Only enums allowed for native.
-			if (!ClassDB::has_enum(result.native_type, p_type->type_chain[1]->name)) {
-				push_error(vformat(R"(Could not find nested type "%s" under base "%s".)", p_type->type_chain[1]->name, result.to_string()), p_type->type_chain[1]);
-				return bad_type;
-			}
-			if (p_type->type_chain.size() > 2) {
-				push_error(R"(Enums cannot contain nested types.)", p_type->type_chain[2]);
+			if (ClassDB::has_enum(result.native_type, p_type->type_chain[1]->name)) {
+				if (p_type->type_chain.size() > 2) {
+					push_error(R"(Enums cannot contain nested types.)", p_type->type_chain[2]);
+					return bad_type;
+				} else {
+					result = make_native_enum_type(p_type->type_chain[1]->name, result.native_type);
+				}
+			} else {
+				push_error(vformat(R"(Could not find type "%s" in "%s".)", p_type->type_chain[1]->name, first), p_type->type_chain[1]);
 				return bad_type;
 			}
-			result = make_native_enum_type(result.native_type, p_type->type_chain[1]->name);
 		} else {
 			push_error(vformat(R"(Could not find nested type "%s" under base "%s".)", p_type->type_chain[1]->name, result.to_string()), p_type->type_chain[1]);
 			return bad_type;
@@ -804,15 +845,7 @@ void GDScriptAnalyzer::resolve_class_member(GDScriptParser::ClassNode *p_class,
 				check_class_member_name_conflict(p_class, member.m_enum->identifier->name, member.m_enum);
 
 				member.m_enum->set_datatype(resolving_datatype);
-
-				GDScriptParser::DataType enum_type;
-				enum_type.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT;
-				enum_type.kind = GDScriptParser::DataType::ENUM;
-				enum_type.builtin_type = Variant::DICTIONARY;
-				enum_type.enum_type = member.m_enum->identifier->name;
-				enum_type.native_type = p_class->fqcn + "." + member.m_enum->identifier->name;
-				enum_type.is_meta_type = true;
-				enum_type.is_constant = true;
+				GDScriptParser::DataType enum_type = make_enum_type(member.m_enum->identifier->name, p_class->fqcn, true);
 
 				const GDScriptParser::EnumNode *prev_enum = current_enum;
 				current_enum = member.m_enum;
@@ -846,6 +879,7 @@ void GDScriptAnalyzer::resolve_class_member(GDScriptParser::ClassNode *p_class,
 
 				current_enum = prev_enum;
 
+				dictionary.set_read_only(true);
 				member.m_enum->set_datatype(enum_type);
 				member.m_enum->dictionary = dictionary;
 
@@ -892,11 +926,7 @@ void GDScriptAnalyzer::resolve_class_member(GDScriptParser::ClassNode *p_class,
 				// Also update the original references.
 				member.enum_value.parent_enum->values.set(member.enum_value.index, member.enum_value);
 
-				GDScriptParser::DataType datatype;
-				datatype.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT;
-				datatype.kind = GDScriptParser::DataType::BUILTIN;
-				datatype.builtin_type = Variant::INT;
-				member.enum_value.identifier->set_datatype(datatype);
+				member.enum_value.identifier->set_datatype(make_enum_type(UNNAMED_ENUM, p_class->fqcn, false));
 			} break;
 			case GDScriptParser::ClassNode::Member::CLASS:
 				check_class_member_name_conflict(p_class, member.m_class->identifier->name, member.m_class);
@@ -1330,7 +1360,7 @@ void GDScriptAnalyzer::resolve_function_signature(GDScriptParser::FunctionNode *
 		}
 	} else {
 		if (p_function->return_type != nullptr) {
-			p_function->set_datatype(resolve_datatype(p_function->return_type));
+			p_function->set_datatype(type_from_metatype(resolve_datatype(p_function->return_type)));
 		} else {
 			// In case the function is not typed, we can safely assume it's a Variant, so it's okay to mark as "inferred" here.
 			// It's not "undetected" to not mix up with unknown functions.
@@ -1364,7 +1394,7 @@ void GDScriptAnalyzer::resolve_function_signature(GDScriptParser::FunctionNode *
 
 			if (!valid) {
 				// Compute parent signature as a string to show in the error message.
-				String parent_signature = function_name.operator String() + "(";
+				String parent_signature = String(function_name) + "(";
 				int j = 0;
 				for (const GDScriptParser::DataType &par_type : parameters_types) {
 					if (j > 0) {
@@ -2081,13 +2111,15 @@ void GDScriptAnalyzer::reduce_assignment(GDScriptParser::AssignmentNode *p_assig
 	}
 	p_assignment->set_datatype(op_type);
 
+	// If Assignee is a variant, then you can assign anything
+	// When the assigned value has a known type, further checks are possible.
 	if (assignee_type.is_hard_type() && !assignee_type.is_variant() && op_type.is_hard_type()) {
 		if (compatible) {
 			compatible = is_type_compatible(assignee_type, op_type, true, p_assignment->assigned_value);
 			if (!compatible) {
 				// Try reverse test since it can be a masked subtype.
 				if (!is_type_compatible(op_type, assignee_type, true)) {
-					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"(Value of type "%s" cannot be assigned to a variable of type "%s".)", assigned_value_type.to_string(), assignee_type.to_string()), p_assignment->assigned_value);
 				} else {
 					// TODO: Add warning.
 					mark_node_unsafe(p_assignment);
@@ -2141,7 +2173,7 @@ void GDScriptAnalyzer::reduce_assignment(GDScriptParser::AssignmentNode *p_assig
 				if (!id_type.is_hard_type()) {
 					id_type.kind = GDScriptParser::DataType::VARIANT;
 					id_type.type_source = GDScriptParser::DataType::UNDETECTED;
-					identifier->variable_source->set_datatype(id_type);
+					identifier->bind_source->set_datatype(id_type);
 				}
 			} break;
 			default:
@@ -2368,7 +2400,7 @@ void GDScriptAnalyzer::reduce_call(GDScriptParser::CallNode *p_call, bool p_is_a
 
 				switch (err.error) {
 					case Callable::CallError::CALL_ERROR_INVALID_ARGUMENT:
-						push_error(vformat(R"(Invalid argument for %s constructor: argument %d should be %s but is %s.)", Variant::get_type_name(builtin_type), err.argument + 1,
+						push_error(vformat(R"(Invalid argument for %s constructor: argument %d should be "%s" but is "%s".)", Variant::get_type_name(builtin_type), err.argument + 1,
 										   Variant::get_type_name(Variant::Type(err.expected)), p_call->arguments[err.argument]->get_datatype().to_string()),
 								p_call->arguments[err.argument]);
 						break;
@@ -2484,7 +2516,7 @@ void GDScriptAnalyzer::reduce_call(GDScriptParser::CallNode *p_call, bool p_is_a
 				switch (err.error) {
 					case Callable::CallError::CALL_ERROR_INVALID_ARGUMENT: {
 						PropertyInfo wrong_arg = function_info.arguments[err.argument];
-						push_error(vformat(R"*(Invalid argument for "%s()" function: argument %d should be %s but is %s.)*", function_name, err.argument + 1,
+						push_error(vformat(R"*(Invalid argument for "%s()" function: argument %d should be "%s" but is "%s".)*", function_name, err.argument + 1,
 										   type_from_property(wrong_arg).to_string(), p_call->arguments[err.argument]->get_datatype().to_string()),
 								p_call->arguments[err.argument]);
 					} break;
@@ -2537,7 +2569,7 @@ void GDScriptAnalyzer::reduce_call(GDScriptParser::CallNode *p_call, bool p_is_a
 							expected_type_name = Variant::get_type_name((Variant::Type)err.expected);
 						}
 
-						push_error(vformat(R"*(Invalid argument for "%s()" function: argument %d should be %s but is %s.)*", function_name, err.argument + 1,
+						push_error(vformat(R"*(Invalid argument for "%s()" function: argument %d should be "%s" but is "%s".)*", function_name, err.argument + 1,
 										   expected_type_name, p_call->arguments[err.argument]->get_datatype().to_string()),
 								p_call->arguments[err.argument]);
 					} break;
@@ -2683,8 +2715,10 @@ void GDScriptAnalyzer::reduce_call(GDScriptParser::CallNode *p_call, bool p_is_a
 	} else {
 		bool found = false;
 
-		// Check if the name exists as something else.
-		if (!p_call->is_super && callee_type != GDScriptParser::Node::NONE) {
+		// Enums do not have functions other than the built-in dictionary ones.
+		if (base_type.kind == GDScriptParser::DataType::ENUM && base_type.is_meta_type) {
+			push_error(vformat(R"*(Enums only have Dictionary built-in methods. Function "%s()" does not exist for enum "%s".)*", p_call->function_name, base_type.enum_type), p_call->callee);
+		} else if (!p_call->is_super && callee_type != GDScriptParser::Node::NONE) { // Check if the name exists as something else.
 			GDScriptParser::IdentifierNode *callee_id;
 			if (callee_type == GDScriptParser::Node::IDENTIFIER) {
 				callee_id = static_cast<GDScriptParser::IdentifierNode *>(p_call->callee);
@@ -2714,7 +2748,7 @@ void GDScriptAnalyzer::reduce_call(GDScriptParser::CallNode *p_call, bool p_is_a
 			String base_name = is_self && !p_call->is_super ? "self" : base_type.to_string();
 			push_error(vformat(R"*(Function "%s()" not found in base %s.)*", p_call->function_name, base_name), p_call->is_super ? p_call : p_call->callee);
 		} else if (!found && (!p_call->is_super && base_type.is_hard_type() && base_type.kind == GDScriptParser::DataType::NATIVE && base_type.is_meta_type)) {
-			push_error(vformat(R"*(Static function "%s()" not found in base "%s".)*", p_call->function_name, base_type.native_type.operator String()), p_call);
+			push_error(vformat(R"*(Static function "%s()" not found in base "%s".)*", p_call->function_name, base_type.native_type), p_call);
 		}
 	}
 
@@ -2742,19 +2776,48 @@ void GDScriptAnalyzer::reduce_cast(GDScriptParser::CastNode *p_cast) {
 		GDScriptParser::DataType op_type = p_cast->operand->get_datatype();
 		if (!op_type.is_variant()) {
 			bool valid = false;
+			bool more_informative_error = false;
 			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;
+				// 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) {
-				// Convertint int to enum is always valid.
-				valid = true;
+				// 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;
+				}
 			} 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) {
+			if (!valid && !more_informative_error) {
 				push_error(vformat(R"(Invalid cast. Cannot convert from "%s" to "%s".)", op_type.to_string(), cast_type.to_string()), p_cast->cast_type);
 			}
 		}
@@ -2869,26 +2932,14 @@ void GDScriptAnalyzer::reduce_identifier_from_base(GDScriptParser::IdentifierNod
 	if (base.kind == GDScriptParser::DataType::ENUM) {
 		if (base.is_meta_type) {
 			if (base.enum_values.has(name)) {
+				p_identifier->set_datatype(type_from_metatype(base));
 				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;
-				result.enum_values = base.enum_values;
-				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;
 			}
+
+			// Enum does not have this value, return.
+			return;
 		} else {
 			push_error(R"(Cannot get property from enum value.)", p_identifier);
 			return;
@@ -3000,12 +3051,18 @@ void GDScriptAnalyzer::reduce_identifier_from_base(GDScriptParser::IdentifierNod
 			}
 			return;
 		}
+
 		// Check outer constants.
 		// TODO: Allow outer static functions.
 		if (base_class->outer != nullptr) {
 			List<GDScriptParser::ClassNode *> script_classes;
 			get_class_node_current_scope_classes(base_class->outer, &script_classes);
 			for (GDScriptParser::ClassNode *script_class : script_classes) {
+				if (script_class->identifier && script_class->identifier->name == name) {
+					p_identifier->set_datatype(script_class->get_datatype());
+					return;
+				}
+
 				if (script_class->has_member(name)) {
 					resolve_class_member(script_class, name, p_identifier);
 
@@ -3067,35 +3124,39 @@ void GDScriptAnalyzer::reduce_identifier_from_base(GDScriptParser::IdentifierNod
 			return;
 		}
 		if (ClassDB::has_enum(native, name)) {
-			p_identifier->set_datatype(make_native_enum_type(native, name));
+			p_identifier->set_datatype(make_native_enum_type(name, native));
 			p_identifier->source = GDScriptParser::IdentifierNode::MEMBER_CONSTANT;
 			return;
 		}
 		bool valid = false;
+
 		int64_t int_constant = ClassDB::get_integer_constant(native, name, &valid);
 		if (valid) {
 			p_identifier->is_constant = true;
 			p_identifier->reduced_value = int_constant;
-			p_identifier->set_datatype(type_from_variant(int_constant, p_identifier));
 			p_identifier->source = GDScriptParser::IdentifierNode::MEMBER_CONSTANT;
-			return;
+
+			// Check whether this constant, which exists, belongs to an enum
+			StringName enum_name = ClassDB::get_integer_constant_enum(native, name);
+			if (enum_name != StringName()) {
+				p_identifier->set_datatype(make_native_enum_type(enum_name, native, false));
+			} else {
+				p_identifier->set_datatype(type_from_variant(int_constant, p_identifier));
+			}
 		}
 	}
 }
 
 void GDScriptAnalyzer::reduce_identifier(GDScriptParser::IdentifierNode *p_identifier, bool can_be_builtin) {
-	// TODO: This is opportunity to further infer types.
+	// TODO: This is an opportunity to further infer types.
 
-	// Check if we are inside and enum. This allows enum values to access other elements of the same enum.
+	// Check if we are inside an enum. This allows enum values to access other elements of the same enum.
 	if (current_enum) {
 		for (int i = 0; i < current_enum->values.size(); i++) {
 			const GDScriptParser::EnumNode::Value &element = current_enum->values[i];
 			if (element.identifier->name == p_identifier->name) {
-				GDScriptParser::DataType type;
-				type.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT;
-				type.kind = element.parent_enum->identifier ? GDScriptParser::DataType::ENUM : GDScriptParser::DataType::BUILTIN;
-				type.builtin_type = Variant::INT;
-				type.is_constant = true;
+				StringName enum_name = current_enum->identifier->name ? current_enum->identifier->name : UNNAMED_ENUM;
+				GDScriptParser::DataType type = make_enum_type(enum_name, parser->current_class->fqcn, false);
 				if (element.parent_enum->identifier) {
 					type.enum_type = element.parent_enum->identifier->name;
 				}
@@ -3865,12 +3926,13 @@ GDScriptParser::DataType GDScriptAnalyzer::type_from_variant(const Variant &p_va
 	return result;
 }
 
-GDScriptParser::DataType GDScriptAnalyzer::type_from_metatype(const GDScriptParser::DataType &p_meta_type) const {
+GDScriptParser::DataType GDScriptAnalyzer::type_from_metatype(const GDScriptParser::DataType &p_meta_type) {
 	GDScriptParser::DataType result = p_meta_type;
 	result.is_meta_type = false;
-	result.is_constant = false;
 	if (p_meta_type.kind == GDScriptParser::DataType::ENUM) {
 		result.builtin_type = Variant::INT;
+	} else {
+		result.is_constant = false;
 	}
 	return result;
 }
@@ -3928,11 +3990,12 @@ bool GDScriptAnalyzer::get_function_signature(GDScriptParser::Node *p_source, bo
 	r_default_arg_count = 0;
 	StringName function_name = p_function;
 
+	bool was_enum = false;
 	if (p_base_type.kind == GDScriptParser::DataType::ENUM) {
+		was_enum = true;
 		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);
@@ -3955,6 +4018,10 @@ bool GDScriptAnalyzer::get_function_signature(GDScriptParser::Node *p_source, bo
 			if (E.name == p_function) {
 				function_signature_from_info(E, r_return_type, r_par_types, r_default_arg_count, r_static, r_vararg);
 				r_static = Variant::is_builtin_method_static(p_base_type.builtin_type, function_name);
+				// Cannot use non-const methods on enums.
+				if (!r_static && was_enum && !(E.flags & METHOD_FLAG_CONST)) {
+					push_error(vformat(R"*(Cannot call non-const Dictionary function "%s()" on enum "%s".)*", p_function, p_base_type.enum_type), p_source);
+				}
 				return true;
 			}
 		}
@@ -4102,7 +4169,7 @@ bool GDScriptAnalyzer::validate_call_arg(const List<GDScriptParser::DataType> &p
 			// Supertypes are acceptable for dynamic compliance, but it's unsafe.
 			mark_node_unsafe(p_call);
 			if (!is_type_compatible(arg_type, par_type)) {
-				push_error(vformat(R"*(Invalid argument for "%s()" function: argument %d should be %s but is %s.)*",
+				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;

+ 1 - 1
modules/gdscript/gdscript_analyzer.h

@@ -107,7 +107,7 @@ class GDScriptAnalyzer {
 
 	// Helpers.
 	GDScriptParser::DataType type_from_variant(const Variant &p_value, const GDScriptParser::Node *p_source);
-	GDScriptParser::DataType type_from_metatype(const GDScriptParser::DataType &p_meta_type) const;
+	static GDScriptParser::DataType type_from_metatype(const GDScriptParser::DataType &p_meta_type);
 	GDScriptParser::DataType type_from_property(const PropertyInfo &p_property) const;
 	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);

+ 1 - 1
modules/gdscript/gdscript_compiler.cpp

@@ -213,7 +213,7 @@ static bool _have_exact_arguments(const MethodBind *p_method, const Vector<GDScr
 }
 
 GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &codegen, Error &r_error, const GDScriptParser::ExpressionNode *p_expression, bool p_root, bool p_initializer, const GDScriptCodeGenerator::Address &p_index_addr) {
-	if (p_expression->is_constant) {
+	if (p_expression->is_constant && !p_expression->get_datatype().is_meta_type) {
 		return codegen.add_constant(p_expression->reduced_value);
 	}
 

+ 13 - 6
modules/gdscript/gdscript_editor.cpp

@@ -995,9 +995,8 @@ static void _find_identifiers_in_base(const GDScriptCompletionIdentifier &p_base
 	ERR_FAIL_COND(p_recursion_depth > COMPLETION_RECURSION_LIMIT);
 
 	GDScriptParser::DataType base_type = p_base.type;
-	bool _static = base_type.is_meta_type;
 
-	if (_static && base_type.kind != GDScriptParser::DataType::BUILTIN) {
+	if (base_type.is_meta_type && base_type.kind != GDScriptParser::DataType::BUILTIN && base_type.kind != GDScriptParser::DataType::ENUM) {
 		ScriptLanguage::CodeCompletionOption option("new", ScriptLanguage::CODE_COMPLETION_KIND_FUNCTION, ScriptLanguage::LOCATION_LOCAL);
 		option.insert_text += "(";
 		r_result.insert(option.display, option);
@@ -1006,7 +1005,7 @@ static void _find_identifiers_in_base(const GDScriptCompletionIdentifier &p_base
 	while (!base_type.has_no_type()) {
 		switch (base_type.kind) {
 			case GDScriptParser::DataType::CLASS: {
-				_find_identifiers_in_class(base_type.class_type, p_only_functions, _static, false, r_result, p_recursion_depth + 1);
+				_find_identifiers_in_class(base_type.class_type, p_only_functions, base_type.is_meta_type, false, r_result, p_recursion_depth + 1);
 				// This already finds all parent identifiers, so we are done.
 				base_type = GDScriptParser::DataType();
 			} break;
@@ -1014,7 +1013,7 @@ static void _find_identifiers_in_base(const GDScriptCompletionIdentifier &p_base
 				Ref<Script> scr = base_type.script_type;
 				if (scr.is_valid()) {
 					if (!p_only_functions) {
-						if (!_static) {
+						if (!base_type.is_meta_type) {
 							List<PropertyInfo> members;
 							scr->get_script_property_list(&members);
 							for (const PropertyInfo &E : members) {
@@ -1090,7 +1089,7 @@ static void _find_identifiers_in_base(const GDScriptCompletionIdentifier &p_base
 						r_result.insert(option.display, option);
 					}
 
-					if (!_static || Engine::get_singleton()->has_singleton(type)) {
+					if (!base_type.is_meta_type || Engine::get_singleton()->has_singleton(type)) {
 						List<PropertyInfo> pinfo;
 						ClassDB::get_property_list(type, &pinfo);
 						for (const PropertyInfo &E : pinfo) {
@@ -1107,7 +1106,7 @@ static void _find_identifiers_in_base(const GDScriptCompletionIdentifier &p_base
 					}
 				}
 
-				bool only_static = _static && !Engine::get_singleton()->has_singleton(type);
+				bool only_static = base_type.is_meta_type && !Engine::get_singleton()->has_singleton(type);
 
 				List<MethodInfo> methods;
 				ClassDB::get_method_list(type, &methods, false, true);
@@ -1129,6 +1128,7 @@ static void _find_identifiers_in_base(const GDScriptCompletionIdentifier &p_base
 				}
 				return;
 			} break;
+			case GDScriptParser::DataType::ENUM:
 			case GDScriptParser::DataType::BUILTIN: {
 				Callable::CallError err;
 				Variant tmp;
@@ -1156,6 +1156,10 @@ static void _find_identifiers_in_base(const GDScriptCompletionIdentifier &p_base
 				List<MethodInfo> methods;
 				tmp.get_method_list(&methods);
 				for (const MethodInfo &E : methods) {
+					if (base_type.kind == GDScriptParser::DataType::ENUM && base_type.is_meta_type && !(E.flags & METHOD_FLAG_CONST)) {
+						// Enum types are static and cannot change, therefore we skip non-const dictionary methods.
+						continue;
+					}
 					ScriptLanguage::CodeCompletionOption option(E.name, ScriptLanguage::CODE_COMPLETION_KIND_FUNCTION);
 					if (E.arguments.size()) {
 						option.insert_text += "(";
@@ -1364,6 +1368,9 @@ static bool _guess_expression_type(GDScriptParser::CompletionContext &p_context,
 	if (p_expression->is_constant) {
 		// Already has a value, so just use that.
 		r_type = _type_from_variant(p_expression->reduced_value);
+		if (p_expression->get_datatype().kind == GDScriptParser::DataType::ENUM) {
+			r_type.type = p_expression->get_datatype();
+		}
 		found = true;
 	} else {
 		switch (p_expression->type) {

+ 14 - 9
modules/gdscript/gdscript_parser.cpp

@@ -1299,16 +1299,18 @@ GDScriptParser::EnumNode *GDScriptParser::parse_enum() {
 			EnumNode::Value item;
 			GDScriptParser::IdentifierNode *identifier = parse_identifier();
 #ifdef DEBUG_ENABLED
-			for (MethodInfo &info : gdscript_funcs) {
-				if (info.name == identifier->name) {
+			if (!named) { // Named enum identifiers do not shadow anything since you can only access them with NamedEnum.ENUM_VALUE
+				for (MethodInfo &info : gdscript_funcs) {
+					if (info.name == identifier->name) {
+						push_warning(identifier, GDScriptWarning::SHADOWED_GLOBAL_IDENTIFIER, "enum member", identifier->name, "built-in function");
+					}
+				}
+				if (Variant::has_utility_function(identifier->name)) {
 					push_warning(identifier, GDScriptWarning::SHADOWED_GLOBAL_IDENTIFIER, "enum member", identifier->name, "built-in function");
+				} else if (ClassDB::class_exists(identifier->name)) {
+					push_warning(identifier, GDScriptWarning::SHADOWED_GLOBAL_IDENTIFIER, "enum member", identifier->name, "global class");
 				}
 			}
-			if (Variant::has_utility_function(identifier->name)) {
-				push_warning(identifier, GDScriptWarning::SHADOWED_GLOBAL_IDENTIFIER, "enum member", identifier->name, "built-in function");
-			} else if (ClassDB::class_exists(identifier->name)) {
-				push_warning(identifier, GDScriptWarning::SHADOWED_GLOBAL_IDENTIFIER, "enum member", identifier->name, "global class");
-			}
 #endif
 			item.identifier = identifier;
 			item.parent_enum = enum_node;
@@ -4087,8 +4089,11 @@ String GDScriptParser::DataType::to_string() const {
 			}
 			return native_type.operator String();
 		}
-		case ENUM:
-			return enum_type.operator String() + " (enum)";
+		case ENUM: {
+			// native_type contains either the native class defining the enum
+			// or the fully qualified class name of the script defining the enum
+			return String(native_type).get_file(); // Remove path, keep filename
+		}
 		case RESOLVING:
 		case UNRESOLVED:
 			return "<unresolved type>";

+ 2 - 2
modules/gdscript/gdscript_parser.h

@@ -185,8 +185,8 @@ public:
 				case BUILTIN:
 					return builtin_type == p_other.builtin_type;
 				case NATIVE:
-				case ENUM:
-					return native_type == p_other.native_type && enum_type == p_other.enum_type;
+				case ENUM: // Enums use native_type to identify the enum and its base class.
+					return native_type == p_other.native_type;
 				case SCRIPT:
 					return script_type == p_other.script_type;
 				case CLASS:

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

@@ -0,0 +1,3 @@
+enum { V }
+func test():
+	V = 1

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

@@ -0,0 +1,2 @@
+GDTEST_ANALYZER_ERROR
+Cannot assign a new value to a constant.

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

@@ -0,0 +1,3 @@
+enum NamedEnum { V }
+func test():
+	NamedEnum.V = 1

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

@@ -0,0 +1,2 @@
+GDTEST_ANALYZER_ERROR
+Cannot assign a new value to a constant.

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

@@ -0,0 +1,5 @@
+enum MyEnum { ENUM_VALUE_1, ENUM_VALUE_2 }
+enum MyOtherEnum { OTHER_ENUM_VALUE_1, OTHER_ENUM_VALUE_2, OTHER_ENUM_VALUE_3 }
+
+func test():
+	print(MyOtherEnum.OTHER_ENUM_VALUE_3 as MyEnum)

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

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

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

@@ -0,0 +1,4 @@
+enum MyEnum { ENUM_VALUE_1, ENUM_VALUE_2 }
+
+func test():
+	print(2 as MyEnum)

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

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

+ 4 - 0
modules/gdscript/tests/scripts/analyzer/errors/enum_bad_method.gd

@@ -0,0 +1,4 @@
+enum Enum {V1, V2}
+
+func test():
+	Enum.clear()

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

@@ -0,0 +1,2 @@
+GDTEST_ANALYZER_ERROR
+Cannot call non-const Dictionary function "clear()" on enum "Enum".

+ 4 - 0
modules/gdscript/tests/scripts/analyzer/errors/enum_bad_value.gd

@@ -0,0 +1,4 @@
+enum Enum {V1, V2}
+
+func test():
+	var bad = Enum.V3

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

@@ -0,0 +1,2 @@
+GDTEST_ANALYZER_ERROR
+Cannot find member "V3" in base "enum_bad_value.gd::Enum".

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

@@ -1,2 +1,2 @@
 GDTEST_ANALYZER_ERROR
-Cannot assign a value of type "MyOtherEnum (enum)" to a target of type "MyEnum (enum)".
+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".

+ 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 MyOtherEnum (enum) to variable "class_var" with specified type MyEnum (enum).
+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.

+ 5 - 0
modules/gdscript/tests/scripts/analyzer/errors/enum_duplicate_bad_method.gd

@@ -0,0 +1,5 @@
+enum Enum {V1, V2}
+
+func test():
+	var Enum2 = Enum
+	Enum2.clear()

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

@@ -0,0 +1,2 @@
+GDTEST_ANALYZER_ERROR
+Cannot call non-const Dictionary function "clear()" on enum "Enum".

+ 8 - 0
modules/gdscript/tests/scripts/analyzer/errors/enum_function_parameter_wrong_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 enum_func(e : MyEnum) -> void:
+	print(e)
+
+func test():
+	enum_func(MyOtherEnum.OTHER_ENUM_VALUE_1)

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

@@ -0,0 +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".

+ 8 - 0
modules/gdscript/tests/scripts/analyzer/errors/enum_function_return_wrong_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 enum_func() -> MyEnum:
+	return MyOtherEnum.OTHER_ENUM_VALUE_1
+
+func test():
+	print(enum_func())

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

@@ -0,0 +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".

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

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

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

@@ -0,0 +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".

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

@@ -1,2 +1,2 @@
 GDTEST_ANALYZER_ERROR
-Cannot assign a value of type "MyOtherEnum (enum)" to a target of type "MyEnum (enum)".
+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".

+ 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 MyOtherEnum (enum) to variable "local_var" with specified type MyEnum (enum).
+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.

+ 2 - 0
modules/gdscript/tests/scripts/analyzer/errors/enum_native_bad_value.gd

@@ -0,0 +1,2 @@
+func test():
+	var _bad = TileSet.TileShape.THIS_DOES_NOT_EXIST

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

@@ -0,0 +1,2 @@
+GDTEST_ANALYZER_ERROR
+Cannot find member "THIS_DOES_NOT_EXIST" in base "TileSet::TileShape".

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

@@ -0,0 +1,6 @@
+enum MyEnum { VALUE_A, VALUE_B, VALUE_C = 42 }
+func test():
+	const P = preload("../features/enum_value_from_parent.gd")
+	var local_var: MyEnum
+	local_var = P.VALUE_B
+	print(local_var)

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

@@ -0,0 +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".

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

@@ -0,0 +1,8 @@
+class A:
+	enum { V }
+
+class B extends A:
+	enum { V }
+
+func test():
+	pass

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

@@ -0,0 +1,2 @@
+GDTEST_ANALYZER_ERROR
+The member "V" already exists in parent class A.

+ 7 - 0
modules/gdscript/tests/scripts/analyzer/errors/enum_shadows_outer_enum.gd

@@ -0,0 +1,7 @@
+enum { V }
+
+class InnerClass:
+	enum { V }
+
+func test():
+	pass

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

@@ -0,0 +1,2 @@
+GDTEST_PARSER_ERROR
+Name "V" is already used as a class enum value.

+ 7 - 0
modules/gdscript/tests/scripts/analyzer/errors/enum_unnamed_assign_to_named.gd

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

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

@@ -0,0 +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.

+ 2 - 0
modules/gdscript/tests/scripts/analyzer/errors/native_type_errors.gd

@@ -0,0 +1,2 @@
+func test():
+	TileSet.this_does_not_exist # Does not exist

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

@@ -0,0 +1,2 @@
+GDTEST_ANALYZER_ERROR
+Cannot find member "this_does_not_exist" in base "TileSet".

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

@@ -0,0 +1,6 @@
+enum LocalNamed { VALUE_A, VALUE_B, VALUE_C = 42 }
+
+func test():
+	const P = preload("../features/enum_from_outer.gd")
+	var x : LocalNamed
+	x = P.Named.VALUE_A

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

@@ -0,0 +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".

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

@@ -1,2 +1,2 @@
 GDTEST_ANALYZER_ERROR
-Cannot assign a value of type "String" to a target of type "int".
+Value of type "String" cannot be assigned to a variable of type "int".

+ 29 - 0
modules/gdscript/tests/scripts/analyzer/features/enum_access_types.gd

@@ -0,0 +1,29 @@
+class_name EnumAccessOuterClass
+
+class InnerClass:
+	enum MyEnum { V0, V2, V1 }
+
+	static func print_enums():
+		print("Inner - Inner")
+		print(MyEnum.V0, MyEnum.V1, MyEnum.V2)
+		print(InnerClass.MyEnum.V0, InnerClass.MyEnum.V1, InnerClass.MyEnum.V2)
+		print(EnumAccessOuterClass.InnerClass.MyEnum.V0, EnumAccessOuterClass.InnerClass.MyEnum.V1, EnumAccessOuterClass.InnerClass.MyEnum.V2)
+
+		print("Inner - Outer")
+		print(EnumAccessOuterClass.MyEnum.V0, EnumAccessOuterClass.MyEnum.V1, EnumAccessOuterClass.MyEnum.V2)
+
+
+enum MyEnum { V0, V1, V2 }
+
+func print_enums():
+	print("Outer - Outer")
+	print(MyEnum.V0, MyEnum.V1, MyEnum.V2)
+	print(EnumAccessOuterClass.MyEnum.V0, EnumAccessOuterClass.MyEnum.V1, EnumAccessOuterClass.MyEnum.V2)
+
+	print("Outer - Inner")
+	print(InnerClass.MyEnum.V0, InnerClass.MyEnum.V1, InnerClass.MyEnum.V2)
+	print(EnumAccessOuterClass.InnerClass.MyEnum.V0, EnumAccessOuterClass.InnerClass.MyEnum.V1, EnumAccessOuterClass.InnerClass.MyEnum.V2)
+
+func test():
+	print_enums()
+	InnerClass.print_enums()

+ 13 - 0
modules/gdscript/tests/scripts/analyzer/features/enum_access_types.out

@@ -0,0 +1,13 @@
+GDTEST_OK
+Outer - Outer
+012
+012
+Outer - Inner
+021
+021
+Inner - Inner
+021
+021
+021
+Inner - Outer
+012

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

@@ -0,0 +1,13 @@
+enum Enum {V1, V2}
+
+func test():
+	var enumAsDict : Dictionary = Enum.duplicate()
+	var enumAsVariant = Enum.duplicate()
+	print(Enum.has("V1"))
+	print(enumAsDict.has("V1"))
+	print(enumAsVariant.has("V1"))
+	enumAsDict.clear()
+	enumAsVariant.clear()
+	print(Enum.has("V1"))
+	print(enumAsDict.has("V1"))
+	print(enumAsVariant.has("V1"))

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

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

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

@@ -0,0 +1,13 @@
+class A:
+	enum Named { VALUE_A, VALUE_B, VALUE_C = 42 }
+
+class B extends A:
+	var a = Named.VALUE_A
+	var b = Named.VALUE_B
+	var c = Named.VALUE_C
+
+func test():
+	var test_instance = B.new()
+	prints("a", test_instance.a, test_instance.a == A.Named.VALUE_A)
+	prints("b", test_instance.b, test_instance.b == A.Named.VALUE_B)
+	prints("c", test_instance.c, test_instance.c == B.Named.VALUE_C)

+ 0 - 0
modules/gdscript/tests/scripts/analyzer/features/enum_from_parent.out → modules/gdscript/tests/scripts/analyzer/features/enum_from_base.out


+ 0 - 2
modules/gdscript/tests/scripts/analyzer/features/enum_from_parent.gd → modules/gdscript/tests/scripts/analyzer/features/enum_from_outer.gd

@@ -1,5 +1,3 @@
-extends Node
-
 enum Named { VALUE_A, VALUE_B, VALUE_C = 42 }
 
 class Test:

+ 4 - 0
modules/gdscript/tests/scripts/analyzer/features/enum_from_outer.out

@@ -0,0 +1,4 @@
+GDTEST_OK
+a 0 true
+b 1 true
+c 42 true

+ 112 - 0
modules/gdscript/tests/scripts/analyzer/features/enum_function_typecheck.gd

@@ -0,0 +1,112 @@
+class_name EnumFunctionTypecheckOuterClass
+
+enum MyEnum { V0, V1, V2 }
+
+class InnerClass:
+	enum MyEnum { V0, V2, V1 }
+
+	func inner_inner_no_class(e : MyEnum) -> MyEnum:
+		print(e)
+		return e
+
+	func inner_inner_class(e : InnerClass.MyEnum) -> InnerClass.MyEnum:
+		print(e)
+		return e
+
+	func inner_inner_class_class(e : EnumFunctionTypecheckOuterClass.InnerClass.MyEnum) -> EnumFunctionTypecheckOuterClass.InnerClass.MyEnum:
+		print(e)
+		return e
+
+	func inner_outer(e : EnumFunctionTypecheckOuterClass.MyEnum) -> EnumFunctionTypecheckOuterClass.MyEnum:
+		print(e)
+		return e
+
+	func test():
+		var _d
+		print("Inner")
+
+		var o := EnumFunctionTypecheckOuterClass.new()
+
+		_d = o.outer_outer_no_class(EnumFunctionTypecheckOuterClass.MyEnum.V1)
+		print()
+		_d = o.outer_outer_class(EnumFunctionTypecheckOuterClass.MyEnum.V1)
+		print()
+		_d = o.outer_inner_class(MyEnum.V1)
+		_d = o.outer_inner_class(InnerClass.MyEnum.V1)
+		_d = o.outer_inner_class(EnumFunctionTypecheckOuterClass.InnerClass.MyEnum.V1)
+		print()
+		_d = o.outer_inner_class_class(MyEnum.V1)
+		_d = o.outer_inner_class_class(InnerClass.MyEnum.V1)
+		_d = o.outer_inner_class_class(EnumFunctionTypecheckOuterClass.InnerClass.MyEnum.V1)
+		print()
+		print()
+
+
+		_d = inner_inner_no_class(MyEnum.V1)
+		_d = inner_inner_no_class(InnerClass.MyEnum.V1)
+		_d = inner_inner_no_class(EnumFunctionTypecheckOuterClass.InnerClass.MyEnum.V1)
+		print()
+		_d = inner_inner_class(MyEnum.V1)
+		_d = inner_inner_class(InnerClass.MyEnum.V1)
+		_d = inner_inner_class(EnumFunctionTypecheckOuterClass.InnerClass.MyEnum.V1)
+		print()
+		_d = inner_inner_class_class(MyEnum.V1)
+		_d = inner_inner_class_class(InnerClass.MyEnum.V1)
+		_d = inner_inner_class_class(EnumFunctionTypecheckOuterClass.InnerClass.MyEnum.V1)
+		print()
+		_d = inner_outer(EnumFunctionTypecheckOuterClass.MyEnum.V1)
+		print()
+		print()
+
+
+func outer_outer_no_class(e : MyEnum) -> MyEnum:
+	print(e)
+	return e
+
+func outer_outer_class(e : EnumFunctionTypecheckOuterClass.MyEnum) -> EnumFunctionTypecheckOuterClass.MyEnum:
+	print(e)
+	return e
+
+func outer_inner_class(e : InnerClass.MyEnum) -> InnerClass.MyEnum:
+	print(e)
+	return e
+
+func outer_inner_class_class(e : EnumFunctionTypecheckOuterClass.InnerClass.MyEnum) -> EnumFunctionTypecheckOuterClass.InnerClass.MyEnum:
+	print(e)
+	return e
+
+func test():
+	var _d
+	print("Outer")
+
+	_d = outer_outer_no_class(MyEnum.V1)
+	_d = outer_outer_no_class(EnumFunctionTypecheckOuterClass.MyEnum.V1)
+	print()
+	_d = outer_outer_class(MyEnum.V1)
+	_d = outer_outer_class(EnumFunctionTypecheckOuterClass.MyEnum.V1)
+	print()
+	_d = outer_inner_class(InnerClass.MyEnum.V1)
+	_d = outer_inner_class(EnumFunctionTypecheckOuterClass.InnerClass.MyEnum.V1)
+	print()
+	_d = outer_inner_class_class(InnerClass.MyEnum.V1)
+	_d = outer_inner_class_class(EnumFunctionTypecheckOuterClass.InnerClass.MyEnum.V1)
+	print()
+	print()
+
+	var i := EnumFunctionTypecheckOuterClass.InnerClass.new()
+
+	_d = i.inner_inner_no_class(InnerClass.MyEnum.V1)
+	_d = i.inner_inner_no_class(EnumFunctionTypecheckOuterClass.InnerClass.MyEnum.V1)
+	print()
+	_d = i.inner_inner_class(InnerClass.MyEnum.V1)
+	_d = i.inner_inner_class(EnumFunctionTypecheckOuterClass.InnerClass.MyEnum.V1)
+	print()
+	_d = i.inner_inner_class_class(InnerClass.MyEnum.V1)
+	_d = i.inner_inner_class_class(EnumFunctionTypecheckOuterClass.InnerClass.MyEnum.V1)
+	print()
+	_d = i.inner_outer(MyEnum.V1)
+	_d = i.inner_outer(EnumFunctionTypecheckOuterClass.MyEnum.V1)
+	print()
+	print()
+
+	i.test()

+ 55 - 0
modules/gdscript/tests/scripts/analyzer/features/enum_function_typecheck.out

@@ -0,0 +1,55 @@
+GDTEST_OK
+Outer
+1
+1
+
+1
+1
+
+2
+2
+
+2
+2
+
+
+2
+2
+
+2
+2
+
+2
+2
+
+1
+1
+
+
+Inner
+1
+
+1
+
+2
+2
+2
+
+2
+2
+2
+
+
+2
+2
+2
+
+2
+2
+2
+
+2
+2
+2
+
+1

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

@@ -0,0 +1,16 @@
+const A := 1
+enum { B }
+enum NamedEnum { C }
+
+class Parent:
+	const D := 2
+	enum { E }
+	enum NamedEnum2 { F }
+
+class Child extends Parent:
+	enum TestEnum { A, B, C, D, E, F, Node, Object, Child, Parent}
+
+func test():
+	print(A, B, NamedEnum.C, Parent.D, Parent.E, Parent.NamedEnum2.F)
+	print(Child.TestEnum.A, Child.TestEnum.B, Child.TestEnum.C, Child.TestEnum.D, Child.TestEnum.E, Child.TestEnum.F)
+	print(Child.TestEnum.Node, Child.TestEnum.Object, Child.TestEnum.Child, Child.TestEnum.Parent)

+ 4 - 0
modules/gdscript/tests/scripts/analyzer/features/enum_named_no_shadow.out

@@ -0,0 +1,4 @@
+GDTEST_OK
+100200
+012345
+6789

+ 19 - 0
modules/gdscript/tests/scripts/analyzer/features/enum_native_access_types.gd

@@ -0,0 +1,19 @@
+func print_enum(e : TileSet.TileShape) -> TileSet.TileShape:
+	print(e)
+	return e
+
+func test():
+	var v : TileSet.TileShape
+	v = TileSet.TILE_SHAPE_SQUARE
+	v = print_enum(v)
+	v = print_enum(TileSet.TILE_SHAPE_SQUARE)
+	v = TileSet.TileShape.TILE_SHAPE_SQUARE
+	v = print_enum(v)
+	v = print_enum(TileSet.TileShape.TILE_SHAPE_SQUARE)
+
+	v = TileSet.TILE_SHAPE_ISOMETRIC
+	v = print_enum(v)
+	v = print_enum(TileSet.TILE_SHAPE_ISOMETRIC)
+	v = TileSet.TileShape.TILE_SHAPE_ISOMETRIC
+	v = print_enum(v)
+	v = print_enum(TileSet.TileShape.TILE_SHAPE_ISOMETRIC)

+ 9 - 0
modules/gdscript/tests/scripts/analyzer/features/enum_native_access_types.out

@@ -0,0 +1,9 @@
+GDTEST_OK
+0
+0
+0
+0
+1
+1
+1
+1

+ 86 - 0
modules/gdscript/tests/scripts/analyzer/features/enum_typecheck_inner_class.gd

@@ -0,0 +1,86 @@
+class_name EnumTypecheckOuterClass
+
+enum MyEnum { V0, V1, V2 }
+
+class InnerClass:
+	enum MyEnum { V0, V2, V1 }
+
+	static func test_inner_from_inner():
+		print("Inner - Inner")
+		var e1 : MyEnum
+		var e2 : InnerClass.MyEnum
+		var e3 : EnumTypecheckOuterClass.InnerClass.MyEnum
+
+		print("Self ", e1, e2, e3)
+		e1 = MyEnum.V1
+		e2 = MyEnum.V1
+		e3 = MyEnum.V1
+		print("MyEnum ", e1, e2, e3)
+		e1 = InnerClass.MyEnum.V1
+		e2 = InnerClass.MyEnum.V1
+		e3 = InnerClass.MyEnum.V1
+		print("Inner.MyEnum ", e1, e2, e3)
+		e1 = EnumTypecheckOuterClass.InnerClass.MyEnum.V1
+		e2 = EnumTypecheckOuterClass.InnerClass.MyEnum.V1
+		e3 = EnumTypecheckOuterClass.InnerClass.MyEnum.V1
+		print("Outer.Inner.MyEnum ", e1, e2, e3)
+
+		e1 = e2
+		e1 = e3
+		e2 = e1
+		e2 = e3
+		e3 = e1
+		e3 = e2
+
+		print()
+
+	static func test_outer_from_inner():
+		print("Inner - Outer")
+		var e : EnumTypecheckOuterClass.MyEnum
+
+		e = EnumTypecheckOuterClass.MyEnum.V1
+		print("Outer.MyEnum ", e)
+
+		print()
+
+func test_outer_from_outer():
+	print("Outer - Outer")
+	var e1 : MyEnum
+	var e2 : EnumTypecheckOuterClass.MyEnum
+
+	print("Self ", e1, e2)
+	e1 = MyEnum.V1
+	e2 = MyEnum.V1
+	print("Outer ", e1, e2)
+	e1 = EnumTypecheckOuterClass.MyEnum.V1
+	e2 = EnumTypecheckOuterClass.MyEnum.V1
+	print("Outer.MyEnum ", e1, e2)
+
+	e1 = e2
+	e2 = e1
+
+	print()
+
+func test_inner_from_outer():
+	print("Outer - Inner")
+	var e1 : InnerClass.MyEnum
+	var e2 : EnumTypecheckOuterClass.InnerClass.MyEnum
+
+	print("Inner ", e1, e2)
+	e1 = InnerClass.MyEnum.V1
+	e2 = InnerClass.MyEnum.V1
+	print("Outer.Inner ", e1, e2)
+	e1 = EnumTypecheckOuterClass.InnerClass.MyEnum.V1
+	e2 = EnumTypecheckOuterClass.InnerClass.MyEnum.V1
+	print("Outer.Inner.MyEnum ", e1, e2)
+
+	e1 = e2
+	e2 = e1
+
+	print()
+
+func test():
+	test_outer_from_outer()
+	test_inner_from_outer()
+	InnerClass.test_outer_from_inner()
+	InnerClass.test_inner_from_inner()

+ 19 - 0
modules/gdscript/tests/scripts/analyzer/features/enum_typecheck_inner_class.out

@@ -0,0 +1,19 @@
+GDTEST_OK
+Outer - Outer
+Self 00
+Outer 11
+Outer.MyEnum 11
+
+Outer - Inner
+Inner 00
+Outer.Inner 22
+Outer.Inner.MyEnum 22
+
+Inner - Outer
+Outer.MyEnum 1
+
+Inner - Inner
+Self 000
+MyEnum 222
+Inner.MyEnum 222
+Outer.Inner.MyEnum 222