You've already forked godot
mirror of
https://github.com/godotengine/godot.git
synced 2025-11-21 14:57:09 +00:00
GDScript: Treat enum values as int and enum types as dictionary
Since enums resolve to a dictionary at runtime, calling dictionary methods on an enum type is a valid use case. This ensures this is true by adding test cases. This also makes enum values be treated as ints when used in operations.
This commit is contained in:
@@ -108,7 +108,7 @@ static GDScriptParser::DataType make_native_enum_type(const StringName &p_native
|
|||||||
GDScriptParser::DataType type;
|
GDScriptParser::DataType type;
|
||||||
type.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT;
|
type.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT;
|
||||||
type.kind = GDScriptParser::DataType::ENUM;
|
type.kind = GDScriptParser::DataType::ENUM;
|
||||||
type.builtin_type = Variant::OBJECT;
|
type.builtin_type = Variant::INT;
|
||||||
type.is_constant = true;
|
type.is_constant = true;
|
||||||
type.is_meta_type = true;
|
type.is_meta_type = true;
|
||||||
|
|
||||||
@@ -2416,6 +2416,11 @@ void GDScriptAnalyzer::reduce_call(GDScriptParser::CallNode *p_call, bool p_is_a
|
|||||||
}
|
}
|
||||||
validate_call_arg(par_types, default_arg_count, is_vararg, p_call);
|
validate_call_arg(par_types, default_arg_count, is_vararg, p_call);
|
||||||
|
|
||||||
|
if (base_type.kind == GDScriptParser::DataType::ENUM && base_type.is_meta_type) {
|
||||||
|
// Enum type is treated as a dictionary value for function calls.
|
||||||
|
base_type.is_meta_type = false;
|
||||||
|
}
|
||||||
|
|
||||||
if (is_self && parser->current_function != nullptr && parser->current_function->is_static && !is_static) {
|
if (is_self && parser->current_function != nullptr && parser->current_function->is_static && !is_static) {
|
||||||
push_error(vformat(R"*(Cannot call non-static function "%s()" from static function "%s()".)*", p_call->function_name, parser->current_function->identifier->name), p_call->callee);
|
push_error(vformat(R"*(Cannot call non-static function "%s()" from static function "%s()".)*", p_call->function_name, parser->current_function->identifier->name), p_call->callee);
|
||||||
} else if (!is_self && base_type.is_meta_type && !is_static) {
|
} else if (!is_self && base_type.is_meta_type && !is_static) {
|
||||||
@@ -2478,7 +2483,7 @@ void GDScriptAnalyzer::reduce_cast(GDScriptParser::CastNode *p_cast) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
cast_type.is_meta_type = false; // The casted value won't be a type name.
|
cast_type = type_from_metatype(cast_type); // The casted value won't be a type name.
|
||||||
p_cast->set_datatype(cast_type);
|
p_cast->set_datatype(cast_type);
|
||||||
|
|
||||||
if (!cast_type.is_variant()) {
|
if (!cast_type.is_variant()) {
|
||||||
@@ -2593,6 +2598,34 @@ void GDScriptAnalyzer::reduce_identifier_from_base(GDScriptParser::IdentifierNod
|
|||||||
|
|
||||||
const StringName &name = p_identifier->name;
|
const StringName &name = p_identifier->name;
|
||||||
|
|
||||||
|
if (base.kind == GDScriptParser::DataType::ENUM) {
|
||||||
|
if (base.is_meta_type) {
|
||||||
|
if (base.enum_values.has(name)) {
|
||||||
|
p_identifier->is_constant = true;
|
||||||
|
p_identifier->reduced_value = base.enum_values[name];
|
||||||
|
|
||||||
|
GDScriptParser::DataType result;
|
||||||
|
result.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT;
|
||||||
|
result.kind = GDScriptParser::DataType::ENUM;
|
||||||
|
result.is_constant = true;
|
||||||
|
result.builtin_type = Variant::INT;
|
||||||
|
result.native_type = base.native_type;
|
||||||
|
result.enum_type = base.enum_type;
|
||||||
|
p_identifier->set_datatype(result);
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
// Consider as a Dictionary, so it can be anything.
|
||||||
|
// This will be evaluated in the next if block.
|
||||||
|
base.kind = GDScriptParser::DataType::BUILTIN;
|
||||||
|
base.builtin_type = Variant::DICTIONARY;
|
||||||
|
base.is_meta_type = false;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
push_error(R"(Cannot get property from enum value.)", p_identifier);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (base.kind == GDScriptParser::DataType::BUILTIN) {
|
if (base.kind == GDScriptParser::DataType::BUILTIN) {
|
||||||
if (base.is_meta_type) {
|
if (base.is_meta_type) {
|
||||||
bool valid = true;
|
bool valid = true;
|
||||||
@@ -2639,33 +2672,6 @@ void GDScriptAnalyzer::reduce_identifier_from_base(GDScriptParser::IdentifierNod
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (base.kind == GDScriptParser::DataType::ENUM) {
|
|
||||||
if (base.is_meta_type) {
|
|
||||||
if (base.enum_values.has(name)) {
|
|
||||||
p_identifier->is_constant = true;
|
|
||||||
p_identifier->reduced_value = base.enum_values[name];
|
|
||||||
|
|
||||||
GDScriptParser::DataType result;
|
|
||||||
result.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT;
|
|
||||||
result.kind = GDScriptParser::DataType::ENUM;
|
|
||||||
result.is_constant = true;
|
|
||||||
result.builtin_type = Variant::INT;
|
|
||||||
result.native_type = base.native_type;
|
|
||||||
result.enum_type = base.enum_type;
|
|
||||||
p_identifier->set_datatype(result);
|
|
||||||
} else {
|
|
||||||
// Consider as a Dictionary, so it can be anything.
|
|
||||||
GDScriptParser::DataType dummy;
|
|
||||||
dummy.type_source = GDScriptParser::DataType::UNDETECTED;
|
|
||||||
dummy.kind = GDScriptParser::DataType::VARIANT;
|
|
||||||
p_identifier->set_datatype(dummy);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
push_error(R"(Cannot get property from enum value.)", p_identifier);
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
GDScriptParser::ClassNode *base_class = base.class_type;
|
GDScriptParser::ClassNode *base_class = base.class_type;
|
||||||
|
|
||||||
// TODO: Switch current class/function/suite here to avoid misrepresenting identifiers (in recursive reduce calls).
|
// TODO: Switch current class/function/suite here to avoid misrepresenting identifiers (in recursive reduce calls).
|
||||||
@@ -3501,6 +3507,9 @@ GDScriptParser::DataType GDScriptAnalyzer::type_from_metatype(const GDScriptPars
|
|||||||
GDScriptParser::DataType result = p_meta_type;
|
GDScriptParser::DataType result = p_meta_type;
|
||||||
result.is_meta_type = false;
|
result.is_meta_type = false;
|
||||||
result.is_constant = false;
|
result.is_constant = false;
|
||||||
|
if (p_meta_type.kind == GDScriptParser::DataType::ENUM) {
|
||||||
|
result.builtin_type = Variant::INT;
|
||||||
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -3557,6 +3566,18 @@ bool GDScriptAnalyzer::get_function_signature(GDScriptParser::CallNode *p_source
|
|||||||
r_default_arg_count = 0;
|
r_default_arg_count = 0;
|
||||||
StringName function_name = p_function;
|
StringName function_name = p_function;
|
||||||
|
|
||||||
|
if (p_base_type.kind == GDScriptParser::DataType::ENUM) {
|
||||||
|
if (p_base_type.is_meta_type) {
|
||||||
|
// Enum type can be treated as a dictionary value.
|
||||||
|
p_base_type.kind = GDScriptParser::DataType::BUILTIN;
|
||||||
|
p_base_type.builtin_type = Variant::DICTIONARY;
|
||||||
|
p_base_type.is_meta_type = false;
|
||||||
|
} else {
|
||||||
|
push_error("Cannot call function on enum value.", p_source);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (p_base_type.kind == GDScriptParser::DataType::BUILTIN) {
|
if (p_base_type.kind == GDScriptParser::DataType::BUILTIN) {
|
||||||
// Construct a base type to get methods.
|
// Construct a base type to get methods.
|
||||||
Callable::CallError err;
|
Callable::CallError err;
|
||||||
@@ -3807,6 +3828,22 @@ GDScriptParser::DataType GDScriptAnalyzer::get_operation_type(Variant::Operator
|
|||||||
|
|
||||||
Variant::Type a_type = p_a.builtin_type;
|
Variant::Type a_type = p_a.builtin_type;
|
||||||
Variant::Type b_type = p_b.builtin_type;
|
Variant::Type b_type = p_b.builtin_type;
|
||||||
|
|
||||||
|
if (p_a.kind == GDScriptParser::DataType::ENUM) {
|
||||||
|
if (p_a.is_meta_type) {
|
||||||
|
a_type = Variant::DICTIONARY;
|
||||||
|
} else {
|
||||||
|
a_type = Variant::INT;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (p_b.kind == GDScriptParser::DataType::ENUM) {
|
||||||
|
if (p_b.is_meta_type) {
|
||||||
|
b_type = Variant::DICTIONARY;
|
||||||
|
} else {
|
||||||
|
b_type = Variant::INT;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Variant::ValidatedOperatorEvaluator op_eval = Variant::get_validated_operator_evaluator(p_operation, a_type, b_type);
|
Variant::ValidatedOperatorEvaluator op_eval = Variant::get_validated_operator_evaluator(p_operation, a_type, b_type);
|
||||||
|
|
||||||
bool hard_operation = p_a.is_hard_type() && p_b.is_hard_type();
|
bool hard_operation = p_a.is_hard_type() && p_b.is_hard_type();
|
||||||
|
|||||||
@@ -143,7 +143,11 @@ GDScriptDataType GDScriptCompiler::_gdtype_from_datatype(const GDScriptParser::D
|
|||||||
case GDScriptParser::DataType::ENUM:
|
case GDScriptParser::DataType::ENUM:
|
||||||
result.has_type = true;
|
result.has_type = true;
|
||||||
result.kind = GDScriptDataType::BUILTIN;
|
result.kind = GDScriptDataType::BUILTIN;
|
||||||
|
if (p_datatype.is_meta_type) {
|
||||||
|
result.builtin_type = Variant::DICTIONARY;
|
||||||
|
} else {
|
||||||
result.builtin_type = Variant::INT;
|
result.builtin_type = Variant::INT;
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
case GDScriptParser::DataType::UNRESOLVED: {
|
case GDScriptParser::DataType::UNRESOLVED: {
|
||||||
ERR_PRINT("Parser bug: converting unresolved type.");
|
ERR_PRINT("Parser bug: converting unresolved type.");
|
||||||
@@ -468,7 +472,14 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &code
|
|||||||
} break;
|
} break;
|
||||||
case GDScriptParser::Node::CAST: {
|
case GDScriptParser::Node::CAST: {
|
||||||
const GDScriptParser::CastNode *cn = static_cast<const GDScriptParser::CastNode *>(p_expression);
|
const GDScriptParser::CastNode *cn = static_cast<const GDScriptParser::CastNode *>(p_expression);
|
||||||
GDScriptDataType cast_type = _gdtype_from_datatype(cn->cast_type->get_datatype());
|
GDScriptParser::DataType og_cast_type = cn->cast_type->get_datatype();
|
||||||
|
GDScriptDataType cast_type = _gdtype_from_datatype(og_cast_type);
|
||||||
|
|
||||||
|
if (og_cast_type.kind == GDScriptParser::DataType::ENUM) {
|
||||||
|
// Enum types are usually treated as dictionaries, but in this case we want to cast to an integer.
|
||||||
|
cast_type.kind = GDScriptDataType::BUILTIN;
|
||||||
|
cast_type.builtin_type = Variant::INT;
|
||||||
|
}
|
||||||
|
|
||||||
// Create temporary for result first since it will be deleted last.
|
// Create temporary for result first since it will be deleted last.
|
||||||
GDScriptCodeGenerator::Address result = codegen.add_temporary(cast_type);
|
GDScriptCodeGenerator::Address result = codegen.add_temporary(cast_type);
|
||||||
|
|||||||
@@ -0,0 +1,21 @@
|
|||||||
|
# Enum is equivalent to int for comparisons and operations.
|
||||||
|
enum MyEnum {
|
||||||
|
ZERO,
|
||||||
|
ONE,
|
||||||
|
TWO,
|
||||||
|
}
|
||||||
|
|
||||||
|
enum OtherEnum {
|
||||||
|
ZERO,
|
||||||
|
ONE,
|
||||||
|
TWO,
|
||||||
|
}
|
||||||
|
|
||||||
|
func test():
|
||||||
|
print(MyEnum.ZERO == OtherEnum.ZERO)
|
||||||
|
print(MyEnum.ZERO == 1)
|
||||||
|
print(MyEnum.ZERO != OtherEnum.ONE)
|
||||||
|
print(MyEnum.ZERO != 0)
|
||||||
|
|
||||||
|
print(MyEnum.ONE + OtherEnum.TWO)
|
||||||
|
print(2 - MyEnum.ONE)
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
GDTEST_OK
|
||||||
|
true
|
||||||
|
false
|
||||||
|
true
|
||||||
|
false
|
||||||
|
3
|
||||||
|
1
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
enum MyEnum {
|
||||||
|
ZERO,
|
||||||
|
ONE,
|
||||||
|
TWO,
|
||||||
|
}
|
||||||
|
|
||||||
|
func test():
|
||||||
|
for key in MyEnum.keys():
|
||||||
|
prints(key, MyEnum[key])
|
||||||
|
|
||||||
|
# https://github.com/godotengine/godot/issues/55491
|
||||||
|
for key in MyEnum:
|
||||||
|
prints(key, MyEnum[key])
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
GDTEST_OK
|
||||||
|
ZERO 0
|
||||||
|
ONE 1
|
||||||
|
TWO 2
|
||||||
|
ZERO 0
|
||||||
|
ONE 1
|
||||||
|
TWO 2
|
||||||
Reference in New Issue
Block a user