Merge pull request #113228 from dalexeev/gds-improve-const-eval-array-dict

GDScript: Improve evaluation of constant expressions with arrays/dictionaries
This commit is contained in:
Thaddeus Crews 2026-01-26 13:14:24 -06:00
commit f5d21bfa9c
No known key found for this signature in database
GPG key ID: 8C6E5FEB5FC03CCC
9 changed files with 446 additions and 115 deletions

View file

@ -3433,14 +3433,24 @@ bool Variant::is_ref_counted() const {
bool Variant::is_type_shared(Variant::Type p_type) {
switch (p_type) {
case OBJECT:
case ARRAY:
case DICTIONARY:
case ARRAY:
// NOTE: Packed array constructors **do** copies (unlike `Array()` and `Dictionary()`),
// whereas they pass by reference when inside a `Variant`.
case PACKED_BYTE_ARRAY:
case PACKED_INT32_ARRAY:
case PACKED_INT64_ARRAY:
case PACKED_FLOAT32_ARRAY:
case PACKED_FLOAT64_ARRAY:
case PACKED_STRING_ARRAY:
case PACKED_VECTOR2_ARRAY:
case PACKED_VECTOR3_ARRAY:
case PACKED_COLOR_ARRAY:
case PACKED_VECTOR4_ARRAY:
return true;
default: {
}
default:
return false;
}
return false;
}
bool Variant::is_shared() const {

View file

@ -113,6 +113,8 @@ static GDScriptParser::DataType make_native_meta_type(const StringName &p_class_
return type;
}
// WARNING: Use this function **only** to create non-GDScript script metatypes. Otherwise, `check_type_compatibility()`
// may return an incorrect result due to the assumption "A script type cannot be a subtype of a GDScript class".
static GDScriptParser::DataType make_script_meta_type(const Ref<Script> &p_script) {
GDScriptParser::DataType type;
type.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT;
@ -1688,13 +1690,13 @@ void GDScriptAnalyzer::resolve_annotation(GDScriptParser::AnnotationNode *p_anno
reduce_expression(argument);
if (!argument->is_constant) {
bool is_argument_value_reduced = false;
Variant value = make_expression_reduced_value(argument, is_argument_value_reduced);
if (!is_argument_value_reduced) {
push_error(vformat(R"(Argument %d of annotation "%s" isn't a constant expression.)", i + 1, p_annotation->name), argument);
return;
}
Variant value = argument->reduced_value;
if (value.get_type() != argument_info.type) {
#ifdef DEBUG_ENABLED
if (argument_info.type == Variant::INT && value.get_type() == Variant::FLOAT) {
@ -2879,8 +2881,7 @@ void GDScriptAnalyzer::reduce_assignment(GDScriptParser::AssignmentNode *p_assig
if (id_type.is_hard_type()) {
switch (id_type.kind) {
case GDScriptParser::DataType::BUILTIN:
// TODO: Change `Variant::is_type_shared()` to include packed arrays?
need_warn = !Variant::is_type_shared(id_type.builtin_type) && id_type.builtin_type < Variant::PACKED_BYTE_ARRAY;
need_warn = !Variant::is_type_shared(id_type.builtin_type);
break;
case GDScriptParser::DataType::ENUM:
need_warn = true;
@ -3113,7 +3114,10 @@ void GDScriptAnalyzer::reduce_binary_op(GDScriptParser::BinaryOpNode *p_binary_o
}
#endif // DEBUG_ENABLED
if (p_binary_op->left_operand->is_constant && p_binary_op->right_operand->is_constant) {
if (p_binary_op->left_operand->is_constant &&
p_binary_op->right_operand->is_constant &&
!p_binary_op->left_operand->reduced_value.is_shared() &&
!p_binary_op->right_operand->reduced_value.is_shared()) {
p_binary_op->is_constant = true;
if (p_binary_op->variant_op < Variant::OP_MAX) {
bool valid = false;
@ -3257,30 +3261,9 @@ void GDScriptAnalyzer::reduce_call(GDScriptParser::CallNode *p_call, bool p_is_a
call_type.kind = GDScriptParser::DataType::BUILTIN;
call_type.builtin_type = builtin_type;
bool safe_to_fold = true;
switch (builtin_type) {
// Those are stored by reference so not suited for compile-time construction.
// Because in this case they would be the same reference in all constructed values.
case Variant::OBJECT:
case Variant::DICTIONARY:
case Variant::ARRAY:
case Variant::PACKED_BYTE_ARRAY:
case Variant::PACKED_INT32_ARRAY:
case Variant::PACKED_INT64_ARRAY:
case Variant::PACKED_FLOAT32_ARRAY:
case Variant::PACKED_FLOAT64_ARRAY:
case Variant::PACKED_STRING_ARRAY:
case Variant::PACKED_VECTOR2_ARRAY:
case Variant::PACKED_VECTOR3_ARRAY:
case Variant::PACKED_COLOR_ARRAY:
case Variant::PACKED_VECTOR4_ARRAY:
safe_to_fold = false;
break;
default:
break;
}
if (all_is_constant && safe_to_fold) {
// Reference types are not suited for compile-time construction.
// Because in this case they would be the same reference in all constructed values.
if (all_is_constant && !Variant::is_type_shared(builtin_type)) {
// Construct here.
Vector<const Variant *> args;
for (int i = 0; i < p_call->arguments.size(); i++) {
@ -5212,9 +5195,9 @@ void GDScriptAnalyzer::reduce_type_test(GDScriptParser::TypeTestNode *p_type_tes
p_type_test->is_constant = true;
p_type_test->reduced_value = false;
if (!is_type_compatible(test_type, operand_type)) {
if (!is_type_compatible_strict_collections(test_type, operand_type)) {
push_error(vformat(R"(Expression is of type "%s" so it can't be of type "%s".)", operand_type.to_string(), test_type.to_string()), p_type_test->operand);
} else if (is_type_compatible(test_type, type_from_variant(p_type_test->operand->reduced_value, p_type_test->operand))) {
} else if (is_type_compatible_strict_collections(test_type, type_from_variant(p_type_test->operand->reduced_value, p_type_test->operand))) {
p_type_test->reduced_value = test_type.builtin_type != Variant::OBJECT || !p_type_test->operand->reduced_value.is_null();
}
@ -5264,25 +5247,33 @@ void GDScriptAnalyzer::reduce_unary_op(GDScriptParser::UnaryOpNode *p_unary_op)
p_unary_op->set_datatype(result);
}
Variant GDScriptAnalyzer::make_expression_reduced_value(GDScriptParser::ExpressionNode *p_expression, bool &is_reduced) {
Variant GDScriptAnalyzer::make_expression_reduced_value(GDScriptParser::ExpressionNode *p_expression, bool &r_is_reduced) {
if (p_expression == nullptr) {
return Variant();
}
if (p_expression->is_constant) {
is_reduced = true;
r_is_reduced = true;
return p_expression->reduced_value;
}
switch (p_expression->type) {
case GDScriptParser::Node::ARRAY:
return make_array_reduced_value(static_cast<GDScriptParser::ArrayNode *>(p_expression), is_reduced);
return make_array_reduced_value(static_cast<GDScriptParser::ArrayNode *>(p_expression), r_is_reduced);
case GDScriptParser::Node::DICTIONARY:
return make_dictionary_reduced_value(static_cast<GDScriptParser::DictionaryNode *>(p_expression), is_reduced);
return make_dictionary_reduced_value(static_cast<GDScriptParser::DictionaryNode *>(p_expression), r_is_reduced);
case GDScriptParser::Node::SUBSCRIPT:
return make_subscript_reduced_value(static_cast<GDScriptParser::SubscriptNode *>(p_expression), is_reduced);
return make_subscript_reduced_value(static_cast<GDScriptParser::SubscriptNode *>(p_expression), r_is_reduced);
case GDScriptParser::Node::CALL:
return make_call_reduced_value(static_cast<GDScriptParser::CallNode *>(p_expression), is_reduced);
return make_call_reduced_value(static_cast<GDScriptParser::CallNode *>(p_expression), r_is_reduced);
case GDScriptParser::Node::BINARY_OPERATOR:
return make_binary_op_reduced_value(static_cast<GDScriptParser::BinaryOpNode *>(p_expression), r_is_reduced);
case GDScriptParser::Node::TERNARY_OPERATOR:
return make_ternary_op_reduced_value(static_cast<GDScriptParser::TernaryOpNode *>(p_expression), r_is_reduced);
case GDScriptParser::Node::CAST:
return make_cast_reduced_value(static_cast<GDScriptParser::CastNode *>(p_expression), r_is_reduced);
case GDScriptParser::Node::TYPE_TEST:
return make_type_test_reduced_value(static_cast<GDScriptParser::TypeTestNode *>(p_expression), r_is_reduced);
default:
break;
}
@ -5290,7 +5281,7 @@ Variant GDScriptAnalyzer::make_expression_reduced_value(GDScriptParser::Expressi
return Variant();
}
Variant GDScriptAnalyzer::make_array_reduced_value(GDScriptParser::ArrayNode *p_array, bool &is_reduced) {
Variant GDScriptAnalyzer::make_array_reduced_value(GDScriptParser::ArrayNode *p_array, bool &r_is_reduced) {
Array array = p_array->get_datatype().has_container_element_type(0) ? make_array_from_element_datatype(p_array->get_datatype().get_container_element_type(0)) : Array();
array.resize(p_array->elements.size());
@ -5308,11 +5299,11 @@ Variant GDScriptAnalyzer::make_array_reduced_value(GDScriptParser::ArrayNode *p_
array.make_read_only();
is_reduced = true;
r_is_reduced = true;
return array;
}
Variant GDScriptAnalyzer::make_dictionary_reduced_value(GDScriptParser::DictionaryNode *p_dictionary, bool &is_reduced) {
Variant GDScriptAnalyzer::make_dictionary_reduced_value(GDScriptParser::DictionaryNode *p_dictionary, bool &r_is_reduced) {
Dictionary dictionary = p_dictionary->get_datatype().has_container_element_types()
? make_dictionary_from_element_datatype(p_dictionary->get_datatype().get_container_element_type_or_variant(0), p_dictionary->get_datatype().get_container_element_type_or_variant(1))
: Dictionary();
@ -5337,11 +5328,11 @@ Variant GDScriptAnalyzer::make_dictionary_reduced_value(GDScriptParser::Dictiona
dictionary.make_read_only();
is_reduced = true;
r_is_reduced = true;
return dictionary;
}
Variant GDScriptAnalyzer::make_subscript_reduced_value(GDScriptParser::SubscriptNode *p_subscript, bool &is_reduced) {
Variant GDScriptAnalyzer::make_subscript_reduced_value(GDScriptParser::SubscriptNode *p_subscript, bool &r_is_reduced) {
if (p_subscript->base == nullptr || p_subscript->index == nullptr) {
return Variant();
}
@ -5356,7 +5347,7 @@ Variant GDScriptAnalyzer::make_subscript_reduced_value(GDScriptParser::Subscript
bool is_valid = false;
Variant value = base_value.get_named(p_subscript->attribute->name, is_valid);
if (is_valid) {
is_reduced = true;
r_is_reduced = true;
return value;
} else {
return Variant();
@ -5371,7 +5362,7 @@ Variant GDScriptAnalyzer::make_subscript_reduced_value(GDScriptParser::Subscript
bool is_valid = false;
Variant value = base_value.get(index_value, &is_valid);
if (is_valid) {
is_reduced = true;
r_is_reduced = true;
return value;
} else {
return Variant();
@ -5379,7 +5370,7 @@ Variant GDScriptAnalyzer::make_subscript_reduced_value(GDScriptParser::Subscript
}
}
Variant GDScriptAnalyzer::make_call_reduced_value(GDScriptParser::CallNode *p_call, bool &is_reduced) {
Variant GDScriptAnalyzer::make_call_reduced_value(GDScriptParser::CallNode *p_call, bool &r_is_reduced) {
if (p_call->get_callee_type() == GDScriptParser::Node::IDENTIFIER) {
Variant::Type type = Variant::NIL;
if (p_call->function_name == SNAME("Array")) {
@ -5407,7 +5398,6 @@ Variant GDScriptAnalyzer::make_call_reduced_value(GDScriptParser::CallNode *p_ca
Callable::CallError ce;
Variant::construct(type, result, argptrs, args.size(), ce);
if (ce.error) {
push_error(vformat(R"(Failed to construct "%s".)", Variant::get_type_name(type)), p_call);
return Variant();
}
@ -5419,13 +5409,146 @@ Variant GDScriptAnalyzer::make_call_reduced_value(GDScriptParser::CallNode *p_ca
dictionary.make_read_only();
}
is_reduced = true;
r_is_reduced = true;
return result;
}
return Variant();
}
Variant GDScriptAnalyzer::make_binary_op_reduced_value(GDScriptParser::BinaryOpNode *p_binary_op, bool &r_is_reduced) {
if (p_binary_op->variant_op == Variant::OP_MAX) {
return Variant();
}
bool is_left_op_value_reduced = false;
Variant left_op_value = make_expression_reduced_value(p_binary_op->left_operand, is_left_op_value_reduced);
if (!is_left_op_value_reduced) {
return Variant();
}
bool is_right_op_value_reduced = false;
Variant right_op_value = make_expression_reduced_value(p_binary_op->right_operand, is_right_op_value_reduced);
if (!is_right_op_value_reduced) {
return Variant();
}
Variant result;
bool valid = false;
Variant::evaluate(p_binary_op->variant_op, left_op_value, right_op_value, result, valid);
if (!valid) {
return Variant();
}
if (result.get_type() == Variant::ARRAY) {
Array array = result;
array.make_read_only();
} else if (result.get_type() == Variant::DICTIONARY) {
Dictionary dictionary = result;
dictionary.make_read_only();
}
r_is_reduced = true;
return result;
}
Variant GDScriptAnalyzer::make_ternary_op_reduced_value(GDScriptParser::TernaryOpNode *p_ternary_op, bool &r_is_reduced) {
bool is_condition_value_reduced = false;
Variant condition_value = make_expression_reduced_value(p_ternary_op->condition, is_condition_value_reduced);
if (!is_condition_value_reduced) {
return Variant();
}
bool is_true_expr_value_reduced = false;
Variant true_expr_value = make_expression_reduced_value(p_ternary_op->true_expr, is_true_expr_value_reduced);
if (!is_true_expr_value_reduced) {
return Variant();
}
bool is_false_expr_value_reduced = false;
Variant false_expr_value = make_expression_reduced_value(p_ternary_op->false_expr, is_false_expr_value_reduced);
if (!is_false_expr_value_reduced) {
return Variant();
}
r_is_reduced = true;
return condition_value.booleanize() ? true_expr_value : false_expr_value;
}
Variant GDScriptAnalyzer::make_cast_reduced_value(GDScriptParser::CastNode *p_cast, bool &r_is_reduced) {
bool is_operand_value_reduced = false;
Variant operand_value = make_expression_reduced_value(p_cast->operand, is_operand_value_reduced);
if (!is_operand_value_reduced) {
return Variant();
}
GDScriptParser::DataType cast_type = type_from_metatype(resolve_datatype(p_cast->cast_type));
if (!cast_type.is_set()) {
return Variant();
}
if (cast_type.is_variant()) {
r_is_reduced = true;
return operand_value;
}
if (cast_type.kind == GDScriptParser::DataType::BUILTIN || cast_type.kind == GDScriptParser::DataType::ENUM) {
Variant result;
const Variant *argptr = &operand_value;
Callable::CallError ce;
Variant::construct(cast_type.builtin_type, result, &argptr, 1, ce);
if (ce.error) {
return Variant();
}
if (result.get_type() == Variant::ARRAY) {
Array array = cast_type.has_container_element_type(0) ? make_array_from_element_datatype(cast_type.get_container_element_type(0)) : Array();
array.assign(result);
array.make_read_only();
result = array;
} else if (result.get_type() == Variant::DICTIONARY) {
Dictionary dictionary = cast_type.has_container_element_types()
? make_dictionary_from_element_datatype(cast_type.get_container_element_type_or_variant(0), cast_type.get_container_element_type_or_variant(1))
: Dictionary();
dictionary.assign(result);
dictionary.make_read_only();
result = dictionary;
}
r_is_reduced = true;
return result;
}
return Variant();
}
Variant GDScriptAnalyzer::make_type_test_reduced_value(GDScriptParser::TypeTestNode *p_type_test, bool &r_is_reduced) {
bool is_operand_value_reduced = false;
Variant operand_value = make_expression_reduced_value(p_type_test->operand, is_operand_value_reduced);
if (!is_operand_value_reduced) {
return Variant();
}
GDScriptParser::DataType test_type = type_from_metatype(p_type_test->test_type->get_datatype());
if (!test_type.is_set()) {
return Variant();
}
GDScriptParser::DataType operand_type = type_from_variant(operand_value, p_type_test->operand);
if (!operand_type.is_set()) {
return Variant();
}
bool result = false;
if (is_type_compatible_strict_collections(test_type, operand_type)) {
result = test_type.builtin_type != Variant::OBJECT || !operand_value.is_null();
}
r_is_reduced = true;
return result;
}
Array GDScriptAnalyzer::make_array_from_element_datatype(const GDScriptParser::DataType &p_element_datatype, const GDScriptParser::Node *p_source_node) {
Array array;
@ -5523,6 +5646,57 @@ Variant GDScriptAnalyzer::make_variable_default_value(GDScriptParser::VariableNo
return result;
}
GDScriptParser::DataType GDScriptAnalyzer::type_from_script(const Ref<Script> &p_script, const GDScriptParser::Node *p_source, bool p_is_meta_type) {
ERR_FAIL_COND_V(!p_script.is_valid(), GDScriptParser::DataType());
GDScriptParser::DataType result;
result.is_constant = true;
result.kind = GDScriptParser::DataType::NATIVE;
result.builtin_type = Variant::OBJECT;
result.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT; // Constant has explicit type.
result.is_meta_type = p_is_meta_type;
Ref<GDScript> gds = p_script;
if (gds.is_valid()) {
// This might be an inner class, so we want to get the parser for the root.
// But still get the inner class from that tree.
String script_path = gds->get_script_path();
Ref<GDScriptParserRef> ref = parser->get_depended_parser_for(script_path);
if (ref.is_null()) {
push_error(vformat(R"(Could not find script "%s".)", script_path), p_source);
GDScriptParser::DataType error_type;
error_type.kind = GDScriptParser::DataType::VARIANT;
return error_type;
}
Error err = ref->raise_status(GDScriptParserRef::INHERITANCE_SOLVED);
GDScriptParser::ClassNode *found = nullptr;
if (err == OK) {
found = ref->get_parser()->find_class(gds->fully_qualified_name);
if (found != nullptr) {
err = resolve_class_inheritance(found, p_source);
}
}
if (err || found == nullptr) {
push_error(vformat(R"(Could not resolve script "%s".)", script_path), p_source);
GDScriptParser::DataType error_type;
error_type.kind = GDScriptParser::DataType::VARIANT;
return error_type;
}
result.kind = GDScriptParser::DataType::CLASS;
result.native_type = found->get_datatype().native_type;
result.class_type = found;
result.script_path = ref->get_parser()->script_path;
} else {
result.kind = GDScriptParser::DataType::SCRIPT;
result.native_type = p_script->get_instance_base_type();
result.script_path = p_script->get_path();
}
result.script_type = p_script;
return result;
}
GDScriptParser::DataType GDScriptAnalyzer::type_from_variant(const Variant &p_value, const GDScriptParser::Node *p_source) {
GDScriptParser::DataType result;
result.is_constant = true;
@ -5533,7 +5707,7 @@ GDScriptParser::DataType GDScriptAnalyzer::type_from_variant(const Variant &p_va
if (p_value.get_type() == Variant::ARRAY) {
const Array &array = p_value;
if (array.get_typed_script()) {
result.set_container_element_type(0, type_from_metatype(make_script_meta_type(array.get_typed_script())));
result.set_container_element_type(0, type_from_metatype(type_from_script(array.get_typed_script(), p_source, true)));
} else if (array.get_typed_class_name()) {
result.set_container_element_type(0, type_from_metatype(make_native_meta_type(array.get_typed_class_name())));
} else if (array.get_typed_builtin() != Variant::NIL) {
@ -5542,14 +5716,14 @@ GDScriptParser::DataType GDScriptAnalyzer::type_from_variant(const Variant &p_va
} else if (p_value.get_type() == Variant::DICTIONARY) {
const Dictionary &dict = p_value;
if (dict.get_typed_key_script()) {
result.set_container_element_type(0, type_from_metatype(make_script_meta_type(dict.get_typed_key_script())));
result.set_container_element_type(0, type_from_metatype(type_from_script(dict.get_typed_key_script(), p_source, true)));
} else if (dict.get_typed_key_class_name()) {
result.set_container_element_type(0, type_from_metatype(make_native_meta_type(dict.get_typed_key_class_name())));
} else if (dict.get_typed_key_builtin() != Variant::NIL) {
result.set_container_element_type(0, type_from_metatype(make_builtin_meta_type((Variant::Type)dict.get_typed_key_builtin())));
}
if (dict.get_typed_value_script()) {
result.set_container_element_type(1, type_from_metatype(make_script_meta_type(dict.get_typed_value_script())));
result.set_container_element_type(1, type_from_metatype(type_from_script(dict.get_typed_value_script(), p_source, true)));
} else if (dict.get_typed_value_class_name()) {
result.set_container_element_type(1, type_from_metatype(make_native_meta_type(dict.get_typed_value_class_name())));
} else if (dict.get_typed_value_builtin() != Variant::NIL) {
@ -5566,50 +5740,16 @@ GDScriptParser::DataType GDScriptAnalyzer::type_from_variant(const Variant &p_va
result.native_type = obj->get_class_name();
Ref<Script> scr = p_value; // Check if value is a script itself.
bool is_meta_type;
if (scr.is_valid()) {
result.is_meta_type = true;
is_meta_type = true;
} else {
result.is_meta_type = false;
is_meta_type = false;
scr = obj->get_script();
}
if (scr.is_valid()) {
Ref<GDScript> gds = scr;
if (gds.is_valid()) {
// This might be an inner class, so we want to get the parser for the root.
// But still get the inner class from that tree.
String script_path = gds->get_script_path();
Ref<GDScriptParserRef> ref = parser->get_depended_parser_for(script_path);
if (ref.is_null()) {
push_error(vformat(R"(Could not find script "%s".)", script_path), p_source);
GDScriptParser::DataType error_type;
error_type.kind = GDScriptParser::DataType::VARIANT;
return error_type;
}
Error err = ref->raise_status(GDScriptParserRef::INHERITANCE_SOLVED);
GDScriptParser::ClassNode *found = nullptr;
if (err == OK) {
found = ref->get_parser()->find_class(gds->fully_qualified_name);
if (found != nullptr) {
err = resolve_class_inheritance(found, p_source);
}
}
if (err || found == nullptr) {
push_error(vformat(R"(Could not resolve script "%s".)", script_path), p_source);
GDScriptParser::DataType error_type;
error_type.kind = GDScriptParser::DataType::VARIANT;
return error_type;
}
result.kind = GDScriptParser::DataType::CLASS;
result.native_type = found->get_datatype().native_type;
result.class_type = found;
result.script_path = ref->get_parser()->script_path;
} else {
result.kind = GDScriptParser::DataType::SCRIPT;
result.native_type = scr->get_instance_base_type();
result.script_path = scr->get_path();
}
result.script_type = scr;
if (scr.is_valid()) {
result = type_from_script(scr, p_source, is_meta_type);
} else {
result.kind = GDScriptParser::DataType::NATIVE;
if (result.native_type == GDScriptNativeClass::get_class_static()) {
@ -6150,6 +6290,21 @@ bool GDScriptAnalyzer::is_type_compatible(const GDScriptParser::DataType &p_targ
return check_type_compatibility(p_target, p_source, p_allow_implicit_conversion, p_source_node);
}
// NOTE:`is_type_compatible()` considers typed arrays/dictionaries compatible with untyped ones (but the operation is unsafe).
// However, in the case of constant expressions, this leads to incorrect results.
bool GDScriptAnalyzer::is_type_compatible_strict_collections(const GDScriptParser::DataType &p_target, const GDScriptParser::DataType &p_source) {
if (p_target.builtin_type == Variant::ARRAY && p_source.builtin_type == Variant::ARRAY) {
if (p_target.has_container_element_type(0) && !p_source.has_container_element_type(0)) {
return false;
}
} else if (p_target.builtin_type == Variant::DICTIONARY && p_source.builtin_type == Variant::DICTIONARY) {
if (p_target.has_container_element_types() && !p_source.has_container_element_types()) {
return false;
}
}
return is_type_compatible(p_target, p_source);
}
// TODO: Add safe/unsafe return variable (for variant cases)
bool GDScriptAnalyzer::check_type_compatibility(const GDScriptParser::DataType &p_target, const GDScriptParser::DataType &p_source, bool p_allow_implicit_conversion, const GDScriptParser::Node *p_source_node) {
// These return "true" so it doesn't affect users negatively.

View file

@ -116,15 +116,24 @@ class GDScriptAnalyzer {
void reduce_type_test(GDScriptParser::TypeTestNode *p_type_test);
void reduce_unary_op(GDScriptParser::UnaryOpNode *p_unary_op);
Variant make_expression_reduced_value(GDScriptParser::ExpressionNode *p_expression, bool &is_reduced);
Variant make_array_reduced_value(GDScriptParser::ArrayNode *p_array, bool &is_reduced);
Variant make_dictionary_reduced_value(GDScriptParser::DictionaryNode *p_dictionary, bool &is_reduced);
Variant make_subscript_reduced_value(GDScriptParser::SubscriptNode *p_subscript, bool &is_reduced);
Variant make_call_reduced_value(GDScriptParser::CallNode *p_call, bool &is_reduced);
// These methods provide a fallback mechanism for constant folding in constant contexts (constant initializers,
// annotation arguments) and allow constant folding even when the normal constant folding mechanism does not consider
// an expression to be constant (usually due to the presence of `[...]` and `{...}` constructs in it).
// These methods assume that the normal expression reduction mechanism has already been performed.
Variant make_expression_reduced_value(GDScriptParser::ExpressionNode *p_expression, bool &r_is_reduced);
Variant make_array_reduced_value(GDScriptParser::ArrayNode *p_array, bool &r_is_reduced);
Variant make_dictionary_reduced_value(GDScriptParser::DictionaryNode *p_dictionary, bool &r_is_reduced);
Variant make_subscript_reduced_value(GDScriptParser::SubscriptNode *p_subscript, bool &r_is_reduced);
Variant make_call_reduced_value(GDScriptParser::CallNode *p_call, bool &r_is_reduced);
Variant make_binary_op_reduced_value(GDScriptParser::BinaryOpNode *p_binary_op, bool &r_is_reduced);
Variant make_ternary_op_reduced_value(GDScriptParser::TernaryOpNode *p_ternary_op, bool &r_is_reduced);
Variant make_cast_reduced_value(GDScriptParser::CastNode *p_cast, bool &r_is_reduced);
Variant make_type_test_reduced_value(GDScriptParser::TypeTestNode *p_type_test, bool &r_is_reduced);
// Helpers.
Array make_array_from_element_datatype(const GDScriptParser::DataType &p_element_datatype, const GDScriptParser::Node *p_source_node = nullptr);
Dictionary make_dictionary_from_element_datatype(const GDScriptParser::DataType &p_key_element_datatype, const GDScriptParser::DataType &p_value_element_datatype, const GDScriptParser::Node *p_source_node = nullptr);
GDScriptParser::DataType type_from_script(const Ref<Script> &p_script, const GDScriptParser::Node *p_source, bool p_is_meta_type);
GDScriptParser::DataType type_from_variant(const Variant &p_value, const GDScriptParser::Node *p_source);
GDScriptParser::DataType type_from_property(const PropertyInfo &p_property, bool p_is_arg = false, bool p_is_readonly = false) const;
GDScriptParser::DataType make_global_class_meta_type(const StringName &p_class_name, const GDScriptParser::Node *p_source);
@ -138,6 +147,7 @@ class GDScriptAnalyzer {
void update_array_literal_element_type(GDScriptParser::ArrayNode *p_array, const GDScriptParser::DataType &p_element_type);
void update_dictionary_literal_element_type(GDScriptParser::DictionaryNode *p_dictionary, const GDScriptParser::DataType &p_key_element_type, const GDScriptParser::DataType &p_value_element_type);
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);
bool is_type_compatible_strict_collections(const GDScriptParser::DataType &p_target, const GDScriptParser::DataType &p_source);
void push_error(const String &p_message, const GDScriptParser::Node *p_origin = nullptr);
void mark_node_unsafe(const GDScriptParser::Node *p_node);
void downgrade_node_type_source(GDScriptParser::Node *p_node);

View file

@ -0,0 +1,69 @@
const UNTYPED_ARRAY: Variant = [1000]
const TYPED_ARRAY: Variant = [2000] as Array[int]
const UNTYPED_DICT: Variant = {1000: 1000}
const TYPED_DICT: Variant = {2000: 2000} as Dictionary[int, int]
const TEST_STRING_INDEX = "data"[0]
const TEST_VECTOR2_ATTR = Vector2(1, 2).x
const TEST_ARRAY_INDEX = [100, 200][0]
const TEST_DICT_ATTR = {a = 1, b = 2}.a
const TEST_CALL_ARRAY = Array()
const TEST_CALL_DICT = Dictionary()
const TEST_BINARY_OP = [1] + [2]
const TEST_TERNARY_OP = [123] if [true] else [456]
const TEST_CAST_UNTYPED_ARRAY = [11] as Array
const TEST_CAST_TYPED_ARRAY = [22] as Array[int]
const TEST_CAST_UNTYPED_DICT = {11: 111} as Dictionary
const TEST_CAST_TYPED_DICT = {22: 222} as Dictionary[int, int]
const TEST_TYPE_TEST_LITERAL_UNTYPED_ARRAY = [33] is Array
const TEST_TYPE_TEST_LITERAL_TYPED_ARRAY = [44] is Array[int]
const TEST_TYPE_TEST_LITERAL_UNTYPED_DICT = {33: 333} is Dictionary
const TEST_TYPE_TEST_LITERAL_TYPED_DICT = {44: 444} is Dictionary[int, int]
const TEST_TYPE_TEST_UNTYPED_UNTYPED_ARRAY = UNTYPED_ARRAY is Array
const TEST_TYPE_TEST_UNTYPED_TYPED_ARRAY = UNTYPED_ARRAY is Array[int]
const TEST_TYPE_TEST_UNTYPED_UNTYPED_DICT = UNTYPED_DICT is Dictionary
const TEST_TYPE_TEST_UNTYPED_TYPED_DICT = UNTYPED_DICT is Dictionary[int, int]
const TEST_TYPE_TEST_TYPED_UNTYPED_ARRAY = TYPED_ARRAY is Array
const TEST_TYPE_TEST_TYPED_TYPED_ARRAY = TYPED_ARRAY is Array[int]
const TEST_TYPE_TEST_TYPED_UNTYPED_DICT = TYPED_DICT is Dictionary
const TEST_TYPE_TEST_TYPED_TYPED_DICT = TYPED_DICT is Dictionary[int, int]
@export_custom(
PROPERTY_HINT_TYPE_STRING,
"%d/%d:Zero,One,Two" % [TYPE_INT, PROPERTY_HINT_ENUM],
)
var test_export_custom: Array
func test():
var script: Script = get_script()
var constants: Dictionary = script.get_script_constant_map()
for constant: StringName in constants:
if constant.begins_with("TEST_"):
print("const %s = %s" % [constant, var_to_str(constants[constant]).replace("\n", "")])
if constants[constant] is Array:
var array: Array = constants[constant]
Utils.check(array.is_read_only())
array = get(constant)
Utils.check(array.is_read_only())
elif constants[constant] is Dictionary:
var dict: Dictionary = constants[constant]
Utils.check(dict.is_read_only())
dict = get(constant)
Utils.check(dict.is_read_only())
for property in get_property_list():
if str(property.name).begins_with("test_"):
Utils.print_property_extended_info(property, self)
# GH-96152
const ARRAY = [42]
const CONST_CONCAT = ARRAY + ARRAY
var var_concat := ARRAY + ARRAY
prints(CONST_CONCAT, CONST_CONCAT.is_read_only())
prints(var_concat, var_concat.is_read_only())

View file

@ -0,0 +1,29 @@
GDTEST_OK
const TEST_STRING_INDEX = "d"
const TEST_VECTOR2_ATTR = 1.0
const TEST_ARRAY_INDEX = 100
const TEST_DICT_ATTR = 1
const TEST_CALL_ARRAY = []
const TEST_CALL_DICT = {}
const TEST_BINARY_OP = [1, 2]
const TEST_TERNARY_OP = [123]
const TEST_CAST_UNTYPED_ARRAY = [11]
const TEST_CAST_TYPED_ARRAY = Array[int]([22])
const TEST_CAST_UNTYPED_DICT = {11: 111}
const TEST_CAST_TYPED_DICT = Dictionary[int, int]({22: 222})
const TEST_TYPE_TEST_LITERAL_UNTYPED_ARRAY = true
const TEST_TYPE_TEST_LITERAL_TYPED_ARRAY = false
const TEST_TYPE_TEST_LITERAL_UNTYPED_DICT = true
const TEST_TYPE_TEST_LITERAL_TYPED_DICT = false
const TEST_TYPE_TEST_UNTYPED_UNTYPED_ARRAY = true
const TEST_TYPE_TEST_UNTYPED_TYPED_ARRAY = false
const TEST_TYPE_TEST_UNTYPED_UNTYPED_DICT = true
const TEST_TYPE_TEST_UNTYPED_TYPED_DICT = false
const TEST_TYPE_TEST_TYPED_UNTYPED_ARRAY = true
const TEST_TYPE_TEST_TYPED_TYPED_ARRAY = true
const TEST_TYPE_TEST_TYPED_UNTYPED_DICT = true
const TEST_TYPE_TEST_TYPED_TYPED_DICT = true
var test_export_custom: Array = []
hint=TYPE_STRING hint_string="<int>/<ENUM>:Zero,One,Two" usage=DEFAULT|SCRIPT_VARIABLE class_name=&""
[42, 42] true
[42, 42] false

View file

@ -19,6 +19,24 @@ func test():
Utils.check((const_builtin is float) == false)
Utils.check(is_instance_of(const_builtin, TYPE_FLOAT) == false)
var untyped_array: Variant = []
Utils.check((untyped_array is Variant) == true)
Utils.check((untyped_array is Array) == true)
Utils.check(is_instance_of(untyped_array, TYPE_ARRAY) == true)
Utils.check((untyped_array is Array[int]) == false)
Utils.check((untyped_array is Array[float]) == false)
Utils.check((untyped_array is int) == false)
Utils.check(is_instance_of(untyped_array, TYPE_INT) == false)
const const_untyped_array: Variant = []
Utils.check((const_untyped_array is Variant) == true)
Utils.check((const_untyped_array is Array) == true)
Utils.check(is_instance_of(const_untyped_array, TYPE_ARRAY) == true)
Utils.check((const_untyped_array is Array[int]) == false)
Utils.check((const_untyped_array is Array[float]) == false)
Utils.check((const_untyped_array is int) == false)
Utils.check(is_instance_of(const_untyped_array, TYPE_INT) == false)
var int_array: Variant = [] as Array[int]
Utils.check((int_array is Variant) == true)
Utils.check((int_array is Array) == true)
@ -28,7 +46,7 @@ func test():
Utils.check((int_array is int) == false)
Utils.check(is_instance_of(int_array, TYPE_INT) == false)
var const_int_array: Variant = [] as Array[int]
const const_int_array: Variant = [] as Array[int]
Utils.check((const_int_array is Variant) == true)
Utils.check((const_int_array is Array) == true)
Utils.check(is_instance_of(const_int_array, TYPE_ARRAY) == true)
@ -47,7 +65,7 @@ func test():
Utils.check((b_array is int) == false)
Utils.check(is_instance_of(b_array, TYPE_INT) == false)
var const_b_array: Variant = [] as Array[B]
const const_b_array: Variant = [] as Array[B]
Utils.check((const_b_array is Variant) == true)
Utils.check((const_b_array is Array) == true)
Utils.check(is_instance_of(const_b_array, TYPE_ARRAY) == true)

View file

@ -0,0 +1,31 @@
var a: Array = [1]:
set(v):
prints("set a", v)
a = v
get:
prints("get a")
return a
var b: PackedByteArray = [1]:
set(v):
prints("set b", v)
b = v
get:
prints("get b")
return b
var c: PackedVector2Array = [Vector2.ONE]:
set(v):
prints("set c", v)
c = v
get:
prints("get c")
return c
func test():
a[0] = 2
print(a)
b[0] = 2
print(b)
c[0].x = 2
print(c)

View file

@ -0,0 +1,10 @@
GDTEST_OK
get a
get a
[2]
get b
get b
[2]
get c
get c
[(2.0, 1.0)]

View file

@ -10,19 +10,18 @@ static func check(condition: Variant) -> void:
return
printerr("Check failed. Backtrace (most recent call first):")
for stack: ScriptBacktrace in Engine.capture_script_backtraces():
if stack.get_language_name() == "GDScript":
var dir: String
for i: int in stack.get_frame_count():
if i == 0:
dir = stack.get_frame_file(i).trim_suffix("utils.notest.gd")
else:
printerr(" %s:%d @ %s()" % [
stack.get_frame_file(i).trim_prefix(dir),
stack.get_frame_line(i),
stack.get_frame_function(i),
])
break
var stack: Array = get_stack()
var dir: String
for i: int in stack.size():
var frame: Dictionary = stack[i]
if i == 0:
dir = str(frame.source).trim_suffix("utils.notest.gd")
else:
printerr(" %s:%d @ %s()" % [
str(frame.source).trim_prefix(dir),
frame.line,
frame.function,
])
static func get_type(property: Dictionary, is_return: bool = false) -> String: