diff options
author | Rémi Verschelde <rverschelde@gmail.com> | 2024-09-06 22:38:13 +0200 |
---|---|---|
committer | Rémi Verschelde <rverschelde@gmail.com> | 2024-09-06 22:38:13 +0200 |
commit | 0b4ae20156c1597671a748192ed8c032ed1d053e (patch) | |
tree | ebb6b242506eb6697e34962cce83e49f3ad96ec9 /modules | |
parent | c9dc1eb1d59bf67bb123ed1ac5f9e0cfb93e275b (diff) | |
parent | 9853a691447cd4e279f48820067174d3833b0065 (diff) | |
download | redot-engine-0b4ae20156c1597671a748192ed8c032ed1d053e.tar.gz |
Merge pull request #78656 from Repiteo/typed-dictionary
Implement typed dictionaries
Diffstat (limited to 'modules')
58 files changed, 1557 insertions, 67 deletions
diff --git a/modules/gdscript/editor/gdscript_docgen.cpp b/modules/gdscript/editor/gdscript_docgen.cpp index 35b69fab8c..32ef429b0d 100644 --- a/modules/gdscript/editor/gdscript_docgen.cpp +++ b/modules/gdscript/editor/gdscript_docgen.cpp @@ -84,6 +84,15 @@ void GDScriptDocGen::_doctype_from_gdtype(const GDType &p_gdtype, String &r_type return; } } + if (p_gdtype.builtin_type == Variant::DICTIONARY && p_gdtype.has_container_element_types()) { + String key, value; + _doctype_from_gdtype(p_gdtype.get_container_element_type_or_variant(0), key, r_enum); + _doctype_from_gdtype(p_gdtype.get_container_element_type_or_variant(1), value, r_enum); + if (key != "Variant" || value != "Variant") { + r_type = "Dictionary[" + key + ", " + value + "]"; + return; + } + } r_type = Variant::get_type_name(p_gdtype.builtin_type); return; case GDType::NATIVE: @@ -155,34 +164,82 @@ String GDScriptDocGen::_docvalue_from_variant(const Variant &p_variant, int p_re return "<Object>"; case Variant::DICTIONARY: { const Dictionary dict = p_variant; + String result; - if (dict.is_empty()) { - return "{}"; - } + if (dict.is_typed()) { + result += "Dictionary["; + + Ref<Script> key_script = dict.get_typed_key_script(); + if (key_script.is_valid()) { + if (key_script->get_global_name() != StringName()) { + result += key_script->get_global_name(); + } else if (!key_script->get_path().get_file().is_empty()) { + result += key_script->get_path().get_file(); + } else { + result += dict.get_typed_key_class_name(); + } + } else if (dict.get_typed_key_class_name() != StringName()) { + result += dict.get_typed_key_class_name(); + } else if (dict.is_typed_key()) { + result += Variant::get_type_name((Variant::Type)dict.get_typed_key_builtin()); + } else { + result += "Variant"; + } + + result += ", "; - if (p_recursion_level > MAX_RECURSION_LEVEL) { - return "{...}"; + Ref<Script> value_script = dict.get_typed_value_script(); + if (value_script.is_valid()) { + if (value_script->get_global_name() != StringName()) { + result += value_script->get_global_name(); + } else if (!value_script->get_path().get_file().is_empty()) { + result += value_script->get_path().get_file(); + } else { + result += dict.get_typed_value_class_name(); + } + } else if (dict.get_typed_value_class_name() != StringName()) { + result += dict.get_typed_value_class_name(); + } else if (dict.is_typed_value()) { + result += Variant::get_type_name((Variant::Type)dict.get_typed_value_builtin()); + } else { + result += "Variant"; + } + + result += "]("; } - List<Variant> keys; - dict.get_key_list(&keys); - keys.sort(); + if (dict.is_empty()) { + result += "{}"; + } else if (p_recursion_level > MAX_RECURSION_LEVEL) { + result += "{...}"; + } else { + result += "{"; + + List<Variant> keys; + dict.get_key_list(&keys); + keys.sort(); - String data; - for (List<Variant>::Element *E = keys.front(); E; E = E->next()) { - if (E->prev()) { - data += ", "; + for (List<Variant>::Element *E = keys.front(); E; E = E->next()) { + if (E->prev()) { + result += ", "; + } + result += _docvalue_from_variant(E->get(), p_recursion_level + 1) + ": " + _docvalue_from_variant(dict[E->get()], p_recursion_level + 1); } - data += _docvalue_from_variant(E->get(), p_recursion_level + 1) + ": " + _docvalue_from_variant(dict[E->get()], p_recursion_level + 1); + + result += "}"; + } + + if (dict.is_typed()) { + result += ")"; } - return "{" + data + "}"; + return result; } break; case Variant::ARRAY: { const Array array = p_variant; String result; - if (array.get_typed_builtin() != Variant::NIL) { + if (array.is_typed()) { result += "Array["; Ref<Script> script = array.get_typed_script(); @@ -209,16 +266,18 @@ String GDScriptDocGen::_docvalue_from_variant(const Variant &p_variant, int p_re result += "[...]"; } else { result += "["; + for (int i = 0; i < array.size(); i++) { if (i > 0) { result += ", "; } result += _docvalue_from_variant(array[i], p_recursion_level + 1); } + result += "]"; } - if (array.get_typed_builtin() != Variant::NIL) { + if (array.is_typed()) { result += ")"; } diff --git a/modules/gdscript/gdscript_analyzer.cpp b/modules/gdscript/gdscript_analyzer.cpp index 2c5e6d46e7..7f0d5005cb 100644 --- a/modules/gdscript/gdscript_analyzer.cpp +++ b/modules/gdscript/gdscript_analyzer.cpp @@ -724,6 +724,18 @@ GDScriptParser::DataType GDScriptAnalyzer::resolve_datatype(GDScriptParser::Type result.set_container_element_type(0, container_type); } } + if (result.builtin_type == Variant::DICTIONARY) { + GDScriptParser::DataType key_type = type_from_metatype(resolve_datatype(p_type->get_container_type_or_null(0))); + if (key_type.kind != GDScriptParser::DataType::VARIANT) { + key_type.is_constant = false; + result.set_container_element_type(0, key_type); + } + GDScriptParser::DataType value_type = type_from_metatype(resolve_datatype(p_type->get_container_type_or_null(1))); + if (value_type.kind != GDScriptParser::DataType::VARIANT) { + value_type.is_constant = false; + result.set_container_element_type(1, value_type); + } + } } else if (class_exists(first)) { // Native engine classes. result.kind = GDScriptParser::DataType::NATIVE; @@ -884,11 +896,16 @@ GDScriptParser::DataType GDScriptAnalyzer::resolve_datatype(GDScriptParser::Type if (!p_type->container_types.is_empty()) { if (result.builtin_type == Variant::ARRAY) { if (p_type->container_types.size() != 1) { - push_error("Arrays require exactly one collection element type.", p_type); + push_error(R"(Typed arrays require exactly one collection element type.)", p_type); + return bad_type; + } + } else if (result.builtin_type == Variant::DICTIONARY) { + if (p_type->container_types.size() != 2) { + push_error(R"(Typed dictionaries require exactly two collection element types.)", p_type); return bad_type; } } else { - push_error("Only arrays can specify collection element types.", p_type); + push_error(R"(Only arrays and dictionaries can specify collection element types.)", p_type); return bad_type; } } @@ -1926,6 +1943,11 @@ void GDScriptAnalyzer::resolve_assignable(GDScriptParser::AssignableNode *p_assi if (has_specified_type && specified_type.has_container_element_type(0)) { update_array_literal_element_type(array, specified_type.get_container_element_type(0)); } + } else if (p_assignable->initializer->type == GDScriptParser::Node::DICTIONARY) { + GDScriptParser::DictionaryNode *dictionary = static_cast<GDScriptParser::DictionaryNode *>(p_assignable->initializer); + if (has_specified_type && specified_type.has_container_element_types()) { + update_dictionary_literal_element_type(dictionary, specified_type.get_container_element_type_or_variant(0), specified_type.get_container_element_type_or_variant(1)); + } } if (is_constant && !p_assignable->initializer->is_constant) { @@ -1987,7 +2009,7 @@ void GDScriptAnalyzer::resolve_assignable(GDScriptParser::AssignableNode *p_assi } else { push_error(vformat(R"(Cannot assign a value of type %s to %s "%s" with specified type %s.)", initializer_type.to_string(), p_kind, p_assignable->identifier->name, specified_type.to_string()), p_assignable->initializer); } - } else if (specified_type.has_container_element_type(0) && !initializer_type.has_container_element_type(0)) { + } else if ((specified_type.has_container_element_type(0) && !initializer_type.has_container_element_type(0)) || (specified_type.has_container_element_type(1) && !initializer_type.has_container_element_type(1))) { mark_node_unsafe(p_assignable->initializer); #ifdef DEBUG_ENABLED } else if (specified_type.builtin_type == Variant::INT && initializer_type.builtin_type == Variant::FLOAT) { @@ -2229,8 +2251,12 @@ void GDScriptAnalyzer::resolve_for(GDScriptParser::ForNode *p_for) { } else if (!is_type_compatible(specified_type, variable_type)) { p_for->use_conversion_assign = true; } - if (p_for->list && p_for->list->type == GDScriptParser::Node::ARRAY) { - update_array_literal_element_type(static_cast<GDScriptParser::ArrayNode *>(p_for->list), specified_type); + if (p_for->list) { + if (p_for->list->type == GDScriptParser::Node::ARRAY) { + update_array_literal_element_type(static_cast<GDScriptParser::ArrayNode *>(p_for->list), specified_type); + } else if (p_for->list->type == GDScriptParser::Node::DICTIONARY) { + update_dictionary_literal_element_type(static_cast<GDScriptParser::DictionaryNode *>(p_for->list), specified_type, GDScriptParser::DataType::get_variant_type()); + } } } p_for->variable->set_datatype(specified_type); @@ -2432,6 +2458,9 @@ void GDScriptAnalyzer::resolve_return(GDScriptParser::ReturnNode *p_return) { } else { if (p_return->return_value->type == GDScriptParser::Node::ARRAY && has_expected_type && expected_type.has_container_element_type(0)) { update_array_literal_element_type(static_cast<GDScriptParser::ArrayNode *>(p_return->return_value), expected_type.get_container_element_type(0)); + } else if (p_return->return_value->type == GDScriptParser::Node::DICTIONARY && has_expected_type && expected_type.has_container_element_types()) { + update_dictionary_literal_element_type(static_cast<GDScriptParser::DictionaryNode *>(p_return->return_value), + expected_type.get_container_element_type_or_variant(0), expected_type.get_container_element_type_or_variant(1)); } if (has_expected_type && expected_type.is_hard_type() && p_return->return_value->is_constant) { update_const_expression_builtin_type(p_return->return_value, expected_type, "return"); @@ -2678,6 +2707,54 @@ void GDScriptAnalyzer::update_array_literal_element_type(GDScriptParser::ArrayNo p_array->set_datatype(array_type); } +// When a dictionary literal is stored (or passed as function argument) to a typed context, we then assume the dictionary is typed. +// This function determines which type is that (if any). +void GDScriptAnalyzer::update_dictionary_literal_element_type(GDScriptParser::DictionaryNode *p_dictionary, const GDScriptParser::DataType &p_key_element_type, const GDScriptParser::DataType &p_value_element_type) { + GDScriptParser::DataType expected_key_type = p_key_element_type; + GDScriptParser::DataType expected_value_type = p_value_element_type; + expected_key_type.container_element_types.clear(); // Nested types (like `Dictionary[String, Array[int]]`) are not currently supported. + expected_value_type.container_element_types.clear(); + + for (int i = 0; i < p_dictionary->elements.size(); i++) { + GDScriptParser::ExpressionNode *key_element_node = p_dictionary->elements[i].key; + if (key_element_node->is_constant) { + update_const_expression_builtin_type(key_element_node, expected_key_type, "include"); + } + const GDScriptParser::DataType &actual_key_type = key_element_node->get_datatype(); + if (actual_key_type.has_no_type() || actual_key_type.is_variant() || !actual_key_type.is_hard_type()) { + mark_node_unsafe(key_element_node); + } else if (!is_type_compatible(expected_key_type, actual_key_type, true, p_dictionary)) { + if (is_type_compatible(actual_key_type, expected_key_type)) { + mark_node_unsafe(key_element_node); + } else { + push_error(vformat(R"(Cannot have a key of type "%s" in a dictionary of type "Dictionary[%s, %s]".)", actual_key_type.to_string(), expected_key_type.to_string(), expected_value_type.to_string()), key_element_node); + return; + } + } + + GDScriptParser::ExpressionNode *value_element_node = p_dictionary->elements[i].value; + if (value_element_node->is_constant) { + update_const_expression_builtin_type(value_element_node, expected_value_type, "include"); + } + const GDScriptParser::DataType &actual_value_type = value_element_node->get_datatype(); + if (actual_value_type.has_no_type() || actual_value_type.is_variant() || !actual_value_type.is_hard_type()) { + mark_node_unsafe(value_element_node); + } else if (!is_type_compatible(expected_value_type, actual_value_type, true, p_dictionary)) { + if (is_type_compatible(actual_value_type, expected_value_type)) { + mark_node_unsafe(value_element_node); + } else { + push_error(vformat(R"(Cannot have a value of type "%s" in a dictionary of type "Dictionary[%s, %s]".)", actual_value_type.to_string(), expected_key_type.to_string(), expected_value_type.to_string()), value_element_node); + return; + } + } + } + + GDScriptParser::DataType dictionary_type = p_dictionary->get_datatype(); + dictionary_type.set_container_element_type(0, expected_key_type); + dictionary_type.set_container_element_type(1, expected_value_type); + p_dictionary->set_datatype(dictionary_type); +} + void GDScriptAnalyzer::reduce_assignment(GDScriptParser::AssignmentNode *p_assignment) { reduce_expression(p_assignment->assigned_value); @@ -2770,9 +2847,12 @@ void GDScriptAnalyzer::reduce_assignment(GDScriptParser::AssignmentNode *p_assig } } - // Check if assigned value is an array literal, so we can make it a typed array too if appropriate. + // Check if assigned value is an array/dictionary literal, so we can make it a typed container too if appropriate. if (p_assignment->assigned_value->type == GDScriptParser::Node::ARRAY && assignee_type.is_hard_type() && assignee_type.has_container_element_type(0)) { update_array_literal_element_type(static_cast<GDScriptParser::ArrayNode *>(p_assignment->assigned_value), assignee_type.get_container_element_type(0)); + } else if (p_assignment->assigned_value->type == GDScriptParser::Node::DICTIONARY && assignee_type.is_hard_type() && assignee_type.has_container_element_types()) { + update_dictionary_literal_element_type(static_cast<GDScriptParser::DictionaryNode *>(p_assignment->assigned_value), + assignee_type.get_container_element_type_or_variant(0), assignee_type.get_container_element_type_or_variant(1)); } if (p_assignment->operation == GDScriptParser::AssignmentNode::OP_NONE && assignee_type.is_hard_type() && p_assignment->assigned_value->is_constant) { @@ -2850,8 +2930,8 @@ void GDScriptAnalyzer::reduce_assignment(GDScriptParser::AssignmentNode *p_assig // weak non-variant assignee and incompatible result downgrades_assignee = true; } - } else if (assignee_type.has_container_element_type(0) && !op_type.has_container_element_type(0)) { - // typed array assignee and untyped array result + } else if ((assignee_type.has_container_element_type(0) && !op_type.has_container_element_type(0)) || (assignee_type.has_container_element_type(1) && !op_type.has_container_element_type(1))) { + // Typed assignee and untyped result. mark_node_unsafe(p_assignment); } } @@ -3049,10 +3129,13 @@ const char *check_for_renamed_identifier(String identifier, GDScriptParser::Node void GDScriptAnalyzer::reduce_call(GDScriptParser::CallNode *p_call, bool p_is_await, bool p_is_root) { bool all_is_constant = true; HashMap<int, GDScriptParser::ArrayNode *> arrays; // For array literal to potentially type when passing. + HashMap<int, GDScriptParser::DictionaryNode *> dictionaries; // Same, but for dictionaries. for (int i = 0; i < p_call->arguments.size(); i++) { reduce_expression(p_call->arguments[i]); if (p_call->arguments[i]->type == GDScriptParser::Node::ARRAY) { arrays[i] = static_cast<GDScriptParser::ArrayNode *>(p_call->arguments[i]); + } else if (p_call->arguments[i]->type == GDScriptParser::Node::DICTIONARY) { + dictionaries[i] = static_cast<GDScriptParser::DictionaryNode *>(p_call->arguments[i]); } all_is_constant = all_is_constant && p_call->arguments[i]->is_constant; } @@ -3457,6 +3540,14 @@ void GDScriptAnalyzer::reduce_call(GDScriptParser::CallNode *p_call, bool p_is_a update_array_literal_element_type(E.value, par_types.get(index).get_container_element_type(0)); } } + for (const KeyValue<int, GDScriptParser::DictionaryNode *> &E : dictionaries) { + int index = E.key; + if (index < par_types.size() && par_types.get(index).is_hard_type() && par_types.get(index).has_container_element_types()) { + GDScriptParser::DataType key = par_types.get(index).get_container_element_type_or_variant(0); + GDScriptParser::DataType value = par_types.get(index).get_container_element_type_or_variant(1); + update_dictionary_literal_element_type(E.value, key, value); + } + } validate_call_arg(par_types, default_arg_count, method_flags.has_flag(METHOD_FLAG_VARARG), p_call); if (base_type.kind == GDScriptParser::DataType::ENUM && base_type.is_meta_type) { @@ -3601,6 +3692,11 @@ void GDScriptAnalyzer::reduce_cast(GDScriptParser::CastNode *p_cast) { update_array_literal_element_type(static_cast<GDScriptParser::ArrayNode *>(p_cast->operand), cast_type.get_container_element_type(0)); } + if (p_cast->operand->type == GDScriptParser::Node::DICTIONARY && cast_type.has_container_element_types()) { + update_dictionary_literal_element_type(static_cast<GDScriptParser::DictionaryNode *>(p_cast->operand), + cast_type.get_container_element_type_or_variant(0), cast_type.get_container_element_type_or_variant(1)); + } + if (!cast_type.is_variant()) { GDScriptParser::DataType op_type = p_cast->operand->get_datatype(); if (op_type.is_variant() || !op_type.is_hard_type()) { @@ -4625,10 +4721,23 @@ void GDScriptAnalyzer::reduce_subscript(GDScriptParser::SubscriptNode *p_subscri reduce_identifier_from_base(p_subscript->attribute, &base_type); GDScriptParser::DataType attr_type = p_subscript->attribute->get_datatype(); if (attr_type.is_set()) { - valid = !attr_type.is_pseudo_type || p_can_be_pseudo_type; - result_type = attr_type; - p_subscript->is_constant = p_subscript->attribute->is_constant; - p_subscript->reduced_value = p_subscript->attribute->reduced_value; + if (base_type.builtin_type == Variant::DICTIONARY && base_type.has_container_element_types()) { + Variant::Type key_type = base_type.get_container_element_type_or_variant(0).builtin_type; + valid = key_type == Variant::NIL || key_type == Variant::STRING || key_type == Variant::STRING_NAME; + if (base_type.has_container_element_type(1)) { + result_type = base_type.get_container_element_type(1); + result_type.type_source = base_type.type_source; + } else { + result_type.builtin_type = Variant::NIL; + result_type.kind = GDScriptParser::DataType::VARIANT; + result_type.type_source = GDScriptParser::DataType::UNDETECTED; + } + } else { + valid = !attr_type.is_pseudo_type || p_can_be_pseudo_type; + result_type = attr_type; + p_subscript->is_constant = p_subscript->attribute->is_constant; + p_subscript->reduced_value = p_subscript->attribute->reduced_value; + } } else if (!base_type.is_meta_type || !base_type.is_constant) { valid = base_type.kind != GDScriptParser::DataType::BUILTIN; #ifdef DEBUG_ENABLED @@ -4735,8 +4844,40 @@ void GDScriptAnalyzer::reduce_subscript(GDScriptParser::SubscriptNode *p_subscri case Variant::SIGNAL: case Variant::STRING_NAME: break; - // Here for completeness. + // Support depends on if the dictionary has a typed key, otherwise anything is valid. case Variant::DICTIONARY: + if (base_type.has_container_element_type(0)) { + GDScriptParser::DataType key_type = base_type.get_container_element_type(0); + switch (index_type.builtin_type) { + // Null value will be treated as an empty object, allow. + case Variant::NIL: + error = key_type.builtin_type != Variant::OBJECT; + break; + // Objects are parsed for validity in a similar manner to container types. + case Variant::OBJECT: + if (key_type.builtin_type == Variant::OBJECT) { + error = !key_type.can_reference(index_type); + } else { + error = key_type.builtin_type != Variant::NIL; + } + break; + // String and StringName interchangeable in this context. + case Variant::STRING: + case Variant::STRING_NAME: + error = key_type.builtin_type != Variant::STRING_NAME && key_type.builtin_type != Variant::STRING; + break; + // Ints are valid indices for floats, but not the other way around. + case Variant::INT: + error = key_type.builtin_type != Variant::INT && key_type.builtin_type != Variant::FLOAT; + break; + // All other cases require the types to match exactly. + default: + error = key_type.builtin_type != index_type.builtin_type; + break; + } + } + break; + // Here for completeness. case Variant::VARIANT_MAX: break; } @@ -4825,7 +4966,6 @@ void GDScriptAnalyzer::reduce_subscript(GDScriptParser::SubscriptNode *p_subscri case Variant::PROJECTION: case Variant::PLANE: case Variant::COLOR: - case Variant::DICTIONARY: case Variant::OBJECT: result_type.kind = GDScriptParser::DataType::VARIANT; result_type.type_source = GDScriptParser::DataType::UNDETECTED; @@ -4840,6 +4980,16 @@ void GDScriptAnalyzer::reduce_subscript(GDScriptParser::SubscriptNode *p_subscri result_type.type_source = GDScriptParser::DataType::UNDETECTED; } break; + // Can have two element types, but we only care about the value. + case Variant::DICTIONARY: + if (base_type.has_container_element_type(1)) { + result_type = base_type.get_container_element_type(1); + result_type.type_source = base_type.type_source; + } else { + result_type.kind = GDScriptParser::DataType::VARIANT; + result_type.type_source = GDScriptParser::DataType::UNDETECTED; + } + break; // Here for completeness. case Variant::VARIANT_MAX: break; @@ -5019,7 +5169,9 @@ Variant GDScriptAnalyzer::make_array_reduced_value(GDScriptParser::ArrayNode *p_ } Variant GDScriptAnalyzer::make_dictionary_reduced_value(GDScriptParser::DictionaryNode *p_dictionary, bool &is_reduced) { - Dictionary dictionary; + 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(); for (int i = 0; i < p_dictionary->elements.size(); i++) { const GDScriptParser::DictionaryNode::Pair &element = p_dictionary->elements[i]; @@ -5106,6 +5258,49 @@ Array GDScriptAnalyzer::make_array_from_element_datatype(const GDScriptParser::D return array; } +Dictionary GDScriptAnalyzer::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) { + Dictionary dictionary; + StringName key_name; + Variant key_script; + StringName value_name; + Variant value_script; + + if (p_key_element_datatype.builtin_type == Variant::OBJECT) { + Ref<Script> script_type = p_key_element_datatype.script_type; + if (p_key_element_datatype.kind == GDScriptParser::DataType::CLASS && script_type.is_null()) { + Error err = OK; + Ref<GDScript> scr = get_depended_shallow_script(p_key_element_datatype.script_path, err); + if (err) { + push_error(vformat(R"(Error while getting cache for script "%s".)", p_key_element_datatype.script_path), p_source_node); + return dictionary; + } + script_type.reference_ptr(scr->find_class(p_key_element_datatype.class_type->fqcn)); + } + + key_name = p_key_element_datatype.native_type; + key_script = script_type; + } + + if (p_value_element_datatype.builtin_type == Variant::OBJECT) { + Ref<Script> script_type = p_value_element_datatype.script_type; + if (p_value_element_datatype.kind == GDScriptParser::DataType::CLASS && script_type.is_null()) { + Error err = OK; + Ref<GDScript> scr = get_depended_shallow_script(p_value_element_datatype.script_path, err); + if (err) { + push_error(vformat(R"(Error while getting cache for script "%s".)", p_value_element_datatype.script_path), p_source_node); + return dictionary; + } + script_type.reference_ptr(scr->find_class(p_value_element_datatype.class_type->fqcn)); + } + + value_name = p_value_element_datatype.native_type; + value_script = script_type; + } + + dictionary.set_typed(p_key_element_datatype.builtin_type, key_name, key_script, p_value_element_datatype.builtin_type, value_name, value_script); + return dictionary; +} + Variant GDScriptAnalyzer::make_variable_default_value(GDScriptParser::VariableNode *p_variable) { Variant result = Variant(); @@ -5121,6 +5316,10 @@ Variant GDScriptAnalyzer::make_variable_default_value(GDScriptParser::VariableNo if (datatype.kind == GDScriptParser::DataType::BUILTIN && datatype.builtin_type != Variant::OBJECT) { if (datatype.builtin_type == Variant::ARRAY && datatype.has_container_element_type(0)) { result = make_array_from_element_datatype(datatype.get_container_element_type(0)); + } else if (datatype.builtin_type == Variant::DICTIONARY && datatype.has_container_element_types()) { + GDScriptParser::DataType key = datatype.get_container_element_type_or_variant(0); + GDScriptParser::DataType value = datatype.get_container_element_type_or_variant(1); + result = make_dictionary_from_element_datatype(key, value); } else { VariantInternal::initialize(&result, datatype.builtin_type); } @@ -5149,6 +5348,22 @@ GDScriptParser::DataType GDScriptAnalyzer::type_from_variant(const Variant &p_va } else if (array.get_typed_builtin() != Variant::NIL) { result.set_container_element_type(0, type_from_metatype(make_builtin_meta_type((Variant::Type)array.get_typed_builtin()))); } + } 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()))); + } 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()))); + } 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) { + result.set_container_element_type(1, type_from_metatype(make_builtin_meta_type((Variant::Type)dict.get_typed_value_builtin()))); + } } else if (p_value.get_type() == Variant::OBJECT) { // Object is treated as a native type, not a builtin type. result.kind = GDScriptParser::DataType::NATIVE; @@ -5281,6 +5496,60 @@ GDScriptParser::DataType GDScriptAnalyzer::type_from_property(const PropertyInfo } elem_type.is_constant = false; result.set_container_element_type(0, elem_type); + } else if (p_property.type == Variant::DICTIONARY && p_property.hint == PROPERTY_HINT_DICTIONARY_TYPE) { + // Check element type. + StringName key_elem_type_name = p_property.hint_string.get_slice(";", 0); + GDScriptParser::DataType key_elem_type; + key_elem_type.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT; + + Variant::Type key_elem_builtin_type = GDScriptParser::get_builtin_type(key_elem_type_name); + if (key_elem_builtin_type < Variant::VARIANT_MAX) { + // Builtin type. + key_elem_type.kind = GDScriptParser::DataType::BUILTIN; + key_elem_type.builtin_type = key_elem_builtin_type; + } else if (class_exists(key_elem_type_name)) { + key_elem_type.kind = GDScriptParser::DataType::NATIVE; + key_elem_type.builtin_type = Variant::OBJECT; + key_elem_type.native_type = key_elem_type_name; + } else if (ScriptServer::is_global_class(key_elem_type_name)) { + // Just load this as it shouldn't be a GDScript. + Ref<Script> script = ResourceLoader::load(ScriptServer::get_global_class_path(key_elem_type_name)); + key_elem_type.kind = GDScriptParser::DataType::SCRIPT; + key_elem_type.builtin_type = Variant::OBJECT; + key_elem_type.native_type = script->get_instance_base_type(); + key_elem_type.script_type = script; + } else { + ERR_FAIL_V_MSG(result, "Could not find element type from property hint of a typed dictionary."); + } + key_elem_type.is_constant = false; + + StringName value_elem_type_name = p_property.hint_string.get_slice(";", 1); + GDScriptParser::DataType value_elem_type; + value_elem_type.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT; + + Variant::Type value_elem_builtin_type = GDScriptParser::get_builtin_type(value_elem_type_name); + if (value_elem_builtin_type < Variant::VARIANT_MAX) { + // Builtin type. + value_elem_type.kind = GDScriptParser::DataType::BUILTIN; + value_elem_type.builtin_type = value_elem_builtin_type; + } else if (class_exists(value_elem_type_name)) { + value_elem_type.kind = GDScriptParser::DataType::NATIVE; + value_elem_type.builtin_type = Variant::OBJECT; + value_elem_type.native_type = value_elem_type_name; + } else if (ScriptServer::is_global_class(value_elem_type_name)) { + // Just load this as it shouldn't be a GDScript. + Ref<Script> script = ResourceLoader::load(ScriptServer::get_global_class_path(value_elem_type_name)); + value_elem_type.kind = GDScriptParser::DataType::SCRIPT; + value_elem_type.builtin_type = Variant::OBJECT; + value_elem_type.native_type = script->get_instance_base_type(); + value_elem_type.script_type = script; + } else { + ERR_FAIL_V_MSG(result, "Could not find element type from property hint of a typed dictionary."); + } + value_elem_type.is_constant = false; + + result.set_container_element_type(0, key_elem_type); + result.set_container_element_type(1, value_elem_type); } else if (p_property.type == Variant::INT) { // Check if it's enum. if ((p_property.usage & PROPERTY_USAGE_CLASS_IS_ENUM) && p_property.class_name != StringName()) { @@ -5701,6 +5970,15 @@ bool GDScriptAnalyzer::check_type_compatibility(const GDScriptParser::DataType & valid = p_target.get_container_element_type(0) == p_source.get_container_element_type(0); } } + if (valid && p_target.builtin_type == Variant::DICTIONARY && p_source.builtin_type == Variant::DICTIONARY) { + // Check the element types. + if (p_target.has_container_element_type(0) && p_source.has_container_element_type(0)) { + valid = p_target.get_container_element_type(0) == p_source.get_container_element_type(0); + } + if (valid && p_target.has_container_element_type(1) && p_source.has_container_element_type(1)) { + valid = p_target.get_container_element_type(1) == p_source.get_container_element_type(1); + } + } return valid; } diff --git a/modules/gdscript/gdscript_analyzer.h b/modules/gdscript/gdscript_analyzer.h index 25e5aa9a2c..3b781409a4 100644 --- a/modules/gdscript/gdscript_analyzer.h +++ b/modules/gdscript/gdscript_analyzer.h @@ -125,6 +125,7 @@ class GDScriptAnalyzer { // 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_variant(const Variant &p_value, const GDScriptParser::Node *p_source); static GDScriptParser::DataType type_from_metatype(const GDScriptParser::DataType &p_meta_type); GDScriptParser::DataType type_from_property(const PropertyInfo &p_property, bool p_is_arg = false, bool p_is_readonly = false) const; @@ -137,6 +138,7 @@ class GDScriptAnalyzer { GDScriptParser::DataType get_operation_type(Variant::Operator p_operation, const GDScriptParser::DataType &p_a, bool &r_valid, const GDScriptParser::Node *p_source); void update_const_expression_builtin_type(GDScriptParser::ExpressionNode *p_expression, const GDScriptParser::DataType &p_type, const char *p_usage, bool p_is_cast = false); 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); void push_error(const String &p_message, const GDScriptParser::Node *p_origin = nullptr); void mark_node_unsafe(const GDScriptParser::Node *p_node); diff --git a/modules/gdscript/gdscript_byte_codegen.cpp b/modules/gdscript/gdscript_byte_codegen.cpp index 4cda3d3037..b77c641eb5 100644 --- a/modules/gdscript/gdscript_byte_codegen.cpp +++ b/modules/gdscript/gdscript_byte_codegen.cpp @@ -634,6 +634,18 @@ void GDScriptByteCodeGenerator::write_type_test(const Address &p_target, const A append(get_constant_pos(element_type.script_type) | (GDScriptFunction::ADDR_TYPE_CONSTANT << GDScriptFunction::ADDR_BITS)); append(element_type.builtin_type); append(element_type.native_type); + } else if (p_type.builtin_type == Variant::DICTIONARY && p_type.has_container_element_types()) { + const GDScriptDataType &key_element_type = p_type.get_container_element_type_or_variant(0); + const GDScriptDataType &value_element_type = p_type.get_container_element_type_or_variant(1); + append_opcode(GDScriptFunction::OPCODE_TYPE_TEST_DICTIONARY); + append(p_target); + append(p_source); + append(get_constant_pos(key_element_type.script_type) | (GDScriptFunction::ADDR_TYPE_CONSTANT << GDScriptFunction::ADDR_BITS)); + append(get_constant_pos(value_element_type.script_type) | (GDScriptFunction::ADDR_TYPE_CONSTANT << GDScriptFunction::ADDR_BITS)); + append(key_element_type.builtin_type); + append(key_element_type.native_type); + append(value_element_type.builtin_type); + append(value_element_type.native_type); } else { append_opcode(GDScriptFunction::OPCODE_TYPE_TEST_BUILTIN); append(p_target); @@ -889,6 +901,18 @@ void GDScriptByteCodeGenerator::write_assign_with_conversion(const Address &p_ta append(get_constant_pos(element_type.script_type) | (GDScriptFunction::ADDR_TYPE_CONSTANT << GDScriptFunction::ADDR_BITS)); append(element_type.builtin_type); append(element_type.native_type); + } else if (p_target.type.builtin_type == Variant::DICTIONARY && p_target.type.has_container_element_types()) { + const GDScriptDataType &key_type = p_target.type.get_container_element_type_or_variant(0); + const GDScriptDataType &value_type = p_target.type.get_container_element_type_or_variant(1); + append_opcode(GDScriptFunction::OPCODE_ASSIGN_TYPED_DICTIONARY); + append(p_target); + append(p_source); + append(get_constant_pos(key_type.script_type) | (GDScriptFunction::ADDR_TYPE_CONSTANT << GDScriptFunction::ADDR_BITS)); + append(get_constant_pos(value_type.script_type) | (GDScriptFunction::ADDR_TYPE_CONSTANT << GDScriptFunction::ADDR_BITS)); + append(key_type.builtin_type); + append(key_type.native_type); + append(value_type.builtin_type); + append(value_type.native_type); } else { append_opcode(GDScriptFunction::OPCODE_ASSIGN_TYPED_BUILTIN); append(p_target); @@ -935,6 +959,18 @@ void GDScriptByteCodeGenerator::write_assign(const Address &p_target, const Addr append(get_constant_pos(element_type.script_type) | (GDScriptFunction::ADDR_TYPE_CONSTANT << GDScriptFunction::ADDR_BITS)); append(element_type.builtin_type); append(element_type.native_type); + } else if (p_target.type.kind == GDScriptDataType::BUILTIN && p_target.type.builtin_type == Variant::DICTIONARY && p_target.type.has_container_element_types()) { + const GDScriptDataType &key_type = p_target.type.get_container_element_type_or_variant(0); + const GDScriptDataType &value_type = p_target.type.get_container_element_type_or_variant(1); + append_opcode(GDScriptFunction::OPCODE_ASSIGN_TYPED_DICTIONARY); + append(p_target); + append(p_source); + append(get_constant_pos(key_type.script_type) | (GDScriptFunction::ADDR_TYPE_CONSTANT << GDScriptFunction::ADDR_BITS)); + append(get_constant_pos(value_type.script_type) | (GDScriptFunction::ADDR_TYPE_CONSTANT << GDScriptFunction::ADDR_BITS)); + append(key_type.builtin_type); + append(key_type.native_type); + append(value_type.builtin_type); + append(value_type.native_type); } else if (p_target.type.kind == GDScriptDataType::BUILTIN && p_source.type.kind == GDScriptDataType::BUILTIN && p_target.type.builtin_type != p_source.type.builtin_type) { // Need conversion. append_opcode(GDScriptFunction::OPCODE_ASSIGN_TYPED_BUILTIN); @@ -1434,6 +1470,23 @@ void GDScriptByteCodeGenerator::write_construct_dictionary(const Address &p_targ ct.cleanup(); } +void GDScriptByteCodeGenerator::write_construct_typed_dictionary(const Address &p_target, const GDScriptDataType &p_key_type, const GDScriptDataType &p_value_type, const Vector<Address> &p_arguments) { + append_opcode_and_argcount(GDScriptFunction::OPCODE_CONSTRUCT_TYPED_DICTIONARY, 3 + p_arguments.size()); + for (int i = 0; i < p_arguments.size(); i++) { + append(p_arguments[i]); + } + CallTarget ct = get_call_target(p_target); + append(ct.target); + append(get_constant_pos(p_key_type.script_type) | (GDScriptFunction::ADDR_TYPE_CONSTANT << GDScriptFunction::ADDR_BITS)); + append(get_constant_pos(p_value_type.script_type) | (GDScriptFunction::ADDR_TYPE_CONSTANT << GDScriptFunction::ADDR_BITS)); + append(p_arguments.size() / 2); // This is number of key-value pairs, so only half of actual arguments. + append(p_key_type.builtin_type); + append(p_key_type.native_type); + append(p_value_type.builtin_type); + append(p_value_type.native_type); + ct.cleanup(); +} + void GDScriptByteCodeGenerator::write_await(const Address &p_target, const Address &p_operand) { append_opcode(GDScriptFunction::OPCODE_AWAIT); append(p_operand); @@ -1711,6 +1764,19 @@ void GDScriptByteCodeGenerator::write_return(const Address &p_return_value) { append(get_constant_pos(element_type.script_type) | (GDScriptFunction::ADDR_TYPE_CONSTANT << GDScriptFunction::ADDR_BITS)); append(element_type.builtin_type); append(element_type.native_type); + } else if (function->return_type.kind == GDScriptDataType::BUILTIN && function->return_type.builtin_type == Variant::DICTIONARY && + function->return_type.has_container_element_types()) { + // Typed dictionary. + const GDScriptDataType &key_type = function->return_type.get_container_element_type_or_variant(0); + const GDScriptDataType &value_type = function->return_type.get_container_element_type_or_variant(1); + append_opcode(GDScriptFunction::OPCODE_RETURN_TYPED_DICTIONARY); + append(p_return_value); + append(get_constant_pos(key_type.script_type) | (GDScriptFunction::ADDR_TYPE_CONSTANT << GDScriptFunction::ADDR_BITS)); + append(get_constant_pos(value_type.script_type) | (GDScriptFunction::ADDR_TYPE_CONSTANT << GDScriptFunction::ADDR_BITS)); + append(key_type.builtin_type); + append(key_type.native_type); + append(value_type.builtin_type); + append(value_type.native_type); } else if (function->return_type.kind == GDScriptDataType::BUILTIN && p_return_value.type.kind == GDScriptDataType::BUILTIN && function->return_type.builtin_type != p_return_value.type.builtin_type) { // Add conversion. append_opcode(GDScriptFunction::OPCODE_RETURN_TYPED_BUILTIN); @@ -1735,6 +1801,17 @@ void GDScriptByteCodeGenerator::write_return(const Address &p_return_value) { append(get_constant_pos(element_type.script_type) | (GDScriptFunction::ADDR_TYPE_CONSTANT << GDScriptFunction::ADDR_BITS)); append(element_type.builtin_type); append(element_type.native_type); + } else if (function->return_type.builtin_type == Variant::DICTIONARY && function->return_type.has_container_element_types()) { + const GDScriptDataType &key_type = function->return_type.get_container_element_type_or_variant(0); + const GDScriptDataType &value_type = function->return_type.get_container_element_type_or_variant(1); + append_opcode(GDScriptFunction::OPCODE_RETURN_TYPED_DICTIONARY); + append(p_return_value); + append(get_constant_pos(key_type.script_type) | (GDScriptFunction::ADDR_TYPE_CONSTANT << GDScriptFunction::ADDR_BITS)); + append(get_constant_pos(value_type.script_type) | (GDScriptFunction::ADDR_TYPE_CONSTANT << GDScriptFunction::ADDR_BITS)); + append(key_type.builtin_type); + append(key_type.native_type); + append(value_type.builtin_type); + append(value_type.native_type); } else { append_opcode(GDScriptFunction::OPCODE_RETURN_TYPED_BUILTIN); append(p_return_value); @@ -1803,6 +1880,13 @@ void GDScriptByteCodeGenerator::clear_address(const Address &p_address) { case Variant::BOOL: write_assign_false(p_address); break; + case Variant::DICTIONARY: + if (p_address.type.has_container_element_types()) { + write_construct_typed_dictionary(p_address, p_address.type.get_container_element_type_or_variant(0), p_address.type.get_container_element_type_or_variant(1), Vector<GDScriptCodeGenerator::Address>()); + } else { + write_construct(p_address, p_address.type.builtin_type, Vector<GDScriptCodeGenerator::Address>()); + } + break; case Variant::ARRAY: if (p_address.type.has_container_element_type(0)) { write_construct_typed_array(p_address, p_address.type.get_container_element_type(0), Vector<GDScriptCodeGenerator::Address>()); diff --git a/modules/gdscript/gdscript_byte_codegen.h b/modules/gdscript/gdscript_byte_codegen.h index 34f56a2f5c..6303db71fd 100644 --- a/modules/gdscript/gdscript_byte_codegen.h +++ b/modules/gdscript/gdscript_byte_codegen.h @@ -529,6 +529,7 @@ public: virtual void write_construct_array(const Address &p_target, const Vector<Address> &p_arguments) override; virtual void write_construct_typed_array(const Address &p_target, const GDScriptDataType &p_element_type, const Vector<Address> &p_arguments) override; virtual void write_construct_dictionary(const Address &p_target, const Vector<Address> &p_arguments) override; + virtual void write_construct_typed_dictionary(const Address &p_target, const GDScriptDataType &p_key_type, const GDScriptDataType &p_value_type, const Vector<Address> &p_arguments) override; virtual void write_await(const Address &p_target, const Address &p_operand) override; virtual void write_if(const Address &p_condition) override; virtual void write_else() override; diff --git a/modules/gdscript/gdscript_codegen.h b/modules/gdscript/gdscript_codegen.h index c1c0b61395..f3c4acf1c3 100644 --- a/modules/gdscript/gdscript_codegen.h +++ b/modules/gdscript/gdscript_codegen.h @@ -142,6 +142,7 @@ public: virtual void write_construct_array(const Address &p_target, const Vector<Address> &p_arguments) = 0; virtual void write_construct_typed_array(const Address &p_target, const GDScriptDataType &p_element_type, const Vector<Address> &p_arguments) = 0; virtual void write_construct_dictionary(const Address &p_target, const Vector<Address> &p_arguments) = 0; + virtual void write_construct_typed_dictionary(const Address &p_target, const GDScriptDataType &p_key_type, const GDScriptDataType &p_value_type, const Vector<Address> &p_arguments) = 0; virtual void write_await(const Address &p_target, const Address &p_operand) = 0; virtual void write_if(const Address &p_condition) = 0; virtual void write_else() = 0; diff --git a/modules/gdscript/gdscript_compiler.cpp b/modules/gdscript/gdscript_compiler.cpp index d8b44a558f..eebf282633 100644 --- a/modules/gdscript/gdscript_compiler.cpp +++ b/modules/gdscript/gdscript_compiler.cpp @@ -532,10 +532,7 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &code Vector<GDScriptCodeGenerator::Address> elements; // Create the result temporary first since it's the last to be killed. - GDScriptDataType dict_type; - dict_type.has_type = true; - dict_type.kind = GDScriptDataType::BUILTIN; - dict_type.builtin_type = Variant::DICTIONARY; + GDScriptDataType dict_type = _gdtype_from_datatype(dn->get_datatype(), codegen.script); GDScriptCodeGenerator::Address result = codegen.add_temporary(dict_type); for (int i = 0; i < dn->elements.size(); i++) { @@ -566,7 +563,11 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &code elements.push_back(element); } - gen->write_construct_dictionary(result, elements); + if (dict_type.has_container_element_types()) { + gen->write_construct_typed_dictionary(result, dict_type.get_container_element_type_or_variant(0), dict_type.get_container_element_type_or_variant(1), elements); + } else { + gen->write_construct_dictionary(result, elements); + } for (int i = 0; i < elements.size(); i++) { if (elements[i].mode == GDScriptCodeGenerator::Address::TEMPORARY) { @@ -2325,8 +2326,11 @@ GDScriptFunction *GDScriptCompiler::_parse_function(Error &r_error, GDScript *p_ GDScriptCodeGenerator::Address dst_address(GDScriptCodeGenerator::Address::MEMBER, codegen.script->member_indices[field->identifier->name].index, field_type); - if (field_type.has_container_element_type(0)) { + if (field_type.builtin_type == Variant::ARRAY && field_type.has_container_element_type(0)) { codegen.generator->write_construct_typed_array(dst_address, field_type.get_container_element_type(0), Vector<GDScriptCodeGenerator::Address>()); + } else if (field_type.builtin_type == Variant::DICTIONARY && field_type.has_container_element_types()) { + codegen.generator->write_construct_typed_dictionary(dst_address, field_type.get_container_element_type_or_variant(0), + field_type.get_container_element_type_or_variant(1), Vector<GDScriptCodeGenerator::Address>()); } else if (field_type.kind == GDScriptDataType::BUILTIN) { codegen.generator->write_construct(dst_address, field_type.builtin_type, Vector<GDScriptCodeGenerator::Address>()); } @@ -2515,11 +2519,17 @@ GDScriptFunction *GDScriptCompiler::_make_static_initializer(Error &r_error, GDS if (field_type.has_type) { codegen.generator->write_newline(field->start_line); - if (field_type.has_container_element_type(0)) { + if (field_type.builtin_type == Variant::ARRAY && field_type.has_container_element_type(0)) { GDScriptCodeGenerator::Address temp = codegen.add_temporary(field_type); codegen.generator->write_construct_typed_array(temp, field_type.get_container_element_type(0), Vector<GDScriptCodeGenerator::Address>()); codegen.generator->write_set_static_variable(temp, class_addr, p_script->static_variables_indices[field->identifier->name].index); codegen.generator->pop_temporary(); + } else if (field_type.builtin_type == Variant::DICTIONARY && field_type.has_container_element_types()) { + GDScriptCodeGenerator::Address temp = codegen.add_temporary(field_type); + codegen.generator->write_construct_typed_dictionary(temp, field_type.get_container_element_type_or_variant(0), + field_type.get_container_element_type_or_variant(1), Vector<GDScriptCodeGenerator::Address>()); + codegen.generator->write_set_static_variable(temp, class_addr, p_script->static_variables_indices[field->identifier->name].index); + codegen.generator->pop_temporary(); } else if (field_type.kind == GDScriptDataType::BUILTIN) { GDScriptCodeGenerator::Address temp = codegen.add_temporary(field_type); codegen.generator->write_construct(temp, field_type.builtin_type, Vector<GDScriptCodeGenerator::Address>()); diff --git a/modules/gdscript/gdscript_disassembler.cpp b/modules/gdscript/gdscript_disassembler.cpp index 0331045078..bc063693a3 100644 --- a/modules/gdscript/gdscript_disassembler.cpp +++ b/modules/gdscript/gdscript_disassembler.cpp @@ -176,6 +176,47 @@ void GDScriptFunction::disassemble(const Vector<String> &p_code_lines) const { incr += 6; } break; + case OPCODE_TYPE_TEST_DICTIONARY: { + text += "type test "; + text += DADDR(1); + text += " = "; + text += DADDR(2); + text += " is Dictionary["; + + Ref<Script> key_script_type = get_constant(_code_ptr[ip + 3] & ADDR_MASK); + Variant::Type key_builtin_type = (Variant::Type)_code_ptr[ip + 5]; + StringName key_native_type = get_global_name(_code_ptr[ip + 6]); + + if (key_script_type.is_valid() && key_script_type->is_valid()) { + text += "script("; + text += GDScript::debug_get_script_name(key_script_type); + text += ")"; + } else if (key_native_type != StringName()) { + text += key_native_type; + } else { + text += Variant::get_type_name(key_builtin_type); + } + + text += ", "; + + Ref<Script> value_script_type = get_constant(_code_ptr[ip + 4] & ADDR_MASK); + Variant::Type value_builtin_type = (Variant::Type)_code_ptr[ip + 7]; + StringName value_native_type = get_global_name(_code_ptr[ip + 8]); + + if (value_script_type.is_valid() && value_script_type->is_valid()) { + text += "script("; + text += GDScript::debug_get_script_name(value_script_type); + text += ")"; + } else if (value_native_type != StringName()) { + text += value_native_type; + } else { + text += Variant::get_type_name(value_builtin_type); + } + + text += "]"; + + incr += 9; + } break; case OPCODE_TYPE_TEST_NATIVE: { text += "type test "; text += DADDR(1); @@ -399,6 +440,14 @@ void GDScriptFunction::disassemble(const Vector<String> &p_code_lines) const { incr += 6; } break; + case OPCODE_ASSIGN_TYPED_DICTIONARY: { + text += "assign typed dictionary "; + text += DADDR(1); + text += " = "; + text += DADDR(2); + + incr += 9; + } break; case OPCODE_ASSIGN_TYPED_NATIVE: { text += "assign typed native ("; text += DADDR(3); @@ -564,6 +613,58 @@ void GDScriptFunction::disassemble(const Vector<String> &p_code_lines) const { incr += 3 + argc * 2; } break; + case OPCODE_CONSTRUCT_TYPED_DICTIONARY: { + int instr_var_args = _code_ptr[++ip]; + int argc = _code_ptr[ip + 1 + instr_var_args]; + + Ref<Script> key_script_type = get_constant(_code_ptr[ip + argc * 2 + 2] & ADDR_MASK); + Variant::Type key_builtin_type = (Variant::Type)_code_ptr[ip + argc * 2 + 5]; + StringName key_native_type = get_global_name(_code_ptr[ip + argc * 2 + 6]); + + String key_type_name; + if (key_script_type.is_valid() && key_script_type->is_valid()) { + key_type_name = "script(" + GDScript::debug_get_script_name(key_script_type) + ")"; + } else if (key_native_type != StringName()) { + key_type_name = key_native_type; + } else { + key_type_name = Variant::get_type_name(key_builtin_type); + } + + Ref<Script> value_script_type = get_constant(_code_ptr[ip + argc * 2 + 3] & ADDR_MASK); + Variant::Type value_builtin_type = (Variant::Type)_code_ptr[ip + argc * 2 + 7]; + StringName value_native_type = get_global_name(_code_ptr[ip + argc * 2 + 8]); + + String value_type_name; + if (value_script_type.is_valid() && value_script_type->is_valid()) { + value_type_name = "script(" + GDScript::debug_get_script_name(value_script_type) + ")"; + } else if (value_native_type != StringName()) { + value_type_name = value_native_type; + } else { + value_type_name = Variant::get_type_name(value_builtin_type); + } + + text += "make_typed_dict ("; + text += key_type_name; + text += ", "; + text += value_type_name; + text += ") "; + + text += DADDR(1 + argc * 2); + text += " = {"; + + for (int i = 0; i < argc; i++) { + if (i > 0) { + text += ", "; + } + text += DADDR(1 + i * 2 + 0); + text += ": "; + text += DADDR(1 + i * 2 + 1); + } + + text += "}"; + + incr += 9 + argc * 2; + } break; case OPCODE_CALL: case OPCODE_CALL_RETURN: case OPCODE_CALL_ASYNC: { @@ -978,6 +1079,12 @@ void GDScriptFunction::disassemble(const Vector<String> &p_code_lines) const { incr += 5; } break; + case OPCODE_RETURN_TYPED_DICTIONARY: { + text += "return typed dictionary "; + text += DADDR(1); + + incr += 8; + } break; case OPCODE_RETURN_TYPED_NATIVE: { text += "return typed native ("; text += DADDR(2); diff --git a/modules/gdscript/gdscript_editor.cpp b/modules/gdscript/gdscript_editor.cpp index db7bef2f07..524f528f76 100644 --- a/modules/gdscript/gdscript_editor.cpp +++ b/modules/gdscript/gdscript_editor.cpp @@ -697,6 +697,10 @@ static String _get_visual_datatype(const PropertyInfo &p_info, bool p_is_arg, co return _trim_parent_class(class_name, p_base_class); } else if (p_info.type == Variant::ARRAY && p_info.hint == PROPERTY_HINT_ARRAY_TYPE && !p_info.hint_string.is_empty()) { return "Array[" + _trim_parent_class(p_info.hint_string, p_base_class) + "]"; + } else if (p_info.type == Variant::DICTIONARY && p_info.hint == PROPERTY_HINT_DICTIONARY_TYPE && !p_info.hint_string.is_empty()) { + const String key = p_info.hint_string.get_slice(";", 0); + const String value = p_info.hint_string.get_slice(";", 1); + return "Dictionary[" + _trim_parent_class(key, p_base_class) + ", " + _trim_parent_class(value, p_base_class) + "]"; } else if (p_info.type == Variant::NIL) { if (p_is_arg || (p_info.usage & PROPERTY_USAGE_NIL_IS_VARIANT)) { return "Variant"; diff --git a/modules/gdscript/gdscript_function.h b/modules/gdscript/gdscript_function.h index ac4bab6d84..6433072b55 100644 --- a/modules/gdscript/gdscript_function.h +++ b/modules/gdscript/gdscript_function.h @@ -93,6 +93,41 @@ public: } else { valid = false; } + } else if (valid && builtin_type == Variant::DICTIONARY && has_container_element_types()) { + Dictionary dictionary = p_variant; + if (dictionary.is_typed()) { + if (dictionary.is_typed_key()) { + GDScriptDataType key = get_container_element_type_or_variant(0); + Variant::Type key_builtin_type = (Variant::Type)dictionary.get_typed_key_builtin(); + StringName key_native_type = dictionary.get_typed_key_class_name(); + Ref<Script> key_script_type_ref = dictionary.get_typed_key_script(); + + if (key_script_type_ref.is_valid()) { + valid = (key.kind == SCRIPT || key.kind == GDSCRIPT) && key.script_type == key_script_type_ref.ptr(); + } else if (key_native_type != StringName()) { + valid = key.kind == NATIVE && key.native_type == key_native_type; + } else { + valid = key.kind == BUILTIN && key.builtin_type == key_builtin_type; + } + } + + if (valid && dictionary.is_typed_value()) { + GDScriptDataType value = get_container_element_type_or_variant(1); + Variant::Type value_builtin_type = (Variant::Type)dictionary.get_typed_value_builtin(); + StringName value_native_type = dictionary.get_typed_value_class_name(); + Ref<Script> value_script_type_ref = dictionary.get_typed_value_script(); + + if (value_script_type_ref.is_valid()) { + valid = (value.kind == SCRIPT || value.kind == GDSCRIPT) && value.script_type == value_script_type_ref.ptr(); + } else if (value_native_type != StringName()) { + valid = value.kind == NATIVE && value.native_type == value_native_type; + } else { + valid = value.kind == BUILTIN && value.builtin_type == value_builtin_type; + } + } + } else { + valid = false; + } } else if (!valid && p_allow_implicit_conversion) { valid = Variant::can_convert_strict(var_type, builtin_type); } @@ -156,6 +191,10 @@ public: } return true; case Variant::DICTIONARY: + if (has_container_element_types()) { + return get_container_element_type_or_variant(0).can_contain_object() || get_container_element_type_or_variant(1).can_contain_object(); + } + return true; case Variant::NIL: case Variant::OBJECT: return true; @@ -220,6 +259,7 @@ public: OPCODE_OPERATOR_VALIDATED, OPCODE_TYPE_TEST_BUILTIN, OPCODE_TYPE_TEST_ARRAY, + OPCODE_TYPE_TEST_DICTIONARY, OPCODE_TYPE_TEST_NATIVE, OPCODE_TYPE_TEST_SCRIPT, OPCODE_SET_KEYED, @@ -242,6 +282,7 @@ public: OPCODE_ASSIGN_FALSE, OPCODE_ASSIGN_TYPED_BUILTIN, OPCODE_ASSIGN_TYPED_ARRAY, + OPCODE_ASSIGN_TYPED_DICTIONARY, OPCODE_ASSIGN_TYPED_NATIVE, OPCODE_ASSIGN_TYPED_SCRIPT, OPCODE_CAST_TO_BUILTIN, @@ -252,6 +293,7 @@ public: OPCODE_CONSTRUCT_ARRAY, OPCODE_CONSTRUCT_TYPED_ARRAY, OPCODE_CONSTRUCT_DICTIONARY, + OPCODE_CONSTRUCT_TYPED_DICTIONARY, OPCODE_CALL, OPCODE_CALL_RETURN, OPCODE_CALL_ASYNC, @@ -280,6 +322,7 @@ public: OPCODE_RETURN, OPCODE_RETURN_TYPED_BUILTIN, OPCODE_RETURN_TYPED_ARRAY, + OPCODE_RETURN_TYPED_DICTIONARY, OPCODE_RETURN_TYPED_NATIVE, OPCODE_RETURN_TYPED_SCRIPT, OPCODE_ITERATE_BEGIN, diff --git a/modules/gdscript/gdscript_parser.cpp b/modules/gdscript/gdscript_parser.cpp index 92f9c5fa11..65aa150be3 100644 --- a/modules/gdscript/gdscript_parser.cpp +++ b/modules/gdscript/gdscript_parser.cpp @@ -3568,7 +3568,7 @@ GDScriptParser::TypeNode *GDScriptParser::parse_type(bool p_allow_void) { type->type_chain.push_back(type_element); if (match(GDScriptTokenizer::Token::BRACKET_OPEN)) { - // Typed collection (like Array[int]). + // Typed collection (like Array[int], Dictionary[String, int]). bool first_pass = true; do { TypeNode *container_type = parse_type(false); // Don't allow void for element type. @@ -4385,6 +4385,14 @@ bool GDScriptParser::export_annotations(AnnotationNode *p_annotation, Node *p_ta export_type.type_source = variable->datatype.type_source; } + bool is_dict = false; + if (export_type.builtin_type == Variant::DICTIONARY && export_type.has_container_element_types()) { + is_dict = true; + DataType inner_type = export_type.get_container_element_type_or_variant(1); + export_type = export_type.get_container_element_type_or_variant(0); + export_type.set_container_element_type(0, inner_type); // Store earlier extracted value within key to separately parse after. + } + bool use_default_variable_type_check = true; if (p_annotation->name == SNAME("@export_range")) { @@ -4412,8 +4420,13 @@ bool GDScriptParser::export_annotations(AnnotationNode *p_annotation, Node *p_ta } if (export_type.is_variant() || export_type.has_no_type()) { - push_error(R"(Cannot use simple "@export" annotation because the type of the initialized value can't be inferred.)", p_annotation); - return false; + if (is_dict) { + // Dictionary allowed to have a variant key/value. + export_type.kind = GDScriptParser::DataType::BUILTIN; + } else { + push_error(R"(Cannot use simple "@export" annotation because the type of the initialized value can't be inferred.)", p_annotation); + return false; + } } switch (export_type.kind) { @@ -4473,6 +4486,90 @@ bool GDScriptParser::export_annotations(AnnotationNode *p_annotation, Node *p_ta push_error(vformat(R"(Node export is only supported in Node-derived classes, but the current class inherits "%s".)", p_class->base_type.to_string()), p_annotation); return false; } + + if (is_dict) { + String key_prefix = itos(variable->export_info.type); + if (variable->export_info.hint) { + key_prefix += "/" + itos(variable->export_info.hint); + } + key_prefix += ":" + variable->export_info.hint_string; + + // Now parse value. + export_type = export_type.get_container_element_type(0); + + if (export_type.is_variant() || export_type.has_no_type()) { + export_type.kind = GDScriptParser::DataType::BUILTIN; + } + switch (export_type.kind) { + case GDScriptParser::DataType::BUILTIN: + variable->export_info.type = export_type.builtin_type; + variable->export_info.hint = PROPERTY_HINT_NONE; + variable->export_info.hint_string = String(); + break; + case GDScriptParser::DataType::NATIVE: + case GDScriptParser::DataType::SCRIPT: + case GDScriptParser::DataType::CLASS: { + const StringName class_name = _find_narrowest_native_or_global_class(export_type); + if (ClassDB::is_parent_class(export_type.native_type, SNAME("Resource"))) { + variable->export_info.type = Variant::OBJECT; + variable->export_info.hint = PROPERTY_HINT_RESOURCE_TYPE; + variable->export_info.hint_string = class_name; + } else if (ClassDB::is_parent_class(export_type.native_type, SNAME("Node"))) { + variable->export_info.type = Variant::OBJECT; + variable->export_info.hint = PROPERTY_HINT_NODE_TYPE; + variable->export_info.hint_string = class_name; + } else { + push_error(R"(Export type can only be built-in, a resource, a node, or an enum.)", p_annotation); + return false; + } + } break; + case GDScriptParser::DataType::ENUM: { + if (export_type.is_meta_type) { + variable->export_info.type = Variant::DICTIONARY; + } else { + variable->export_info.type = Variant::INT; + variable->export_info.hint = PROPERTY_HINT_ENUM; + + String enum_hint_string; + bool first = true; + for (const KeyValue<StringName, int64_t> &E : export_type.enum_values) { + if (!first) { + enum_hint_string += ","; + } else { + first = false; + } + enum_hint_string += E.key.operator String().capitalize().xml_escape(); + enum_hint_string += ":"; + enum_hint_string += String::num_int64(E.value).xml_escape(); + } + + variable->export_info.hint_string = enum_hint_string; + variable->export_info.usage |= PROPERTY_USAGE_CLASS_IS_ENUM; + variable->export_info.class_name = String(export_type.native_type).replace("::", "."); + } + } break; + default: + push_error(R"(Export type can only be built-in, a resource, a node, or an enum.)", p_annotation); + return false; + } + + if (variable->export_info.hint == PROPERTY_HINT_NODE_TYPE && !ClassDB::is_parent_class(p_class->base_type.native_type, SNAME("Node"))) { + push_error(vformat(R"(Node export is only supported in Node-derived classes, but the current class inherits "%s".)", p_class->base_type.to_string()), p_annotation); + return false; + } + + String value_prefix = itos(variable->export_info.type); + if (variable->export_info.hint) { + value_prefix += "/" + itos(variable->export_info.hint); + } + value_prefix += ":" + variable->export_info.hint_string; + + variable->export_info.type = Variant::DICTIONARY; + variable->export_info.hint = PROPERTY_HINT_TYPE_STRING; + variable->export_info.hint_string = key_prefix + ";" + value_prefix; + variable->export_info.usage = PROPERTY_USAGE_DEFAULT; + variable->export_info.class_name = StringName(); + } } else if (p_annotation->name == SNAME("@export_enum")) { use_default_variable_type_check = false; @@ -4794,7 +4891,10 @@ String GDScriptParser::DataType::to_string() const { return "null"; } if (builtin_type == Variant::ARRAY && has_container_element_type(0)) { - return vformat("Array[%s]", container_element_types[0].to_string()); + return vformat("Array[%s]", get_container_element_type(0).to_string()); + } + if (builtin_type == Variant::DICTIONARY && has_container_element_types()) { + return vformat("Dictionary[%s, %s]", get_container_element_type_or_variant(0).to_string(), get_container_element_type_or_variant(1).to_string()); } return Variant::get_type_name(builtin_type); case NATIVE: @@ -4883,6 +4983,72 @@ PropertyInfo GDScriptParser::DataType::to_property_info(const String &p_name) co case UNRESOLVED: break; } + } else if (builtin_type == Variant::DICTIONARY && has_container_element_types()) { + const DataType key_type = get_container_element_type_or_variant(0); + const DataType value_type = get_container_element_type_or_variant(1); + if ((key_type.kind == VARIANT && value_type.kind == VARIANT) || key_type.kind == RESOLVING || + key_type.kind == UNRESOLVED || value_type.kind == RESOLVING || value_type.kind == UNRESOLVED) { + break; + } + String key_hint, value_hint; + switch (key_type.kind) { + case BUILTIN: + key_hint = Variant::get_type_name(key_type.builtin_type); + break; + case NATIVE: + key_hint = key_type.native_type; + break; + case SCRIPT: + if (key_type.script_type.is_valid() && key_type.script_type->get_global_name() != StringName()) { + key_hint = key_type.script_type->get_global_name(); + } else { + key_hint = key_type.native_type; + } + break; + case CLASS: + if (key_type.class_type != nullptr && key_type.class_type->get_global_name() != StringName()) { + key_hint = key_type.class_type->get_global_name(); + } else { + key_hint = key_type.native_type; + } + break; + case ENUM: + key_hint = String(key_type.native_type).replace("::", "."); + break; + default: + key_hint = "Variant"; + break; + } + switch (value_type.kind) { + case BUILTIN: + value_hint = Variant::get_type_name(value_type.builtin_type); + break; + case NATIVE: + value_hint = value_type.native_type; + break; + case SCRIPT: + if (value_type.script_type.is_valid() && value_type.script_type->get_global_name() != StringName()) { + value_hint = value_type.script_type->get_global_name(); + } else { + value_hint = value_type.native_type; + } + break; + case CLASS: + if (value_type.class_type != nullptr && value_type.class_type->get_global_name() != StringName()) { + value_hint = value_type.class_type->get_global_name(); + } else { + value_hint = value_type.native_type; + } + break; + case ENUM: + value_hint = String(value_type.native_type).replace("::", "."); + break; + default: + value_hint = "Variant"; + break; + } + result.hint = PROPERTY_HINT_DICTIONARY_TYPE; + result.hint_string = key_hint + ";" + value_hint; } break; case NATIVE: @@ -4967,6 +5133,50 @@ GDScriptParser::DataType GDScriptParser::DataType::get_typed_container_type() co return type; } +bool GDScriptParser::DataType::can_reference(const GDScriptParser::DataType &p_other) const { + if (p_other.is_meta_type) { + return false; + } else if (builtin_type != p_other.builtin_type) { + return false; + } else if (builtin_type != Variant::OBJECT) { + return true; + } + + if (native_type == StringName()) { + return true; + } else if (p_other.native_type == StringName()) { + return false; + } else if (native_type != p_other.native_type && !ClassDB::is_parent_class(p_other.native_type, native_type)) { + return false; + } + + Ref<Script> script = script_type; + if (kind == GDScriptParser::DataType::CLASS && script.is_null()) { + Error err = OK; + Ref<GDScript> scr = GDScriptCache::get_shallow_script(script_path, err); + ERR_FAIL_COND_V_MSG(err, false, vformat(R"(Error while getting cache for script "%s".)", script_path)); + script.reference_ptr(scr->find_class(class_type->fqcn)); + } + + Ref<Script> script_other = p_other.script_type; + if (p_other.kind == GDScriptParser::DataType::CLASS && script_other.is_null()) { + Error err = OK; + Ref<GDScript> scr = GDScriptCache::get_shallow_script(p_other.script_path, err); + ERR_FAIL_COND_V_MSG(err, false, vformat(R"(Error while getting cache for script "%s".)", p_other.script_path)); + script_other.reference_ptr(scr->find_class(p_other.class_type->fqcn)); + } + + if (script.is_null()) { + return true; + } else if (script_other.is_null()) { + return false; + } else if (script != script_other && !script_other->inherits_script(script)) { + return false; + } + + return true; +} + void GDScriptParser::complete_extents(Node *p_node) { while (!nodes_in_progress.is_empty() && nodes_in_progress.back()->get() != p_node) { ERR_PRINT("Parser bug: Mismatch in extents tracking stack."); diff --git a/modules/gdscript/gdscript_parser.h b/modules/gdscript/gdscript_parser.h index 2c7e730772..7840474a89 100644 --- a/modules/gdscript/gdscript_parser.h +++ b/modules/gdscript/gdscript_parser.h @@ -189,6 +189,8 @@ public: GDScriptParser::DataType get_typed_container_type() const; + bool can_reference(const DataType &p_other) const; + bool operator==(const DataType &p_other) const { if (type_source == UNDETECTED || p_other.type_source == UNDETECTED) { return true; // Can be considered equal for parsing purposes. diff --git a/modules/gdscript/gdscript_vm.cpp b/modules/gdscript/gdscript_vm.cpp index ddb0cf9502..4617a0dbb9 100644 --- a/modules/gdscript/gdscript_vm.cpp +++ b/modules/gdscript/gdscript_vm.cpp @@ -84,9 +84,15 @@ static String _get_var_type(const Variant *p_var) { if (p_var->get_type() == Variant::ARRAY) { basestr = "Array"; const Array *p_array = VariantInternal::get_array(p_var); - Variant::Type builtin_type = (Variant::Type)p_array->get_typed_builtin(); - if (builtin_type != Variant::NIL) { - basestr += "[" + _get_element_type(builtin_type, p_array->get_typed_class_name(), p_array->get_typed_script()) + "]"; + if (p_array->is_typed()) { + basestr += "[" + _get_element_type((Variant::Type)p_array->get_typed_builtin(), p_array->get_typed_class_name(), p_array->get_typed_script()) + "]"; + } + } else if (p_var->get_type() == Variant::DICTIONARY) { + basestr = "Dictionary"; + const Dictionary *p_dictionary = VariantInternal::get_dictionary(p_var); + if (p_dictionary->is_typed()) { + basestr += "[" + _get_element_type((Variant::Type)p_dictionary->get_typed_key_builtin(), p_dictionary->get_typed_key_class_name(), p_dictionary->get_typed_key_script()) + + ", " + _get_element_type((Variant::Type)p_dictionary->get_typed_value_builtin(), p_dictionary->get_typed_value_class_name(), p_dictionary->get_typed_value_script()) + "]"; } } else { basestr = Variant::get_type_name(p_var->get_type()); @@ -120,6 +126,16 @@ Variant GDScriptFunction::_get_default_variant_for_data_type(const GDScriptDataT } return array; + } else if (p_data_type.builtin_type == Variant::DICTIONARY) { + Dictionary dict; + // Typed dictionary. + if (p_data_type.has_container_element_types()) { + const GDScriptDataType &key_type = p_data_type.get_container_element_type_or_variant(0); + const GDScriptDataType &value_type = p_data_type.get_container_element_type_or_variant(1); + dict.set_typed(key_type.builtin_type, key_type.native_type, key_type.script_type, value_type.builtin_type, value_type.native_type, value_type.script_type); + } + + return dict; } else { Callable::CallError ce; Variant variant; @@ -153,6 +169,9 @@ String GDScriptFunction::_get_call_error(const String &p_where, const Variant ** if (p_err.expected == Variant::ARRAY && p_argptrs[p_err.argument]->get_type() == p_err.expected) { return "Invalid type in " + p_where + ". The array of argument " + itos(p_err.argument + 1) + " (" + _get_var_type(p_argptrs[p_err.argument]) + ") does not have the same element type as the expected typed array argument."; } + if (p_err.expected == Variant::DICTIONARY && p_argptrs[p_err.argument]->get_type() == p_err.expected) { + return "Invalid type in " + p_where + ". The dictionary of argument " + itos(p_err.argument + 1) + " (" + _get_var_type(p_argptrs[p_err.argument]) + ") does not have the same element type as the expected typed dictionary argument."; + } #endif // DEBUG_ENABLED return "Invalid type in " + p_where + ". Cannot convert argument " + itos(p_err.argument + 1) + " from " + Variant::get_type_name(p_argptrs[p_err.argument]->get_type()) + " to " + Variant::get_type_name(Variant::Type(p_err.expected)) + "."; case Callable::CallError::CALL_ERROR_TOO_MANY_ARGUMENTS: @@ -215,6 +234,7 @@ void (*type_init_function_table[])(Variant *) = { &&OPCODE_OPERATOR_VALIDATED, \ &&OPCODE_TYPE_TEST_BUILTIN, \ &&OPCODE_TYPE_TEST_ARRAY, \ + &&OPCODE_TYPE_TEST_DICTIONARY, \ &&OPCODE_TYPE_TEST_NATIVE, \ &&OPCODE_TYPE_TEST_SCRIPT, \ &&OPCODE_SET_KEYED, \ @@ -237,6 +257,7 @@ void (*type_init_function_table[])(Variant *) = { &&OPCODE_ASSIGN_FALSE, \ &&OPCODE_ASSIGN_TYPED_BUILTIN, \ &&OPCODE_ASSIGN_TYPED_ARRAY, \ + &&OPCODE_ASSIGN_TYPED_DICTIONARY, \ &&OPCODE_ASSIGN_TYPED_NATIVE, \ &&OPCODE_ASSIGN_TYPED_SCRIPT, \ &&OPCODE_CAST_TO_BUILTIN, \ @@ -247,6 +268,7 @@ void (*type_init_function_table[])(Variant *) = { &&OPCODE_CONSTRUCT_ARRAY, \ &&OPCODE_CONSTRUCT_TYPED_ARRAY, \ &&OPCODE_CONSTRUCT_DICTIONARY, \ + &&OPCODE_CONSTRUCT_TYPED_DICTIONARY, \ &&OPCODE_CALL, \ &&OPCODE_CALL_RETURN, \ &&OPCODE_CALL_ASYNC, \ @@ -275,6 +297,7 @@ void (*type_init_function_table[])(Variant *) = { &&OPCODE_RETURN, \ &&OPCODE_RETURN_TYPED_BUILTIN, \ &&OPCODE_RETURN_TYPED_ARRAY, \ + &&OPCODE_RETURN_TYPED_DICTIONARY, \ &&OPCODE_RETURN_TYPED_NATIVE, \ &&OPCODE_RETURN_TYPED_SCRIPT, \ &&OPCODE_ITERATE_BEGIN, \ @@ -548,7 +571,12 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a return _get_default_variant_for_data_type(return_type); } if (argument_types[i].kind == GDScriptDataType::BUILTIN) { - if (argument_types[i].builtin_type == Variant::ARRAY && argument_types[i].has_container_element_type(0)) { + if (argument_types[i].builtin_type == Variant::DICTIONARY && argument_types[i].has_container_element_types()) { + const GDScriptDataType &arg_key_type = argument_types[i].get_container_element_type_or_variant(0); + const GDScriptDataType &arg_value_type = argument_types[i].get_container_element_type_or_variant(1); + Dictionary dict(p_args[i]->operator Dictionary(), arg_key_type.builtin_type, arg_key_type.native_type, arg_key_type.script_type, arg_value_type.builtin_type, arg_value_type.native_type, arg_value_type.script_type); + memnew_placement(&stack[i + 3], Variant(dict)); + } else if (argument_types[i].builtin_type == Variant::ARRAY && argument_types[i].has_container_element_type(0)) { const GDScriptDataType &arg_type = argument_types[i].container_element_types[0]; Array array(p_args[i]->operator Array(), arg_type.builtin_type, arg_type.native_type, arg_type.script_type); memnew_placement(&stack[i + 3], Variant(array)); @@ -827,6 +855,36 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a } DISPATCH_OPCODE; + OPCODE(OPCODE_TYPE_TEST_DICTIONARY) { + CHECK_SPACE(9); + + GET_VARIANT_PTR(dst, 0); + GET_VARIANT_PTR(value, 1); + + GET_VARIANT_PTR(key_script_type, 2); + Variant::Type key_builtin_type = (Variant::Type)_code_ptr[ip + 5]; + int key_native_type_idx = _code_ptr[ip + 6]; + GD_ERR_BREAK(key_native_type_idx < 0 || key_native_type_idx >= _global_names_count); + const StringName key_native_type = _global_names_ptr[key_native_type_idx]; + + GET_VARIANT_PTR(value_script_type, 3); + Variant::Type value_builtin_type = (Variant::Type)_code_ptr[ip + 7]; + int value_native_type_idx = _code_ptr[ip + 8]; + GD_ERR_BREAK(value_native_type_idx < 0 || value_native_type_idx >= _global_names_count); + const StringName value_native_type = _global_names_ptr[value_native_type_idx]; + + bool result = false; + if (value->get_type() == Variant::DICTIONARY) { + Dictionary *dictionary = VariantInternal::get_dictionary(value); + result = dictionary->get_typed_key_builtin() == ((uint32_t)key_builtin_type) && dictionary->get_typed_key_class_name() == key_native_type && dictionary->get_typed_key_script() == *key_script_type && + dictionary->get_typed_value_builtin() == ((uint32_t)value_builtin_type) && dictionary->get_typed_value_class_name() == value_native_type && dictionary->get_typed_value_script() == *value_script_type; + } + + *dst = result; + ip += 9; + } + DISPATCH_OPCODE; + OPCODE(OPCODE_TYPE_TEST_NATIVE) { CHECK_SPACE(4); @@ -1384,6 +1442,50 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a } DISPATCH_OPCODE; + OPCODE(OPCODE_ASSIGN_TYPED_DICTIONARY) { + CHECK_SPACE(9); + GET_VARIANT_PTR(dst, 0); + GET_VARIANT_PTR(src, 1); + + GET_VARIANT_PTR(key_script_type, 2); + Variant::Type key_builtin_type = (Variant::Type)_code_ptr[ip + 5]; + int key_native_type_idx = _code_ptr[ip + 6]; + GD_ERR_BREAK(key_native_type_idx < 0 || key_native_type_idx >= _global_names_count); + const StringName key_native_type = _global_names_ptr[key_native_type_idx]; + + GET_VARIANT_PTR(value_script_type, 3); + Variant::Type value_builtin_type = (Variant::Type)_code_ptr[ip + 7]; + int value_native_type_idx = _code_ptr[ip + 8]; + GD_ERR_BREAK(value_native_type_idx < 0 || value_native_type_idx >= _global_names_count); + const StringName value_native_type = _global_names_ptr[value_native_type_idx]; + + if (src->get_type() != Variant::DICTIONARY) { +#ifdef DEBUG_ENABLED + err_text = vformat(R"(Trying to assign a value of type "%s" to a variable of type "Dictionary[%s, %s]".)", + _get_var_type(src), _get_element_type(key_builtin_type, key_native_type, *key_script_type), + _get_element_type(value_builtin_type, value_native_type, *value_script_type)); +#endif // DEBUG_ENABLED + OPCODE_BREAK; + } + + Dictionary *dictionary = VariantInternal::get_dictionary(src); + + if (dictionary->get_typed_key_builtin() != ((uint32_t)key_builtin_type) || dictionary->get_typed_key_class_name() != key_native_type || dictionary->get_typed_key_script() != *key_script_type || + dictionary->get_typed_value_builtin() != ((uint32_t)value_builtin_type) || dictionary->get_typed_value_class_name() != value_native_type || dictionary->get_typed_value_script() != *value_script_type) { +#ifdef DEBUG_ENABLED + err_text = vformat(R"(Trying to assign a dictionary of type "%s" to a variable of type "Dictionary[%s, %s]".)", + _get_var_type(src), _get_element_type(key_builtin_type, key_native_type, *key_script_type), + _get_element_type(value_builtin_type, value_native_type, *value_script_type)); +#endif // DEBUG_ENABLED + OPCODE_BREAK; + } + + *dst = *src; + + ip += 9; + } + DISPATCH_OPCODE; + OPCODE(OPCODE_ASSIGN_TYPED_NATIVE) { CHECK_SPACE(4); GET_VARIANT_PTR(dst, 0); @@ -1703,12 +1805,51 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a GET_INSTRUCTION_ARG(dst, argc * 2); + *dst = Variant(); // Clear potential previous typed dictionary. + *dst = dict; ip += 2; } DISPATCH_OPCODE; + OPCODE(OPCODE_CONSTRUCT_TYPED_DICTIONARY) { + LOAD_INSTRUCTION_ARGS + CHECK_SPACE(6 + instr_arg_count); + ip += instr_arg_count; + + int argc = _code_ptr[ip + 1]; + + GET_INSTRUCTION_ARG(key_script_type, argc * 2 + 1); + Variant::Type key_builtin_type = (Variant::Type)_code_ptr[ip + 2]; + int key_native_type_idx = _code_ptr[ip + 3]; + GD_ERR_BREAK(key_native_type_idx < 0 || key_native_type_idx >= _global_names_count); + const StringName key_native_type = _global_names_ptr[key_native_type_idx]; + + GET_INSTRUCTION_ARG(value_script_type, argc * 2 + 2); + Variant::Type value_builtin_type = (Variant::Type)_code_ptr[ip + 4]; + int value_native_type_idx = _code_ptr[ip + 5]; + GD_ERR_BREAK(value_native_type_idx < 0 || value_native_type_idx >= _global_names_count); + const StringName value_native_type = _global_names_ptr[value_native_type_idx]; + + Dictionary dict; + + for (int i = 0; i < argc; i++) { + GET_INSTRUCTION_ARG(k, i * 2 + 0); + GET_INSTRUCTION_ARG(v, i * 2 + 1); + dict[*k] = *v; + } + + GET_INSTRUCTION_ARG(dst, argc * 2); + + *dst = Variant(); // Clear potential previous typed dictionary. + + *dst = Dictionary(dict, key_builtin_type, key_native_type, *key_script_type, value_builtin_type, value_native_type, *value_script_type); + + ip += 6; + } + DISPATCH_OPCODE; + OPCODE(OPCODE_CALL_ASYNC) OPCODE(OPCODE_CALL_RETURN) OPCODE(OPCODE_CALL) { @@ -2657,6 +2798,51 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a OPCODE_BREAK; } + OPCODE(OPCODE_RETURN_TYPED_DICTIONARY) { + CHECK_SPACE(8); + GET_VARIANT_PTR(r, 0); + + GET_VARIANT_PTR(key_script_type, 1); + Variant::Type key_builtin_type = (Variant::Type)_code_ptr[ip + 4]; + int key_native_type_idx = _code_ptr[ip + 5]; + GD_ERR_BREAK(key_native_type_idx < 0 || key_native_type_idx >= _global_names_count); + const StringName key_native_type = _global_names_ptr[key_native_type_idx]; + + GET_VARIANT_PTR(value_script_type, 2); + Variant::Type value_builtin_type = (Variant::Type)_code_ptr[ip + 6]; + int value_native_type_idx = _code_ptr[ip + 7]; + GD_ERR_BREAK(value_native_type_idx < 0 || value_native_type_idx >= _global_names_count); + const StringName value_native_type = _global_names_ptr[value_native_type_idx]; + + if (r->get_type() != Variant::DICTIONARY) { +#ifdef DEBUG_ENABLED + err_text = vformat(R"(Trying to return a value of type "%s" where expected return type is "Dictionary[%s, %s]".)", + _get_var_type(r), _get_element_type(key_builtin_type, key_native_type, *key_script_type), + _get_element_type(value_builtin_type, value_native_type, *value_script_type)); +#endif // DEBUG_ENABLED + OPCODE_BREAK; + } + + Dictionary *dictionary = VariantInternal::get_dictionary(r); + + if (dictionary->get_typed_key_builtin() != ((uint32_t)key_builtin_type) || dictionary->get_typed_key_class_name() != key_native_type || dictionary->get_typed_key_script() != *key_script_type || + dictionary->get_typed_value_builtin() != ((uint32_t)value_builtin_type) || dictionary->get_typed_value_class_name() != value_native_type || dictionary->get_typed_value_script() != *value_script_type) { +#ifdef DEBUG_ENABLED + err_text = vformat(R"(Trying to return a dictionary of type "%s" where expected return type is "Dictionary[%s, %s]".)", + _get_var_type(r), _get_element_type(key_builtin_type, key_native_type, *key_script_type), + _get_element_type(value_builtin_type, value_native_type, *value_script_type)); +#endif // DEBUG_ENABLED + OPCODE_BREAK; + } + + retvalue = *dictionary; + +#ifdef DEBUG_ENABLED + exit_ok = true; +#endif // DEBUG_ENABLED + OPCODE_BREAK; + } + OPCODE(OPCODE_RETURN_TYPED_NATIVE) { CHECK_SPACE(3); GET_VARIANT_PTR(r, 0); diff --git a/modules/gdscript/tests/scripts/analyzer/errors/for_loop_wrong_specified_type_with_literal_dictionary.gd b/modules/gdscript/tests/scripts/analyzer/errors/for_loop_wrong_specified_type_with_literal_dictionary.gd new file mode 100644 index 0000000000..c180cca03c --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/for_loop_wrong_specified_type_with_literal_dictionary.gd @@ -0,0 +1,3 @@ +func test(): + for key: int in { "a": 1 }: + print(key) diff --git a/modules/gdscript/tests/scripts/analyzer/errors/for_loop_wrong_specified_type_with_literal_dictionary.out b/modules/gdscript/tests/scripts/analyzer/errors/for_loop_wrong_specified_type_with_literal_dictionary.out new file mode 100644 index 0000000000..8530783673 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/for_loop_wrong_specified_type_with_literal_dictionary.out @@ -0,0 +1,2 @@ +GDTEST_ANALYZER_ERROR +Cannot include a value of type "String" as "int". diff --git a/modules/gdscript/tests/scripts/analyzer/errors/typed_dictionary_assign_differently_typed.gd b/modules/gdscript/tests/scripts/analyzer/errors/typed_dictionary_assign_differently_typed.gd new file mode 100644 index 0000000000..75d1b7fe62 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/typed_dictionary_assign_differently_typed.gd @@ -0,0 +1,4 @@ +func test(): + var differently: Dictionary[float, float] = { 1.0: 0.0 } + var typed: Dictionary[int, int] = differently + print('not ok') diff --git a/modules/gdscript/tests/scripts/analyzer/errors/typed_dictionary_assign_differently_typed.out b/modules/gdscript/tests/scripts/analyzer/errors/typed_dictionary_assign_differently_typed.out new file mode 100644 index 0000000000..e05d4be8c9 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/typed_dictionary_assign_differently_typed.out @@ -0,0 +1,2 @@ +GDTEST_ANALYZER_ERROR +Cannot assign a value of type Dictionary[float, float] to variable "typed" with specified type Dictionary[int, int]. diff --git a/modules/gdscript/tests/scripts/analyzer/errors/typed_dictionary_assignment.gd b/modules/gdscript/tests/scripts/analyzer/errors/typed_dictionary_assignment.gd new file mode 100644 index 0000000000..e0af71823a --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/typed_dictionary_assignment.gd @@ -0,0 +1,2 @@ +func test(): + const dict: Dictionary[int, int] = { "Hello": "World" } diff --git a/modules/gdscript/tests/scripts/analyzer/errors/typed_dictionary_assignment.out b/modules/gdscript/tests/scripts/analyzer/errors/typed_dictionary_assignment.out new file mode 100644 index 0000000000..8530783673 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/typed_dictionary_assignment.out @@ -0,0 +1,2 @@ +GDTEST_ANALYZER_ERROR +Cannot include a value of type "String" as "int". diff --git a/modules/gdscript/tests/scripts/analyzer/errors/typed_dictionary_init_with_unconvertible_in_literal.gd b/modules/gdscript/tests/scripts/analyzer/errors/typed_dictionary_init_with_unconvertible_in_literal.gd new file mode 100644 index 0000000000..814ba12aef --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/typed_dictionary_init_with_unconvertible_in_literal.gd @@ -0,0 +1,4 @@ +func test(): + var unconvertible := 1 + var typed: Dictionary[Object, Object] = { unconvertible: unconvertible } + print('not ok') diff --git a/modules/gdscript/tests/scripts/analyzer/errors/typed_dictionary_init_with_unconvertible_in_literal.out b/modules/gdscript/tests/scripts/analyzer/errors/typed_dictionary_init_with_unconvertible_in_literal.out new file mode 100644 index 0000000000..9d6c9d9144 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/typed_dictionary_init_with_unconvertible_in_literal.out @@ -0,0 +1,2 @@ +GDTEST_ANALYZER_ERROR +Cannot have a key of type "int" in a dictionary of type "Dictionary[Object, Object]". diff --git a/modules/gdscript/tests/scripts/analyzer/errors/typed_dictionary_pass_differently_to_typed.gd b/modules/gdscript/tests/scripts/analyzer/errors/typed_dictionary_pass_differently_to_typed.gd new file mode 100644 index 0000000000..73d8ce2b96 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/typed_dictionary_pass_differently_to_typed.gd @@ -0,0 +1,7 @@ +func expect_typed(typed: Dictionary[int, int]): + print(typed.size()) + +func test(): + var differently: Dictionary[float, float] = { 1.0: 0.0 } + expect_typed(differently) + print('not ok') diff --git a/modules/gdscript/tests/scripts/analyzer/errors/typed_dictionary_pass_differently_to_typed.out b/modules/gdscript/tests/scripts/analyzer/errors/typed_dictionary_pass_differently_to_typed.out new file mode 100644 index 0000000000..302109cf8a --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/typed_dictionary_pass_differently_to_typed.out @@ -0,0 +1,2 @@ +GDTEST_ANALYZER_ERROR +Invalid argument for "expect_typed()" function: argument 1 should be "Dictionary[int, int]" but is "Dictionary[float, float]". diff --git a/modules/gdscript/tests/scripts/analyzer/features/typed_dictionary_as_default_parameter.gd b/modules/gdscript/tests/scripts/analyzer/features/typed_dictionary_as_default_parameter.gd new file mode 100644 index 0000000000..65f5e7da07 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/typed_dictionary_as_default_parameter.gd @@ -0,0 +1,20 @@ +func print_untyped(dictionary = { 0: 1 }) -> void: + print(dictionary) + print(dictionary.get_typed_key_builtin()) + print(dictionary.get_typed_value_builtin()) + +func print_inferred(dictionary := { 2: 3 }) -> void: + print(dictionary) + print(dictionary.get_typed_key_builtin()) + print(dictionary.get_typed_value_builtin()) + +func print_typed(dictionary: Dictionary[int, int] = { 4: 5 }) -> void: + print(dictionary) + print(dictionary.get_typed_key_builtin()) + print(dictionary.get_typed_value_builtin()) + +func test(): + print_untyped() + print_inferred() + print_typed() + print('ok') diff --git a/modules/gdscript/tests/scripts/analyzer/features/typed_dictionary_as_default_parameter.out b/modules/gdscript/tests/scripts/analyzer/features/typed_dictionary_as_default_parameter.out new file mode 100644 index 0000000000..c31561bee3 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/typed_dictionary_as_default_parameter.out @@ -0,0 +1,11 @@ +GDTEST_OK +{ 0: 1 } +0 +0 +{ 2: 3 } +0 +0 +{ 4: 5 } +2 +2 +ok diff --git a/modules/gdscript/tests/scripts/analyzer/features/typed_dictionary_inferred_access_isnt_constant.gd b/modules/gdscript/tests/scripts/analyzer/features/typed_dictionary_inferred_access_isnt_constant.gd new file mode 100644 index 0000000000..0aa3de2c4a --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/typed_dictionary_inferred_access_isnt_constant.gd @@ -0,0 +1,4 @@ +func test(): + var dict := { 0: 0 } + dict[0] = 1 + print(dict[0]) diff --git a/modules/gdscript/tests/scripts/analyzer/features/typed_dictionary_inferred_access_isnt_constant.out b/modules/gdscript/tests/scripts/analyzer/features/typed_dictionary_inferred_access_isnt_constant.out new file mode 100644 index 0000000000..a7f1357bb2 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/typed_dictionary_inferred_access_isnt_constant.out @@ -0,0 +1,2 @@ +GDTEST_OK +1 diff --git a/modules/gdscript/tests/scripts/analyzer/features/typed_dictionary_usage.gd b/modules/gdscript/tests/scripts/analyzer/features/typed_dictionary_usage.gd new file mode 100644 index 0000000000..9d3fffd1de --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/typed_dictionary_usage.gd @@ -0,0 +1,214 @@ +class A: pass +class B extends A: pass + +enum E { E0 = 391, E1 = 193 } + +func floats_identity(floats: Dictionary[float, float]): return floats + +class Members: + var one: Dictionary[int, int] = { 104: 401 } + var two: Dictionary[int, int] = one + + func check_passing() -> bool: + Utils.check(str(one) == '{ 104: 401 }') + Utils.check(str(two) == '{ 104: 401 }') + two[582] = 285 + Utils.check(str(one) == '{ 104: 401, 582: 285 }') + Utils.check(str(two) == '{ 104: 401, 582: 285 }') + two = { 486: 684 } + Utils.check(str(one) == '{ 104: 401, 582: 285 }') + Utils.check(str(two) == '{ 486: 684 }') + return true + + +@warning_ignore("unsafe_method_access") +@warning_ignore("assert_always_true") +@warning_ignore("return_value_discarded") +func test(): + var untyped_basic = { 459: 954 } + Utils.check(str(untyped_basic) == '{ 459: 954 }') + Utils.check(untyped_basic.get_typed_key_builtin() == TYPE_NIL) + Utils.check(untyped_basic.get_typed_value_builtin() == TYPE_NIL) + + var inferred_basic := { 366: 663 } + Utils.check(str(inferred_basic) == '{ 366: 663 }') + Utils.check(inferred_basic.get_typed_key_builtin() == TYPE_NIL) + Utils.check(inferred_basic.get_typed_value_builtin() == TYPE_NIL) + + var typed_basic: Dictionary = { 521: 125 } + Utils.check(str(typed_basic) == '{ 521: 125 }') + Utils.check(typed_basic.get_typed_key_builtin() == TYPE_NIL) + Utils.check(typed_basic.get_typed_value_builtin() == TYPE_NIL) + + + var empty_floats: Dictionary[float, float] = {} + Utils.check(str(empty_floats) == '{ }') + Utils.check(empty_floats.get_typed_key_builtin() == TYPE_FLOAT) + Utils.check(empty_floats.get_typed_value_builtin() == TYPE_FLOAT) + + untyped_basic = empty_floats + Utils.check(untyped_basic.get_typed_key_builtin() == TYPE_FLOAT) + Utils.check(untyped_basic.get_typed_value_builtin() == TYPE_FLOAT) + + inferred_basic = empty_floats + Utils.check(inferred_basic.get_typed_key_builtin() == TYPE_FLOAT) + Utils.check(inferred_basic.get_typed_value_builtin() == TYPE_FLOAT) + + typed_basic = empty_floats + Utils.check(typed_basic.get_typed_key_builtin() == TYPE_FLOAT) + Utils.check(typed_basic.get_typed_value_builtin() == TYPE_FLOAT) + + empty_floats[705.0] = 507.0 + untyped_basic[430.0] = 34.0 + inferred_basic[263.0] = 362.0 + typed_basic[518.0] = 815.0 + Utils.check(str(empty_floats) == '{ 705: 507, 430: 34, 263: 362, 518: 815 }') + Utils.check(str(untyped_basic) == '{ 705: 507, 430: 34, 263: 362, 518: 815 }') + Utils.check(str(inferred_basic) == '{ 705: 507, 430: 34, 263: 362, 518: 815 }') + Utils.check(str(typed_basic) == '{ 705: 507, 430: 34, 263: 362, 518: 815 }') + + + const constant_float := 950.0 + const constant_int := 170 + var typed_float := 954.0 + var filled_floats: Dictionary[float, float] = { constant_float: constant_int, typed_float: empty_floats[430.0] + empty_floats[263.0] } + Utils.check(str(filled_floats) == '{ 950: 170, 954: 396 }') + Utils.check(filled_floats.get_typed_key_builtin() == TYPE_FLOAT) + Utils.check(filled_floats.get_typed_value_builtin() == TYPE_FLOAT) + + var casted_floats := { empty_floats[263.0] * 2: empty_floats[263.0] / 2 } as Dictionary[float, float] + Utils.check(str(casted_floats) == '{ 724: 181 }') + Utils.check(casted_floats.get_typed_key_builtin() == TYPE_FLOAT) + Utils.check(casted_floats.get_typed_value_builtin() == TYPE_FLOAT) + + var returned_floats = (func () -> Dictionary[float, float]: return { 554: 455 }).call() + Utils.check(str(returned_floats) == '{ 554: 455 }') + Utils.check(returned_floats.get_typed_key_builtin() == TYPE_FLOAT) + Utils.check(returned_floats.get_typed_value_builtin() == TYPE_FLOAT) + + var passed_floats = floats_identity({ 663.0 if randf() > 0.5 else 663.0: 366.0 if randf() <= 0.5 else 366.0 }) + Utils.check(str(passed_floats) == '{ 663: 366 }') + Utils.check(passed_floats.get_typed_key_builtin() == TYPE_FLOAT) + Utils.check(passed_floats.get_typed_value_builtin() == TYPE_FLOAT) + + var default_floats = (func (floats: Dictionary[float, float] = { 364.0: 463.0 }): return floats).call() + Utils.check(str(default_floats) == '{ 364: 463 }') + Utils.check(default_floats.get_typed_key_builtin() == TYPE_FLOAT) + Utils.check(default_floats.get_typed_value_builtin() == TYPE_FLOAT) + + var typed_int := 556 + var converted_floats: Dictionary[float, float] = { typed_int: typed_int } + converted_floats[498.0] = 894 + Utils.check(str(converted_floats) == '{ 556: 556, 498: 894 }') + Utils.check(converted_floats.get_typed_key_builtin() == TYPE_FLOAT) + Utils.check(converted_floats.get_typed_value_builtin() == TYPE_FLOAT) + + + const constant_basic = { 228: 822 } + Utils.check(str(constant_basic) == '{ 228: 822 }') + Utils.check(constant_basic.get_typed_key_builtin() == TYPE_NIL) + Utils.check(constant_basic.get_typed_value_builtin() == TYPE_NIL) + + const constant_floats: Dictionary[float, float] = { constant_float - constant_basic[228] - constant_int: constant_float + constant_basic[228] + constant_int } + Utils.check(str(constant_floats) == '{ -42: 1942 }') + Utils.check(constant_floats.get_typed_key_builtin() == TYPE_FLOAT) + Utils.check(constant_floats.get_typed_value_builtin() == TYPE_FLOAT) + + + var source_floats: Dictionary[float, float] = { 999.74: 47.999 } + untyped_basic = source_floats + var destination_floats: Dictionary[float, float] = untyped_basic + destination_floats[999.74] -= 0.999 + Utils.check(str(source_floats) == '{ 999.74: 47 }') + Utils.check(str(untyped_basic) == '{ 999.74: 47 }') + Utils.check(str(destination_floats) == '{ 999.74: 47 }') + Utils.check(destination_floats.get_typed_key_builtin() == TYPE_FLOAT) + Utils.check(destination_floats.get_typed_value_builtin() == TYPE_FLOAT) + + + var duplicated_floats := empty_floats.duplicate() + duplicated_floats.erase(705.0) + duplicated_floats.erase(430.0) + duplicated_floats.erase(518.0) + duplicated_floats[263.0] *= 3 + Utils.check(str(duplicated_floats) == '{ 263: 1086 }') + Utils.check(duplicated_floats.get_typed_key_builtin() == TYPE_FLOAT) + Utils.check(duplicated_floats.get_typed_value_builtin() == TYPE_FLOAT) + + + var b_objects: Dictionary[int, B] = { 0: B.new(), 1: B.new() as A, 2: null } + Utils.check(b_objects.size() == 3) + Utils.check(b_objects.get_typed_value_builtin() == TYPE_OBJECT) + Utils.check(b_objects.get_typed_value_script() == B) + + var a_objects: Dictionary[int, A] = { 0: A.new(), 1: B.new(), 2: null, 3: b_objects[0] } + Utils.check(a_objects.size() == 4) + Utils.check(a_objects.get_typed_value_builtin() == TYPE_OBJECT) + Utils.check(a_objects.get_typed_value_script() == A) + + var a_passed = (func check_a_passing(p_objects: Dictionary[int, A]): return p_objects.size()).call(a_objects) + Utils.check(a_passed == 4) + + var b_passed = (func check_b_passing(basic: Dictionary): return basic[0] != null).call(b_objects) + Utils.check(b_passed == true) + + + var empty_strings: Dictionary[String, String] = {} + var empty_bools: Dictionary[bool, bool] = {} + var empty_basic_one := {} + var empty_basic_two := {} + Utils.check(empty_strings == empty_bools) + Utils.check(empty_basic_one == empty_basic_two) + Utils.check(empty_strings.hash() == empty_bools.hash()) + Utils.check(empty_basic_one.hash() == empty_basic_two.hash()) + + + var assign_source: Dictionary[int, int] = { 527: 725 } + var assign_target: Dictionary[int, int] = {} + assign_target.assign(assign_source) + Utils.check(str(assign_source) == '{ 527: 725 }') + Utils.check(str(assign_target) == '{ 527: 725 }') + assign_source[657] = 756 + Utils.check(str(assign_source) == '{ 527: 725, 657: 756 }') + Utils.check(str(assign_target) == '{ 527: 725 }') + + + var defaults_passed = (func check_defaults_passing(one: Dictionary[int, int] = {}, two := one): + one[887] = 788 + two[198] = 891 + Utils.check(str(one) == '{ 887: 788, 198: 891 }') + Utils.check(str(two) == '{ 887: 788, 198: 891 }') + two = {130: 31} + Utils.check(str(one) == '{ 887: 788, 198: 891 }') + Utils.check(str(two) == '{ 130: 31 }') + return true + ).call() + Utils.check(defaults_passed == true) + + + var members := Members.new() + var members_passed := members.check_passing() + Utils.check(members_passed == true) + + + var typed_enums: Dictionary[E, E] = {} + typed_enums[E.E0] = E.E1 + Utils.check(str(typed_enums) == '{ 391: 193 }') + Utils.check(typed_enums.get_typed_key_builtin() == TYPE_INT) + Utils.check(typed_enums.get_typed_value_builtin() == TYPE_INT) + + const const_enums: Dictionary[E, E] = {} + Utils.check(const_enums.get_typed_key_builtin() == TYPE_INT) + Utils.check(const_enums.get_typed_key_class_name() == &'') + Utils.check(const_enums.get_typed_value_builtin() == TYPE_INT) + Utils.check(const_enums.get_typed_value_class_name() == &'') + + + var a := A.new() + var b := B.new() + var typed_natives: Dictionary[RefCounted, RefCounted] = { a: b } + var typed_scripts = Dictionary(typed_natives, TYPE_OBJECT, "RefCounted", A, TYPE_OBJECT, "RefCounted", B) + Utils.check(typed_scripts[a] == b) + + + print('ok') diff --git a/modules/gdscript/tests/scripts/analyzer/features/typed_dictionary_usage.out b/modules/gdscript/tests/scripts/analyzer/features/typed_dictionary_usage.out new file mode 100644 index 0000000000..1b47ed10dc --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/typed_dictionary_usage.out @@ -0,0 +1,2 @@ +GDTEST_OK +ok diff --git a/modules/gdscript/tests/scripts/analyzer/features/typed_dictionary_with_custom_class.gd b/modules/gdscript/tests/scripts/analyzer/features/typed_dictionary_with_custom_class.gd new file mode 100644 index 0000000000..f4a23ade14 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/typed_dictionary_with_custom_class.gd @@ -0,0 +1,9 @@ +class Inner: + var prop = "Inner" + +var dict: Dictionary[int, Inner] = { 0: Inner.new() } + + +func test(): + var element: Inner = dict[0] + print(element.prop) diff --git a/modules/gdscript/tests/scripts/analyzer/features/typed_dictionary_with_custom_class.out b/modules/gdscript/tests/scripts/analyzer/features/typed_dictionary_with_custom_class.out new file mode 100644 index 0000000000..8f250d2632 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/typed_dictionary_with_custom_class.out @@ -0,0 +1,2 @@ +GDTEST_OK +Inner diff --git a/modules/gdscript/tests/scripts/parser/features/typed_dictionaries.gd b/modules/gdscript/tests/scripts/parser/features/typed_dictionaries.gd new file mode 100644 index 0000000000..57e6489484 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/features/typed_dictionaries.gd @@ -0,0 +1,5 @@ +func test(): + var my_dictionary: Dictionary[int, String] = { 1: "one", 2: "two", 3: "three" } + var inferred_dictionary := { 1: "one", 2: "two", 3: "three" } # This is Dictionary[int, String]. + print(my_dictionary) + print(inferred_dictionary) diff --git a/modules/gdscript/tests/scripts/parser/features/typed_dictionaries.out b/modules/gdscript/tests/scripts/parser/features/typed_dictionaries.out new file mode 100644 index 0000000000..6021c338ee --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/features/typed_dictionaries.out @@ -0,0 +1,3 @@ +GDTEST_OK +{ 1: "one", 2: "two", 3: "three" } +{ 1: "one", 2: "two", 3: "three" } diff --git a/modules/gdscript/tests/scripts/runtime/errors/typed_dictionary_assign_basic_to_typed.gd b/modules/gdscript/tests/scripts/runtime/errors/typed_dictionary_assign_basic_to_typed.gd new file mode 100644 index 0000000000..75004742a2 --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/errors/typed_dictionary_assign_basic_to_typed.gd @@ -0,0 +1,4 @@ +func test(): + var basic := { 1: 1 } + var typed: Dictionary[int, int] = basic + print('not ok') diff --git a/modules/gdscript/tests/scripts/runtime/errors/typed_dictionary_assign_basic_to_typed.out b/modules/gdscript/tests/scripts/runtime/errors/typed_dictionary_assign_basic_to_typed.out new file mode 100644 index 0000000000..cadb17f570 --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/errors/typed_dictionary_assign_basic_to_typed.out @@ -0,0 +1,6 @@ +GDTEST_RUNTIME_ERROR +>> SCRIPT ERROR +>> on function: test() +>> runtime/errors/typed_dictionary_assign_basic_to_typed.gd +>> 3 +>> Trying to assign a dictionary of type "Dictionary" to a variable of type "Dictionary[int, int]". diff --git a/modules/gdscript/tests/scripts/runtime/errors/typed_dictionary_assign_differently_typed.gd b/modules/gdscript/tests/scripts/runtime/errors/typed_dictionary_assign_differently_typed.gd new file mode 100644 index 0000000000..e5ab4a1a85 --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/errors/typed_dictionary_assign_differently_typed.gd @@ -0,0 +1,4 @@ +func test(): + var differently: Variant = { 1.0: 0.0 } as Dictionary[float, float] + var typed: Dictionary[int, int] = differently + print('not ok') diff --git a/modules/gdscript/tests/scripts/runtime/errors/typed_dictionary_assign_differently_typed.out b/modules/gdscript/tests/scripts/runtime/errors/typed_dictionary_assign_differently_typed.out new file mode 100644 index 0000000000..fe1e5d1285 --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/errors/typed_dictionary_assign_differently_typed.out @@ -0,0 +1,6 @@ +GDTEST_RUNTIME_ERROR +>> SCRIPT ERROR +>> on function: test() +>> runtime/errors/typed_dictionary_assign_differently_typed.gd +>> 3 +>> Trying to assign a dictionary of type "Dictionary[float, float]" to a variable of type "Dictionary[int, int]". diff --git a/modules/gdscript/tests/scripts/runtime/errors/typed_dictionary_assign_wrong_to_typed.gd b/modules/gdscript/tests/scripts/runtime/errors/typed_dictionary_assign_wrong_to_typed.gd new file mode 100644 index 0000000000..6cc0e57255 --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/errors/typed_dictionary_assign_wrong_to_typed.gd @@ -0,0 +1,7 @@ +class Foo: pass +class Bar extends Foo: pass +class Baz extends Foo: pass + +func test(): + var typed: Dictionary[Bar, Bar] = { Baz.new() as Foo: Baz.new() as Foo } + print('not ok') diff --git a/modules/gdscript/tests/scripts/runtime/errors/typed_dictionary_assign_wrong_to_typed.out b/modules/gdscript/tests/scripts/runtime/errors/typed_dictionary_assign_wrong_to_typed.out new file mode 100644 index 0000000000..18a4c360e2 --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/errors/typed_dictionary_assign_wrong_to_typed.out @@ -0,0 +1,5 @@ +GDTEST_RUNTIME_ERROR +>> ERROR +>> Method/function failed. +>> Unable to convert key from "Object" to "Object". +not ok diff --git a/modules/gdscript/tests/scripts/runtime/errors/typed_dictionary_pass_basic_to_typed.gd b/modules/gdscript/tests/scripts/runtime/errors/typed_dictionary_pass_basic_to_typed.gd new file mode 100644 index 0000000000..8f7d732584 --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/errors/typed_dictionary_pass_basic_to_typed.gd @@ -0,0 +1,7 @@ +func expect_typed(typed: Dictionary[int, int]): + print(typed.size()) + +func test(): + var basic := { 1: 1 } + expect_typed(basic) + print('not ok') diff --git a/modules/gdscript/tests/scripts/runtime/errors/typed_dictionary_pass_basic_to_typed.out b/modules/gdscript/tests/scripts/runtime/errors/typed_dictionary_pass_basic_to_typed.out new file mode 100644 index 0000000000..fb45461701 --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/errors/typed_dictionary_pass_basic_to_typed.out @@ -0,0 +1,6 @@ +GDTEST_RUNTIME_ERROR +>> SCRIPT ERROR +>> on function: test() +>> runtime/errors/typed_dictionary_pass_basic_to_typed.gd +>> 6 +>> Invalid type in function 'expect_typed' in base 'RefCounted (typed_dictionary_pass_basic_to_typed.gd)'. The dictionary of argument 1 (Dictionary) does not have the same element type as the expected typed dictionary argument. diff --git a/modules/gdscript/tests/scripts/runtime/errors/typed_dictionary_pass_differently_to_typed.gd b/modules/gdscript/tests/scripts/runtime/errors/typed_dictionary_pass_differently_to_typed.gd new file mode 100644 index 0000000000..978a9fdfee --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/errors/typed_dictionary_pass_differently_to_typed.gd @@ -0,0 +1,7 @@ +func expect_typed(typed: Dictionary[int, int]): + print(typed.size()) + +func test(): + var differently: Variant = { 1.0: 0.0 } as Dictionary[float, float] + expect_typed(differently) + print('not ok') diff --git a/modules/gdscript/tests/scripts/runtime/errors/typed_dictionary_pass_differently_to_typed.out b/modules/gdscript/tests/scripts/runtime/errors/typed_dictionary_pass_differently_to_typed.out new file mode 100644 index 0000000000..4036a1bf01 --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/errors/typed_dictionary_pass_differently_to_typed.out @@ -0,0 +1,6 @@ +GDTEST_RUNTIME_ERROR +>> SCRIPT ERROR +>> on function: test() +>> runtime/errors/typed_dictionary_pass_differently_to_typed.gd +>> 6 +>> Invalid type in function 'expect_typed' in base 'RefCounted (typed_dictionary_pass_differently_to_typed.gd)'. The dictionary of argument 1 (Dictionary[float, float]) does not have the same element type as the expected typed dictionary argument. diff --git a/modules/gdscript/tests/scripts/runtime/features/for_loop_iterator_specified_types.gd b/modules/gdscript/tests/scripts/runtime/features/for_loop_iterator_specified_types.gd index bc899a3a6f..393500bd9b 100644 --- a/modules/gdscript/tests/scripts/runtime/features/for_loop_iterator_specified_types.gd +++ b/modules/gdscript/tests/scripts/runtime/features/for_loop_iterator_specified_types.gd @@ -28,13 +28,18 @@ func test(): prints(var_to_str(e), var_to_str(elem)) print("Test String-keys dictionary.") - var d1 := {a = 1, b = 2, c = 3} + var d1 := { a = 1, b = 2, c = 3 } for k: StringName in d1: var key := k prints(var_to_str(k), var_to_str(key)) print("Test RefCounted-keys dictionary.") - var d2 := {RefCounted.new(): 1, Resource.new(): 2, ConfigFile.new(): 3} + var d2 := { RefCounted.new(): 1, Resource.new(): 2, ConfigFile.new(): 3 } for k: RefCounted in d2: var key := k prints(k.get_class(), key.get_class()) + + print("Test implicitly typed dictionary literal.") + for k: StringName in { x = 123, y = 456, z = 789 }: + var key := k + prints(var_to_str(k), var_to_str(key)) diff --git a/modules/gdscript/tests/scripts/runtime/features/for_loop_iterator_specified_types.out b/modules/gdscript/tests/scripts/runtime/features/for_loop_iterator_specified_types.out index eeebdc4be5..89cc1b76fe 100644 --- a/modules/gdscript/tests/scripts/runtime/features/for_loop_iterator_specified_types.out +++ b/modules/gdscript/tests/scripts/runtime/features/for_loop_iterator_specified_types.out @@ -27,3 +27,7 @@ Test RefCounted-keys dictionary. RefCounted RefCounted Resource Resource ConfigFile ConfigFile +Test implicitly typed dictionary literal. +&"x" &"x" +&"y" &"y" +&"z" &"z" diff --git a/modules/gdscript/tests/scripts/runtime/features/member_info.gd b/modules/gdscript/tests/scripts/runtime/features/member_info.gd index 91d5a501c8..4ce53aa395 100644 --- a/modules/gdscript/tests/scripts/runtime/features/member_info.gd +++ b/modules/gdscript/tests/scripts/runtime/features/member_info.gd @@ -31,6 +31,16 @@ var test_var_hard_array_my_enum: Array[MyEnum] var test_var_hard_array_resource: Array[Resource] var test_var_hard_array_this: Array[TestMemberInfo] var test_var_hard_array_my_class: Array[MyClass] +var test_var_hard_dictionary: Dictionary +var test_var_hard_dictionary_int_variant: Dictionary[int, Variant] +var test_var_hard_dictionary_variant_int: Dictionary[Variant, int] +var test_var_hard_dictionary_int_int: Dictionary[int, int] +var test_var_hard_dictionary_variant_type: Dictionary[Variant.Type, Variant.Type] +var test_var_hard_dictionary_node_process_mode: Dictionary[Node.ProcessMode, Node.ProcessMode] +var test_var_hard_dictionary_my_enum: Dictionary[MyEnum, MyEnum] +var test_var_hard_dictionary_resource: Dictionary[Resource, Resource] +var test_var_hard_dictionary_this: Dictionary[TestMemberInfo, TestMemberInfo] +var test_var_hard_dictionary_my_class: Dictionary[MyClass, MyClass] var test_var_hard_resource: Resource var test_var_hard_this: TestMemberInfo var test_var_hard_my_class: MyClass @@ -43,17 +53,17 @@ func test_func_weak_null(): return null func test_func_weak_int(): return 1 func test_func_hard_variant() -> Variant: return null func test_func_hard_int() -> int: return 1 -func test_func_args_1(_a: int, _b: Array[int], _c: int = 1, _d = 2): pass +func test_func_args_1(_a: int, _b: Array[int], _c: Dictionary[int, int], _d: int = 1, _e = 2): pass func test_func_args_2(_a = 1, _b = _a, _c = [2], _d = 3): pass signal test_signal_1() signal test_signal_2(a: Variant, b) -signal test_signal_3(a: int, b: Array[int]) -signal test_signal_4(a: Variant.Type, b: Array[Variant.Type]) -signal test_signal_5(a: MyEnum, b: Array[MyEnum]) -signal test_signal_6(a: Resource, b: Array[Resource]) -signal test_signal_7(a: TestMemberInfo, b: Array[TestMemberInfo]) -signal test_signal_8(a: MyClass, b: Array[MyClass]) +signal test_signal_3(a: int, b: Array[int], c: Dictionary[int, int]) +signal test_signal_4(a: Variant.Type, b: Array[Variant.Type], c: Dictionary[Variant.Type, Variant.Type]) +signal test_signal_5(a: MyEnum, b: Array[MyEnum], c: Dictionary[MyEnum, MyEnum]) +signal test_signal_6(a: Resource, b: Array[Resource], c: Dictionary[Resource, Resource]) +signal test_signal_7(a: TestMemberInfo, b: Array[TestMemberInfo], c: Dictionary[TestMemberInfo, TestMemberInfo]) +signal test_signal_8(a: MyClass, b: Array[MyClass], c: Dictionary[MyClass, MyClass]) func no_exec(): test_signal_1.emit() diff --git a/modules/gdscript/tests/scripts/runtime/features/member_info.out b/modules/gdscript/tests/scripts/runtime/features/member_info.out index 7c826ac05a..2baf451aa5 100644 --- a/modules/gdscript/tests/scripts/runtime/features/member_info.out +++ b/modules/gdscript/tests/scripts/runtime/features/member_info.out @@ -23,6 +23,16 @@ var test_var_hard_array_my_enum: Array[TestMemberInfo.MyEnum] var test_var_hard_array_resource: Array[Resource] var test_var_hard_array_this: Array[TestMemberInfo] var test_var_hard_array_my_class: Array[RefCounted] +var test_var_hard_dictionary: Dictionary +var test_var_hard_dictionary_int_variant: Dictionary[int, Variant] +var test_var_hard_dictionary_variant_int: Dictionary[Variant, int] +var test_var_hard_dictionary_int_int: Dictionary[int, int] +var test_var_hard_dictionary_variant_type: Dictionary[Variant.Type, Variant.Type] +var test_var_hard_dictionary_node_process_mode: Dictionary[Node.ProcessMode, Node.ProcessMode] +var test_var_hard_dictionary_my_enum: Dictionary[TestMemberInfo.MyEnum, TestMemberInfo.MyEnum] +var test_var_hard_dictionary_resource: Dictionary[Resource, Resource] +var test_var_hard_dictionary_this: Dictionary[TestMemberInfo, TestMemberInfo] +var test_var_hard_dictionary_my_class: Dictionary[RefCounted, RefCounted] var test_var_hard_resource: Resource var test_var_hard_this: TestMemberInfo var test_var_hard_my_class: RefCounted @@ -33,13 +43,13 @@ func test_func_weak_null() -> Variant func test_func_weak_int() -> Variant func test_func_hard_variant() -> Variant func test_func_hard_int() -> int -func test_func_args_1(_a: int, _b: Array[int], _c: int = 1, _d: Variant = 2) -> void +func test_func_args_1(_a: int, _b: Array[int], _c: Dictionary[int, int], _d: int = 1, _e: Variant = 2) -> void func test_func_args_2(_a: Variant = 1, _b: Variant = null, _c: Variant = null, _d: Variant = 3) -> void signal test_signal_1() signal test_signal_2(a: Variant, b: Variant) -signal test_signal_3(a: int, b: Array[int]) -signal test_signal_4(a: Variant.Type, b: Array[Variant.Type]) -signal test_signal_5(a: TestMemberInfo.MyEnum, b: Array[TestMemberInfo.MyEnum]) -signal test_signal_6(a: Resource, b: Array[Resource]) -signal test_signal_7(a: TestMemberInfo, b: Array[TestMemberInfo]) -signal test_signal_8(a: RefCounted, b: Array[RefCounted]) +signal test_signal_3(a: int, b: Array[int], c: Dictionary[int, int]) +signal test_signal_4(a: Variant.Type, b: Array[Variant.Type], c: Dictionary[Variant.Type, Variant.Type]) +signal test_signal_5(a: TestMemberInfo.MyEnum, b: Array[TestMemberInfo.MyEnum], c: Dictionary[TestMemberInfo.MyEnum, TestMemberInfo.MyEnum]) +signal test_signal_6(a: Resource, b: Array[Resource], c: Dictionary[Resource, Resource]) +signal test_signal_7(a: TestMemberInfo, b: Array[TestMemberInfo], c: Dictionary[TestMemberInfo, TestMemberInfo]) +signal test_signal_8(a: RefCounted, b: Array[RefCounted], c: Dictionary[RefCounted, RefCounted]) diff --git a/modules/gdscript/tests/scripts/runtime/features/typed_dictionary_implicit_cast_param.gd b/modules/gdscript/tests/scripts/runtime/features/typed_dictionary_implicit_cast_param.gd new file mode 100644 index 0000000000..0371ee5630 --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/features/typed_dictionary_implicit_cast_param.gd @@ -0,0 +1,6 @@ +func test_param(dictionary: Dictionary[int, String]) -> void: + print(dictionary.get_typed_key_builtin() == TYPE_INT) + print(dictionary.get_typed_value_builtin() == TYPE_STRING) + +func test() -> void: + test_param({ 123: "some_string" }) diff --git a/modules/gdscript/tests/scripts/runtime/features/typed_dictionary_implicit_cast_param.out b/modules/gdscript/tests/scripts/runtime/features/typed_dictionary_implicit_cast_param.out new file mode 100644 index 0000000000..9d111a8322 --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/features/typed_dictionary_implicit_cast_param.out @@ -0,0 +1,3 @@ +GDTEST_OK +true +true diff --git a/modules/gdscript/tests/scripts/runtime/features/typed_dictionary_init_with_untyped_in_literal.gd b/modules/gdscript/tests/scripts/runtime/features/typed_dictionary_init_with_untyped_in_literal.gd new file mode 100644 index 0000000000..ee51440d80 --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/features/typed_dictionary_init_with_untyped_in_literal.gd @@ -0,0 +1,7 @@ +func test(): + var untyped: Variant = 32 + var typed: Dictionary[int, int] = { untyped: untyped } + Utils.check(typed.get_typed_key_builtin() == TYPE_INT) + Utils.check(typed.get_typed_value_builtin() == TYPE_INT) + Utils.check(str(typed) == '{ 32: 32 }') + print('ok') diff --git a/modules/gdscript/tests/scripts/runtime/features/typed_dictionary_init_with_untyped_in_literal.out b/modules/gdscript/tests/scripts/runtime/features/typed_dictionary_init_with_untyped_in_literal.out new file mode 100644 index 0000000000..1b47ed10dc --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/features/typed_dictionary_init_with_untyped_in_literal.out @@ -0,0 +1,2 @@ +GDTEST_OK +ok diff --git a/modules/gdscript/tests/scripts/utils.notest.gd b/modules/gdscript/tests/scripts/utils.notest.gd index 5d615d8557..1e2788f765 100644 --- a/modules/gdscript/tests/scripts/utils.notest.gd +++ b/modules/gdscript/tests/scripts/utils.notest.gd @@ -24,6 +24,11 @@ static func get_type(property: Dictionary, is_return: bool = false) -> String: if str(property.hint_string).is_empty(): return "Array[<unknown type>]" return "Array[%s]" % property.hint_string + TYPE_DICTIONARY: + if property.hint == PROPERTY_HINT_DICTIONARY_TYPE: + if str(property.hint_string).is_empty(): + return "Dictionary[<unknown type>, <unknown type>]" + return "Dictionary[%s]" % str(property.hint_string).replace(";", ", ") TYPE_OBJECT: if not str(property.class_name).is_empty(): return property.class_name @@ -188,6 +193,8 @@ static func get_property_hint_name(hint: PropertyHint) -> String: return "PROPERTY_HINT_INT_IS_POINTER" PROPERTY_HINT_ARRAY_TYPE: return "PROPERTY_HINT_ARRAY_TYPE" + PROPERTY_HINT_DICTIONARY_TYPE: + return "PROPERTY_HINT_DICTIONARY_TYPE" PROPERTY_HINT_LOCALE_ID: return "PROPERTY_HINT_LOCALE_ID" PROPERTY_HINT_LOCALIZABLE_STRING: diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/GeneratedSources/ExportedFields_ScriptProperties.generated.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/GeneratedSources/ExportedFields_ScriptProperties.generated.cs index 6a3884dabf..c734dc7be1 100644 --- a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/GeneratedSources/ExportedFields_ScriptProperties.generated.cs +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/GeneratedSources/ExportedFields_ScriptProperties.generated.cs @@ -807,7 +807,7 @@ partial class ExportedFields properties.Add(new(type: (global::Godot.Variant.Type)23, name: PropertyName.@_fieldRid, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4102, exported: true)); properties.Add(new(type: (global::Godot.Variant.Type)27, name: PropertyName.@_fieldGodotDictionary, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4102, exported: true)); properties.Add(new(type: (global::Godot.Variant.Type)28, name: PropertyName.@_fieldGodotArray, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4102, exported: true)); - properties.Add(new(type: (global::Godot.Variant.Type)27, name: PropertyName.@_fieldGodotGenericDictionary, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4102, exported: true)); + properties.Add(new(type: (global::Godot.Variant.Type)27, name: PropertyName.@_fieldGodotGenericDictionary, hint: (global::Godot.PropertyHint)38, hintString: "4/0:;1/0:", usage: (global::Godot.PropertyUsageFlags)4102, exported: true)); properties.Add(new(type: (global::Godot.Variant.Type)28, name: PropertyName.@_fieldGodotGenericArray, hint: (global::Godot.PropertyHint)23, hintString: "2/0:", usage: (global::Godot.PropertyUsageFlags)4102, exported: true)); properties.Add(new(type: (global::Godot.Variant.Type)31, name: PropertyName.@_fieldEmptyInt64Array, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4102, exported: true)); return properties; diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/GeneratedSources/ExportedProperties_ScriptProperties.generated.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/GeneratedSources/ExportedProperties_ScriptProperties.generated.cs index 3c48740773..0de840aa34 100644 --- a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/GeneratedSources/ExportedProperties_ScriptProperties.generated.cs +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/GeneratedSources/ExportedProperties_ScriptProperties.generated.cs @@ -925,7 +925,7 @@ partial class ExportedProperties properties.Add(new(type: (global::Godot.Variant.Type)23, name: PropertyName.@PropertyRid, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4102, exported: true)); properties.Add(new(type: (global::Godot.Variant.Type)27, name: PropertyName.@PropertyGodotDictionary, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4102, exported: true)); properties.Add(new(type: (global::Godot.Variant.Type)28, name: PropertyName.@PropertyGodotArray, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4102, exported: true)); - properties.Add(new(type: (global::Godot.Variant.Type)27, name: PropertyName.@PropertyGodotGenericDictionary, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4102, exported: true)); + properties.Add(new(type: (global::Godot.Variant.Type)27, name: PropertyName.@PropertyGodotGenericDictionary, hint: (global::Godot.PropertyHint)38, hintString: "4/0:;1/0:", usage: (global::Godot.PropertyUsageFlags)4102, exported: true)); properties.Add(new(type: (global::Godot.Variant.Type)28, name: PropertyName.@PropertyGodotGenericArray, hint: (global::Godot.PropertyHint)23, hintString: "2/0:", usage: (global::Godot.PropertyUsageFlags)4102, exported: true)); return properties; } diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/GodotEnums.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/GodotEnums.cs index 4cf6a9f431..bb4c4824e7 100644 --- a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/GodotEnums.cs +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/GodotEnums.cs @@ -88,7 +88,8 @@ namespace Godot.SourceGenerators HideQuaternionEdit = 35, Password = 36, LayersAvoidance = 37, - Max = 38 + DictionaryType = 38, + Max = 39 } [Flags] diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/MarshalUtils.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/MarshalUtils.cs index d272832950..f5f51722b4 100644 --- a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/MarshalUtils.cs +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/MarshalUtils.cs @@ -274,6 +274,14 @@ namespace Godot.SourceGenerators return null; } + public static ITypeSymbol[]? GetGenericElementTypes(ITypeSymbol typeSymbol) + { + if (typeSymbol is INamedTypeSymbol { IsGenericType: true } genericType) + return genericType.TypeArguments.ToArray(); + + return null; + } + private static StringBuilder Append(this StringBuilder source, string a, string b) => source.Append(a).Append(b); diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptPropertiesGenerator.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptPropertiesGenerator.cs index 29bae6413d..0f86b3b91c 100644 --- a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptPropertiesGenerator.cs +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptPropertiesGenerator.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using System.Diagnostics; using System.Linq; using System.Text; using Microsoft.CodeAnalysis; @@ -728,8 +729,72 @@ namespace Godot.SourceGenerators if (!isTypeArgument && variantType == VariantType.Dictionary) { - // TODO: Dictionaries are not supported in the inspector - return false; + var elementTypes = MarshalUtils.GetGenericElementTypes(type); + + if (elementTypes == null) + return false; // Non-generic Dictionary, so there's no hint to add + Debug.Assert(elementTypes.Length == 2); + + var keyElementMarshalType = MarshalUtils.ConvertManagedTypeToMarshalType(elementTypes[0], typeCache)!.Value; + var keyElementVariantType = MarshalUtils.ConvertMarshalTypeToVariantType(keyElementMarshalType)!.Value; + var keyIsPresetHint = false; + var keyHintString = (string?)null; + + if (keyElementVariantType == VariantType.String || keyElementVariantType == VariantType.StringName) + keyIsPresetHint = GetStringArrayEnumHint(keyElementVariantType, exportAttr, out keyHintString); + + if (!keyIsPresetHint) + { + bool hintRes = TryGetMemberExportHint(typeCache, elementTypes[0], + exportAttr, keyElementVariantType, isTypeArgument: true, + out var keyElementHint, out var keyElementHintString); + + // Format: type/hint:hint_string + if (hintRes) + { + keyHintString = (int)keyElementVariantType + "/" + (int)keyElementHint + ":"; + + if (keyElementHintString != null) + keyHintString += keyElementHintString; + } + else + { + keyHintString = (int)keyElementVariantType + "/" + (int)PropertyHint.None + ":"; + } + } + + var valueElementMarshalType = MarshalUtils.ConvertManagedTypeToMarshalType(elementTypes[1], typeCache)!.Value; + var valueElementVariantType = MarshalUtils.ConvertMarshalTypeToVariantType(valueElementMarshalType)!.Value; + var valueIsPresetHint = false; + var valueHintString = (string?)null; + + if (valueElementVariantType == VariantType.String || valueElementVariantType == VariantType.StringName) + valueIsPresetHint = GetStringArrayEnumHint(valueElementVariantType, exportAttr, out valueHintString); + + if (!valueIsPresetHint) + { + bool hintRes = TryGetMemberExportHint(typeCache, elementTypes[1], + exportAttr, valueElementVariantType, isTypeArgument: true, + out var valueElementHint, out var valueElementHintString); + + // Format: type/hint:hint_string + if (hintRes) + { + valueHintString = (int)valueElementVariantType + "/" + (int)valueElementHint + ":"; + + if (valueElementHintString != null) + valueHintString += valueElementHintString; + } + else + { + valueHintString = (int)valueElementVariantType + "/" + (int)PropertyHint.None + ":"; + } + } + + hint = PropertyHint.DictionaryType; + + hintString = keyHintString != null && valueHintString != null ? $"{keyHintString};{valueHintString}" : null; + return hintString != null; } return false; diff --git a/modules/mono/editor/bindings_generator.cpp b/modules/mono/editor/bindings_generator.cpp index b26f6d1bbf..2ec073e4fa 100644 --- a/modules/mono/editor/bindings_generator.cpp +++ b/modules/mono/editor/bindings_generator.cpp @@ -3809,6 +3809,11 @@ bool BindingsGenerator::_populate_object_type_interfaces() { } else if (return_info.type == Variant::ARRAY && return_info.hint == PROPERTY_HINT_ARRAY_TYPE) { imethod.return_type.cname = Variant::get_type_name(return_info.type) + "_@generic"; imethod.return_type.generic_type_parameters.push_back(TypeReference(return_info.hint_string)); + } else if (return_info.type == Variant::DICTIONARY && return_info.hint == PROPERTY_HINT_DICTIONARY_TYPE) { + imethod.return_type.cname = Variant::get_type_name(return_info.type) + "_@generic"; + Vector<String> split = return_info.hint_string.split(";"); + imethod.return_type.generic_type_parameters.push_back(TypeReference(split.get(0))); + imethod.return_type.generic_type_parameters.push_back(TypeReference(split.get(1))); } else if (return_info.hint == PROPERTY_HINT_RESOURCE_TYPE) { imethod.return_type.cname = return_info.hint_string; } else if (return_info.type == Variant::NIL && return_info.usage & PROPERTY_USAGE_NIL_IS_VARIANT) { @@ -3836,6 +3841,11 @@ bool BindingsGenerator::_populate_object_type_interfaces() { } else if (arginfo.type == Variant::ARRAY && arginfo.hint == PROPERTY_HINT_ARRAY_TYPE) { iarg.type.cname = Variant::get_type_name(arginfo.type) + "_@generic"; iarg.type.generic_type_parameters.push_back(TypeReference(arginfo.hint_string)); + } else if (arginfo.type == Variant::DICTIONARY && arginfo.hint == PROPERTY_HINT_DICTIONARY_TYPE) { + iarg.type.cname = Variant::get_type_name(arginfo.type) + "_@generic"; + Vector<String> split = arginfo.hint_string.split(";"); + iarg.type.generic_type_parameters.push_back(TypeReference(split.get(0))); + iarg.type.generic_type_parameters.push_back(TypeReference(split.get(1))); } else if (arginfo.hint == PROPERTY_HINT_RESOURCE_TYPE) { iarg.type.cname = arginfo.hint_string; } else if (arginfo.type == Variant::NIL) { @@ -3963,6 +3973,11 @@ bool BindingsGenerator::_populate_object_type_interfaces() { } else if (arginfo.type == Variant::ARRAY && arginfo.hint == PROPERTY_HINT_ARRAY_TYPE) { iarg.type.cname = Variant::get_type_name(arginfo.type) + "_@generic"; iarg.type.generic_type_parameters.push_back(TypeReference(arginfo.hint_string)); + } else if (arginfo.type == Variant::DICTIONARY && arginfo.hint == PROPERTY_HINT_DICTIONARY_TYPE) { + iarg.type.cname = Variant::get_type_name(arginfo.type) + "_@generic"; + Vector<String> split = arginfo.hint_string.split(";"); + iarg.type.generic_type_parameters.push_back(TypeReference(split.get(0))); + iarg.type.generic_type_parameters.push_back(TypeReference(split.get(1))); } else if (arginfo.hint == PROPERTY_HINT_RESOURCE_TYPE) { iarg.type.cname = arginfo.hint_string; } else if (arginfo.type == Variant::NIL) { |