From 7721e13a92076aa50a69169a9d24b642140cddbb Mon Sep 17 00:00:00 2001 From: Danil Alexeev Date: Mon, 31 Mar 2025 20:40:49 +0300 Subject: [PATCH] GDScript: Add constant `Array` and `Dictionary` constructors --- modules/gdscript/gdscript_analyzer.cpp | 74 ++++++++++++++++--- modules/gdscript/gdscript_analyzer.h | 1 + ...const_array_and_dictionary_constructors.gd | 64 ++++++++++++++++ ...onst_array_and_dictionary_constructors.out | 29 ++++++++ 4 files changed, 157 insertions(+), 11 deletions(-) create mode 100644 modules/gdscript/tests/scripts/analyzer/features/const_array_and_dictionary_constructors.gd create mode 100644 modules/gdscript/tests/scripts/analyzer/features/const_array_and_dictionary_constructors.out diff --git a/modules/gdscript/gdscript_analyzer.cpp b/modules/gdscript/gdscript_analyzer.cpp index bc55603a194..9c0b2f5477d 100644 --- a/modules/gdscript/gdscript_analyzer.cpp +++ b/modules/gdscript/gdscript_analyzer.cpp @@ -5197,24 +5197,29 @@ void GDScriptAnalyzer::reduce_unary_op(GDScriptParser::UnaryOpNode *p_unary_op) } Variant GDScriptAnalyzer::make_expression_reduced_value(GDScriptParser::ExpressionNode *p_expression, bool &is_reduced) { - Variant value; - if (p_expression == nullptr) { - return value; + return Variant(); } if (p_expression->is_constant) { is_reduced = true; - value = p_expression->reduced_value; - } else if (p_expression->type == GDScriptParser::Node::ARRAY) { - value = make_array_reduced_value(static_cast(p_expression), is_reduced); - } else if (p_expression->type == GDScriptParser::Node::DICTIONARY) { - value = make_dictionary_reduced_value(static_cast(p_expression), is_reduced); - } else if (p_expression->type == GDScriptParser::Node::SUBSCRIPT) { - value = make_subscript_reduced_value(static_cast(p_expression), is_reduced); + return p_expression->reduced_value; } - return value; + switch (p_expression->type) { + case GDScriptParser::Node::ARRAY: + return make_array_reduced_value(static_cast(p_expression), is_reduced); + case GDScriptParser::Node::DICTIONARY: + return make_dictionary_reduced_value(static_cast(p_expression), is_reduced); + case GDScriptParser::Node::SUBSCRIPT: + return make_subscript_reduced_value(static_cast(p_expression), is_reduced); + case GDScriptParser::Node::CALL: + return make_call_reduced_value(static_cast(p_expression), is_reduced); + default: + break; + } + + return Variant(); } Variant GDScriptAnalyzer::make_array_reduced_value(GDScriptParser::ArrayNode *p_array, bool &is_reduced) { @@ -5306,6 +5311,53 @@ Variant GDScriptAnalyzer::make_subscript_reduced_value(GDScriptParser::Subscript } } +Variant GDScriptAnalyzer::make_call_reduced_value(GDScriptParser::CallNode *p_call, bool &is_reduced) { + if (p_call->get_callee_type() == GDScriptParser::Node::IDENTIFIER) { + Variant::Type type = Variant::NIL; + if (p_call->function_name == SNAME("Array")) { + type = Variant::ARRAY; + } else if (p_call->function_name == SNAME("Dictionary")) { + type = Variant::DICTIONARY; + } else { + return Variant(); + } + + Vector args; + args.resize(p_call->arguments.size()); + const Variant **argptrs = (const Variant **)alloca(sizeof(const Variant **) * args.size()); + for (int i = 0; i < p_call->arguments.size(); i++) { + bool is_arg_value_reduced = false; + Variant arg_value = make_expression_reduced_value(p_call->arguments[i], is_arg_value_reduced); + if (!is_arg_value_reduced) { + return Variant(); + } + args.write[i] = arg_value; + argptrs[i] = &args[i]; + } + + Variant result; + 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(); + } + + if (type == Variant::ARRAY) { + Array array = result; + array.make_read_only(); + } else if (type == Variant::DICTIONARY) { + Dictionary dictionary = result; + dictionary.make_read_only(); + } + + is_reduced = true; + return result; + } + + return Variant(); +} + Array GDScriptAnalyzer::make_array_from_element_datatype(const GDScriptParser::DataType &p_element_datatype, const GDScriptParser::Node *p_source_node) { Array array; diff --git a/modules/gdscript/gdscript_analyzer.h b/modules/gdscript/gdscript_analyzer.h index 2a90e65c0bb..576276471d3 100644 --- a/modules/gdscript/gdscript_analyzer.h +++ b/modules/gdscript/gdscript_analyzer.h @@ -120,6 +120,7 @@ class GDScriptAnalyzer { 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); // Helpers. Array make_array_from_element_datatype(const GDScriptParser::DataType &p_element_datatype, const GDScriptParser::Node *p_source_node = nullptr); diff --git a/modules/gdscript/tests/scripts/analyzer/features/const_array_and_dictionary_constructors.gd b/modules/gdscript/tests/scripts/analyzer/features/const_array_and_dictionary_constructors.gd new file mode 100644 index 00000000000..752ffc1311a --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/const_array_and_dictionary_constructors.gd @@ -0,0 +1,64 @@ +const A1 = Array() +const A2 = Array(Array()) +const A3 = Array([]) +const A4 = [Array()] +const A5 = [[]] +const A6 = Array([1], TYPE_INT, &"", null) + +const D1 = Dictionary() +const D2 = Dictionary(Dictionary()) +const D3 = Dictionary({}) +const D4 = { Dictionary(): Dictionary() } +const D5 = { {}: {} } +const D6 = Dictionary({ 1: 1 }, TYPE_INT, &"", null, TYPE_INT, &"", null) + +var a1 = Array() +var a2 = Array(Array()) +var a3 = Array([]) +var a4 = [Array()] +var a5 = [[]] +var a6 = Array([1], TYPE_INT, &"", null) + +var d1 = Dictionary() +var d2 = Dictionary(Dictionary()) +var d3 = Dictionary({}) +var d4 = { Dictionary(): Dictionary() } +var d5 = { {}: {} } +var d6 = Dictionary({ 1: 1 }, TYPE_INT, &"", null, TYPE_INT, &"", null) + +func test_value(value: Variant) -> void: + @warning_ignore("unsafe_method_access") + prints(value.is_read_only(), var_to_str(value).replace("\n", " ")) + +func test(): + print('---') + test_value(A1) + test_value(A2) + test_value(A3) + test_value(A4) + test_value(A5) + test_value(A6) + + print('---') + test_value(D1) + test_value(D2) + test_value(D3) + test_value(D4) + test_value(D5) + test_value(D6) + + print('---') + test_value(a1) + test_value(a2) + test_value(a3) + test_value(a4) + test_value(a5) + test_value(a6) + + print('---') + test_value(d1) + test_value(d2) + test_value(d3) + test_value(d4) + test_value(d5) + test_value(d6) diff --git a/modules/gdscript/tests/scripts/analyzer/features/const_array_and_dictionary_constructors.out b/modules/gdscript/tests/scripts/analyzer/features/const_array_and_dictionary_constructors.out new file mode 100644 index 00000000000..428251a3115 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/const_array_and_dictionary_constructors.out @@ -0,0 +1,29 @@ +GDTEST_OK +--- +true [] +true [] +true [] +true [[]] +true [[]] +true Array[int]([1]) +--- +true {} +true {} +true {} +true { {}: {} } +true { {}: {} } +true Dictionary[int, int]({ 1: 1 }) +--- +false [] +false [] +false [] +false [[]] +false [[]] +false Array[int]([1]) +--- +false {} +false {} +false {} +false { {}: {} } +false { {}: {} } +false Dictionary[int, int]({ 1: 1 })