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:
commit
f5d21bfa9c
9 changed files with 446 additions and 115 deletions
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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())
|
||||
|
|
@ -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
|
||||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
GDTEST_OK
|
||||
get a
|
||||
get a
|
||||
[2]
|
||||
get b
|
||||
get b
|
||||
[2]
|
||||
get c
|
||||
get c
|
||||
[(2.0, 1.0)]
|
||||
|
|
@ -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:
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue