2
0
Эх сурвалжийг харах

Implement typed dictionaries

Thaddeus Crews 2 жил өмнө
parent
commit
9853a69144
86 өөрчлөгдсөн 3072 нэмэгдсэн , 194 устгасан
  1. 1 0
      core/core_constants.cpp
  2. 6 0
      core/doc_data.cpp
  3. 3 0
      core/extension/extension_api_dump.cpp
  4. 10 0
      core/extension/gdextension_interface.cpp
  5. 16 0
      core/extension/gdextension_interface.h
  6. 15 0
      core/io/resource_format_binary.cpp
  7. 1 0
      core/object/object.h
  8. 265 11
      core/variant/dictionary.cpp
  9. 16 0
      core/variant/dictionary.h
  10. 342 0
      core/variant/typed_dictionary.h
  11. 13 0
      core/variant/variant_call.cpp
  12. 1 0
      core/variant/variant_construct.cpp
  13. 106 0
      core/variant/variant_construct.h
  14. 227 17
      core/variant/variant_parser.cpp
  15. 44 47
      core/variant/variant_setget.cpp
  16. 4 1
      doc/classes/@GlobalScope.xml
  17. 95 0
      doc/classes/Dictionary.xml
  18. 17 18
      doc/tools/make_rst.py
  19. 16 0
      editor/connections_dialog.cpp
  20. 2 0
      editor/doc_tools.cpp
  21. 19 3
      editor/editor_help.cpp
  22. 1 1
      editor/editor_properties.cpp
  23. 116 11
      editor/editor_properties_array_dict.cpp
  24. 10 2
      editor/editor_properties_array_dict.h
  25. 75 16
      modules/gdscript/editor/gdscript_docgen.cpp
  26. 293 15
      modules/gdscript/gdscript_analyzer.cpp
  27. 2 0
      modules/gdscript/gdscript_analyzer.h
  28. 84 0
      modules/gdscript/gdscript_byte_codegen.cpp
  29. 1 0
      modules/gdscript/gdscript_byte_codegen.h
  30. 1 0
      modules/gdscript/gdscript_codegen.h
  31. 17 7
      modules/gdscript/gdscript_compiler.cpp
  32. 107 0
      modules/gdscript/gdscript_disassembler.cpp
  33. 4 0
      modules/gdscript/gdscript_editor.cpp
  34. 43 0
      modules/gdscript/gdscript_function.h
  35. 214 4
      modules/gdscript/gdscript_parser.cpp
  36. 2 0
      modules/gdscript/gdscript_parser.h
  37. 190 4
      modules/gdscript/gdscript_vm.cpp
  38. 3 0
      modules/gdscript/tests/scripts/analyzer/errors/for_loop_wrong_specified_type_with_literal_dictionary.gd
  39. 2 0
      modules/gdscript/tests/scripts/analyzer/errors/for_loop_wrong_specified_type_with_literal_dictionary.out
  40. 4 0
      modules/gdscript/tests/scripts/analyzer/errors/typed_dictionary_assign_differently_typed.gd
  41. 2 0
      modules/gdscript/tests/scripts/analyzer/errors/typed_dictionary_assign_differently_typed.out
  42. 2 0
      modules/gdscript/tests/scripts/analyzer/errors/typed_dictionary_assignment.gd
  43. 2 0
      modules/gdscript/tests/scripts/analyzer/errors/typed_dictionary_assignment.out
  44. 4 0
      modules/gdscript/tests/scripts/analyzer/errors/typed_dictionary_init_with_unconvertible_in_literal.gd
  45. 2 0
      modules/gdscript/tests/scripts/analyzer/errors/typed_dictionary_init_with_unconvertible_in_literal.out
  46. 7 0
      modules/gdscript/tests/scripts/analyzer/errors/typed_dictionary_pass_differently_to_typed.gd
  47. 2 0
      modules/gdscript/tests/scripts/analyzer/errors/typed_dictionary_pass_differently_to_typed.out
  48. 20 0
      modules/gdscript/tests/scripts/analyzer/features/typed_dictionary_as_default_parameter.gd
  49. 11 0
      modules/gdscript/tests/scripts/analyzer/features/typed_dictionary_as_default_parameter.out
  50. 4 0
      modules/gdscript/tests/scripts/analyzer/features/typed_dictionary_inferred_access_isnt_constant.gd
  51. 2 0
      modules/gdscript/tests/scripts/analyzer/features/typed_dictionary_inferred_access_isnt_constant.out
  52. 214 0
      modules/gdscript/tests/scripts/analyzer/features/typed_dictionary_usage.gd
  53. 2 0
      modules/gdscript/tests/scripts/analyzer/features/typed_dictionary_usage.out
  54. 9 0
      modules/gdscript/tests/scripts/analyzer/features/typed_dictionary_with_custom_class.gd
  55. 2 0
      modules/gdscript/tests/scripts/analyzer/features/typed_dictionary_with_custom_class.out
  56. 5 0
      modules/gdscript/tests/scripts/parser/features/typed_dictionaries.gd
  57. 3 0
      modules/gdscript/tests/scripts/parser/features/typed_dictionaries.out
  58. 4 0
      modules/gdscript/tests/scripts/runtime/errors/typed_dictionary_assign_basic_to_typed.gd
  59. 6 0
      modules/gdscript/tests/scripts/runtime/errors/typed_dictionary_assign_basic_to_typed.out
  60. 4 0
      modules/gdscript/tests/scripts/runtime/errors/typed_dictionary_assign_differently_typed.gd
  61. 6 0
      modules/gdscript/tests/scripts/runtime/errors/typed_dictionary_assign_differently_typed.out
  62. 7 0
      modules/gdscript/tests/scripts/runtime/errors/typed_dictionary_assign_wrong_to_typed.gd
  63. 5 0
      modules/gdscript/tests/scripts/runtime/errors/typed_dictionary_assign_wrong_to_typed.out
  64. 7 0
      modules/gdscript/tests/scripts/runtime/errors/typed_dictionary_pass_basic_to_typed.gd
  65. 6 0
      modules/gdscript/tests/scripts/runtime/errors/typed_dictionary_pass_basic_to_typed.out
  66. 7 0
      modules/gdscript/tests/scripts/runtime/errors/typed_dictionary_pass_differently_to_typed.gd
  67. 6 0
      modules/gdscript/tests/scripts/runtime/errors/typed_dictionary_pass_differently_to_typed.out
  68. 7 2
      modules/gdscript/tests/scripts/runtime/features/for_loop_iterator_specified_types.gd
  69. 4 0
      modules/gdscript/tests/scripts/runtime/features/for_loop_iterator_specified_types.out
  70. 17 7
      modules/gdscript/tests/scripts/runtime/features/member_info.gd
  71. 17 7
      modules/gdscript/tests/scripts/runtime/features/member_info.out
  72. 6 0
      modules/gdscript/tests/scripts/runtime/features/typed_dictionary_implicit_cast_param.gd
  73. 3 0
      modules/gdscript/tests/scripts/runtime/features/typed_dictionary_implicit_cast_param.out
  74. 7 0
      modules/gdscript/tests/scripts/runtime/features/typed_dictionary_init_with_untyped_in_literal.gd
  75. 2 0
      modules/gdscript/tests/scripts/runtime/features/typed_dictionary_init_with_untyped_in_literal.out
  76. 7 0
      modules/gdscript/tests/scripts/utils.notest.gd
  77. 1 1
      modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/GeneratedSources/ExportedFields_ScriptProperties.generated.cs
  78. 1 1
      modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/GeneratedSources/ExportedProperties_ScriptProperties.generated.cs
  79. 2 1
      modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/GodotEnums.cs
  80. 8 0
      modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/MarshalUtils.cs
  81. 67 2
      modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptPropertiesGenerator.cs
  82. 15 0
      modules/mono/editor/bindings_generator.cpp
  83. 102 15
      scene/resources/packed_scene.cpp
  84. 1 0
      scene/resources/packed_scene.h
  85. 28 0
      scene/resources/resource_format_text.cpp
  86. 38 1
      tests/core/variant/test_dictionary.h

+ 1 - 0
core/core_constants.cpp

@@ -671,6 +671,7 @@ void register_global_constants() {
 	BIND_CORE_ENUM_CONSTANT(PROPERTY_HINT_INT_IS_OBJECTID);
 	BIND_CORE_ENUM_CONSTANT(PROPERTY_HINT_INT_IS_OBJECTID);
 	BIND_CORE_ENUM_CONSTANT(PROPERTY_HINT_INT_IS_POINTER);
 	BIND_CORE_ENUM_CONSTANT(PROPERTY_HINT_INT_IS_POINTER);
 	BIND_CORE_ENUM_CONSTANT(PROPERTY_HINT_ARRAY_TYPE);
 	BIND_CORE_ENUM_CONSTANT(PROPERTY_HINT_ARRAY_TYPE);
+	BIND_CORE_ENUM_CONSTANT(PROPERTY_HINT_DICTIONARY_TYPE);
 	BIND_CORE_ENUM_CONSTANT(PROPERTY_HINT_LOCALE_ID);
 	BIND_CORE_ENUM_CONSTANT(PROPERTY_HINT_LOCALE_ID);
 	BIND_CORE_ENUM_CONSTANT(PROPERTY_HINT_LOCALIZABLE_STRING);
 	BIND_CORE_ENUM_CONSTANT(PROPERTY_HINT_LOCALIZABLE_STRING);
 	BIND_CORE_ENUM_CONSTANT(PROPERTY_HINT_NODE_TYPE);
 	BIND_CORE_ENUM_CONSTANT(PROPERTY_HINT_NODE_TYPE);

+ 6 - 0
core/doc_data.cpp

@@ -33,6 +33,8 @@
 String DocData::get_default_value_string(const Variant &p_value) {
 String DocData::get_default_value_string(const Variant &p_value) {
 	if (p_value.get_type() == Variant::ARRAY) {
 	if (p_value.get_type() == Variant::ARRAY) {
 		return Variant(Array(p_value, 0, StringName(), Variant())).get_construct_string().replace("\n", " ");
 		return Variant(Array(p_value, 0, StringName(), Variant())).get_construct_string().replace("\n", " ");
+	} else if (p_value.get_type() == Variant::DICTIONARY) {
+		return Variant(Dictionary(p_value, 0, StringName(), Variant(), 0, StringName(), Variant())).get_construct_string().replace("\n", " ");
 	} else {
 	} else {
 		return p_value.get_construct_string().replace("\n", " ");
 		return p_value.get_construct_string().replace("\n", " ");
 	}
 	}
@@ -57,6 +59,8 @@ void DocData::return_doc_from_retinfo(DocData::MethodDoc &p_method, const Proper
 		p_method.return_type = p_retinfo.class_name;
 		p_method.return_type = p_retinfo.class_name;
 	} else if (p_retinfo.type == Variant::ARRAY && p_retinfo.hint == PROPERTY_HINT_ARRAY_TYPE) {
 	} else if (p_retinfo.type == Variant::ARRAY && p_retinfo.hint == PROPERTY_HINT_ARRAY_TYPE) {
 		p_method.return_type = p_retinfo.hint_string + "[]";
 		p_method.return_type = p_retinfo.hint_string + "[]";
+	} else if (p_retinfo.type == Variant::DICTIONARY && p_retinfo.hint == PROPERTY_HINT_DICTIONARY_TYPE) {
+		p_method.return_type = "Dictionary[" + p_retinfo.hint_string.replace(";", ", ") + "]";
 	} else if (p_retinfo.hint == PROPERTY_HINT_RESOURCE_TYPE) {
 	} else if (p_retinfo.hint == PROPERTY_HINT_RESOURCE_TYPE) {
 		p_method.return_type = p_retinfo.hint_string;
 		p_method.return_type = p_retinfo.hint_string;
 	} else if (p_retinfo.type == Variant::NIL && p_retinfo.usage & PROPERTY_USAGE_NIL_IS_VARIANT) {
 	} else if (p_retinfo.type == Variant::NIL && p_retinfo.usage & PROPERTY_USAGE_NIL_IS_VARIANT) {
@@ -89,6 +93,8 @@ void DocData::argument_doc_from_arginfo(DocData::ArgumentDoc &p_argument, const
 		p_argument.type = p_arginfo.class_name;
 		p_argument.type = p_arginfo.class_name;
 	} else if (p_arginfo.type == Variant::ARRAY && p_arginfo.hint == PROPERTY_HINT_ARRAY_TYPE) {
 	} else if (p_arginfo.type == Variant::ARRAY && p_arginfo.hint == PROPERTY_HINT_ARRAY_TYPE) {
 		p_argument.type = p_arginfo.hint_string + "[]";
 		p_argument.type = p_arginfo.hint_string + "[]";
+	} else if (p_arginfo.type == Variant::DICTIONARY && p_arginfo.hint == PROPERTY_HINT_DICTIONARY_TYPE) {
+		p_argument.type = "Dictionary[" + p_arginfo.hint_string.replace(";", ", ") + "]";
 	} else if (p_arginfo.hint == PROPERTY_HINT_RESOURCE_TYPE) {
 	} else if (p_arginfo.hint == PROPERTY_HINT_RESOURCE_TYPE) {
 		p_argument.type = p_arginfo.hint_string;
 		p_argument.type = p_arginfo.hint_string;
 	} else if (p_arginfo.type == Variant::NIL) {
 	} else if (p_arginfo.type == Variant::NIL) {

+ 3 - 0
core/extension/extension_api_dump.cpp

@@ -60,6 +60,9 @@ static String get_property_info_type_name(const PropertyInfo &p_info) {
 	if (p_info.type == Variant::ARRAY && (p_info.hint == PROPERTY_HINT_ARRAY_TYPE)) {
 	if (p_info.type == Variant::ARRAY && (p_info.hint == PROPERTY_HINT_ARRAY_TYPE)) {
 		return String("typedarray::") + p_info.hint_string;
 		return String("typedarray::") + p_info.hint_string;
 	}
 	}
+	if (p_info.type == Variant::DICTIONARY && (p_info.hint == PROPERTY_HINT_DICTIONARY_TYPE)) {
+		return String("typeddictionary::") + p_info.hint_string;
+	}
 	if (p_info.type == Variant::INT && (p_info.usage & (PROPERTY_USAGE_CLASS_IS_ENUM))) {
 	if (p_info.type == Variant::INT && (p_info.usage & (PROPERTY_USAGE_CLASS_IS_ENUM))) {
 		return String("enum::") + String(p_info.class_name);
 		return String("enum::") + String(p_info.class_name);
 	}
 	}

+ 10 - 0
core/extension/gdextension_interface.cpp

@@ -1199,6 +1199,15 @@ static GDExtensionVariantPtr gdextension_dictionary_operator_index_const(GDExten
 	return (GDExtensionVariantPtr)&self->operator[](*(const Variant *)p_key);
 	return (GDExtensionVariantPtr)&self->operator[](*(const Variant *)p_key);
 }
 }
 
 
+void gdextension_dictionary_set_typed(GDExtensionTypePtr p_self, GDExtensionVariantType p_key_type, GDExtensionConstStringNamePtr p_key_class_name, GDExtensionConstVariantPtr p_key_script, GDExtensionVariantType p_value_type, GDExtensionConstStringNamePtr p_value_class_name, GDExtensionConstVariantPtr p_value_script) {
+	Dictionary *self = reinterpret_cast<Dictionary *>(p_self);
+	const StringName *key_class_name = reinterpret_cast<const StringName *>(p_key_class_name);
+	const Variant *key_script = reinterpret_cast<const Variant *>(p_key_script);
+	const StringName *value_class_name = reinterpret_cast<const StringName *>(p_value_class_name);
+	const Variant *value_script = reinterpret_cast<const Variant *>(p_value_script);
+	self->set_typed((uint32_t)p_key_type, *key_class_name, *key_script, (uint32_t)p_value_type, *value_class_name, *value_script);
+}
+
 /* OBJECT API */
 /* OBJECT API */
 
 
 static void gdextension_object_method_bind_call(GDExtensionMethodBindPtr p_method_bind, GDExtensionObjectPtr p_instance, const GDExtensionConstVariantPtr *p_args, GDExtensionInt p_arg_count, GDExtensionUninitializedVariantPtr r_return, GDExtensionCallError *r_error) {
 static void gdextension_object_method_bind_call(GDExtensionMethodBindPtr p_method_bind, GDExtensionObjectPtr p_instance, const GDExtensionConstVariantPtr *p_args, GDExtensionInt p_arg_count, GDExtensionUninitializedVariantPtr r_return, GDExtensionCallError *r_error) {
@@ -1679,6 +1688,7 @@ void gdextension_setup_interface() {
 	REGISTER_INTERFACE_FUNC(array_set_typed);
 	REGISTER_INTERFACE_FUNC(array_set_typed);
 	REGISTER_INTERFACE_FUNC(dictionary_operator_index);
 	REGISTER_INTERFACE_FUNC(dictionary_operator_index);
 	REGISTER_INTERFACE_FUNC(dictionary_operator_index_const);
 	REGISTER_INTERFACE_FUNC(dictionary_operator_index_const);
+	REGISTER_INTERFACE_FUNC(dictionary_set_typed);
 	REGISTER_INTERFACE_FUNC(object_method_bind_call);
 	REGISTER_INTERFACE_FUNC(object_method_bind_call);
 	REGISTER_INTERFACE_FUNC(object_method_bind_ptrcall);
 	REGISTER_INTERFACE_FUNC(object_method_bind_ptrcall);
 	REGISTER_INTERFACE_FUNC(object_destroy);
 	REGISTER_INTERFACE_FUNC(object_destroy);

+ 16 - 0
core/extension/gdextension_interface.h

@@ -2372,6 +2372,22 @@ typedef GDExtensionVariantPtr (*GDExtensionInterfaceDictionaryOperatorIndex)(GDE
  */
  */
 typedef GDExtensionVariantPtr (*GDExtensionInterfaceDictionaryOperatorIndexConst)(GDExtensionConstTypePtr p_self, GDExtensionConstVariantPtr p_key);
 typedef GDExtensionVariantPtr (*GDExtensionInterfaceDictionaryOperatorIndexConst)(GDExtensionConstTypePtr p_self, GDExtensionConstVariantPtr p_key);
 
 
+/**
+ * @name dictionary_set_typed
+ * @since 4.4
+ *
+ * Makes a Dictionary into a typed Dictionary.
+ *
+ * @param p_self A pointer to the Dictionary.
+ * @param p_key_type The type of Variant the Dictionary key will store.
+ * @param p_key_class_name A pointer to a StringName with the name of the object (if p_key_type is GDEXTENSION_VARIANT_TYPE_OBJECT).
+ * @param p_key_script A pointer to a Script object (if p_key_type is GDEXTENSION_VARIANT_TYPE_OBJECT and the base class is extended by a script).
+ * @param p_value_type The type of Variant the Dictionary value will store.
+ * @param p_value_class_name A pointer to a StringName with the name of the object (if p_value_type is GDEXTENSION_VARIANT_TYPE_OBJECT).
+ * @param p_value_script A pointer to a Script object (if p_value_type is GDEXTENSION_VARIANT_TYPE_OBJECT and the base class is extended by a script).
+ */
+typedef void (*GDExtensionInterfaceDictionarySetTyped)(GDExtensionTypePtr p_self, GDExtensionVariantType p_key_type, GDExtensionConstStringNamePtr p_key_class_name, GDExtensionConstVariantPtr p_key_script, GDExtensionVariantType p_value_type, GDExtensionConstStringNamePtr p_value_class_name, GDExtensionConstVariantPtr p_value_script);
+
 /* INTERFACE: Object */
 /* INTERFACE: Object */
 
 
 /**
 /**

+ 15 - 0
core/io/resource_format_binary.cpp

@@ -857,6 +857,19 @@ Error ResourceLoaderBinary::load() {
 				}
 				}
 			}
 			}
 
 
+			if (value.get_type() == Variant::DICTIONARY) {
+				Dictionary set_dict = value;
+				bool is_get_valid = false;
+				Variant get_value = res->get(name, &is_get_valid);
+				if (is_get_valid && get_value.get_type() == Variant::DICTIONARY) {
+					Dictionary get_dict = get_value;
+					if (!set_dict.is_same_typed(get_dict)) {
+						value = Dictionary(set_dict, get_dict.get_typed_key_builtin(), get_dict.get_typed_key_class_name(), get_dict.get_typed_key_script(),
+								get_dict.get_typed_value_builtin(), get_dict.get_typed_value_class_name(), get_dict.get_typed_value_script());
+					}
+				}
+			}
+
 			if (set_valid) {
 			if (set_valid) {
 				res->set(name, value);
 				res->set(name, value);
 			}
 			}
@@ -2064,6 +2077,8 @@ void ResourceFormatSaverBinaryInstance::_find_resources(const Variant &p_variant
 
 
 		case Variant::DICTIONARY: {
 		case Variant::DICTIONARY: {
 			Dictionary d = p_variant;
 			Dictionary d = p_variant;
+			_find_resources(d.get_typed_key_script());
+			_find_resources(d.get_typed_value_script());
 			List<Variant> keys;
 			List<Variant> keys;
 			d.get_key_list(&keys);
 			d.get_key_list(&keys);
 			for (const Variant &E : keys) {
 			for (const Variant &E : keys) {

+ 1 - 0
core/object/object.h

@@ -86,6 +86,7 @@ enum PropertyHint {
 	PROPERTY_HINT_HIDE_QUATERNION_EDIT, /// Only Node3D::transform should hide the quaternion editor.
 	PROPERTY_HINT_HIDE_QUATERNION_EDIT, /// Only Node3D::transform should hide the quaternion editor.
 	PROPERTY_HINT_PASSWORD,
 	PROPERTY_HINT_PASSWORD,
 	PROPERTY_HINT_LAYERS_AVOIDANCE,
 	PROPERTY_HINT_LAYERS_AVOIDANCE,
+	PROPERTY_HINT_DICTIONARY_TYPE,
 	PROPERTY_HINT_MAX,
 	PROPERTY_HINT_MAX,
 };
 };
 
 

+ 265 - 11
core/variant/dictionary.cpp

@@ -32,6 +32,7 @@
 
 
 #include "core/templates/hash_map.h"
 #include "core/templates/hash_map.h"
 #include "core/templates/safe_refcount.h"
 #include "core/templates/safe_refcount.h"
+#include "core/variant/container_type_validate.h"
 #include "core/variant/variant.h"
 #include "core/variant/variant.h"
 // required in this order by VariantInternal, do not remove this comment.
 // required in this order by VariantInternal, do not remove this comment.
 #include "core/object/class_db.h"
 #include "core/object/class_db.h"
@@ -43,6 +44,9 @@ struct DictionaryPrivate {
 	SafeRefCount refcount;
 	SafeRefCount refcount;
 	Variant *read_only = nullptr; // If enabled, a pointer is used to a temporary value that is used to return read-only values.
 	Variant *read_only = nullptr; // If enabled, a pointer is used to a temporary value that is used to return read-only values.
 	HashMap<Variant, Variant, VariantHasher, StringLikeVariantComparator> variant_map;
 	HashMap<Variant, Variant, VariantHasher, StringLikeVariantComparator> variant_map;
+	ContainerTypeValidate typed_key;
+	ContainerTypeValidate typed_value;
+	Variant *typed_fallback = nullptr; // Allows a typed dictionary to return dummy values when attempting an invalid access.
 };
 };
 
 
 void Dictionary::get_key_list(List<Variant> *p_keys) const {
 void Dictionary::get_key_list(List<Variant> *p_keys) const {
@@ -120,7 +124,9 @@ Variant *Dictionary::getptr(const Variant &p_key) {
 }
 }
 
 
 Variant Dictionary::get_valid(const Variant &p_key) const {
 Variant Dictionary::get_valid(const Variant &p_key) const {
-	HashMap<Variant, Variant, VariantHasher, StringLikeVariantComparator>::ConstIterator E(_p->variant_map.find(p_key));
+	Variant key = p_key;
+	ERR_FAIL_COND_V(!_p->typed_key.validate(key, "get_valid"), Variant());
+	HashMap<Variant, Variant, VariantHasher, StringLikeVariantComparator>::ConstIterator E(_p->variant_map.find(key));
 
 
 	if (!E) {
 	if (!E) {
 		return Variant();
 		return Variant();
@@ -129,7 +135,9 @@ Variant Dictionary::get_valid(const Variant &p_key) const {
 }
 }
 
 
 Variant Dictionary::get(const Variant &p_key, const Variant &p_default) const {
 Variant Dictionary::get(const Variant &p_key, const Variant &p_default) const {
-	const Variant *result = getptr(p_key);
+	Variant key = p_key;
+	ERR_FAIL_COND_V(!_p->typed_key.validate(key, "get"), p_default);
+	const Variant *result = getptr(key);
 	if (!result) {
 	if (!result) {
 		return p_default;
 		return p_default;
 	}
 	}
@@ -138,10 +146,14 @@ Variant Dictionary::get(const Variant &p_key, const Variant &p_default) const {
 }
 }
 
 
 Variant Dictionary::get_or_add(const Variant &p_key, const Variant &p_default) {
 Variant Dictionary::get_or_add(const Variant &p_key, const Variant &p_default) {
-	const Variant *result = getptr(p_key);
+	Variant key = p_key;
+	ERR_FAIL_COND_V(!_p->typed_key.validate(key, "get"), p_default);
+	const Variant *result = getptr(key);
 	if (!result) {
 	if (!result) {
-		operator[](p_key) = p_default;
-		return p_default;
+		Variant value = p_default;
+		ERR_FAIL_COND_V(!_p->typed_value.validate(value, "add"), value);
+		operator[](key) = value;
+		return value;
 	}
 	}
 	return *result;
 	return *result;
 }
 }
@@ -155,12 +167,16 @@ bool Dictionary::is_empty() const {
 }
 }
 
 
 bool Dictionary::has(const Variant &p_key) const {
 bool Dictionary::has(const Variant &p_key) const {
+	Variant key = p_key;
+	ERR_FAIL_COND_V(!_p->typed_key.validate(key, "use 'has'"), false);
 	return _p->variant_map.has(p_key);
 	return _p->variant_map.has(p_key);
 }
 }
 
 
 bool Dictionary::has_all(const Array &p_keys) const {
 bool Dictionary::has_all(const Array &p_keys) const {
 	for (int i = 0; i < p_keys.size(); i++) {
 	for (int i = 0; i < p_keys.size(); i++) {
-		if (!has(p_keys[i])) {
+		Variant key = p_keys[i];
+		ERR_FAIL_COND_V(!_p->typed_key.validate(key, "use 'has_all'"), false);
+		if (!has(key)) {
 			return false;
 			return false;
 		}
 		}
 	}
 	}
@@ -168,8 +184,10 @@ bool Dictionary::has_all(const Array &p_keys) const {
 }
 }
 
 
 Variant Dictionary::find_key(const Variant &p_value) const {
 Variant Dictionary::find_key(const Variant &p_value) const {
+	Variant value = p_value;
+	ERR_FAIL_COND_V(!_p->typed_value.validate(value, "find_key"), Variant());
 	for (const KeyValue<Variant, Variant> &E : _p->variant_map) {
 	for (const KeyValue<Variant, Variant> &E : _p->variant_map) {
-		if (E.value == p_value) {
+		if (E.value == value) {
 			return E.key;
 			return E.key;
 		}
 		}
 	}
 	}
@@ -177,8 +195,10 @@ Variant Dictionary::find_key(const Variant &p_value) const {
 }
 }
 
 
 bool Dictionary::erase(const Variant &p_key) {
 bool Dictionary::erase(const Variant &p_key) {
+	Variant key = p_key;
+	ERR_FAIL_COND_V(!_p->typed_key.validate(key, "erase"), false);
 	ERR_FAIL_COND_V_MSG(_p->read_only, false, "Dictionary is in read-only state.");
 	ERR_FAIL_COND_V_MSG(_p->read_only, false, "Dictionary is in read-only state.");
-	return _p->variant_map.erase(p_key);
+	return _p->variant_map.erase(key);
 }
 }
 
 
 bool Dictionary::operator==(const Dictionary &p_dictionary) const {
 bool Dictionary::operator==(const Dictionary &p_dictionary) const {
@@ -238,8 +258,12 @@ void Dictionary::clear() {
 void Dictionary::merge(const Dictionary &p_dictionary, bool p_overwrite) {
 void Dictionary::merge(const Dictionary &p_dictionary, bool p_overwrite) {
 	ERR_FAIL_COND_MSG(_p->read_only, "Dictionary is in read-only state.");
 	ERR_FAIL_COND_MSG(_p->read_only, "Dictionary is in read-only state.");
 	for (const KeyValue<Variant, Variant> &E : p_dictionary._p->variant_map) {
 	for (const KeyValue<Variant, Variant> &E : p_dictionary._p->variant_map) {
-		if (p_overwrite || !has(E.key)) {
-			operator[](E.key) = E.value;
+		Variant key = E.key;
+		Variant value = E.value;
+		ERR_FAIL_COND(!_p->typed_key.validate(key, "merge"));
+		ERR_FAIL_COND(!_p->typed_key.validate(value, "merge"));
+		if (p_overwrite || !has(key)) {
+			operator[](key) = value;
 		}
 		}
 	}
 	}
 }
 }
@@ -256,6 +280,9 @@ void Dictionary::_unref() const {
 		if (_p->read_only) {
 		if (_p->read_only) {
 			memdelete(_p->read_only);
 			memdelete(_p->read_only);
 		}
 		}
+		if (_p->typed_fallback) {
+			memdelete(_p->typed_fallback);
+		}
 		memdelete(_p);
 		memdelete(_p);
 	}
 	}
 	_p = nullptr;
 	_p = nullptr;
@@ -284,6 +311,9 @@ uint32_t Dictionary::recursive_hash(int recursion_count) const {
 
 
 Array Dictionary::keys() const {
 Array Dictionary::keys() const {
 	Array varr;
 	Array varr;
+	if (is_typed_key()) {
+		varr.set_typed(get_typed_key_builtin(), get_typed_key_class_name(), get_typed_key_script());
+	}
 	if (_p->variant_map.is_empty()) {
 	if (_p->variant_map.is_empty()) {
 		return varr;
 		return varr;
 	}
 	}
@@ -301,6 +331,9 @@ Array Dictionary::keys() const {
 
 
 Array Dictionary::values() const {
 Array Dictionary::values() const {
 	Array varr;
 	Array varr;
+	if (is_typed_value()) {
+		varr.set_typed(get_typed_value_builtin(), get_typed_value_class_name(), get_typed_value_script());
+	}
 	if (_p->variant_map.is_empty()) {
 	if (_p->variant_map.is_empty()) {
 		return varr;
 		return varr;
 	}
 	}
@@ -316,6 +349,146 @@ Array Dictionary::values() const {
 	return varr;
 	return varr;
 }
 }
 
 
+void Dictionary::assign(const Dictionary &p_dictionary) {
+	const ContainerTypeValidate &typed_key = _p->typed_key;
+	const ContainerTypeValidate &typed_key_source = p_dictionary._p->typed_key;
+
+	const ContainerTypeValidate &typed_value = _p->typed_value;
+	const ContainerTypeValidate &typed_value_source = p_dictionary._p->typed_value;
+
+	if ((typed_key == typed_key_source || typed_key.type == Variant::NIL || (typed_key_source.type == Variant::OBJECT && typed_key.can_reference(typed_key_source))) &&
+			(typed_value == typed_value_source || typed_value.type == Variant::NIL || (typed_value_source.type == Variant::OBJECT && typed_value.can_reference(typed_value_source)))) {
+		// From same to same or,
+		// from anything to variants or,
+		// from subclasses to base classes.
+		_p->variant_map = p_dictionary._p->variant_map;
+		return;
+	}
+
+	int size = p_dictionary._p->variant_map.size();
+	HashMap<Variant, Variant, VariantHasher, StringLikeVariantComparator> variant_map = HashMap<Variant, Variant, VariantHasher, StringLikeVariantComparator>(size);
+
+	Vector<Variant> key_array;
+	key_array.resize(size);
+	Variant *key_data = key_array.ptrw();
+
+	Vector<Variant> value_array;
+	value_array.resize(size);
+	Variant *value_data = value_array.ptrw();
+
+	if (typed_key == typed_key_source || typed_key.type == Variant::NIL || (typed_key_source.type == Variant::OBJECT && typed_key.can_reference(typed_key_source))) {
+		// From same to same or,
+		// from anything to variants or,
+		// from subclasses to base classes.
+		int i = 0;
+		for (const KeyValue<Variant, Variant> &E : p_dictionary._p->variant_map) {
+			const Variant *key = &E.key;
+			key_data[i++] = *key;
+		}
+	} else if ((typed_key_source.type == Variant::NIL && typed_key.type == Variant::OBJECT) || (typed_key_source.type == Variant::OBJECT && typed_key_source.can_reference(typed_key))) {
+		// From variants to objects or,
+		// from base classes to subclasses.
+		int i = 0;
+		for (const KeyValue<Variant, Variant> &E : p_dictionary._p->variant_map) {
+			const Variant *key = &E.key;
+			if (key->get_type() != Variant::NIL && (key->get_type() != Variant::OBJECT || !typed_key.validate_object(*key, "assign"))) {
+				ERR_FAIL_MSG(vformat(R"(Unable to convert key from "%s" to "%s".)", Variant::get_type_name(key->get_type()), Variant::get_type_name(typed_key.type)));
+			}
+			key_data[i++] = *key;
+		}
+	} else if (typed_key.type == Variant::OBJECT || typed_key_source.type == Variant::OBJECT) {
+		ERR_FAIL_MSG(vformat(R"(Cannot assign contents of "Dictionary[%s, %s]" to "Dictionary[%s, %s]".)", Variant::get_type_name(typed_key_source.type), Variant::get_type_name(typed_value_source.type),
+				Variant::get_type_name(typed_key.type), Variant::get_type_name(typed_value.type)));
+	} else if (typed_key_source.type == Variant::NIL && typed_key.type != Variant::OBJECT) {
+		// From variants to primitives.
+		int i = 0;
+		for (const KeyValue<Variant, Variant> &E : p_dictionary._p->variant_map) {
+			const Variant *key = &E.key;
+			if (key->get_type() == typed_key.type) {
+				key_data[i++] = *key;
+				continue;
+			}
+			if (!Variant::can_convert_strict(key->get_type(), typed_key.type)) {
+				ERR_FAIL_MSG(vformat(R"(Unable to convert key from "%s" to "%s".)", Variant::get_type_name(key->get_type()), Variant::get_type_name(typed_key.type)));
+			}
+			Callable::CallError ce;
+			Variant::construct(typed_key.type, key_data[i++], &key, 1, ce);
+			ERR_FAIL_COND_MSG(ce.error, vformat(R"(Unable to convert key from "%s" to "%s".)", Variant::get_type_name(key->get_type()), Variant::get_type_name(typed_key.type)));
+		}
+	} else if (Variant::can_convert_strict(typed_key_source.type, typed_key.type)) {
+		// From primitives to different convertible primitives.
+		int i = 0;
+		for (const KeyValue<Variant, Variant> &E : p_dictionary._p->variant_map) {
+			const Variant *key = &E.key;
+			Callable::CallError ce;
+			Variant::construct(typed_key.type, key_data[i++], &key, 1, ce);
+			ERR_FAIL_COND_MSG(ce.error, vformat(R"(Unable to convert key from "%s" to "%s".)", Variant::get_type_name(key->get_type()), Variant::get_type_name(typed_key.type)));
+		}
+	} else {
+		ERR_FAIL_MSG(vformat(R"(Cannot assign contents of "Dictionary[%s, %s]" to "Dictionary[%s, %s].)", Variant::get_type_name(typed_key_source.type), Variant::get_type_name(typed_value_source.type),
+				Variant::get_type_name(typed_key.type), Variant::get_type_name(typed_value.type)));
+	}
+
+	if (typed_value == typed_value_source || typed_value.type == Variant::NIL || (typed_value_source.type == Variant::OBJECT && typed_value.can_reference(typed_value_source))) {
+		// From same to same or,
+		// from anything to variants or,
+		// from subclasses to base classes.
+		int i = 0;
+		for (const KeyValue<Variant, Variant> &E : p_dictionary._p->variant_map) {
+			const Variant *value = &E.value;
+			value_data[i++] = *value;
+		}
+	} else if (((typed_value_source.type == Variant::NIL && typed_value.type == Variant::OBJECT) || (typed_value_source.type == Variant::OBJECT && typed_value_source.can_reference(typed_value)))) {
+		// From variants to objects or,
+		// from base classes to subclasses.
+		int i = 0;
+		for (const KeyValue<Variant, Variant> &E : p_dictionary._p->variant_map) {
+			const Variant *value = &E.value;
+			if (value->get_type() != Variant::NIL && (value->get_type() != Variant::OBJECT || !typed_value.validate_object(*value, "assign"))) {
+				ERR_FAIL_MSG(vformat(R"(Unable to convert value at key "%s" from "%s" to "%s".)", key_data[i], Variant::get_type_name(value->get_type()), Variant::get_type_name(typed_value.type)));
+			}
+			value_data[i++] = *value;
+		}
+	} else if (typed_value.type == Variant::OBJECT || typed_value_source.type == Variant::OBJECT) {
+		ERR_FAIL_MSG(vformat(R"(Cannot assign contents of "Dictionary[%s, %s]" to "Dictionary[%s, %s]".)", Variant::get_type_name(typed_key_source.type), Variant::get_type_name(typed_value_source.type),
+				Variant::get_type_name(typed_key.type), Variant::get_type_name(typed_value.type)));
+	} else if (typed_value_source.type == Variant::NIL && typed_value.type != Variant::OBJECT) {
+		// From variants to primitives.
+		int i = 0;
+		for (const KeyValue<Variant, Variant> &E : p_dictionary._p->variant_map) {
+			const Variant *value = &E.value;
+			if (value->get_type() == typed_value.type) {
+				value_data[i++] = *value;
+				continue;
+			}
+			if (!Variant::can_convert_strict(value->get_type(), typed_value.type)) {
+				ERR_FAIL_MSG(vformat(R"(Unable to convert value at key "%s" from "%s" to "%s".)", key_data[i], Variant::get_type_name(value->get_type()), Variant::get_type_name(typed_value.type)));
+			}
+			Callable::CallError ce;
+			Variant::construct(typed_value.type, value_data[i++], &value, 1, ce);
+			ERR_FAIL_COND_MSG(ce.error, vformat(R"(Unable to convert value at key "%s" from "%s" to "%s".)", key_data[i - 1], Variant::get_type_name(value->get_type()), Variant::get_type_name(typed_value.type)));
+		}
+	} else if (Variant::can_convert_strict(typed_value_source.type, typed_value.type)) {
+		// From primitives to different convertible primitives.
+		int i = 0;
+		for (const KeyValue<Variant, Variant> &E : p_dictionary._p->variant_map) {
+			const Variant *value = &E.value;
+			Callable::CallError ce;
+			Variant::construct(typed_value.type, value_data[i++], &value, 1, ce);
+			ERR_FAIL_COND_MSG(ce.error, vformat(R"(Unable to convert value at key "%s" from "%s" to "%s".)", key_data[i - 1], Variant::get_type_name(value->get_type()), Variant::get_type_name(typed_value.type)));
+		}
+	} else {
+		ERR_FAIL_MSG(vformat(R"(Cannot assign contents of "Dictionary[%s, %s]" to "Dictionary[%s, %s].)", Variant::get_type_name(typed_key_source.type), Variant::get_type_name(typed_value_source.type),
+				Variant::get_type_name(typed_key.type), Variant::get_type_name(typed_value.type)));
+	}
+
+	for (int i = 0; i < size; i++) {
+		variant_map.insert(key_data[i], value_data[i]);
+	}
+
+	_p->variant_map = variant_map;
+}
+
 const Variant *Dictionary::next(const Variant *p_key) const {
 const Variant *Dictionary::next(const Variant *p_key) const {
 	if (p_key == nullptr) {
 	if (p_key == nullptr) {
 		// caller wants to get the first element
 		// caller wants to get the first element
@@ -324,7 +497,9 @@ const Variant *Dictionary::next(const Variant *p_key) const {
 		}
 		}
 		return nullptr;
 		return nullptr;
 	}
 	}
-	HashMap<Variant, Variant, VariantHasher, StringLikeVariantComparator>::Iterator E = _p->variant_map.find(*p_key);
+	Variant key = *p_key;
+	ERR_FAIL_COND_V(!_p->typed_key.validate(key, "next"), nullptr);
+	HashMap<Variant, Variant, VariantHasher, StringLikeVariantComparator>::Iterator E = _p->variant_map.find(key);
 
 
 	if (!E) {
 	if (!E) {
 		return nullptr;
 		return nullptr;
@@ -354,6 +529,8 @@ bool Dictionary::is_read_only() const {
 
 
 Dictionary Dictionary::recursive_duplicate(bool p_deep, int recursion_count) const {
 Dictionary Dictionary::recursive_duplicate(bool p_deep, int recursion_count) const {
 	Dictionary n;
 	Dictionary n;
+	n._p->typed_key = _p->typed_key;
+	n._p->typed_value = _p->typed_value;
 
 
 	if (recursion_count > MAX_RECURSION) {
 	if (recursion_count > MAX_RECURSION) {
 		ERR_PRINT("Max recursion reached");
 		ERR_PRINT("Max recursion reached");
@@ -374,6 +551,76 @@ Dictionary Dictionary::recursive_duplicate(bool p_deep, int recursion_count) con
 	return n;
 	return n;
 }
 }
 
 
+void Dictionary::set_typed(uint32_t p_key_type, const StringName &p_key_class_name, const Variant &p_key_script, uint32_t p_value_type, const StringName &p_value_class_name, const Variant &p_value_script) {
+	ERR_FAIL_COND_MSG(_p->read_only, "Dictionary is in read-only state.");
+	ERR_FAIL_COND_MSG(_p->variant_map.size() > 0, "Type can only be set when dictionary is empty.");
+	ERR_FAIL_COND_MSG(_p->refcount.get() > 1, "Type can only be set when dictionary has no more than one user.");
+	ERR_FAIL_COND_MSG(_p->typed_key.type != Variant::NIL || _p->typed_value.type != Variant::NIL, "Type can only be set once.");
+	ERR_FAIL_COND_MSG((p_key_class_name != StringName() && p_key_type != Variant::OBJECT) || (p_value_class_name != StringName() && p_value_type != Variant::OBJECT), "Class names can only be set for type OBJECT.");
+	Ref<Script> key_script = p_key_script;
+	ERR_FAIL_COND_MSG(key_script.is_valid() && p_key_class_name == StringName(), "Script class can only be set together with base class name.");
+	Ref<Script> value_script = p_value_script;
+	ERR_FAIL_COND_MSG(value_script.is_valid() && p_value_class_name == StringName(), "Script class can only be set together with base class name.");
+
+	_p->typed_key.type = Variant::Type(p_key_type);
+	_p->typed_key.class_name = p_key_class_name;
+	_p->typed_key.script = key_script;
+	_p->typed_key.where = "TypedDictionary.Key";
+
+	_p->typed_value.type = Variant::Type(p_value_type);
+	_p->typed_value.class_name = p_value_class_name;
+	_p->typed_value.script = value_script;
+	_p->typed_value.where = "TypedDictionary.Value";
+}
+
+bool Dictionary::is_typed() const {
+	return is_typed_key() || is_typed_value();
+}
+
+bool Dictionary::is_typed_key() const {
+	return _p->typed_key.type != Variant::NIL;
+}
+
+bool Dictionary::is_typed_value() const {
+	return _p->typed_value.type != Variant::NIL;
+}
+
+bool Dictionary::is_same_typed(const Dictionary &p_other) const {
+	return is_same_typed_key(p_other) && is_same_typed_value(p_other);
+}
+
+bool Dictionary::is_same_typed_key(const Dictionary &p_other) const {
+	return _p->typed_key == p_other._p->typed_key;
+}
+
+bool Dictionary::is_same_typed_value(const Dictionary &p_other) const {
+	return _p->typed_value == p_other._p->typed_value;
+}
+
+uint32_t Dictionary::get_typed_key_builtin() const {
+	return _p->typed_key.type;
+}
+
+uint32_t Dictionary::get_typed_value_builtin() const {
+	return _p->typed_value.type;
+}
+
+StringName Dictionary::get_typed_key_class_name() const {
+	return _p->typed_key.class_name;
+}
+
+StringName Dictionary::get_typed_value_class_name() const {
+	return _p->typed_value.class_name;
+}
+
+Variant Dictionary::get_typed_key_script() const {
+	return _p->typed_key.script;
+}
+
+Variant Dictionary::get_typed_value_script() const {
+	return _p->typed_value.script;
+}
+
 void Dictionary::operator=(const Dictionary &p_dictionary) {
 void Dictionary::operator=(const Dictionary &p_dictionary) {
 	if (this == &p_dictionary) {
 	if (this == &p_dictionary) {
 		return;
 		return;
@@ -385,6 +632,13 @@ const void *Dictionary::id() const {
 	return _p;
 	return _p;
 }
 }
 
 
+Dictionary::Dictionary(const Dictionary &p_base, uint32_t p_key_type, const StringName &p_key_class_name, const Variant &p_key_script, uint32_t p_value_type, const StringName &p_value_class_name, const Variant &p_value_script) {
+	_p = memnew(DictionaryPrivate);
+	_p->refcount.init();
+	set_typed(p_key_type, p_key_class_name, p_key_script, p_value_type, p_value_class_name, p_value_script);
+	assign(p_base);
+}
+
 Dictionary::Dictionary(const Dictionary &p_from) {
 Dictionary::Dictionary(const Dictionary &p_from) {
 	_p = nullptr;
 	_p = nullptr;
 	_ref(p_from);
 	_ref(p_from);

+ 16 - 0
core/variant/dictionary.h

@@ -80,6 +80,7 @@ public:
 	uint32_t recursive_hash(int recursion_count) const;
 	uint32_t recursive_hash(int recursion_count) const;
 	void operator=(const Dictionary &p_dictionary);
 	void operator=(const Dictionary &p_dictionary);
 
 
+	void assign(const Dictionary &p_dictionary);
 	const Variant *next(const Variant *p_key = nullptr) const;
 	const Variant *next(const Variant *p_key = nullptr) const;
 
 
 	Array keys() const;
 	Array keys() const;
@@ -88,11 +89,26 @@ public:
 	Dictionary duplicate(bool p_deep = false) const;
 	Dictionary duplicate(bool p_deep = false) const;
 	Dictionary recursive_duplicate(bool p_deep, int recursion_count) const;
 	Dictionary recursive_duplicate(bool p_deep, int recursion_count) const;
 
 
+	void set_typed(uint32_t p_key_type, const StringName &p_key_class_name, const Variant &p_key_script, uint32_t p_value_type, const StringName &p_value_class_name, const Variant &p_value_script);
+	bool is_typed() const;
+	bool is_typed_key() const;
+	bool is_typed_value() const;
+	bool is_same_typed(const Dictionary &p_other) const;
+	bool is_same_typed_key(const Dictionary &p_other) const;
+	bool is_same_typed_value(const Dictionary &p_other) const;
+	uint32_t get_typed_key_builtin() const;
+	uint32_t get_typed_value_builtin() const;
+	StringName get_typed_key_class_name() const;
+	StringName get_typed_value_class_name() const;
+	Variant get_typed_key_script() const;
+	Variant get_typed_value_script() const;
+
 	void make_read_only();
 	void make_read_only();
 	bool is_read_only() const;
 	bool is_read_only() const;
 
 
 	const void *id() const;
 	const void *id() const;
 
 
+	Dictionary(const Dictionary &p_base, uint32_t p_key_type, const StringName &p_key_class_name, const Variant &p_key_script, uint32_t p_value_type, const StringName &p_value_class_name, const Variant &p_value_script);
 	Dictionary(const Dictionary &p_from);
 	Dictionary(const Dictionary &p_from);
 	Dictionary();
 	Dictionary();
 	~Dictionary();
 	~Dictionary();

+ 342 - 0
core/variant/typed_dictionary.h

@@ -0,0 +1,342 @@
+/**************************************************************************/
+/*  typed_dictionary.h                                                    */
+/**************************************************************************/
+/*                         This file is part of:                          */
+/*                             GODOT ENGINE                               */
+/*                        https://godotengine.org                         */
+/**************************************************************************/
+/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur.                  */
+/*                                                                        */
+/* Permission is hereby granted, free of charge, to any person obtaining  */
+/* a copy of this software and associated documentation files (the        */
+/* "Software"), to deal in the Software without restriction, including    */
+/* without limitation the rights to use, copy, modify, merge, publish,    */
+/* distribute, sublicense, and/or sell copies of the Software, and to     */
+/* permit persons to whom the Software is furnished to do so, subject to  */
+/* the following conditions:                                              */
+/*                                                                        */
+/* The above copyright notice and this permission notice shall be         */
+/* included in all copies or substantial portions of the Software.        */
+/*                                                                        */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,        */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF     */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY   */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,   */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE      */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.                 */
+/**************************************************************************/
+
+#ifndef TYPED_DICTIONARY_H
+#define TYPED_DICTIONARY_H
+
+#include "core/object/object.h"
+#include "core/variant/binder_common.h"
+#include "core/variant/dictionary.h"
+#include "core/variant/method_ptrcall.h"
+#include "core/variant/type_info.h"
+#include "core/variant/variant.h"
+
+template <typename K, typename V>
+class TypedDictionary : public Dictionary {
+public:
+	_FORCE_INLINE_ void operator=(const Dictionary &p_dictionary) {
+		ERR_FAIL_COND_MSG(!is_same_typed(p_dictionary), "Cannot assign a dictionary with a different element type.");
+		Dictionary::operator=(p_dictionary);
+	}
+	_FORCE_INLINE_ TypedDictionary(const Variant &p_variant) :
+			TypedDictionary(Dictionary(p_variant)) {
+	}
+	_FORCE_INLINE_ TypedDictionary(const Dictionary &p_dictionary) {
+		set_typed(Variant::OBJECT, K::get_class_static(), Variant(), Variant::OBJECT, V::get_class_static(), Variant());
+		if (is_same_typed(p_dictionary)) {
+			Dictionary::operator=(p_dictionary);
+		} else {
+			assign(p_dictionary);
+		}
+	}
+	_FORCE_INLINE_ TypedDictionary() {
+		set_typed(Variant::OBJECT, K::get_class_static(), Variant(), Variant::OBJECT, V::get_class_static(), Variant());
+	}
+};
+
+template <typename K, typename V>
+struct VariantInternalAccessor<TypedDictionary<K, V>> {
+	static _FORCE_INLINE_ TypedDictionary<K, V> get(const Variant *v) { return *VariantInternal::get_dictionary(v); }
+	static _FORCE_INLINE_ void set(Variant *v, const TypedDictionary<K, V> &p_dictionary) { *VariantInternal::get_dictionary(v) = p_dictionary; }
+};
+
+template <typename K, typename V>
+struct VariantInternalAccessor<const TypedDictionary<K, V> &> {
+	static _FORCE_INLINE_ TypedDictionary<K, V> get(const Variant *v) { return *VariantInternal::get_dictionary(v); }
+	static _FORCE_INLINE_ void set(Variant *v, const TypedDictionary<K, V> &p_dictionary) { *VariantInternal::get_dictionary(v) = p_dictionary; }
+};
+
+template <typename K, typename V>
+struct PtrToArg<TypedDictionary<K, V>> {
+	_FORCE_INLINE_ static TypedDictionary<K, V> convert(const void *p_ptr) {
+		return TypedDictionary<K, V>(*reinterpret_cast<const Dictionary *>(p_ptr));
+	}
+	typedef Dictionary EncodeT;
+	_FORCE_INLINE_ static void encode(TypedDictionary<K, V> p_val, void *p_ptr) {
+		*(Dictionary *)p_ptr = p_val;
+	}
+};
+
+template <typename K, typename V>
+struct PtrToArg<const TypedDictionary<K, V> &> {
+	typedef Dictionary EncodeT;
+	_FORCE_INLINE_ static TypedDictionary<K, V>
+	convert(const void *p_ptr) {
+		return TypedDictionary<K, V>(*reinterpret_cast<const Dictionary *>(p_ptr));
+	}
+};
+
+template <typename K, typename V>
+struct GetTypeInfo<TypedDictionary<K, V>> {
+	static const Variant::Type VARIANT_TYPE = Variant::DICTIONARY;
+	static const GodotTypeInfo::Metadata METADATA = GodotTypeInfo::METADATA_NONE;
+	static inline PropertyInfo get_class_info() {
+		return PropertyInfo(Variant::DICTIONARY, String(), PROPERTY_HINT_DICTIONARY_TYPE, vformat("%s;%s", K::get_class_static(), V::get_class_static()));
+	}
+};
+
+template <typename K, typename V>
+struct GetTypeInfo<const TypedDictionary<K, V> &> {
+	static const Variant::Type VARIANT_TYPE = Variant::DICTIONARY;
+	static const GodotTypeInfo::Metadata METADATA = GodotTypeInfo::METADATA_NONE;
+	static inline PropertyInfo get_class_info() {
+		return PropertyInfo(Variant::DICTIONARY, String(), PROPERTY_HINT_DICTIONARY_TYPE, vformat("%s;%s", K::get_class_static(), V::get_class_static()));
+	}
+};
+
+// Specialization for the rest of the Variant types.
+
+#define MAKE_TYPED_DICTIONARY_WITH_OBJECT(m_type, m_variant_type)                                                                                  \
+	template <typename T>                                                                                                                          \
+	class TypedDictionary<T, m_type> : public Dictionary {                                                                                         \
+	public:                                                                                                                                        \
+		_FORCE_INLINE_ void operator=(const Dictionary &p_dictionary) {                                                                            \
+			ERR_FAIL_COND_MSG(!is_same_typed(p_dictionary), "Cannot assign an dictionary with a different element type.");                         \
+			Dictionary::operator=(p_dictionary);                                                                                                   \
+		}                                                                                                                                          \
+		_FORCE_INLINE_ TypedDictionary(const Variant &p_variant) :                                                                                 \
+				TypedDictionary(Dictionary(p_variant)) {                                                                                           \
+		}                                                                                                                                          \
+		_FORCE_INLINE_ TypedDictionary(const Dictionary &p_dictionary) {                                                                           \
+			set_typed(Variant::OBJECT, T::get_class_static(), Variant(), m_variant_type, StringName(), Variant());                                 \
+			if (is_same_typed(p_dictionary)) {                                                                                                     \
+				Dictionary::operator=(p_dictionary);                                                                                               \
+			} else {                                                                                                                               \
+				assign(p_dictionary);                                                                                                              \
+			}                                                                                                                                      \
+		}                                                                                                                                          \
+		_FORCE_INLINE_ TypedDictionary() {                                                                                                         \
+			set_typed(Variant::OBJECT, T::get_class_static(), Variant(), m_variant_type, StringName(), Variant());                                 \
+		}                                                                                                                                          \
+	};                                                                                                                                             \
+	template <typename T>                                                                                                                          \
+	struct GetTypeInfo<TypedDictionary<T, m_type>> {                                                                                               \
+		static const Variant::Type VARIANT_TYPE = Variant::DICTIONARY;                                                                             \
+		static const GodotTypeInfo::Metadata METADATA = GodotTypeInfo::METADATA_NONE;                                                              \
+		static inline PropertyInfo get_class_info() {                                                                                              \
+			return PropertyInfo(Variant::DICTIONARY, String(), PROPERTY_HINT_DICTIONARY_TYPE,                                                      \
+					vformat("%s;%s", T::get_class_static(), m_variant_type == Variant::NIL ? "Variant" : Variant::get_type_name(m_variant_type))); \
+		}                                                                                                                                          \
+	};                                                                                                                                             \
+	template <typename T>                                                                                                                          \
+	struct GetTypeInfo<const TypedDictionary<T, m_type> &> {                                                                                       \
+		static const Variant::Type VARIANT_TYPE = Variant::DICTIONARY;                                                                             \
+		static const GodotTypeInfo::Metadata METADATA = GodotTypeInfo::METADATA_NONE;                                                              \
+		static inline PropertyInfo get_class_info() {                                                                                              \
+			return PropertyInfo(Variant::DICTIONARY, String(), PROPERTY_HINT_DICTIONARY_TYPE,                                                      \
+					vformat("%s;%s", T::get_class_static(), m_variant_type == Variant::NIL ? "Variant" : Variant::get_type_name(m_variant_type))); \
+		}                                                                                                                                          \
+	};                                                                                                                                             \
+	template <typename T>                                                                                                                          \
+	class TypedDictionary<m_type, T> : public Dictionary {                                                                                         \
+	public:                                                                                                                                        \
+		_FORCE_INLINE_ void operator=(const Dictionary &p_dictionary) {                                                                            \
+			ERR_FAIL_COND_MSG(!is_same_typed(p_dictionary), "Cannot assign an dictionary with a different element type.");                         \
+			Dictionary::operator=(p_dictionary);                                                                                                   \
+		}                                                                                                                                          \
+		_FORCE_INLINE_ TypedDictionary(const Variant &p_variant) :                                                                                 \
+				TypedDictionary(Dictionary(p_variant)) {                                                                                           \
+		}                                                                                                                                          \
+		_FORCE_INLINE_ TypedDictionary(const Dictionary &p_dictionary) {                                                                           \
+			set_typed(m_variant_type, StringName(), Variant(), Variant::OBJECT, T::get_class_static(), Variant());                                 \
+			if (is_same_typed(p_dictionary)) {                                                                                                     \
+				Dictionary::operator=(p_dictionary);                                                                                               \
+			} else {                                                                                                                               \
+				assign(p_dictionary);                                                                                                              \
+			}                                                                                                                                      \
+		}                                                                                                                                          \
+		_FORCE_INLINE_ TypedDictionary() {                                                                                                         \
+			set_typed(m_variant_type, StringName(), Variant(), Variant::OBJECT, T::get_class_static(), Variant());                                 \
+		}                                                                                                                                          \
+	};                                                                                                                                             \
+	template <typename T>                                                                                                                          \
+	struct GetTypeInfo<TypedDictionary<m_type, T>> {                                                                                               \
+		static const Variant::Type VARIANT_TYPE = Variant::DICTIONARY;                                                                             \
+		static const GodotTypeInfo::Metadata METADATA = GodotTypeInfo::METADATA_NONE;                                                              \
+		static inline PropertyInfo get_class_info() {                                                                                              \
+			return PropertyInfo(Variant::DICTIONARY, String(), PROPERTY_HINT_DICTIONARY_TYPE,                                                      \
+					vformat("%s;%s", m_variant_type == Variant::NIL ? "Variant" : Variant::get_type_name(m_variant_type), T::get_class_static())); \
+		}                                                                                                                                          \
+	};                                                                                                                                             \
+	template <typename T>                                                                                                                          \
+	struct GetTypeInfo<const TypedDictionary<m_type, T> &> {                                                                                       \
+		static const Variant::Type VARIANT_TYPE = Variant::DICTIONARY;                                                                             \
+		static const GodotTypeInfo::Metadata METADATA = GodotTypeInfo::METADATA_NONE;                                                              \
+		static inline PropertyInfo get_class_info() {                                                                                              \
+			return PropertyInfo(Variant::DICTIONARY, String(), PROPERTY_HINT_DICTIONARY_TYPE,                                                      \
+					vformat("%s;%s", m_variant_type == Variant::NIL ? "Variant" : Variant::get_type_name(m_variant_type), T::get_class_static())); \
+		}                                                                                                                                          \
+	};
+
+#define MAKE_TYPED_DICTIONARY_EXPANDED(m_type_key, m_variant_type_key, m_type_value, m_variant_type_value)                        \
+	template <>                                                                                                                   \
+	class TypedDictionary<m_type_key, m_type_value> : public Dictionary {                                                         \
+	public:                                                                                                                       \
+		_FORCE_INLINE_ void operator=(const Dictionary &p_dictionary) {                                                           \
+			ERR_FAIL_COND_MSG(!is_same_typed(p_dictionary), "Cannot assign an dictionary with a different element type.");        \
+			Dictionary::operator=(p_dictionary);                                                                                  \
+		}                                                                                                                         \
+		_FORCE_INLINE_ TypedDictionary(const Variant &p_variant) :                                                                \
+				TypedDictionary(Dictionary(p_variant)) {                                                                          \
+		}                                                                                                                         \
+		_FORCE_INLINE_ TypedDictionary(const Dictionary &p_dictionary) {                                                          \
+			set_typed(m_variant_type_key, StringName(), Variant(), m_variant_type_value, StringName(), Variant());                \
+			if (is_same_typed(p_dictionary)) {                                                                                    \
+				Dictionary::operator=(p_dictionary);                                                                              \
+			} else {                                                                                                              \
+				assign(p_dictionary);                                                                                             \
+			}                                                                                                                     \
+		}                                                                                                                         \
+		_FORCE_INLINE_ TypedDictionary() {                                                                                        \
+			set_typed(m_variant_type_key, StringName(), Variant(), m_variant_type_value, StringName(), Variant());                \
+		}                                                                                                                         \
+	};                                                                                                                            \
+	template <>                                                                                                                   \
+	struct GetTypeInfo<TypedDictionary<m_type_key, m_type_value>> {                                                               \
+		static const Variant::Type VARIANT_TYPE = Variant::DICTIONARY;                                                            \
+		static const GodotTypeInfo::Metadata METADATA = GodotTypeInfo::METADATA_NONE;                                             \
+		static inline PropertyInfo get_class_info() {                                                                             \
+			return PropertyInfo(Variant::DICTIONARY, String(), PROPERTY_HINT_DICTIONARY_TYPE,                                     \
+					vformat("%s;%s", m_variant_type_key == Variant::NIL ? "Variant" : Variant::get_type_name(m_variant_type_key), \
+							m_variant_type_value == Variant::NIL ? "Variant" : Variant::get_type_name(m_variant_type_value)));    \
+		}                                                                                                                         \
+	};                                                                                                                            \
+	template <>                                                                                                                   \
+	struct GetTypeInfo<const TypedDictionary<m_type_key, m_type_value> &> {                                                       \
+		static const Variant::Type VARIANT_TYPE = Variant::DICTIONARY;                                                            \
+		static const GodotTypeInfo::Metadata METADATA = GodotTypeInfo::METADATA_NONE;                                             \
+		static inline PropertyInfo get_class_info() {                                                                             \
+			return PropertyInfo(Variant::DICTIONARY, String(), PROPERTY_HINT_DICTIONARY_TYPE,                                     \
+					vformat("%s;%s", m_variant_type_key == Variant::NIL ? "Variant" : Variant::get_type_name(m_variant_type_key), \
+							m_variant_type_value == Variant::NIL ? "Variant" : Variant::get_type_name(m_variant_type_value)));    \
+		}                                                                                                                         \
+	};
+
+#define MAKE_TYPED_DICTIONARY_NIL(m_type, m_variant_type)                                                     \
+	MAKE_TYPED_DICTIONARY_WITH_OBJECT(m_type, m_variant_type)                                                 \
+	MAKE_TYPED_DICTIONARY_EXPANDED(m_type, m_variant_type, bool, Variant::BOOL)                               \
+	MAKE_TYPED_DICTIONARY_EXPANDED(m_type, m_variant_type, uint8_t, Variant::INT)                             \
+	MAKE_TYPED_DICTIONARY_EXPANDED(m_type, m_variant_type, int8_t, Variant::INT)                              \
+	MAKE_TYPED_DICTIONARY_EXPANDED(m_type, m_variant_type, uint16_t, Variant::INT)                            \
+	MAKE_TYPED_DICTIONARY_EXPANDED(m_type, m_variant_type, int16_t, Variant::INT)                             \
+	MAKE_TYPED_DICTIONARY_EXPANDED(m_type, m_variant_type, uint32_t, Variant::INT)                            \
+	MAKE_TYPED_DICTIONARY_EXPANDED(m_type, m_variant_type, int32_t, Variant::INT)                             \
+	MAKE_TYPED_DICTIONARY_EXPANDED(m_type, m_variant_type, uint64_t, Variant::INT)                            \
+	MAKE_TYPED_DICTIONARY_EXPANDED(m_type, m_variant_type, int64_t, Variant::INT)                             \
+	MAKE_TYPED_DICTIONARY_EXPANDED(m_type, m_variant_type, float, Variant::FLOAT)                             \
+	MAKE_TYPED_DICTIONARY_EXPANDED(m_type, m_variant_type, double, Variant::FLOAT)                            \
+	MAKE_TYPED_DICTIONARY_EXPANDED(m_type, m_variant_type, String, Variant::STRING)                           \
+	MAKE_TYPED_DICTIONARY_EXPANDED(m_type, m_variant_type, Vector2, Variant::VECTOR2)                         \
+	MAKE_TYPED_DICTIONARY_EXPANDED(m_type, m_variant_type, Vector2i, Variant::VECTOR2I)                       \
+	MAKE_TYPED_DICTIONARY_EXPANDED(m_type, m_variant_type, Rect2, Variant::RECT2)                             \
+	MAKE_TYPED_DICTIONARY_EXPANDED(m_type, m_variant_type, Rect2i, Variant::RECT2I)                           \
+	MAKE_TYPED_DICTIONARY_EXPANDED(m_type, m_variant_type, Vector3, Variant::VECTOR3)                         \
+	MAKE_TYPED_DICTIONARY_EXPANDED(m_type, m_variant_type, Vector3i, Variant::VECTOR3I)                       \
+	MAKE_TYPED_DICTIONARY_EXPANDED(m_type, m_variant_type, Transform2D, Variant::TRANSFORM2D)                 \
+	MAKE_TYPED_DICTIONARY_EXPANDED(m_type, m_variant_type, Plane, Variant::PLANE)                             \
+	MAKE_TYPED_DICTIONARY_EXPANDED(m_type, m_variant_type, Quaternion, Variant::QUATERNION)                   \
+	MAKE_TYPED_DICTIONARY_EXPANDED(m_type, m_variant_type, AABB, Variant::AABB)                               \
+	MAKE_TYPED_DICTIONARY_EXPANDED(m_type, m_variant_type, Basis, Variant::BASIS)                             \
+	MAKE_TYPED_DICTIONARY_EXPANDED(m_type, m_variant_type, Transform3D, Variant::TRANSFORM3D)                 \
+	MAKE_TYPED_DICTIONARY_EXPANDED(m_type, m_variant_type, Color, Variant::COLOR)                             \
+	MAKE_TYPED_DICTIONARY_EXPANDED(m_type, m_variant_type, StringName, Variant::STRING_NAME)                  \
+	MAKE_TYPED_DICTIONARY_EXPANDED(m_type, m_variant_type, NodePath, Variant::NODE_PATH)                      \
+	MAKE_TYPED_DICTIONARY_EXPANDED(m_type, m_variant_type, RID, Variant::RID)                                 \
+	MAKE_TYPED_DICTIONARY_EXPANDED(m_type, m_variant_type, Callable, Variant::CALLABLE)                       \
+	MAKE_TYPED_DICTIONARY_EXPANDED(m_type, m_variant_type, Signal, Variant::SIGNAL)                           \
+	MAKE_TYPED_DICTIONARY_EXPANDED(m_type, m_variant_type, Dictionary, Variant::DICTIONARY)                   \
+	MAKE_TYPED_DICTIONARY_EXPANDED(m_type, m_variant_type, Array, Variant::ARRAY)                             \
+	MAKE_TYPED_DICTIONARY_EXPANDED(m_type, m_variant_type, PackedByteArray, Variant::PACKED_BYTE_ARRAY)       \
+	MAKE_TYPED_DICTIONARY_EXPANDED(m_type, m_variant_type, PackedInt32Array, Variant::PACKED_INT32_ARRAY)     \
+	MAKE_TYPED_DICTIONARY_EXPANDED(m_type, m_variant_type, PackedInt64Array, Variant::PACKED_INT64_ARRAY)     \
+	MAKE_TYPED_DICTIONARY_EXPANDED(m_type, m_variant_type, PackedFloat32Array, Variant::PACKED_FLOAT32_ARRAY) \
+	MAKE_TYPED_DICTIONARY_EXPANDED(m_type, m_variant_type, PackedFloat64Array, Variant::PACKED_FLOAT64_ARRAY) \
+	MAKE_TYPED_DICTIONARY_EXPANDED(m_type, m_variant_type, PackedStringArray, Variant::PACKED_STRING_ARRAY)   \
+	MAKE_TYPED_DICTIONARY_EXPANDED(m_type, m_variant_type, PackedVector2Array, Variant::PACKED_VECTOR2_ARRAY) \
+	MAKE_TYPED_DICTIONARY_EXPANDED(m_type, m_variant_type, PackedVector3Array, Variant::PACKED_VECTOR3_ARRAY) \
+	MAKE_TYPED_DICTIONARY_EXPANDED(m_type, m_variant_type, PackedColorArray, Variant::PACKED_COLOR_ARRAY)     \
+	MAKE_TYPED_DICTIONARY_EXPANDED(m_type, m_variant_type, PackedVector4Array, Variant::PACKED_VECTOR4_ARRAY) \
+	MAKE_TYPED_DICTIONARY_EXPANDED(m_type, m_variant_type, IPAddress, Variant::STRING)
+
+#define MAKE_TYPED_DICTIONARY(m_type, m_variant_type)                             \
+	MAKE_TYPED_DICTIONARY_EXPANDED(m_type, m_variant_type, Variant, Variant::NIL) \
+	MAKE_TYPED_DICTIONARY_NIL(m_type, m_variant_type)
+
+MAKE_TYPED_DICTIONARY_NIL(Variant, Variant::NIL)
+MAKE_TYPED_DICTIONARY(bool, Variant::BOOL)
+MAKE_TYPED_DICTIONARY(uint8_t, Variant::INT)
+MAKE_TYPED_DICTIONARY(int8_t, Variant::INT)
+MAKE_TYPED_DICTIONARY(uint16_t, Variant::INT)
+MAKE_TYPED_DICTIONARY(int16_t, Variant::INT)
+MAKE_TYPED_DICTIONARY(uint32_t, Variant::INT)
+MAKE_TYPED_DICTIONARY(int32_t, Variant::INT)
+MAKE_TYPED_DICTIONARY(uint64_t, Variant::INT)
+MAKE_TYPED_DICTIONARY(int64_t, Variant::INT)
+MAKE_TYPED_DICTIONARY(float, Variant::FLOAT)
+MAKE_TYPED_DICTIONARY(double, Variant::FLOAT)
+MAKE_TYPED_DICTIONARY(String, Variant::STRING)
+MAKE_TYPED_DICTIONARY(Vector2, Variant::VECTOR2)
+MAKE_TYPED_DICTIONARY(Vector2i, Variant::VECTOR2I)
+MAKE_TYPED_DICTIONARY(Rect2, Variant::RECT2)
+MAKE_TYPED_DICTIONARY(Rect2i, Variant::RECT2I)
+MAKE_TYPED_DICTIONARY(Vector3, Variant::VECTOR3)
+MAKE_TYPED_DICTIONARY(Vector3i, Variant::VECTOR3I)
+MAKE_TYPED_DICTIONARY(Transform2D, Variant::TRANSFORM2D)
+MAKE_TYPED_DICTIONARY(Plane, Variant::PLANE)
+MAKE_TYPED_DICTIONARY(Quaternion, Variant::QUATERNION)
+MAKE_TYPED_DICTIONARY(AABB, Variant::AABB)
+MAKE_TYPED_DICTIONARY(Basis, Variant::BASIS)
+MAKE_TYPED_DICTIONARY(Transform3D, Variant::TRANSFORM3D)
+MAKE_TYPED_DICTIONARY(Color, Variant::COLOR)
+MAKE_TYPED_DICTIONARY(StringName, Variant::STRING_NAME)
+MAKE_TYPED_DICTIONARY(NodePath, Variant::NODE_PATH)
+MAKE_TYPED_DICTIONARY(RID, Variant::RID)
+MAKE_TYPED_DICTIONARY(Callable, Variant::CALLABLE)
+MAKE_TYPED_DICTIONARY(Signal, Variant::SIGNAL)
+MAKE_TYPED_DICTIONARY(Dictionary, Variant::DICTIONARY)
+MAKE_TYPED_DICTIONARY(Array, Variant::ARRAY)
+MAKE_TYPED_DICTIONARY(PackedByteArray, Variant::PACKED_BYTE_ARRAY)
+MAKE_TYPED_DICTIONARY(PackedInt32Array, Variant::PACKED_INT32_ARRAY)
+MAKE_TYPED_DICTIONARY(PackedInt64Array, Variant::PACKED_INT64_ARRAY)
+MAKE_TYPED_DICTIONARY(PackedFloat32Array, Variant::PACKED_FLOAT32_ARRAY)
+MAKE_TYPED_DICTIONARY(PackedFloat64Array, Variant::PACKED_FLOAT64_ARRAY)
+MAKE_TYPED_DICTIONARY(PackedStringArray, Variant::PACKED_STRING_ARRAY)
+MAKE_TYPED_DICTIONARY(PackedVector2Array, Variant::PACKED_VECTOR2_ARRAY)
+MAKE_TYPED_DICTIONARY(PackedVector3Array, Variant::PACKED_VECTOR3_ARRAY)
+MAKE_TYPED_DICTIONARY(PackedColorArray, Variant::PACKED_COLOR_ARRAY)
+MAKE_TYPED_DICTIONARY(PackedVector4Array, Variant::PACKED_VECTOR4_ARRAY)
+MAKE_TYPED_DICTIONARY(IPAddress, Variant::STRING)
+
+#undef MAKE_TYPED_DICTIONARY
+#undef MAKE_TYPED_DICTIONARY_NIL
+#undef MAKE_TYPED_DICTIONARY_EXPANDED
+#undef MAKE_TYPED_DICTIONARY_WITH_OBJECT
+
+#endif // TYPED_DICTIONARY_H

+ 13 - 0
core/variant/variant_call.cpp

@@ -2254,6 +2254,7 @@ static void _register_variant_builtin_methods_misc() {
 	bind_method(Dictionary, size, sarray(), varray());
 	bind_method(Dictionary, size, sarray(), varray());
 	bind_method(Dictionary, is_empty, sarray(), varray());
 	bind_method(Dictionary, is_empty, sarray(), varray());
 	bind_method(Dictionary, clear, sarray(), varray());
 	bind_method(Dictionary, clear, sarray(), varray());
+	bind_method(Dictionary, assign, sarray("dictionary"), varray());
 	bind_method(Dictionary, merge, sarray("dictionary", "overwrite"), varray(false));
 	bind_method(Dictionary, merge, sarray("dictionary", "overwrite"), varray(false));
 	bind_method(Dictionary, merged, sarray("dictionary", "overwrite"), varray(false));
 	bind_method(Dictionary, merged, sarray("dictionary", "overwrite"), varray(false));
 	bind_method(Dictionary, has, sarray("key"), varray());
 	bind_method(Dictionary, has, sarray("key"), varray());
@@ -2266,6 +2267,18 @@ static void _register_variant_builtin_methods_misc() {
 	bind_method(Dictionary, duplicate, sarray("deep"), varray(false));
 	bind_method(Dictionary, duplicate, sarray("deep"), varray(false));
 	bind_method(Dictionary, get, sarray("key", "default"), varray(Variant()));
 	bind_method(Dictionary, get, sarray("key", "default"), varray(Variant()));
 	bind_method(Dictionary, get_or_add, sarray("key", "default"), varray(Variant()));
 	bind_method(Dictionary, get_or_add, sarray("key", "default"), varray(Variant()));
+	bind_method(Dictionary, is_typed, sarray(), varray());
+	bind_method(Dictionary, is_typed_key, sarray(), varray());
+	bind_method(Dictionary, is_typed_value, sarray(), varray());
+	bind_method(Dictionary, is_same_typed, sarray("dictionary"), varray());
+	bind_method(Dictionary, is_same_typed_key, sarray("dictionary"), varray());
+	bind_method(Dictionary, is_same_typed_value, sarray("dictionary"), varray());
+	bind_method(Dictionary, get_typed_key_builtin, sarray(), varray());
+	bind_method(Dictionary, get_typed_value_builtin, sarray(), varray());
+	bind_method(Dictionary, get_typed_key_class_name, sarray(), varray());
+	bind_method(Dictionary, get_typed_value_class_name, sarray(), varray());
+	bind_method(Dictionary, get_typed_key_script, sarray(), varray());
+	bind_method(Dictionary, get_typed_value_script, sarray(), varray());
 	bind_method(Dictionary, make_read_only, sarray(), varray());
 	bind_method(Dictionary, make_read_only, sarray(), varray());
 	bind_method(Dictionary, is_read_only, sarray(), varray());
 	bind_method(Dictionary, is_read_only, sarray(), varray());
 	bind_method(Dictionary, recursive_equal, sarray("dictionary", "recursion_count"), varray());
 	bind_method(Dictionary, recursive_equal, sarray("dictionary", "recursion_count"), varray());

+ 1 - 0
core/variant/variant_construct.cpp

@@ -198,6 +198,7 @@ void Variant::_register_variant_constructors() {
 
 
 	add_constructor<VariantConstructNoArgs<Dictionary>>(sarray());
 	add_constructor<VariantConstructNoArgs<Dictionary>>(sarray());
 	add_constructor<VariantConstructor<Dictionary, Dictionary>>(sarray("from"));
 	add_constructor<VariantConstructor<Dictionary, Dictionary>>(sarray("from"));
+	add_constructor<VariantConstructorTypedDictionary>(sarray("base", "key_type", "key_class_name", "key_script", "value_type", "value_class_name", "value_script"));
 
 
 	add_constructor<VariantConstructNoArgs<Array>>(sarray());
 	add_constructor<VariantConstructNoArgs<Array>>(sarray());
 	add_constructor<VariantConstructor<Array, Array>>(sarray("from"));
 	add_constructor<VariantConstructor<Array, Array>>(sarray("from"));

+ 106 - 0
core/variant/variant_construct.h

@@ -400,6 +400,112 @@ public:
 	}
 	}
 };
 };
 
 
+class VariantConstructorTypedDictionary {
+public:
+	static void construct(Variant &r_ret, const Variant **p_args, Callable::CallError &r_error) {
+		if (p_args[0]->get_type() != Variant::DICTIONARY) {
+			r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT;
+			r_error.argument = 0;
+			r_error.expected = Variant::DICTIONARY;
+			return;
+		}
+
+		if (p_args[1]->get_type() != Variant::INT) {
+			r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT;
+			r_error.argument = 1;
+			r_error.expected = Variant::INT;
+			return;
+		}
+
+		if (p_args[2]->get_type() != Variant::STRING_NAME) {
+			r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT;
+			r_error.argument = 2;
+			r_error.expected = Variant::STRING_NAME;
+			return;
+		}
+
+		if (p_args[4]->get_type() != Variant::INT) {
+			r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT;
+			r_error.argument = 4;
+			r_error.expected = Variant::INT;
+			return;
+		}
+
+		if (p_args[5]->get_type() != Variant::STRING_NAME) {
+			r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT;
+			r_error.argument = 5;
+			r_error.expected = Variant::STRING_NAME;
+			return;
+		}
+
+		const Dictionary &base_dict = *VariantGetInternalPtr<Dictionary>::get_ptr(p_args[0]);
+		const uint32_t key_type = p_args[1]->operator uint32_t();
+		const StringName &key_class_name = *VariantGetInternalPtr<StringName>::get_ptr(p_args[2]);
+		const uint32_t value_type = p_args[4]->operator uint32_t();
+		const StringName &value_class_name = *VariantGetInternalPtr<StringName>::get_ptr(p_args[5]);
+		r_ret = Dictionary(base_dict, key_type, key_class_name, *p_args[3], value_type, value_class_name, *p_args[6]);
+	}
+
+	static inline void validated_construct(Variant *r_ret, const Variant **p_args) {
+		const Dictionary &base_dict = *VariantGetInternalPtr<Dictionary>::get_ptr(p_args[0]);
+		const uint32_t key_type = p_args[1]->operator uint32_t();
+		const StringName &key_class_name = *VariantGetInternalPtr<StringName>::get_ptr(p_args[2]);
+		const uint32_t value_type = p_args[4]->operator uint32_t();
+		const StringName &value_class_name = *VariantGetInternalPtr<StringName>::get_ptr(p_args[5]);
+		*r_ret = Dictionary(base_dict, key_type, key_class_name, *p_args[3], value_type, value_class_name, *p_args[6]);
+	}
+
+	static void ptr_construct(void *base, const void **p_args) {
+		const Dictionary &base_dict = PtrToArg<Dictionary>::convert(p_args[0]);
+		const uint32_t key_type = PtrToArg<uint32_t>::convert(p_args[1]);
+		const StringName &key_class_name = PtrToArg<StringName>::convert(p_args[2]);
+		const Variant &key_script = PtrToArg<Variant>::convert(p_args[3]);
+		const uint32_t value_type = PtrToArg<uint32_t>::convert(p_args[4]);
+		const StringName &value_class_name = PtrToArg<StringName>::convert(p_args[5]);
+		const Variant &value_script = PtrToArg<Variant>::convert(p_args[6]);
+		Dictionary dst_arr = Dictionary(base_dict, key_type, key_class_name, key_script, value_type, value_class_name, value_script);
+
+		PtrConstruct<Dictionary>::construct(dst_arr, base);
+	}
+
+	static int get_argument_count() {
+		return 7;
+	}
+
+	static Variant::Type get_argument_type(int p_arg) {
+		switch (p_arg) {
+			case 0: {
+				return Variant::DICTIONARY;
+			} break;
+			case 1: {
+				return Variant::INT;
+			} break;
+			case 2: {
+				return Variant::STRING_NAME;
+			} break;
+			case 3: {
+				return Variant::NIL;
+			} break;
+			case 4: {
+				return Variant::INT;
+			} break;
+			case 5: {
+				return Variant::STRING_NAME;
+			} break;
+			case 6: {
+				return Variant::NIL;
+			} break;
+			default: {
+				return Variant::NIL;
+			} break;
+		}
+	}
+
+	static Variant::Type get_base_type() {
+		return Variant::DICTIONARY;
+	}
+};
+
 class VariantConstructorTypedArray {
 class VariantConstructorTypedArray {
 public:
 public:
 	static void construct(Variant &r_ret, const Variant **p_args, Callable::CallError &r_error) {
 	static void construct(Variant &r_ret, const Variant **p_args, Callable::CallError &r_error) {

+ 227 - 17
core/variant/variant_parser.cpp

@@ -1140,6 +1140,146 @@ Error VariantParser::parse_value(Token &token, Variant &value, Stream *p_stream,
 					return ERR_PARSE_ERROR;
 					return ERR_PARSE_ERROR;
 				}
 				}
 			}
 			}
+		} else if (id == "Dictionary") {
+			Error err = OK;
+
+			get_token(p_stream, token, line, r_err_str);
+			if (token.type != TK_BRACKET_OPEN) {
+				r_err_str = "Expected '['";
+				return ERR_PARSE_ERROR;
+			}
+
+			get_token(p_stream, token, line, r_err_str);
+			if (token.type != TK_IDENTIFIER) {
+				r_err_str = "Expected type identifier for key";
+				return ERR_PARSE_ERROR;
+			}
+
+			static HashMap<StringName, Variant::Type> builtin_types;
+			if (builtin_types.is_empty()) {
+				for (int i = 1; i < Variant::VARIANT_MAX; i++) {
+					builtin_types[Variant::get_type_name((Variant::Type)i)] = (Variant::Type)i;
+				}
+			}
+
+			Dictionary dict;
+			Variant::Type key_type = Variant::NIL;
+			StringName key_class_name;
+			Variant key_script;
+			bool got_comma_token = false;
+			if (builtin_types.has(token.value)) {
+				key_type = builtin_types.get(token.value);
+			} else if (token.value == "Resource" || token.value == "SubResource" || token.value == "ExtResource") {
+				Variant resource;
+				err = parse_value(token, resource, p_stream, line, r_err_str, p_res_parser);
+				if (err) {
+					if (token.value == "Resource" && err == ERR_PARSE_ERROR && r_err_str == "Expected '('" && token.type == TK_COMMA) {
+						err = OK;
+						r_err_str = String();
+						key_type = Variant::OBJECT;
+						key_class_name = token.value;
+						got_comma_token = true;
+					} else {
+						return err;
+					}
+				} else {
+					Ref<Script> script = resource;
+					if (script.is_valid() && script->is_valid()) {
+						key_type = Variant::OBJECT;
+						key_class_name = script->get_instance_base_type();
+						key_script = script;
+					}
+				}
+			} else if (ClassDB::class_exists(token.value)) {
+				key_type = Variant::OBJECT;
+				key_class_name = token.value;
+			}
+
+			if (!got_comma_token) {
+				get_token(p_stream, token, line, r_err_str);
+				if (token.type != TK_COMMA) {
+					r_err_str = "Expected ',' after key type";
+					return ERR_PARSE_ERROR;
+				}
+			}
+
+			get_token(p_stream, token, line, r_err_str);
+			if (token.type != TK_IDENTIFIER) {
+				r_err_str = "Expected type identifier for value";
+				return ERR_PARSE_ERROR;
+			}
+
+			Variant::Type value_type = Variant::NIL;
+			StringName value_class_name;
+			Variant value_script;
+			bool got_bracket_token = false;
+			if (builtin_types.has(token.value)) {
+				value_type = builtin_types.get(token.value);
+			} else if (token.value == "Resource" || token.value == "SubResource" || token.value == "ExtResource") {
+				Variant resource;
+				err = parse_value(token, resource, p_stream, line, r_err_str, p_res_parser);
+				if (err) {
+					if (token.value == "Resource" && err == ERR_PARSE_ERROR && r_err_str == "Expected '('" && token.type == TK_BRACKET_CLOSE) {
+						err = OK;
+						r_err_str = String();
+						value_type = Variant::OBJECT;
+						value_class_name = token.value;
+						got_comma_token = true;
+					} else {
+						return err;
+					}
+				} else {
+					Ref<Script> script = resource;
+					if (script.is_valid() && script->is_valid()) {
+						value_type = Variant::OBJECT;
+						value_class_name = script->get_instance_base_type();
+						value_script = script;
+					}
+				}
+			} else if (ClassDB::class_exists(token.value)) {
+				value_type = Variant::OBJECT;
+				value_class_name = token.value;
+			}
+
+			if (key_type != Variant::NIL || value_type != Variant::NIL) {
+				dict.set_typed(key_type, key_class_name, key_script, value_type, value_class_name, value_script);
+			}
+
+			if (!got_bracket_token) {
+				get_token(p_stream, token, line, r_err_str);
+				if (token.type != TK_BRACKET_CLOSE) {
+					r_err_str = "Expected ']'";
+					return ERR_PARSE_ERROR;
+				}
+			}
+
+			get_token(p_stream, token, line, r_err_str);
+			if (token.type != TK_PARENTHESIS_OPEN) {
+				r_err_str = "Expected '('";
+				return ERR_PARSE_ERROR;
+			}
+
+			get_token(p_stream, token, line, r_err_str);
+			if (token.type != TK_CURLY_BRACKET_OPEN) {
+				r_err_str = "Expected '{'";
+				return ERR_PARSE_ERROR;
+			}
+
+			Dictionary values;
+			err = _parse_dictionary(values, p_stream, line, r_err_str, p_res_parser);
+			if (err) {
+				return err;
+			}
+
+			get_token(p_stream, token, line, r_err_str);
+			if (token.type != TK_PARENTHESIS_CLOSE) {
+				r_err_str = "Expected ')'";
+				return ERR_PARSE_ERROR;
+			}
+
+			dict.assign(values);
+
+			value = dict;
 		} else if (id == "Array") {
 		} else if (id == "Array") {
 			Error err = OK;
 			Error err = OK;
 
 
@@ -2036,40 +2176,109 @@ Error VariantWriter::write(const Variant &p_variant, StoreStringFunc p_store_str
 
 
 		case Variant::DICTIONARY: {
 		case Variant::DICTIONARY: {
 			Dictionary dict = p_variant;
 			Dictionary dict = p_variant;
+
+			if (dict.is_typed()) {
+				p_store_string_func(p_store_string_ud, "Dictionary[");
+
+				Variant::Type key_builtin_type = (Variant::Type)dict.get_typed_key_builtin();
+				StringName key_class_name = dict.get_typed_key_class_name();
+				Ref<Script> key_script = dict.get_typed_key_script();
+
+				if (key_script.is_valid()) {
+					String resource_text;
+					if (p_encode_res_func) {
+						resource_text = p_encode_res_func(p_encode_res_ud, key_script);
+					}
+					if (resource_text.is_empty() && key_script->get_path().is_resource_file()) {
+						resource_text = "Resource(\"" + key_script->get_path() + "\")";
+					}
+
+					if (!resource_text.is_empty()) {
+						p_store_string_func(p_store_string_ud, resource_text);
+					} else {
+						ERR_PRINT("Failed to encode a path to a custom script for a dictionary key type.");
+						p_store_string_func(p_store_string_ud, key_class_name);
+					}
+				} else if (key_class_name != StringName()) {
+					p_store_string_func(p_store_string_ud, key_class_name);
+				} else if (key_builtin_type == Variant::NIL) {
+					p_store_string_func(p_store_string_ud, "Variant");
+				} else {
+					p_store_string_func(p_store_string_ud, Variant::get_type_name(key_builtin_type));
+				}
+
+				p_store_string_func(p_store_string_ud, ", ");
+
+				Variant::Type value_builtin_type = (Variant::Type)dict.get_typed_value_builtin();
+				StringName value_class_name = dict.get_typed_value_class_name();
+				Ref<Script> value_script = dict.get_typed_value_script();
+
+				if (value_script.is_valid()) {
+					String resource_text;
+					if (p_encode_res_func) {
+						resource_text = p_encode_res_func(p_encode_res_ud, value_script);
+					}
+					if (resource_text.is_empty() && value_script->get_path().is_resource_file()) {
+						resource_text = "Resource(\"" + value_script->get_path() + "\")";
+					}
+
+					if (!resource_text.is_empty()) {
+						p_store_string_func(p_store_string_ud, resource_text);
+					} else {
+						ERR_PRINT("Failed to encode a path to a custom script for a dictionary value type.");
+						p_store_string_func(p_store_string_ud, value_class_name);
+					}
+				} else if (value_class_name != StringName()) {
+					p_store_string_func(p_store_string_ud, value_class_name);
+				} else if (value_builtin_type == Variant::NIL) {
+					p_store_string_func(p_store_string_ud, "Variant");
+				} else {
+					p_store_string_func(p_store_string_ud, Variant::get_type_name(value_builtin_type));
+				}
+
+				p_store_string_func(p_store_string_ud, "](");
+			}
+
 			if (unlikely(p_recursion_count > MAX_RECURSION)) {
 			if (unlikely(p_recursion_count > MAX_RECURSION)) {
 				ERR_PRINT("Max recursion reached");
 				ERR_PRINT("Max recursion reached");
 				p_store_string_func(p_store_string_ud, "{}");
 				p_store_string_func(p_store_string_ud, "{}");
 			} else {
 			} else {
-				p_recursion_count++;
-
 				List<Variant> keys;
 				List<Variant> keys;
 				dict.get_key_list(&keys);
 				dict.get_key_list(&keys);
 				keys.sort();
 				keys.sort();
 
 
-				if (keys.is_empty()) { // Avoid unnecessary line break.
+				if (keys.is_empty()) {
+					// Avoid unnecessary line break.
 					p_store_string_func(p_store_string_ud, "{}");
 					p_store_string_func(p_store_string_ud, "{}");
-					break;
-				}
+				} else {
+					p_recursion_count++;
 
 
-				p_store_string_func(p_store_string_ud, "{\n");
-				for (List<Variant>::Element *E = keys.front(); E; E = E->next()) {
-					write(E->get(), p_store_string_func, p_store_string_ud, p_encode_res_func, p_encode_res_ud, p_recursion_count, p_compat);
-					p_store_string_func(p_store_string_ud, ": ");
-					write(dict[E->get()], p_store_string_func, p_store_string_ud, p_encode_res_func, p_encode_res_ud, p_recursion_count, p_compat);
-					if (E->next()) {
-						p_store_string_func(p_store_string_ud, ",\n");
-					} else {
-						p_store_string_func(p_store_string_ud, "\n");
+					p_store_string_func(p_store_string_ud, "{\n");
+
+					for (List<Variant>::Element *E = keys.front(); E; E = E->next()) {
+						write(E->get(), p_store_string_func, p_store_string_ud, p_encode_res_func, p_encode_res_ud, p_recursion_count, p_compat);
+						p_store_string_func(p_store_string_ud, ": ");
+						write(dict[E->get()], p_store_string_func, p_store_string_ud, p_encode_res_func, p_encode_res_ud, p_recursion_count, p_compat);
+						if (E->next()) {
+							p_store_string_func(p_store_string_ud, ",\n");
+						} else {
+							p_store_string_func(p_store_string_ud, "\n");
+						}
 					}
 					}
+
+					p_store_string_func(p_store_string_ud, "}");
 				}
 				}
+			}
 
 
-				p_store_string_func(p_store_string_ud, "}");
+			if (dict.is_typed()) {
+				p_store_string_func(p_store_string_ud, ")");
 			}
 			}
 		} break;
 		} break;
 
 
 		case Variant::ARRAY: {
 		case Variant::ARRAY: {
 			Array array = p_variant;
 			Array array = p_variant;
-			if (array.get_typed_builtin() != Variant::NIL) {
+
+			if (array.is_typed()) {
 				p_store_string_func(p_store_string_ud, "Array[");
 				p_store_string_func(p_store_string_ud, "Array[");
 
 
 				Variant::Type builtin_type = (Variant::Type)array.get_typed_builtin();
 				Variant::Type builtin_type = (Variant::Type)array.get_typed_builtin();
@@ -2107,6 +2316,7 @@ Error VariantWriter::write(const Variant &p_variant, StoreStringFunc p_store_str
 				p_recursion_count++;
 				p_recursion_count++;
 
 
 				p_store_string_func(p_store_string_ud, "[");
 				p_store_string_func(p_store_string_ud, "[");
+
 				bool first = true;
 				bool first = true;
 				for (const Variant &var : array) {
 				for (const Variant &var : array) {
 					if (first) {
 					if (first) {
@@ -2120,7 +2330,7 @@ Error VariantWriter::write(const Variant &p_variant, StoreStringFunc p_store_str
 				p_store_string_func(p_store_string_ud, "]");
 				p_store_string_func(p_store_string_ud, "]");
 			}
 			}
 
 
-			if (array.get_typed_builtin() != Variant::NIL) {
+			if (array.is_typed()) {
 				p_store_string_func(p_store_string_ud, ")");
 				p_store_string_func(p_store_string_ud, ")");
 			}
 			}
 		} break;
 		} break;

+ 44 - 47
core/variant/variant_setget.cpp

@@ -703,6 +703,50 @@ struct VariantIndexedSetGet_Array {
 	static uint64_t get_indexed_size(const Variant *base) { return 0; }
 	static uint64_t get_indexed_size(const Variant *base) { return 0; }
 };
 };
 
 
+struct VariantIndexedSetGet_Dictionary {
+	static void get(const Variant *base, int64_t index, Variant *value, bool *oob) {
+		const Variant *ptr = VariantGetInternalPtr<Dictionary>::get_ptr(base)->getptr(index);
+		if (!ptr) {
+			*oob = true;
+			return;
+		}
+		*value = *ptr;
+		*oob = false;
+	}
+	static void ptr_get(const void *base, int64_t index, void *member) {
+		// Avoid ptrconvert for performance.
+		const Dictionary &v = *reinterpret_cast<const Dictionary *>(base);
+		const Variant *ptr = v.getptr(index);
+		NULL_TEST(ptr);
+		PtrToArg<Variant>::encode(*ptr, member);
+	}
+	static void set(Variant *base, int64_t index, const Variant *value, bool *valid, bool *oob) {
+		if (VariantGetInternalPtr<Dictionary>::get_ptr(base)->is_read_only()) {
+			*valid = false;
+			*oob = true;
+			return;
+		}
+		(*VariantGetInternalPtr<Dictionary>::get_ptr(base))[index] = *value;
+		*oob = false;
+		*valid = true;
+	}
+	static void validated_set(Variant *base, int64_t index, const Variant *value, bool *oob) {
+		if (VariantGetInternalPtr<Dictionary>::get_ptr(base)->is_read_only()) {
+			*oob = true;
+			return;
+		}
+		(*VariantGetInternalPtr<Dictionary>::get_ptr(base))[index] = *value;
+		*oob = false;
+	}
+	static void ptr_set(void *base, int64_t index, const void *member) {
+		Dictionary &v = *reinterpret_cast<Dictionary *>(base);
+		v[index] = PtrToArg<Variant>::convert(member);
+	}
+	static Variant::Type get_index_type() { return Variant::NIL; }
+	static uint32_t get_index_usage() { return PROPERTY_USAGE_DEFAULT; }
+	static uint64_t get_indexed_size(const Variant *base) { return VariantGetInternalPtr<Dictionary>::get_ptr(base)->size(); }
+};
+
 struct VariantIndexedSetGet_String {
 struct VariantIndexedSetGet_String {
 	static void get(const Variant *base, int64_t index, Variant *value, bool *oob) {
 	static void get(const Variant *base, int64_t index, Variant *value, bool *oob) {
 		int64_t length = VariantGetInternalPtr<String>::get_ptr(base)->length();
 		int64_t length = VariantGetInternalPtr<String>::get_ptr(base)->length();
@@ -789,51 +833,6 @@ struct VariantIndexedSetGet_String {
 	static uint64_t get_indexed_size(const Variant *base) { return VariantInternal::get_string(base)->length(); }
 	static uint64_t get_indexed_size(const Variant *base) { return VariantInternal::get_string(base)->length(); }
 };
 };
 
 
-#define INDEXED_SETGET_STRUCT_DICT(m_base_type)                                                                                     \
-	struct VariantIndexedSetGet_##m_base_type {                                                                                     \
-		static void get(const Variant *base, int64_t index, Variant *value, bool *oob) {                                            \
-			const Variant *ptr = VariantGetInternalPtr<m_base_type>::get_ptr(base)->getptr(index);                                  \
-			if (!ptr) {                                                                                                             \
-				*oob = true;                                                                                                        \
-				return;                                                                                                             \
-			}                                                                                                                       \
-			*value = *ptr;                                                                                                          \
-			*oob = false;                                                                                                           \
-		}                                                                                                                           \
-		static void ptr_get(const void *base, int64_t index, void *member) {                                                        \
-			/* avoid ptrconvert for performance*/                                                                                   \
-			const m_base_type &v = *reinterpret_cast<const m_base_type *>(base);                                                    \
-			const Variant *ptr = v.getptr(index);                                                                                   \
-			NULL_TEST(ptr);                                                                                                         \
-			PtrToArg<Variant>::encode(*ptr, member);                                                                                \
-		}                                                                                                                           \
-		static void set(Variant *base, int64_t index, const Variant *value, bool *valid, bool *oob) {                               \
-			if (VariantGetInternalPtr<m_base_type>::get_ptr(base)->is_read_only()) {                                                \
-				*valid = false;                                                                                                     \
-				*oob = true;                                                                                                        \
-				return;                                                                                                             \
-			}                                                                                                                       \
-			(*VariantGetInternalPtr<m_base_type>::get_ptr(base))[index] = *value;                                                   \
-			*oob = false;                                                                                                           \
-			*valid = true;                                                                                                          \
-		}                                                                                                                           \
-		static void validated_set(Variant *base, int64_t index, const Variant *value, bool *oob) {                                  \
-			if (VariantGetInternalPtr<m_base_type>::get_ptr(base)->is_read_only()) {                                                \
-				*oob = true;                                                                                                        \
-				return;                                                                                                             \
-			}                                                                                                                       \
-			(*VariantGetInternalPtr<m_base_type>::get_ptr(base))[index] = *value;                                                   \
-			*oob = false;                                                                                                           \
-		}                                                                                                                           \
-		static void ptr_set(void *base, int64_t index, const void *member) {                                                        \
-			m_base_type &v = *reinterpret_cast<m_base_type *>(base);                                                                \
-			v[index] = PtrToArg<Variant>::convert(member);                                                                          \
-		}                                                                                                                           \
-		static Variant::Type get_index_type() { return Variant::NIL; }                                                              \
-		static uint32_t get_index_usage() { return PROPERTY_USAGE_DEFAULT; }                                                        \
-		static uint64_t get_indexed_size(const Variant *base) { return VariantGetInternalPtr<m_base_type>::get_ptr(base)->size(); } \
-	};
-
 INDEXED_SETGET_STRUCT_BULTIN_NUMERIC(Vector2, double, real_t, 2)
 INDEXED_SETGET_STRUCT_BULTIN_NUMERIC(Vector2, double, real_t, 2)
 INDEXED_SETGET_STRUCT_BULTIN_NUMERIC(Vector2i, int64_t, int32_t, 2)
 INDEXED_SETGET_STRUCT_BULTIN_NUMERIC(Vector2i, int64_t, int32_t, 2)
 INDEXED_SETGET_STRUCT_BULTIN_NUMERIC(Vector3, double, real_t, 3)
 INDEXED_SETGET_STRUCT_BULTIN_NUMERIC(Vector3, double, real_t, 3)
@@ -858,8 +857,6 @@ INDEXED_SETGET_STRUCT_TYPED(PackedStringArray, String)
 INDEXED_SETGET_STRUCT_TYPED(PackedColorArray, Color)
 INDEXED_SETGET_STRUCT_TYPED(PackedColorArray, Color)
 INDEXED_SETGET_STRUCT_TYPED(PackedVector4Array, Vector4)
 INDEXED_SETGET_STRUCT_TYPED(PackedVector4Array, Vector4)
 
 
-INDEXED_SETGET_STRUCT_DICT(Dictionary)
-
 struct VariantIndexedSetterGetterInfo {
 struct VariantIndexedSetterGetterInfo {
 	void (*setter)(Variant *base, int64_t index, const Variant *value, bool *valid, bool *oob) = nullptr;
 	void (*setter)(Variant *base, int64_t index, const Variant *value, bool *valid, bool *oob) = nullptr;
 	void (*getter)(const Variant *base, int64_t index, Variant *value, bool *oob) = nullptr;
 	void (*getter)(const Variant *base, int64_t index, Variant *value, bool *oob) = nullptr;

+ 4 - 1
doc/classes/@GlobalScope.xml

@@ -2915,6 +2915,9 @@
 		<constant name="PROPERTY_HINT_ARRAY_TYPE" value="31" enum="PropertyHint">
 		<constant name="PROPERTY_HINT_ARRAY_TYPE" value="31" enum="PropertyHint">
 			Hints that a property is an [Array] with the stored type specified in the hint string.
 			Hints that a property is an [Array] with the stored type specified in the hint string.
 		</constant>
 		</constant>
+		<constant name="PROPERTY_HINT_DICTIONARY_TYPE" value="38" enum="PropertyHint">
+			Hints that a property is a [Dictionary] with the stored types specified in the hint string.
+		</constant>
 		<constant name="PROPERTY_HINT_LOCALE_ID" value="32" enum="PropertyHint">
 		<constant name="PROPERTY_HINT_LOCALE_ID" value="32" enum="PropertyHint">
 			Hints that a string property is a locale code. Editing it will show a locale dialog for picking language and country.
 			Hints that a string property is a locale code. Editing it will show a locale dialog for picking language and country.
 		</constant>
 		</constant>
@@ -2930,7 +2933,7 @@
 		<constant name="PROPERTY_HINT_PASSWORD" value="36" enum="PropertyHint">
 		<constant name="PROPERTY_HINT_PASSWORD" value="36" enum="PropertyHint">
 			Hints that a string property is a password, and every character is replaced with the secret character.
 			Hints that a string property is a password, and every character is replaced with the secret character.
 		</constant>
 		</constant>
-		<constant name="PROPERTY_HINT_MAX" value="38" enum="PropertyHint">
+		<constant name="PROPERTY_HINT_MAX" value="39" enum="PropertyHint">
 			Represents the size of the [enum PropertyHint] enum.
 			Represents the size of the [enum PropertyHint] enum.
 		</constant>
 		</constant>
 		<constant name="PROPERTY_USAGE_NONE" value="0" enum="PropertyUsageFlags" is_bitfield="true">
 		<constant name="PROPERTY_USAGE_NONE" value="0" enum="PropertyUsageFlags" is_bitfield="true">

+ 95 - 0
doc/classes/Dictionary.xml

@@ -148,6 +148,19 @@
 				Constructs an empty [Dictionary].
 				Constructs an empty [Dictionary].
 			</description>
 			</description>
 		</constructor>
 		</constructor>
+		<constructor name="Dictionary">
+			<return type="Dictionary" />
+			<param index="0" name="base" type="Dictionary" />
+			<param index="1" name="key_type" type="int" />
+			<param index="2" name="key_class_name" type="StringName" />
+			<param index="3" name="key_script" type="Variant" />
+			<param index="4" name="value_type" type="int" />
+			<param index="5" name="value_class_name" type="StringName" />
+			<param index="6" name="value_script" type="Variant" />
+			<description>
+				Creates a typed dictionary from the [param base] dictionary. A typed dictionary can only contain keys and values of the given types, or that inherit from the given classes, as described by this constructor's parameters.
+			</description>
+		</constructor>
 		<constructor name="Dictionary">
 		<constructor name="Dictionary">
 			<return type="Dictionary" />
 			<return type="Dictionary" />
 			<param index="0" name="from" type="Dictionary" />
 			<param index="0" name="from" type="Dictionary" />
@@ -157,6 +170,13 @@
 		</constructor>
 		</constructor>
 	</constructors>
 	</constructors>
 	<methods>
 	<methods>
+		<method name="assign">
+			<return type="void" />
+			<param index="0" name="dictionary" type="Dictionary" />
+			<description>
+				Assigns elements of another [param dictionary] into the dictionary. Resizes the dictionary to match [param dictionary]. Performs type conversions if the dictionary is typed.
+			</description>
+		</method>
 		<method name="clear">
 		<method name="clear">
 			<return type="void" />
 			<return type="void" />
 			<description>
 			<description>
@@ -202,6 +222,42 @@
 				Gets a value and ensures the key is set. If the [param key] exists in the dictionary, this behaves like [method get]. Otherwise, the [param default] value is inserted into the dictionary and returned.
 				Gets a value and ensures the key is set. If the [param key] exists in the dictionary, this behaves like [method get]. Otherwise, the [param default] value is inserted into the dictionary and returned.
 			</description>
 			</description>
 		</method>
 		</method>
+		<method name="get_typed_key_builtin" qualifiers="const">
+			<return type="int" />
+			<description>
+				Returns the built-in [Variant] type of the typed dictionary's keys as a [enum Variant.Type] constant. If the keys are not typed, returns [constant TYPE_NIL]. See also [method is_typed_key].
+			</description>
+		</method>
+		<method name="get_typed_key_class_name" qualifiers="const">
+			<return type="StringName" />
+			<description>
+				Returns the [b]built-in[/b] class name of the typed dictionary's keys, if the built-in [Variant] type is [constant TYPE_OBJECT]. Otherwise, returns an empty [StringName]. See also [method is_typed_key] and [method Object.get_class].
+			</description>
+		</method>
+		<method name="get_typed_key_script" qualifiers="const">
+			<return type="Variant" />
+			<description>
+				Returns the [Script] instance associated with this typed dictionary's keys, or [code]null[/code] if it does not exist. See also [method is_typed_key].
+			</description>
+		</method>
+		<method name="get_typed_value_builtin" qualifiers="const">
+			<return type="int" />
+			<description>
+				Returns the built-in [Variant] type of the typed dictionary's values as a [enum Variant.Type] constant. If the values are not typed, returns [constant TYPE_NIL]. See also [method is_typed_value].
+			</description>
+		</method>
+		<method name="get_typed_value_class_name" qualifiers="const">
+			<return type="StringName" />
+			<description>
+				Returns the [b]built-in[/b] class name of the typed dictionary's values, if the built-in [Variant] type is [constant TYPE_OBJECT]. Otherwise, returns an empty [StringName]. See also [method is_typed_value] and [method Object.get_class].
+			</description>
+		</method>
+		<method name="get_typed_value_script" qualifiers="const">
+			<return type="Variant" />
+			<description>
+				Returns the [Script] instance associated with this typed dictionary's values, or [code]null[/code] if it does not exist. See also [method is_typed_value].
+			</description>
+		</method>
 		<method name="has" qualifiers="const">
 		<method name="has" qualifiers="const">
 			<return type="bool" />
 			<return type="bool" />
 			<param index="0" name="key" type="Variant" />
 			<param index="0" name="key" type="Variant" />
@@ -284,6 +340,45 @@
 				Returns [code]true[/code] if the dictionary is read-only. See [method make_read_only]. Dictionaries are automatically read-only if declared with [code]const[/code] keyword.
 				Returns [code]true[/code] if the dictionary is read-only. See [method make_read_only]. Dictionaries are automatically read-only if declared with [code]const[/code] keyword.
 			</description>
 			</description>
 		</method>
 		</method>
+		<method name="is_same_typed" qualifiers="const">
+			<return type="bool" />
+			<param index="0" name="dictionary" type="Dictionary" />
+			<description>
+				Returns [code]true[/code] if the dictionary is typed the same as [param dictionary].
+			</description>
+		</method>
+		<method name="is_same_typed_key" qualifiers="const">
+			<return type="bool" />
+			<param index="0" name="dictionary" type="Dictionary" />
+			<description>
+				Returns [code]true[/code] if the dictionary's keys are typed the same as [param dictionary]'s keys.
+			</description>
+		</method>
+		<method name="is_same_typed_value" qualifiers="const">
+			<return type="bool" />
+			<param index="0" name="dictionary" type="Dictionary" />
+			<description>
+				Returns [code]true[/code] if the dictionary's values are typed the same as [param dictionary]'s values.
+			</description>
+		</method>
+		<method name="is_typed" qualifiers="const">
+			<return type="bool" />
+			<description>
+				Returns [code]true[/code] if the dictionary is typed. Typed dictionaries can only store keys/values of their associated type and provide type safety for the [code][][/code] operator. Methods of typed dictionary still return [Variant].
+			</description>
+		</method>
+		<method name="is_typed_key" qualifiers="const">
+			<return type="bool" />
+			<description>
+				Returns [code]true[/code] if the dictionary's keys are typed.
+			</description>
+		</method>
+		<method name="is_typed_value" qualifiers="const">
+			<return type="bool" />
+			<description>
+				Returns [code]true[/code] if the dictionary's values are typed.
+			</description>
+		</method>
 		<method name="keys" qualifiers="const">
 		<method name="keys" qualifiers="const">
 			<return type="Array" />
 			<return type="Array" />
 			<description>
 			<description>

+ 17 - 18
doc/tools/make_rst.py

@@ -1507,24 +1507,23 @@ def make_type(klass: str, state: State) -> str:
     if klass.find("*") != -1:  # Pointer, ignore
     if klass.find("*") != -1:  # Pointer, ignore
         return f"``{klass}``"
         return f"``{klass}``"
 
 
-    link_type = klass
-    is_array = False
-
-    if link_type.endswith("[]"):  # Typed array, strip [] to link to contained type.
-        link_type = link_type[:-2]
-        is_array = True
-
-    if link_type in state.classes:
-        type_rst = f":ref:`{link_type}<class_{link_type}>`"
-        if is_array:
-            type_rst = f":ref:`Array<class_Array>`\\[{type_rst}\\]"
-        return type_rst
-
-    print_error(f'{state.current_class}.xml: Unresolved type "{link_type}".', state)
-    type_rst = f"``{link_type}``"
-    if is_array:
-        type_rst = f":ref:`Array<class_Array>`\\[{type_rst}\\]"
-    return type_rst
+    def resolve_type(link_type: str) -> str:
+        if link_type in state.classes:
+            return f":ref:`{link_type}<class_{link_type}>`"
+        else:
+            print_error(f'{state.current_class}.xml: Unresolved type "{link_type}".', state)
+            return f"``{link_type}``"
+
+    if klass.endswith("[]"):  # Typed array, strip [] to link to contained type.
+        return f":ref:`Array<class_Array>`\\[{resolve_type(klass[:-len('[]')])}\\]"
+
+    if klass.startswith("Dictionary["):  # Typed dictionary, split elements to link contained types.
+        parts = klass[len("Dictionary[") : -len("]")].partition(", ")
+        key = parts[0]
+        value = parts[2]
+        return f":ref:`Dictionary<class_Dictionary>`\\[{resolve_type(key)}, {resolve_type(value)}\\]"
+
+    return resolve_type(klass)
 
 
 
 
 def make_enum(t: str, is_bitfield: bool, state: State) -> str:
 def make_enum(t: str, is_bitfield: bool, state: State) -> str:

+ 16 - 0
editor/connections_dialog.cpp

@@ -576,6 +576,22 @@ String ConnectDialog::get_signature(const MethodInfo &p_method, PackedStringArra
 					type_name = "Array";
 					type_name = "Array";
 				}
 				}
 				break;
 				break;
+			case Variant::DICTIONARY:
+				type_name = "Dictionary";
+				if (pi.hint == PROPERTY_HINT_DICTIONARY_TYPE && !pi.hint_string.is_empty()) {
+					String key_hint = pi.hint_string.get_slice(";", 0);
+					String value_hint = pi.hint_string.get_slice(";", 1);
+					if (key_hint.is_empty() || key_hint.begins_with("res://")) {
+						key_hint = "Variant";
+					}
+					if (value_hint.is_empty() || value_hint.begins_with("res://")) {
+						value_hint = "Variant";
+					}
+					if (key_hint != "Variant" || value_hint != "Variant") {
+						type_name += "[" + key_hint + ", " + value_hint + "]";
+					}
+				}
+				break;
 			case Variant::OBJECT:
 			case Variant::OBJECT:
 				if (pi.class_name != StringName()) {
 				if (pi.class_name != StringName()) {
 					type_name = pi.class_name;
 					type_name = pi.class_name;

+ 2 - 0
editor/doc_tools.cpp

@@ -571,6 +571,8 @@ void DocTools::generate(BitField<GenerateFlags> p_flags) {
 							prop.type = retinfo.class_name;
 							prop.type = retinfo.class_name;
 						} else if (retinfo.type == Variant::ARRAY && retinfo.hint == PROPERTY_HINT_ARRAY_TYPE) {
 						} else if (retinfo.type == Variant::ARRAY && retinfo.hint == PROPERTY_HINT_ARRAY_TYPE) {
 							prop.type = retinfo.hint_string + "[]";
 							prop.type = retinfo.hint_string + "[]";
+						} else if (retinfo.type == Variant::DICTIONARY && retinfo.hint == PROPERTY_HINT_DICTIONARY_TYPE) {
+							prop.type = "Dictionary[" + retinfo.hint_string.replace(";", ", ") + "]";
 						} else if (retinfo.hint == PROPERTY_HINT_RESOURCE_TYPE) {
 						} else if (retinfo.hint == PROPERTY_HINT_RESOURCE_TYPE) {
 							prop.type = retinfo.hint_string;
 							prop.type = retinfo.hint_string;
 						} else if (retinfo.type == Variant::NIL && retinfo.usage & PROPERTY_USAGE_NIL_IS_VARIANT) {
 						} else if (retinfo.type == Variant::NIL && retinfo.usage & PROPERTY_USAGE_NIL_IS_VARIANT) {

+ 19 - 3
editor/editor_help.cpp

@@ -376,10 +376,10 @@ static void _add_type_to_rt(const String &p_type, const String &p_enum, bool p_i
 	}
 	}
 
 
 	p_rt->push_color(type_color);
 	p_rt->push_color(type_color);
-	bool add_array = false;
+	bool add_typed_container = false;
 	if (can_ref) {
 	if (can_ref) {
 		if (link_t.ends_with("[]")) {
 		if (link_t.ends_with("[]")) {
-			add_array = true;
+			add_typed_container = true;
 			link_t = link_t.trim_suffix("[]");
 			link_t = link_t.trim_suffix("[]");
 			display_t = display_t.trim_suffix("[]");
 			display_t = display_t.trim_suffix("[]");
 
 
@@ -387,6 +387,22 @@ static void _add_type_to_rt(const String &p_type, const String &p_enum, bool p_i
 			p_rt->add_text("Array");
 			p_rt->add_text("Array");
 			p_rt->pop(); // meta
 			p_rt->pop(); // meta
 			p_rt->add_text("[");
 			p_rt->add_text("[");
+		} else if (link_t.begins_with("Dictionary[")) {
+			add_typed_container = true;
+			link_t = link_t.trim_prefix("Dictionary[").trim_suffix("]");
+			display_t = display_t.trim_prefix("Dictionary[").trim_suffix("]");
+
+			p_rt->push_meta("#Dictionary", RichTextLabel::META_UNDERLINE_ON_HOVER); // class
+			p_rt->add_text("Dictionary");
+			p_rt->pop(); // meta
+			p_rt->add_text("[");
+			p_rt->push_meta("#" + link_t.get_slice(", ", 0), RichTextLabel::META_UNDERLINE_ON_HOVER); // class
+			p_rt->add_text(_contextualize_class_specifier(display_t.get_slice(", ", 0), p_class));
+			p_rt->pop(); // meta
+			p_rt->add_text(", ");
+
+			link_t = link_t.get_slice(", ", 1);
+			display_t = _contextualize_class_specifier(display_t.get_slice(", ", 1), p_class);
 		} else if (is_bitfield) {
 		} else if (is_bitfield) {
 			p_rt->push_color(Color(type_color, 0.5));
 			p_rt->push_color(Color(type_color, 0.5));
 			p_rt->push_hint(TTR("This value is an integer composed as a bitmask of the following flags."));
 			p_rt->push_hint(TTR("This value is an integer composed as a bitmask of the following flags."));
@@ -405,7 +421,7 @@ static void _add_type_to_rt(const String &p_type, const String &p_enum, bool p_i
 	p_rt->add_text(display_t);
 	p_rt->add_text(display_t);
 	if (can_ref) {
 	if (can_ref) {
 		p_rt->pop(); // meta
 		p_rt->pop(); // meta
-		if (add_array) {
+		if (add_typed_container) {
 			p_rt->add_text("]");
 			p_rt->add_text("]");
 		} else if (is_bitfield) {
 		} else if (is_bitfield) {
 			p_rt->push_color(Color(type_color, 0.5));
 			p_rt->push_color(Color(type_color, 0.5));

+ 1 - 1
editor/editor_properties.cpp

@@ -3771,7 +3771,7 @@ EditorProperty *EditorInspectorDefaultPlugin::get_editor_for_property(Object *p_
 				return editor;
 				return editor;
 			} else {
 			} else {
 				EditorPropertyDictionary *editor = memnew(EditorPropertyDictionary);
 				EditorPropertyDictionary *editor = memnew(EditorPropertyDictionary);
-				editor->setup(p_hint);
+				editor->setup(p_hint, p_hint_text);
 				return editor;
 				return editor;
 			}
 			}
 		} break;
 		} break;

+ 116 - 11
editor/editor_properties_array_dict.cpp

@@ -863,6 +863,26 @@ EditorPropertyArray::EditorPropertyArray() {
 
 
 ///////////////////// DICTIONARY ///////////////////////////
 ///////////////////// DICTIONARY ///////////////////////////
 
 
+void EditorPropertyDictionary::initialize_dictionary(Variant &p_dictionary) {
+	if (key_subtype != Variant::NIL || value_subtype != Variant::NIL) {
+		Dictionary dict;
+		StringName key_subtype_class;
+		Ref<Script> key_subtype_script;
+		if (key_subtype == Variant::OBJECT && !key_subtype_hint_string.is_empty() && ClassDB::class_exists(key_subtype_hint_string)) {
+			key_subtype_class = key_subtype_hint_string;
+		}
+		StringName value_subtype_class;
+		Ref<Script> value_subtype_script;
+		if (value_subtype == Variant::OBJECT && !value_subtype_hint_string.is_empty() && ClassDB::class_exists(value_subtype_hint_string)) {
+			value_subtype_class = value_subtype_hint_string;
+		}
+		dict.set_typed(key_subtype, key_subtype_class, key_subtype_script, value_subtype, value_subtype_class, value_subtype_script);
+		p_dictionary = dict;
+	} else {
+		VariantInternal::initialize(&p_dictionary, Variant::DICTIONARY);
+	}
+}
+
 void EditorPropertyDictionary::_property_changed(const String &p_property, Variant p_value, const String &p_name, bool p_changing) {
 void EditorPropertyDictionary::_property_changed(const String &p_property, Variant p_value, const String &p_name, bool p_changing) {
 	if (p_value.get_type() == Variant::OBJECT && p_value.is_null()) {
 	if (p_value.get_type() == Variant::OBJECT && p_value.is_null()) {
 		p_value = Variant(); // `EditorResourcePicker` resets to `Ref<Resource>()`. See GH-82716.
 		p_value = Variant(); // `EditorResourcePicker` resets to `Ref<Resource>()`. See GH-82716.
@@ -914,16 +934,29 @@ void EditorPropertyDictionary::_create_new_property_slot(int p_idx) {
 	EditorProperty *prop = memnew(EditorPropertyNil);
 	EditorProperty *prop = memnew(EditorPropertyNil);
 	hbox->add_child(prop);
 	hbox->add_child(prop);
 
 
-	Button *edit_btn = memnew(Button);
-	edit_btn->set_icon(get_editor_theme_icon(SNAME("Edit")));
-	edit_btn->set_disabled(is_read_only());
-	edit_btn->connect(SceneStringName(pressed), callable_mp(this, &EditorPropertyDictionary::_change_type).bind(edit_btn, slots.size()));
-	hbox->add_child(edit_btn);
+	bool use_key = p_idx == EditorPropertyDictionaryObject::NEW_KEY_INDEX;
+	bool is_untyped_dict = (use_key ? key_subtype : value_subtype) == Variant::NIL;
+
+	if (is_untyped_dict) {
+		Button *edit_btn = memnew(Button);
+		edit_btn->set_icon(get_editor_theme_icon(SNAME("Edit")));
+		edit_btn->set_disabled(is_read_only());
+		edit_btn->connect(SceneStringName(pressed), callable_mp(this, &EditorPropertyDictionary::_change_type).bind(edit_btn, slots.size()));
+		hbox->add_child(edit_btn);
+	} else if (p_idx >= 0) {
+		Button *remove_btn = memnew(Button);
+		remove_btn->set_icon(get_editor_theme_icon(SNAME("Remove")));
+		remove_btn->set_disabled(is_read_only());
+		remove_btn->connect(SceneStringName(pressed), callable_mp(this, &EditorPropertyDictionary::_remove_pressed).bind(slots.size()));
+		hbox->add_child(remove_btn);
+	}
+
 	if (add_panel) {
 	if (add_panel) {
 		add_panel->get_child(0)->add_child(hbox);
 		add_panel->get_child(0)->add_child(hbox);
 	} else {
 	} else {
 		property_vbox->add_child(hbox);
 		property_vbox->add_child(hbox);
 	}
 	}
+
 	Slot slot;
 	Slot slot;
 	slot.prop = prop;
 	slot.prop = prop;
 	slot.object = object;
 	slot.object = object;
@@ -969,15 +1002,70 @@ void EditorPropertyDictionary::_change_type_menu(int p_index) {
 	}
 	}
 }
 }
 
 
-void EditorPropertyDictionary::setup(PropertyHint p_hint) {
-	property_hint = p_hint;
+void EditorPropertyDictionary::setup(PropertyHint p_hint, const String &p_hint_string) {
+	PackedStringArray types = p_hint_string.split(";");
+	if (types.size() > 0 && !types[0].is_empty()) {
+		String key = types[0];
+		int hint_key_subtype_separator = key.find(":");
+		if (hint_key_subtype_separator >= 0) {
+			String key_subtype_string = key.substr(0, hint_key_subtype_separator);
+			int slash_pos = key_subtype_string.find("/");
+			if (slash_pos >= 0) {
+				key_subtype_hint = PropertyHint(key_subtype_string.substr(slash_pos + 1, key_subtype_string.size() - slash_pos - 1).to_int());
+				key_subtype_string = key_subtype_string.substr(0, slash_pos);
+			}
+
+			key_subtype_hint_string = key.substr(hint_key_subtype_separator + 1, key.size() - hint_key_subtype_separator - 1);
+			key_subtype = Variant::Type(key_subtype_string.to_int());
+
+			Variant new_key = object->get_new_item_key();
+			VariantInternal::initialize(&new_key, key_subtype);
+			object->set_new_item_key(new_key);
+		}
+	}
+	if (types.size() > 1 && !types[1].is_empty()) {
+		String value = types[1];
+		int hint_value_subtype_separator = value.find(":");
+		if (hint_value_subtype_separator >= 0) {
+			String value_subtype_string = value.substr(0, hint_value_subtype_separator);
+			int slash_pos = value_subtype_string.find("/");
+			if (slash_pos >= 0) {
+				value_subtype_hint = PropertyHint(value_subtype_string.substr(slash_pos + 1, value_subtype_string.size() - slash_pos - 1).to_int());
+				value_subtype_string = value_subtype_string.substr(0, slash_pos);
+			}
+
+			value_subtype_hint_string = value.substr(hint_value_subtype_separator + 1, value.size() - hint_value_subtype_separator - 1);
+			value_subtype = Variant::Type(value_subtype_string.to_int());
+
+			Variant new_value = object->get_new_item_value();
+			VariantInternal::initialize(&new_value, value_subtype);
+			object->set_new_item_value(new_value);
+		}
+	}
 }
 }
 
 
 void EditorPropertyDictionary::update_property() {
 void EditorPropertyDictionary::update_property() {
 	Variant updated_val = get_edited_property_value();
 	Variant updated_val = get_edited_property_value();
 
 
+	String dict_type_name = "Dictionary";
+	if (key_subtype != Variant::NIL || value_subtype != Variant::NIL) {
+		String key_subtype_name = "Variant";
+		if (key_subtype == Variant::OBJECT && (key_subtype_hint == PROPERTY_HINT_RESOURCE_TYPE || key_subtype_hint == PROPERTY_HINT_NODE_TYPE)) {
+			key_subtype_name = key_subtype_hint_string;
+		} else if (key_subtype != Variant::NIL) {
+			key_subtype_name = Variant::get_type_name(key_subtype);
+		}
+		String value_subtype_name = "Variant";
+		if (value_subtype == Variant::OBJECT && (value_subtype_hint == PROPERTY_HINT_RESOURCE_TYPE || value_subtype_hint == PROPERTY_HINT_NODE_TYPE)) {
+			value_subtype_name = value_subtype_hint_string;
+		} else if (value_subtype != Variant::NIL) {
+			value_subtype_name = Variant::get_type_name(value_subtype);
+		}
+		dict_type_name += vformat("[%s, %s]", key_subtype_name, value_subtype_name);
+	}
+
 	if (updated_val.get_type() != Variant::DICTIONARY) {
 	if (updated_val.get_type() != Variant::DICTIONARY) {
-		edit->set_text(TTR("Dictionary (Nil)")); // This provides symmetry with the array property.
+		edit->set_text(vformat(TTR("(Nil) %s"), dict_type_name)); // This provides symmetry with the array property.
 		edit->set_pressed(false);
 		edit->set_pressed(false);
 		if (container) {
 		if (container) {
 			set_bottom_editor(nullptr);
 			set_bottom_editor(nullptr);
@@ -993,7 +1081,7 @@ void EditorPropertyDictionary::update_property() {
 	Dictionary dict = updated_val;
 	Dictionary dict = updated_val;
 	object->set_dict(updated_val);
 	object->set_dict(updated_val);
 
 
-	edit->set_text(vformat(TTR("Dictionary (size %d)"), dict.size()));
+	edit->set_text(vformat(TTR("%s (size %d)"), dict_type_name, dict.size()));
 
 
 	bool unfolded = get_edited_object()->editor_is_section_unfolded(get_edited_property());
 	bool unfolded = get_edited_object()->editor_is_section_unfolded(get_edited_property());
 	if (edit->is_pressed() != unfolded) {
 	if (edit->is_pressed() != unfolded) {
@@ -1074,7 +1162,9 @@ void EditorPropertyDictionary::update_property() {
 					editor->setup("Object");
 					editor->setup("Object");
 					new_prop = editor;
 					new_prop = editor;
 				} else {
 				} else {
-					new_prop = EditorInspector::instantiate_property_editor(this, value_type, "", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NONE);
+					bool use_key = slot.index == EditorPropertyDictionaryObject::NEW_KEY_INDEX;
+					new_prop = EditorInspector::instantiate_property_editor(this, value_type, "", use_key ? key_subtype_hint : value_subtype_hint,
+							use_key ? key_subtype_hint_string : value_subtype_hint_string, PROPERTY_USAGE_NONE);
 				}
 				}
 				new_prop->set_selectable(false);
 				new_prop->set_selectable(false);
 				new_prop->set_use_folding(is_using_folding());
 				new_prop->set_use_folding(is_using_folding());
@@ -1108,6 +1198,13 @@ void EditorPropertyDictionary::update_property() {
 	}
 	}
 }
 }
 
 
+void EditorPropertyDictionary::_remove_pressed(int p_slot_index) {
+	Dictionary dict = object->get_dict().duplicate();
+	dict.erase(dict.get_key_at_index(p_slot_index));
+
+	emit_changed(get_edited_property(), dict);
+}
+
 void EditorPropertyDictionary::_object_id_selected(const StringName &p_property, ObjectID p_id) {
 void EditorPropertyDictionary::_object_id_selected(const StringName &p_property, ObjectID p_id) {
 	emit_signal(SNAME("object_id_selected"), p_property, p_id);
 	emit_signal(SNAME("object_id_selected"), p_property, p_id);
 }
 }
@@ -1140,7 +1237,7 @@ void EditorPropertyDictionary::_notification(int p_what) {
 void EditorPropertyDictionary::_edit_pressed() {
 void EditorPropertyDictionary::_edit_pressed() {
 	Variant prop_val = get_edited_property_value();
 	Variant prop_val = get_edited_property_value();
 	if (prop_val.get_type() == Variant::NIL && edit->is_pressed()) {
 	if (prop_val.get_type() == Variant::NIL && edit->is_pressed()) {
-		VariantInternal::initialize(&prop_val, Variant::DICTIONARY);
+		initialize_dictionary(prop_val);
 		emit_changed(get_edited_property(), prop_val);
 		emit_changed(get_edited_property(), prop_val);
 	}
 	}
 
 
@@ -1187,6 +1284,14 @@ EditorPropertyDictionary::EditorPropertyDictionary() {
 	change_type->connect(SceneStringName(id_pressed), callable_mp(this, &EditorPropertyDictionary::_change_type_menu));
 	change_type->connect(SceneStringName(id_pressed), callable_mp(this, &EditorPropertyDictionary::_change_type_menu));
 	changing_type_index = -1;
 	changing_type_index = -1;
 	has_borders = true;
 	has_borders = true;
+
+	key_subtype = Variant::NIL;
+	key_subtype_hint = PROPERTY_HINT_NONE;
+	key_subtype_hint_string = "";
+
+	value_subtype = Variant::NIL;
+	value_subtype_hint = PROPERTY_HINT_NONE;
+	value_subtype_hint_string = "";
 }
 }
 
 
 ///////////////////// LOCALIZABLE STRING ///////////////////////////
 ///////////////////// LOCALIZABLE STRING ///////////////////////////

+ 10 - 2
editor/editor_properties_array_dict.h

@@ -219,7 +219,6 @@ class EditorPropertyDictionary : public EditorProperty {
 	EditorSpinSlider *size_sliderv = nullptr;
 	EditorSpinSlider *size_sliderv = nullptr;
 	Button *button_add_item = nullptr;
 	Button *button_add_item = nullptr;
 	EditorPaginator *paginator = nullptr;
 	EditorPaginator *paginator = nullptr;
-	PropertyHint property_hint;
 	LocalVector<Slot> slots;
 	LocalVector<Slot> slots;
 	void _create_new_property_slot(int p_idx);
 	void _create_new_property_slot(int p_idx);
 
 
@@ -231,12 +230,21 @@ class EditorPropertyDictionary : public EditorProperty {
 
 
 	void _add_key_value();
 	void _add_key_value();
 	void _object_id_selected(const StringName &p_property, ObjectID p_id);
 	void _object_id_selected(const StringName &p_property, ObjectID p_id);
+	void _remove_pressed(int p_slot_index);
+
+	Variant::Type key_subtype;
+	PropertyHint key_subtype_hint;
+	String key_subtype_hint_string;
+	Variant::Type value_subtype;
+	PropertyHint value_subtype_hint;
+	String value_subtype_hint_string;
+	void initialize_dictionary(Variant &p_dictionary);
 
 
 protected:
 protected:
 	void _notification(int p_what);
 	void _notification(int p_what);
 
 
 public:
 public:
-	void setup(PropertyHint p_hint);
+	void setup(PropertyHint p_hint, const String &p_hint_string = "");
 	virtual void update_property() override;
 	virtual void update_property() override;
 	virtual bool is_colored(ColorationMode p_mode) override;
 	virtual bool is_colored(ColorationMode p_mode) override;
 	EditorPropertyDictionary();
 	EditorPropertyDictionary();

+ 75 - 16
modules/gdscript/editor/gdscript_docgen.cpp

@@ -84,6 +84,15 @@ void GDScriptDocGen::_doctype_from_gdtype(const GDType &p_gdtype, String &r_type
 					return;
 					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);
 			r_type = Variant::get_type_name(p_gdtype.builtin_type);
 			return;
 			return;
 		case GDType::NATIVE:
 		case GDType::NATIVE:
@@ -155,34 +164,82 @@ String GDScriptDocGen::_docvalue_from_variant(const Variant &p_variant, int p_re
 			return "<Object>";
 			return "<Object>";
 		case Variant::DICTIONARY: {
 		case Variant::DICTIONARY: {
 			const Dictionary dict = p_variant;
 			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;
 		} break;
 		case Variant::ARRAY: {
 		case Variant::ARRAY: {
 			const Array array = p_variant;
 			const Array array = p_variant;
 			String result;
 			String result;
 
 
-			if (array.get_typed_builtin() != Variant::NIL) {
+			if (array.is_typed()) {
 				result += "Array[";
 				result += "Array[";
 
 
 				Ref<Script> script = array.get_typed_script();
 				Ref<Script> script = array.get_typed_script();
@@ -209,16 +266,18 @@ String GDScriptDocGen::_docvalue_from_variant(const Variant &p_variant, int p_re
 				result += "[...]";
 				result += "[...]";
 			} else {
 			} else {
 				result += "[";
 				result += "[";
+
 				for (int i = 0; i < array.size(); i++) {
 				for (int i = 0; i < array.size(); i++) {
 					if (i > 0) {
 					if (i > 0) {
 						result += ", ";
 						result += ", ";
 					}
 					}
 					result += _docvalue_from_variant(array[i], p_recursion_level + 1);
 					result += _docvalue_from_variant(array[i], p_recursion_level + 1);
 				}
 				}
+
 				result += "]";
 				result += "]";
 			}
 			}
 
 
-			if (array.get_typed_builtin() != Variant::NIL) {
+			if (array.is_typed()) {
 				result += ")";
 				result += ")";
 			}
 			}
 
 

+ 293 - 15
modules/gdscript/gdscript_analyzer.cpp

@@ -724,6 +724,18 @@ GDScriptParser::DataType GDScriptAnalyzer::resolve_datatype(GDScriptParser::Type
 					result.set_container_element_type(0, container_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)) {
 		} else if (class_exists(first)) {
 			// Native engine classes.
 			// Native engine classes.
 			result.kind = GDScriptParser::DataType::NATIVE;
 			result.kind = GDScriptParser::DataType::NATIVE;
@@ -884,11 +896,16 @@ GDScriptParser::DataType GDScriptAnalyzer::resolve_datatype(GDScriptParser::Type
 	if (!p_type->container_types.is_empty()) {
 	if (!p_type->container_types.is_empty()) {
 		if (result.builtin_type == Variant::ARRAY) {
 		if (result.builtin_type == Variant::ARRAY) {
 			if (p_type->container_types.size() != 1) {
 			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;
 				return bad_type;
 			}
 			}
 		} else {
 		} 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;
 			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)) {
 			if (has_specified_type && specified_type.has_container_element_type(0)) {
 				update_array_literal_element_type(array, specified_type.get_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) {
 		if (is_constant && !p_assignable->initializer->is_constant) {
@@ -1987,7 +2009,7 @@ void GDScriptAnalyzer::resolve_assignable(GDScriptParser::AssignableNode *p_assi
 				} else {
 				} 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);
 					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);
 				mark_node_unsafe(p_assignable->initializer);
 #ifdef DEBUG_ENABLED
 #ifdef DEBUG_ENABLED
 			} else if (specified_type.builtin_type == Variant::INT && initializer_type.builtin_type == Variant::FLOAT) {
 			} 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)) {
 				} else if (!is_type_compatible(specified_type, variable_type)) {
 					p_for->use_conversion_assign = true;
 					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);
 			p_for->variable->set_datatype(specified_type);
@@ -2432,6 +2458,9 @@ void GDScriptAnalyzer::resolve_return(GDScriptParser::ReturnNode *p_return) {
 		} else {
 		} else {
 			if (p_return->return_value->type == GDScriptParser::Node::ARRAY && has_expected_type && expected_type.has_container_element_type(0)) {
 			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));
 				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) {
 			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");
 				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);
 	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) {
 void GDScriptAnalyzer::reduce_assignment(GDScriptParser::AssignmentNode *p_assignment) {
 	reduce_expression(p_assignment->assigned_value);
 	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)) {
 	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));
 		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) {
 	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
 					// weak non-variant assignee and incompatible result
 					downgrades_assignee = true;
 					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);
 				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) {
 void GDScriptAnalyzer::reduce_call(GDScriptParser::CallNode *p_call, bool p_is_await, bool p_is_root) {
 	bool all_is_constant = true;
 	bool all_is_constant = true;
 	HashMap<int, GDScriptParser::ArrayNode *> arrays; // For array literal to potentially type when passing.
 	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++) {
 	for (int i = 0; i < p_call->arguments.size(); i++) {
 		reduce_expression(p_call->arguments[i]);
 		reduce_expression(p_call->arguments[i]);
 		if (p_call->arguments[i]->type == GDScriptParser::Node::ARRAY) {
 		if (p_call->arguments[i]->type == GDScriptParser::Node::ARRAY) {
 			arrays[i] = static_cast<GDScriptParser::ArrayNode *>(p_call->arguments[i]);
 			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;
 		all_is_constant = all_is_constant && p_call->arguments[i]->is_constant;
 	}
 	}
@@ -3437,6 +3520,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));
 				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);
 		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) {
 		if (base_type.kind == GDScriptParser::DataType::ENUM && base_type.is_meta_type) {
@@ -3567,6 +3658,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));
 		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()) {
 	if (!cast_type.is_variant()) {
 		GDScriptParser::DataType op_type = p_cast->operand->get_datatype();
 		GDScriptParser::DataType op_type = p_cast->operand->get_datatype();
 		if (op_type.is_variant() || !op_type.is_hard_type()) {
 		if (op_type.is_variant() || !op_type.is_hard_type()) {
@@ -4591,10 +4687,23 @@ void GDScriptAnalyzer::reduce_subscript(GDScriptParser::SubscriptNode *p_subscri
 			reduce_identifier_from_base(p_subscript->attribute, &base_type);
 			reduce_identifier_from_base(p_subscript->attribute, &base_type);
 			GDScriptParser::DataType attr_type = p_subscript->attribute->get_datatype();
 			GDScriptParser::DataType attr_type = p_subscript->attribute->get_datatype();
 			if (attr_type.is_set()) {
 			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) {
 			} else if (!base_type.is_meta_type || !base_type.is_constant) {
 				valid = base_type.kind != GDScriptParser::DataType::BUILTIN;
 				valid = base_type.kind != GDScriptParser::DataType::BUILTIN;
 #ifdef DEBUG_ENABLED
 #ifdef DEBUG_ENABLED
@@ -4701,8 +4810,40 @@ void GDScriptAnalyzer::reduce_subscript(GDScriptParser::SubscriptNode *p_subscri
 							case Variant::SIGNAL:
 							case Variant::SIGNAL:
 							case Variant::STRING_NAME:
 							case Variant::STRING_NAME:
 								break;
 								break;
-							// Here for completeness.
+							// Support depends on if the dictionary has a typed key, otherwise anything is valid.
 							case Variant::DICTIONARY:
 							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:
 							case Variant::VARIANT_MAX:
 								break;
 								break;
 						}
 						}
@@ -4791,7 +4932,6 @@ void GDScriptAnalyzer::reduce_subscript(GDScriptParser::SubscriptNode *p_subscri
 					case Variant::PROJECTION:
 					case Variant::PROJECTION:
 					case Variant::PLANE:
 					case Variant::PLANE:
 					case Variant::COLOR:
 					case Variant::COLOR:
-					case Variant::DICTIONARY:
 					case Variant::OBJECT:
 					case Variant::OBJECT:
 						result_type.kind = GDScriptParser::DataType::VARIANT;
 						result_type.kind = GDScriptParser::DataType::VARIANT;
 						result_type.type_source = GDScriptParser::DataType::UNDETECTED;
 						result_type.type_source = GDScriptParser::DataType::UNDETECTED;
@@ -4806,6 +4946,16 @@ void GDScriptAnalyzer::reduce_subscript(GDScriptParser::SubscriptNode *p_subscri
 							result_type.type_source = GDScriptParser::DataType::UNDETECTED;
 							result_type.type_source = GDScriptParser::DataType::UNDETECTED;
 						}
 						}
 						break;
 						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.
 					// Here for completeness.
 					case Variant::VARIANT_MAX:
 					case Variant::VARIANT_MAX:
 						break;
 						break;
@@ -4985,7 +5135,9 @@ Variant GDScriptAnalyzer::make_array_reduced_value(GDScriptParser::ArrayNode *p_
 }
 }
 
 
 Variant GDScriptAnalyzer::make_dictionary_reduced_value(GDScriptParser::DictionaryNode *p_dictionary, bool &is_reduced) {
 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++) {
 	for (int i = 0; i < p_dictionary->elements.size(); i++) {
 		const GDScriptParser::DictionaryNode::Pair &element = p_dictionary->elements[i];
 		const GDScriptParser::DictionaryNode::Pair &element = p_dictionary->elements[i];
@@ -5072,6 +5224,49 @@ Array GDScriptAnalyzer::make_array_from_element_datatype(const GDScriptParser::D
 	return array;
 	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 GDScriptAnalyzer::make_variable_default_value(GDScriptParser::VariableNode *p_variable) {
 	Variant result = Variant();
 	Variant result = Variant();
 
 
@@ -5087,6 +5282,10 @@ Variant GDScriptAnalyzer::make_variable_default_value(GDScriptParser::VariableNo
 			if (datatype.kind == GDScriptParser::DataType::BUILTIN && datatype.builtin_type != Variant::OBJECT) {
 			if (datatype.kind == GDScriptParser::DataType::BUILTIN && datatype.builtin_type != Variant::OBJECT) {
 				if (datatype.builtin_type == Variant::ARRAY && datatype.has_container_element_type(0)) {
 				if (datatype.builtin_type == Variant::ARRAY && datatype.has_container_element_type(0)) {
 					result = make_array_from_element_datatype(datatype.get_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 {
 				} else {
 					VariantInternal::initialize(&result, datatype.builtin_type);
 					VariantInternal::initialize(&result, datatype.builtin_type);
 				}
 				}
@@ -5115,6 +5314,22 @@ GDScriptParser::DataType GDScriptAnalyzer::type_from_variant(const Variant &p_va
 		} else if (array.get_typed_builtin() != Variant::NIL) {
 		} 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())));
 			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) {
 	} else if (p_value.get_type() == Variant::OBJECT) {
 		// Object is treated as a native type, not a builtin type.
 		// Object is treated as a native type, not a builtin type.
 		result.kind = GDScriptParser::DataType::NATIVE;
 		result.kind = GDScriptParser::DataType::NATIVE;
@@ -5247,6 +5462,60 @@ GDScriptParser::DataType GDScriptAnalyzer::type_from_property(const PropertyInfo
 			}
 			}
 			elem_type.is_constant = false;
 			elem_type.is_constant = false;
 			result.set_container_element_type(0, elem_type);
 			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) {
 		} else if (p_property.type == Variant::INT) {
 			// Check if it's enum.
 			// Check if it's enum.
 			if ((p_property.usage & PROPERTY_USAGE_CLASS_IS_ENUM) && p_property.class_name != StringName()) {
 			if ((p_property.usage & PROPERTY_USAGE_CLASS_IS_ENUM) && p_property.class_name != StringName()) {
@@ -5667,6 +5936,15 @@ bool GDScriptAnalyzer::check_type_compatibility(const GDScriptParser::DataType &
 				valid = p_target.get_container_element_type(0) == p_source.get_container_element_type(0);
 				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;
 		return valid;
 	}
 	}
 
 

+ 2 - 0
modules/gdscript/gdscript_analyzer.h

@@ -125,6 +125,7 @@ class GDScriptAnalyzer {
 
 
 	// Helpers.
 	// Helpers.
 	Array make_array_from_element_datatype(const GDScriptParser::DataType &p_element_datatype, const GDScriptParser::Node *p_source_node = nullptr);
 	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);
 	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);
 	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;
 	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);
 	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_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_array_literal_element_type(GDScriptParser::ArrayNode *p_array, const GDScriptParser::DataType &p_element_type);
+	void update_dictionary_literal_element_type(GDScriptParser::DictionaryNode *p_dictionary, const GDScriptParser::DataType &p_key_element_type, const GDScriptParser::DataType &p_value_element_type);
 	bool is_type_compatible(const GDScriptParser::DataType &p_target, const GDScriptParser::DataType &p_source, bool p_allow_implicit_conversion = false, const GDScriptParser::Node *p_source_node = nullptr);
 	bool is_type_compatible(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 push_error(const String &p_message, const GDScriptParser::Node *p_origin = nullptr);
 	void mark_node_unsafe(const GDScriptParser::Node *p_node);
 	void mark_node_unsafe(const GDScriptParser::Node *p_node);

+ 84 - 0
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(get_constant_pos(element_type.script_type) | (GDScriptFunction::ADDR_TYPE_CONSTANT << GDScriptFunction::ADDR_BITS));
 				append(element_type.builtin_type);
 				append(element_type.builtin_type);
 				append(element_type.native_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 {
 			} else {
 				append_opcode(GDScriptFunction::OPCODE_TYPE_TEST_BUILTIN);
 				append_opcode(GDScriptFunction::OPCODE_TYPE_TEST_BUILTIN);
 				append(p_target);
 				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(get_constant_pos(element_type.script_type) | (GDScriptFunction::ADDR_TYPE_CONSTANT << GDScriptFunction::ADDR_BITS));
 				append(element_type.builtin_type);
 				append(element_type.builtin_type);
 				append(element_type.native_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 {
 			} else {
 				append_opcode(GDScriptFunction::OPCODE_ASSIGN_TYPED_BUILTIN);
 				append_opcode(GDScriptFunction::OPCODE_ASSIGN_TYPED_BUILTIN);
 				append(p_target);
 				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(get_constant_pos(element_type.script_type) | (GDScriptFunction::ADDR_TYPE_CONSTANT << GDScriptFunction::ADDR_BITS));
 		append(element_type.builtin_type);
 		append(element_type.builtin_type);
 		append(element_type.native_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) {
 	} 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.
 		// Need conversion.
 		append_opcode(GDScriptFunction::OPCODE_ASSIGN_TYPED_BUILTIN);
 		append_opcode(GDScriptFunction::OPCODE_ASSIGN_TYPED_BUILTIN);
@@ -1434,6 +1470,23 @@ void GDScriptByteCodeGenerator::write_construct_dictionary(const Address &p_targ
 	ct.cleanup();
 	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) {
 void GDScriptByteCodeGenerator::write_await(const Address &p_target, const Address &p_operand) {
 	append_opcode(GDScriptFunction::OPCODE_AWAIT);
 	append_opcode(GDScriptFunction::OPCODE_AWAIT);
 	append(p_operand);
 	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(get_constant_pos(element_type.script_type) | (GDScriptFunction::ADDR_TYPE_CONSTANT << GDScriptFunction::ADDR_BITS));
 				append(element_type.builtin_type);
 				append(element_type.builtin_type);
 				append(element_type.native_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) {
 			} 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.
 				// Add conversion.
 				append_opcode(GDScriptFunction::OPCODE_RETURN_TYPED_BUILTIN);
 				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(get_constant_pos(element_type.script_type) | (GDScriptFunction::ADDR_TYPE_CONSTANT << GDScriptFunction::ADDR_BITS));
 					append(element_type.builtin_type);
 					append(element_type.builtin_type);
 					append(element_type.native_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 {
 				} else {
 					append_opcode(GDScriptFunction::OPCODE_RETURN_TYPED_BUILTIN);
 					append_opcode(GDScriptFunction::OPCODE_RETURN_TYPED_BUILTIN);
 					append(p_return_value);
 					append(p_return_value);
@@ -1803,6 +1880,13 @@ void GDScriptByteCodeGenerator::clear_address(const Address &p_address) {
 			case Variant::BOOL:
 			case Variant::BOOL:
 				write_assign_false(p_address);
 				write_assign_false(p_address);
 				break;
 				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:
 			case Variant::ARRAY:
 				if (p_address.type.has_container_element_type(0)) {
 				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>());
 					write_construct_typed_array(p_address, p_address.type.get_container_element_type(0), Vector<GDScriptCodeGenerator::Address>());

+ 1 - 0
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_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_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_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_await(const Address &p_target, const Address &p_operand) override;
 	virtual void write_if(const Address &p_condition) override;
 	virtual void write_if(const Address &p_condition) override;
 	virtual void write_else() override;
 	virtual void write_else() override;

+ 1 - 0
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_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_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_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_await(const Address &p_target, const Address &p_operand) = 0;
 	virtual void write_if(const Address &p_condition) = 0;
 	virtual void write_if(const Address &p_condition) = 0;
 	virtual void write_else() = 0;
 	virtual void write_else() = 0;

+ 17 - 7
modules/gdscript/gdscript_compiler.cpp

@@ -532,10 +532,7 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &code
 			Vector<GDScriptCodeGenerator::Address> elements;
 			Vector<GDScriptCodeGenerator::Address> elements;
 
 
 			// Create the result temporary first since it's the last to be killed.
 			// 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);
 			GDScriptCodeGenerator::Address result = codegen.add_temporary(dict_type);
 
 
 			for (int i = 0; i < dn->elements.size(); i++) {
 			for (int i = 0; i < dn->elements.size(); i++) {
@@ -566,7 +563,11 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &code
 				elements.push_back(element);
 				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++) {
 			for (int i = 0; i < elements.size(); i++) {
 				if (elements[i].mode == GDScriptCodeGenerator::Address::TEMPORARY) {
 				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);
 				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>());
 					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) {
 				} else if (field_type.kind == GDScriptDataType::BUILTIN) {
 					codegen.generator->write_construct(dst_address, field_type.builtin_type, Vector<GDScriptCodeGenerator::Address>());
 					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) {
 		if (field_type.has_type) {
 			codegen.generator->write_newline(field->start_line);
 			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);
 				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_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->write_set_static_variable(temp, class_addr, p_script->static_variables_indices[field->identifier->name].index);
 				codegen.generator->pop_temporary();
 				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) {
 			} else if (field_type.kind == GDScriptDataType::BUILTIN) {
 				GDScriptCodeGenerator::Address temp = codegen.add_temporary(field_type);
 				GDScriptCodeGenerator::Address temp = codegen.add_temporary(field_type);
 				codegen.generator->write_construct(temp, field_type.builtin_type, Vector<GDScriptCodeGenerator::Address>());
 				codegen.generator->write_construct(temp, field_type.builtin_type, Vector<GDScriptCodeGenerator::Address>());

+ 107 - 0
modules/gdscript/gdscript_disassembler.cpp

@@ -176,6 +176,47 @@ void GDScriptFunction::disassemble(const Vector<String> &p_code_lines) const {
 
 
 				incr += 6;
 				incr += 6;
 			} break;
 			} 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: {
 			case OPCODE_TYPE_TEST_NATIVE: {
 				text += "type test ";
 				text += "type test ";
 				text += DADDR(1);
 				text += DADDR(1);
@@ -399,6 +440,14 @@ void GDScriptFunction::disassemble(const Vector<String> &p_code_lines) const {
 
 
 				incr += 6;
 				incr += 6;
 			} break;
 			} 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: {
 			case OPCODE_ASSIGN_TYPED_NATIVE: {
 				text += "assign typed native (";
 				text += "assign typed native (";
 				text += DADDR(3);
 				text += DADDR(3);
@@ -564,6 +613,58 @@ void GDScriptFunction::disassemble(const Vector<String> &p_code_lines) const {
 
 
 				incr += 3 + argc * 2;
 				incr += 3 + argc * 2;
 			} break;
 			} 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:
 			case OPCODE_CALL_RETURN:
 			case OPCODE_CALL_RETURN:
 			case OPCODE_CALL_ASYNC: {
 			case OPCODE_CALL_ASYNC: {
@@ -978,6 +1079,12 @@ void GDScriptFunction::disassemble(const Vector<String> &p_code_lines) const {
 
 
 				incr += 5;
 				incr += 5;
 			} break;
 			} break;
+			case OPCODE_RETURN_TYPED_DICTIONARY: {
+				text += "return typed dictionary ";
+				text += DADDR(1);
+
+				incr += 8;
+			} break;
 			case OPCODE_RETURN_TYPED_NATIVE: {
 			case OPCODE_RETURN_TYPED_NATIVE: {
 				text += "return typed native (";
 				text += "return typed native (";
 				text += DADDR(2);
 				text += DADDR(2);

+ 4 - 0
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);
 		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()) {
 	} 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) + "]";
 		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) {
 	} else if (p_info.type == Variant::NIL) {
 		if (p_is_arg || (p_info.usage & PROPERTY_USAGE_NIL_IS_VARIANT)) {
 		if (p_is_arg || (p_info.usage & PROPERTY_USAGE_NIL_IS_VARIANT)) {
 			return "Variant";
 			return "Variant";

+ 43 - 0
modules/gdscript/gdscript_function.h

@@ -93,6 +93,41 @@ public:
 					} else {
 					} else {
 						valid = false;
 						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) {
 				} else if (!valid && p_allow_implicit_conversion) {
 					valid = Variant::can_convert_strict(var_type, builtin_type);
 					valid = Variant::can_convert_strict(var_type, builtin_type);
 				}
 				}
@@ -156,6 +191,10 @@ public:
 					}
 					}
 					return true;
 					return true;
 				case Variant::DICTIONARY:
 				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::NIL:
 				case Variant::OBJECT:
 				case Variant::OBJECT:
 					return true;
 					return true;
@@ -220,6 +259,7 @@ public:
 		OPCODE_OPERATOR_VALIDATED,
 		OPCODE_OPERATOR_VALIDATED,
 		OPCODE_TYPE_TEST_BUILTIN,
 		OPCODE_TYPE_TEST_BUILTIN,
 		OPCODE_TYPE_TEST_ARRAY,
 		OPCODE_TYPE_TEST_ARRAY,
+		OPCODE_TYPE_TEST_DICTIONARY,
 		OPCODE_TYPE_TEST_NATIVE,
 		OPCODE_TYPE_TEST_NATIVE,
 		OPCODE_TYPE_TEST_SCRIPT,
 		OPCODE_TYPE_TEST_SCRIPT,
 		OPCODE_SET_KEYED,
 		OPCODE_SET_KEYED,
@@ -242,6 +282,7 @@ public:
 		OPCODE_ASSIGN_FALSE,
 		OPCODE_ASSIGN_FALSE,
 		OPCODE_ASSIGN_TYPED_BUILTIN,
 		OPCODE_ASSIGN_TYPED_BUILTIN,
 		OPCODE_ASSIGN_TYPED_ARRAY,
 		OPCODE_ASSIGN_TYPED_ARRAY,
+		OPCODE_ASSIGN_TYPED_DICTIONARY,
 		OPCODE_ASSIGN_TYPED_NATIVE,
 		OPCODE_ASSIGN_TYPED_NATIVE,
 		OPCODE_ASSIGN_TYPED_SCRIPT,
 		OPCODE_ASSIGN_TYPED_SCRIPT,
 		OPCODE_CAST_TO_BUILTIN,
 		OPCODE_CAST_TO_BUILTIN,
@@ -252,6 +293,7 @@ public:
 		OPCODE_CONSTRUCT_ARRAY,
 		OPCODE_CONSTRUCT_ARRAY,
 		OPCODE_CONSTRUCT_TYPED_ARRAY,
 		OPCODE_CONSTRUCT_TYPED_ARRAY,
 		OPCODE_CONSTRUCT_DICTIONARY,
 		OPCODE_CONSTRUCT_DICTIONARY,
+		OPCODE_CONSTRUCT_TYPED_DICTIONARY,
 		OPCODE_CALL,
 		OPCODE_CALL,
 		OPCODE_CALL_RETURN,
 		OPCODE_CALL_RETURN,
 		OPCODE_CALL_ASYNC,
 		OPCODE_CALL_ASYNC,
@@ -280,6 +322,7 @@ public:
 		OPCODE_RETURN,
 		OPCODE_RETURN,
 		OPCODE_RETURN_TYPED_BUILTIN,
 		OPCODE_RETURN_TYPED_BUILTIN,
 		OPCODE_RETURN_TYPED_ARRAY,
 		OPCODE_RETURN_TYPED_ARRAY,
+		OPCODE_RETURN_TYPED_DICTIONARY,
 		OPCODE_RETURN_TYPED_NATIVE,
 		OPCODE_RETURN_TYPED_NATIVE,
 		OPCODE_RETURN_TYPED_SCRIPT,
 		OPCODE_RETURN_TYPED_SCRIPT,
 		OPCODE_ITERATE_BEGIN,
 		OPCODE_ITERATE_BEGIN,

+ 214 - 4
modules/gdscript/gdscript_parser.cpp

@@ -3554,7 +3554,7 @@ GDScriptParser::TypeNode *GDScriptParser::parse_type(bool p_allow_void) {
 	type->type_chain.push_back(type_element);
 	type->type_chain.push_back(type_element);
 
 
 	if (match(GDScriptTokenizer::Token::BRACKET_OPEN)) {
 	if (match(GDScriptTokenizer::Token::BRACKET_OPEN)) {
-		// Typed collection (like Array[int]).
+		// Typed collection (like Array[int], Dictionary[String, int]).
 		bool first_pass = true;
 		bool first_pass = true;
 		do {
 		do {
 			TypeNode *container_type = parse_type(false); // Don't allow void for element type.
 			TypeNode *container_type = parse_type(false); // Don't allow void for element type.
@@ -4371,6 +4371,14 @@ bool GDScriptParser::export_annotations(AnnotationNode *p_annotation, Node *p_ta
 		export_type.type_source = variable->datatype.type_source;
 		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;
 	bool use_default_variable_type_check = true;
 
 
 	if (p_annotation->name == SNAME("@export_range")) {
 	if (p_annotation->name == SNAME("@export_range")) {
@@ -4398,8 +4406,13 @@ bool GDScriptParser::export_annotations(AnnotationNode *p_annotation, Node *p_ta
 		}
 		}
 
 
 		if (export_type.is_variant() || export_type.has_no_type()) {
 		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) {
 		switch (export_type.kind) {
@@ -4459,6 +4472,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);
 			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;
 			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")) {
 	} else if (p_annotation->name == SNAME("@export_enum")) {
 		use_default_variable_type_check = false;
 		use_default_variable_type_check = false;
 
 
@@ -4780,7 +4877,10 @@ String GDScriptParser::DataType::to_string() const {
 				return "null";
 				return "null";
 			}
 			}
 			if (builtin_type == Variant::ARRAY && has_container_element_type(0)) {
 			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);
 			return Variant::get_type_name(builtin_type);
 		case NATIVE:
 		case NATIVE:
@@ -4869,6 +4969,72 @@ PropertyInfo GDScriptParser::DataType::to_property_info(const String &p_name) co
 					case UNRESOLVED:
 					case UNRESOLVED:
 						break;
 						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;
 			break;
 		case NATIVE:
 		case NATIVE:
@@ -4953,6 +5119,50 @@ GDScriptParser::DataType GDScriptParser::DataType::get_typed_container_type() co
 	return type;
 	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) {
 void GDScriptParser::complete_extents(Node *p_node) {
 	while (!nodes_in_progress.is_empty() && nodes_in_progress.back()->get() != p_node) {
 	while (!nodes_in_progress.is_empty() && nodes_in_progress.back()->get() != p_node) {
 		ERR_PRINT("Parser bug: Mismatch in extents tracking stack.");
 		ERR_PRINT("Parser bug: Mismatch in extents tracking stack.");

+ 2 - 0
modules/gdscript/gdscript_parser.h

@@ -189,6 +189,8 @@ public:
 
 
 		GDScriptParser::DataType get_typed_container_type() const;
 		GDScriptParser::DataType get_typed_container_type() const;
 
 
+		bool can_reference(const DataType &p_other) const;
+
 		bool operator==(const DataType &p_other) const {
 		bool operator==(const DataType &p_other) const {
 			if (type_source == UNDETECTED || p_other.type_source == UNDETECTED) {
 			if (type_source == UNDETECTED || p_other.type_source == UNDETECTED) {
 				return true; // Can be considered equal for parsing purposes.
 				return true; // Can be considered equal for parsing purposes.

+ 190 - 4
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) {
 		if (p_var->get_type() == Variant::ARRAY) {
 			basestr = "Array";
 			basestr = "Array";
 			const Array *p_array = VariantInternal::get_array(p_var);
 			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 {
 		} else {
 			basestr = Variant::get_type_name(p_var->get_type());
 			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;
 			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 {
 		} else {
 			Callable::CallError ce;
 			Callable::CallError ce;
 			Variant variant;
 			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) {
 			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.";
 				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
 #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)) + ".";
 			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:
 		case Callable::CallError::CALL_ERROR_TOO_MANY_ARGUMENTS:
@@ -215,6 +234,7 @@ void (*type_init_function_table[])(Variant *) = {
 		&&OPCODE_OPERATOR_VALIDATED,                     \
 		&&OPCODE_OPERATOR_VALIDATED,                     \
 		&&OPCODE_TYPE_TEST_BUILTIN,                      \
 		&&OPCODE_TYPE_TEST_BUILTIN,                      \
 		&&OPCODE_TYPE_TEST_ARRAY,                        \
 		&&OPCODE_TYPE_TEST_ARRAY,                        \
+		&&OPCODE_TYPE_TEST_DICTIONARY,                   \
 		&&OPCODE_TYPE_TEST_NATIVE,                       \
 		&&OPCODE_TYPE_TEST_NATIVE,                       \
 		&&OPCODE_TYPE_TEST_SCRIPT,                       \
 		&&OPCODE_TYPE_TEST_SCRIPT,                       \
 		&&OPCODE_SET_KEYED,                              \
 		&&OPCODE_SET_KEYED,                              \
@@ -237,6 +257,7 @@ void (*type_init_function_table[])(Variant *) = {
 		&&OPCODE_ASSIGN_FALSE,                           \
 		&&OPCODE_ASSIGN_FALSE,                           \
 		&&OPCODE_ASSIGN_TYPED_BUILTIN,                   \
 		&&OPCODE_ASSIGN_TYPED_BUILTIN,                   \
 		&&OPCODE_ASSIGN_TYPED_ARRAY,                     \
 		&&OPCODE_ASSIGN_TYPED_ARRAY,                     \
+		&&OPCODE_ASSIGN_TYPED_DICTIONARY,                \
 		&&OPCODE_ASSIGN_TYPED_NATIVE,                    \
 		&&OPCODE_ASSIGN_TYPED_NATIVE,                    \
 		&&OPCODE_ASSIGN_TYPED_SCRIPT,                    \
 		&&OPCODE_ASSIGN_TYPED_SCRIPT,                    \
 		&&OPCODE_CAST_TO_BUILTIN,                        \
 		&&OPCODE_CAST_TO_BUILTIN,                        \
@@ -247,6 +268,7 @@ void (*type_init_function_table[])(Variant *) = {
 		&&OPCODE_CONSTRUCT_ARRAY,                        \
 		&&OPCODE_CONSTRUCT_ARRAY,                        \
 		&&OPCODE_CONSTRUCT_TYPED_ARRAY,                  \
 		&&OPCODE_CONSTRUCT_TYPED_ARRAY,                  \
 		&&OPCODE_CONSTRUCT_DICTIONARY,                   \
 		&&OPCODE_CONSTRUCT_DICTIONARY,                   \
+		&&OPCODE_CONSTRUCT_TYPED_DICTIONARY,             \
 		&&OPCODE_CALL,                                   \
 		&&OPCODE_CALL,                                   \
 		&&OPCODE_CALL_RETURN,                            \
 		&&OPCODE_CALL_RETURN,                            \
 		&&OPCODE_CALL_ASYNC,                             \
 		&&OPCODE_CALL_ASYNC,                             \
@@ -275,6 +297,7 @@ void (*type_init_function_table[])(Variant *) = {
 		&&OPCODE_RETURN,                                 \
 		&&OPCODE_RETURN,                                 \
 		&&OPCODE_RETURN_TYPED_BUILTIN,                   \
 		&&OPCODE_RETURN_TYPED_BUILTIN,                   \
 		&&OPCODE_RETURN_TYPED_ARRAY,                     \
 		&&OPCODE_RETURN_TYPED_ARRAY,                     \
+		&&OPCODE_RETURN_TYPED_DICTIONARY,                \
 		&&OPCODE_RETURN_TYPED_NATIVE,                    \
 		&&OPCODE_RETURN_TYPED_NATIVE,                    \
 		&&OPCODE_RETURN_TYPED_SCRIPT,                    \
 		&&OPCODE_RETURN_TYPED_SCRIPT,                    \
 		&&OPCODE_ITERATE_BEGIN,                          \
 		&&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);
 				return _get_default_variant_for_data_type(return_type);
 			}
 			}
 			if (argument_types[i].kind == GDScriptDataType::BUILTIN) {
 			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];
 					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);
 					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));
 					memnew_placement(&stack[i + 3], Variant(array));
@@ -827,6 +855,36 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a
 			}
 			}
 			DISPATCH_OPCODE;
 			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) {
 			OPCODE(OPCODE_TYPE_TEST_NATIVE) {
 				CHECK_SPACE(4);
 				CHECK_SPACE(4);
 
 
@@ -1384,6 +1442,50 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a
 			}
 			}
 			DISPATCH_OPCODE;
 			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) {
 			OPCODE(OPCODE_ASSIGN_TYPED_NATIVE) {
 				CHECK_SPACE(4);
 				CHECK_SPACE(4);
 				GET_VARIANT_PTR(dst, 0);
 				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);
 				GET_INSTRUCTION_ARG(dst, argc * 2);
 
 
+				*dst = Variant(); // Clear potential previous typed dictionary.
+
 				*dst = dict;
 				*dst = dict;
 
 
 				ip += 2;
 				ip += 2;
 			}
 			}
 			DISPATCH_OPCODE;
 			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_ASYNC)
 			OPCODE(OPCODE_CALL_RETURN)
 			OPCODE(OPCODE_CALL_RETURN)
 			OPCODE(OPCODE_CALL) {
 			OPCODE(OPCODE_CALL) {
@@ -2651,6 +2792,51 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a
 
 
 				retvalue = *array;
 				retvalue = *array;
 
 
+#ifdef DEBUG_ENABLED
+				exit_ok = true;
+#endif // DEBUG_ENABLED
+				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
 #ifdef DEBUG_ENABLED
 				exit_ok = true;
 				exit_ok = true;
 #endif // DEBUG_ENABLED
 #endif // DEBUG_ENABLED

+ 3 - 0
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)

+ 2 - 0
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".

+ 4 - 0
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')

+ 2 - 0
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].

+ 2 - 0
modules/gdscript/tests/scripts/analyzer/errors/typed_dictionary_assignment.gd

@@ -0,0 +1,2 @@
+func test():
+	const dict: Dictionary[int, int] = { "Hello": "World" }

+ 2 - 0
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".

+ 4 - 0
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')

+ 2 - 0
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]".

+ 7 - 0
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')

+ 2 - 0
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]".

+ 20 - 0
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')

+ 11 - 0
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

+ 4 - 0
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])

+ 2 - 0
modules/gdscript/tests/scripts/analyzer/features/typed_dictionary_inferred_access_isnt_constant.out

@@ -0,0 +1,2 @@
+GDTEST_OK
+1

+ 214 - 0
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')

+ 2 - 0
modules/gdscript/tests/scripts/analyzer/features/typed_dictionary_usage.out

@@ -0,0 +1,2 @@
+GDTEST_OK
+ok

+ 9 - 0
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)

+ 2 - 0
modules/gdscript/tests/scripts/analyzer/features/typed_dictionary_with_custom_class.out

@@ -0,0 +1,2 @@
+GDTEST_OK
+Inner

+ 5 - 0
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)

+ 3 - 0
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" }

+ 4 - 0
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')

+ 6 - 0
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]".

+ 4 - 0
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')

+ 6 - 0
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]".

+ 7 - 0
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')

+ 5 - 0
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

+ 7 - 0
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')

+ 6 - 0
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.

+ 7 - 0
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')

+ 6 - 0
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.

+ 7 - 2
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))
 		prints(var_to_str(e), var_to_str(elem))
 
 
 	print("Test String-keys dictionary.")
 	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:
 	for k: StringName in d1:
 		var key := k
 		var key := k
 		prints(var_to_str(k), var_to_str(key))
 		prints(var_to_str(k), var_to_str(key))
 
 
 	print("Test RefCounted-keys dictionary.")
 	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:
 	for k: RefCounted in d2:
 		var key := k
 		var key := k
 		prints(k.get_class(), key.get_class())
 		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))

+ 4 - 0
modules/gdscript/tests/scripts/runtime/features/for_loop_iterator_specified_types.out

@@ -27,3 +27,7 @@ Test RefCounted-keys dictionary.
 RefCounted RefCounted
 RefCounted RefCounted
 Resource Resource
 Resource Resource
 ConfigFile ConfigFile
 ConfigFile ConfigFile
+Test implicitly typed dictionary literal.
+&"x" &"x"
+&"y" &"y"
+&"z" &"z"

+ 17 - 7
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_resource: Array[Resource]
 var test_var_hard_array_this: Array[TestMemberInfo]
 var test_var_hard_array_this: Array[TestMemberInfo]
 var test_var_hard_array_my_class: Array[MyClass]
 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_resource: Resource
 var test_var_hard_this: TestMemberInfo
 var test_var_hard_this: TestMemberInfo
 var test_var_hard_my_class: MyClass
 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_weak_int(): return 1
 func test_func_hard_variant() -> Variant: return null
 func test_func_hard_variant() -> Variant: return null
 func test_func_hard_int() -> int: return 1
 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
 func test_func_args_2(_a = 1, _b = _a, _c = [2], _d = 3): pass
 
 
 signal test_signal_1()
 signal test_signal_1()
 signal test_signal_2(a: Variant, b)
 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():
 func no_exec():
 	test_signal_1.emit()
 	test_signal_1.emit()

+ 17 - 7
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_resource: Array[Resource]
 var test_var_hard_array_this: Array[TestMemberInfo]
 var test_var_hard_array_this: Array[TestMemberInfo]
 var test_var_hard_array_my_class: Array[RefCounted]
 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_resource: Resource
 var test_var_hard_this: TestMemberInfo
 var test_var_hard_this: TestMemberInfo
 var test_var_hard_my_class: RefCounted
 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_weak_int() -> Variant
 func test_func_hard_variant() -> Variant
 func test_func_hard_variant() -> Variant
 func test_func_hard_int() -> int
 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
 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_1()
 signal test_signal_2(a: Variant, b: Variant)
 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])

+ 6 - 0
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" })

+ 3 - 0
modules/gdscript/tests/scripts/runtime/features/typed_dictionary_implicit_cast_param.out

@@ -0,0 +1,3 @@
+GDTEST_OK
+true
+true

+ 7 - 0
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')

+ 2 - 0
modules/gdscript/tests/scripts/runtime/features/typed_dictionary_init_with_untyped_in_literal.out

@@ -0,0 +1,2 @@
+GDTEST_OK
+ok

+ 7 - 0
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():
 				if str(property.hint_string).is_empty():
 					return "Array[<unknown type>]"
 					return "Array[<unknown type>]"
 				return "Array[%s]" % property.hint_string
 				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:
 		TYPE_OBJECT:
 			if not str(property.class_name).is_empty():
 			if not str(property.class_name).is_empty():
 				return property.class_name
 				return property.class_name
@@ -188,6 +193,8 @@ static func get_property_hint_name(hint: PropertyHint) -> String:
 			return "PROPERTY_HINT_INT_IS_POINTER"
 			return "PROPERTY_HINT_INT_IS_POINTER"
 		PROPERTY_HINT_ARRAY_TYPE:
 		PROPERTY_HINT_ARRAY_TYPE:
 			return "PROPERTY_HINT_ARRAY_TYPE"
 			return "PROPERTY_HINT_ARRAY_TYPE"
+		PROPERTY_HINT_DICTIONARY_TYPE:
+			return "PROPERTY_HINT_DICTIONARY_TYPE"
 		PROPERTY_HINT_LOCALE_ID:
 		PROPERTY_HINT_LOCALE_ID:
 			return "PROPERTY_HINT_LOCALE_ID"
 			return "PROPERTY_HINT_LOCALE_ID"
 		PROPERTY_HINT_LOCALIZABLE_STRING:
 		PROPERTY_HINT_LOCALIZABLE_STRING:

+ 1 - 1
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)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)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)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)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));
         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;
         return properties;

+ 1 - 1
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)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)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)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));
         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;
         return properties;
     }
     }

+ 2 - 1
modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/GodotEnums.cs

@@ -88,7 +88,8 @@ namespace Godot.SourceGenerators
         HideQuaternionEdit = 35,
         HideQuaternionEdit = 35,
         Password = 36,
         Password = 36,
         LayersAvoidance = 37,
         LayersAvoidance = 37,
-        Max = 38
+        DictionaryType = 38,
+        Max = 39
     }
     }
 
 
     [Flags]
     [Flags]

+ 8 - 0
modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/MarshalUtils.cs

@@ -274,6 +274,14 @@ namespace Godot.SourceGenerators
             return null;
             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)
         private static StringBuilder Append(this StringBuilder source, string a, string b)
             => source.Append(a).Append(b);
             => source.Append(a).Append(b);
 
 

+ 67 - 2
modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptPropertiesGenerator.cs

@@ -1,4 +1,5 @@
 using System.Collections.Generic;
 using System.Collections.Generic;
+using System.Diagnostics;
 using System.Linq;
 using System.Linq;
 using System.Text;
 using System.Text;
 using Microsoft.CodeAnalysis;
 using Microsoft.CodeAnalysis;
@@ -728,8 +729,72 @@ namespace Godot.SourceGenerators
 
 
             if (!isTypeArgument && variantType == VariantType.Dictionary)
             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;
             return false;

+ 15 - 0
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) {
 			} 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.cname = Variant::get_type_name(return_info.type) + "_@generic";
 				imethod.return_type.generic_type_parameters.push_back(TypeReference(return_info.hint_string));
 				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) {
 			} else if (return_info.hint == PROPERTY_HINT_RESOURCE_TYPE) {
 				imethod.return_type.cname = return_info.hint_string;
 				imethod.return_type.cname = return_info.hint_string;
 			} else if (return_info.type == Variant::NIL && return_info.usage & PROPERTY_USAGE_NIL_IS_VARIANT) {
 			} 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) {
 				} else if (arginfo.type == Variant::ARRAY && arginfo.hint == PROPERTY_HINT_ARRAY_TYPE) {
 					iarg.type.cname = Variant::get_type_name(arginfo.type) + "_@generic";
 					iarg.type.cname = Variant::get_type_name(arginfo.type) + "_@generic";
 					iarg.type.generic_type_parameters.push_back(TypeReference(arginfo.hint_string));
 					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) {
 				} else if (arginfo.hint == PROPERTY_HINT_RESOURCE_TYPE) {
 					iarg.type.cname = arginfo.hint_string;
 					iarg.type.cname = arginfo.hint_string;
 				} else if (arginfo.type == Variant::NIL) {
 				} 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) {
 				} else if (arginfo.type == Variant::ARRAY && arginfo.hint == PROPERTY_HINT_ARRAY_TYPE) {
 					iarg.type.cname = Variant::get_type_name(arginfo.type) + "_@generic";
 					iarg.type.cname = Variant::get_type_name(arginfo.type) + "_@generic";
 					iarg.type.generic_type_parameters.push_back(TypeReference(arginfo.hint_string));
 					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) {
 				} else if (arginfo.hint == PROPERTY_HINT_RESOURCE_TYPE) {
 					iarg.type.cname = arginfo.hint_string;
 					iarg.type.cname = arginfo.hint_string;
 				} else if (arginfo.type == Variant::NIL) {
 				} else if (arginfo.type == Variant::NIL) {

+ 102 - 15
scene/resources/packed_scene.cpp

@@ -369,6 +369,7 @@ Node *SceneState::instantiate(GenEditState p_edit_state) const {
 								value = make_local_resource(value, n, resources_local_to_sub_scene, node, snames[nprops[j].name], resources_local_to_scene, i, ret_nodes, p_edit_state);
 								value = make_local_resource(value, n, resources_local_to_sub_scene, node, snames[nprops[j].name], resources_local_to_scene, i, ret_nodes, p_edit_state);
 							}
 							}
 						}
 						}
+
 						if (value.get_type() == Variant::ARRAY) {
 						if (value.get_type() == Variant::ARRAY) {
 							Array set_array = value;
 							Array set_array = value;
 							value = setup_resources_in_array(set_array, n, resources_local_to_sub_scene, node, snames[nprops[j].name], resources_local_to_scene, i, ret_nodes, p_edit_state);
 							value = setup_resources_in_array(set_array, n, resources_local_to_sub_scene, node, snames[nprops[j].name], resources_local_to_scene, i, ret_nodes, p_edit_state);
@@ -383,25 +384,20 @@ Node *SceneState::instantiate(GenEditState p_edit_state) const {
 								}
 								}
 							}
 							}
 						}
 						}
-						if (value.get_type() == Variant::DICTIONARY) {
-							Dictionary dictionary = value;
-							const Array keys = dictionary.keys();
-							const Array values = dictionary.values();
 
 
-							if (has_local_resource(values) || has_local_resource(keys)) {
-								Array duplicated_keys = keys.duplicate(true);
-								Array duplicated_values = values.duplicate(true);
-
-								duplicated_keys = setup_resources_in_array(duplicated_keys, n, resources_local_to_sub_scene, node, snames[nprops[j].name], resources_local_to_scene, i, ret_nodes, p_edit_state);
-								duplicated_values = setup_resources_in_array(duplicated_values, n, resources_local_to_sub_scene, node, snames[nprops[j].name], resources_local_to_scene, i, ret_nodes, p_edit_state);
+						if (value.get_type() == Variant::DICTIONARY) {
+							Dictionary set_dict = value;
+							value = setup_resources_in_dictionary(set_dict, n, resources_local_to_sub_scene, node, snames[nprops[j].name], resources_local_to_scene, i, ret_nodes, p_edit_state);
 
 
-								dictionary.clear();
+							bool is_get_valid = false;
+							Variant get_value = node->get(snames[nprops[j].name], &is_get_valid);
 
 
-								for (int dictionary_index = 0; dictionary_index < keys.size(); dictionary_index++) {
-									dictionary[duplicated_keys[dictionary_index]] = duplicated_values[dictionary_index];
+							if (is_get_valid && get_value.get_type() == Variant::DICTIONARY) {
+								Dictionary get_dict = get_value;
+								if (!set_dict.is_same_typed(get_dict)) {
+									value = Dictionary(set_dict, get_dict.get_typed_key_builtin(), get_dict.get_typed_key_class_name(), get_dict.get_typed_key_script(),
+											get_dict.get_typed_value_builtin(), get_dict.get_typed_value_class_name(), get_dict.get_typed_value_script());
 								}
 								}
-
-								value = dictionary;
 							}
 							}
 						}
 						}
 
 
@@ -539,6 +535,30 @@ Node *SceneState::instantiate(GenEditState p_edit_state) const {
 				array.set(i, dnp.base->get_node_or_null(paths[i]));
 				array.set(i, dnp.base->get_node_or_null(paths[i]));
 			}
 			}
 			dnp.base->set(dnp.property, array);
 			dnp.base->set(dnp.property, array);
+		} else if (dnp.value.get_type() == Variant::DICTIONARY) {
+			Dictionary paths = dnp.value;
+
+			bool valid;
+			Dictionary dict = dnp.base->get(dnp.property, &valid);
+			ERR_CONTINUE(!valid);
+			dict = dict.duplicate();
+			bool convert_key = dict.get_typed_key_builtin() == Variant::OBJECT &&
+					ClassDB::is_parent_class(dict.get_typed_key_class_name(), "Node");
+			bool convert_value = dict.get_typed_value_builtin() == Variant::OBJECT &&
+					ClassDB::is_parent_class(dict.get_typed_value_class_name(), "Node");
+
+			for (int i = 0; i < paths.size(); i++) {
+				Variant key = paths.get_key_at_index(i);
+				if (convert_key) {
+					key = dnp.base->get_node_or_null(key);
+				}
+				Variant value = paths.get_value_at_index(i);
+				if (convert_value) {
+					value = dnp.base->get_node_or_null(value);
+				}
+				dict[key] = value;
+			}
+			dnp.base->set(dnp.property, dict);
 		} else {
 		} else {
 			dnp.base->set(dnp.property, dnp.base->get_node_or_null(dnp.value));
 			dnp.base->set(dnp.property, dnp.base->get_node_or_null(dnp.value));
 		}
 		}
@@ -641,6 +661,26 @@ Array SceneState::setup_resources_in_array(Array &p_array_to_scan, const SceneSt
 	return p_array_to_scan;
 	return p_array_to_scan;
 }
 }
 
 
+Dictionary SceneState::setup_resources_in_dictionary(Dictionary &p_dictionary_to_scan, const SceneState::NodeData &p_n, HashMap<Ref<Resource>, Ref<Resource>> &p_resources_local_to_sub_scene, Node *p_node, const StringName p_sname, HashMap<Ref<Resource>, Ref<Resource>> &p_resources_local_to_scene, int p_i, Node **p_ret_nodes, SceneState::GenEditState p_edit_state) const {
+	const Array keys = p_dictionary_to_scan.keys();
+	const Array values = p_dictionary_to_scan.values();
+
+	if (has_local_resource(values) || has_local_resource(keys)) {
+		Array duplicated_keys = keys.duplicate(true);
+		Array duplicated_values = values.duplicate(true);
+
+		duplicated_keys = setup_resources_in_array(duplicated_keys, p_n, p_resources_local_to_sub_scene, p_node, p_sname, p_resources_local_to_scene, p_i, p_ret_nodes, p_edit_state);
+		duplicated_values = setup_resources_in_array(duplicated_values, p_n, p_resources_local_to_sub_scene, p_node, p_sname, p_resources_local_to_scene, p_i, p_ret_nodes, p_edit_state);
+		p_dictionary_to_scan.clear();
+
+		for (int i = 0; i < keys.size(); i++) {
+			p_dictionary_to_scan[duplicated_keys[i]] = duplicated_values[i];
+		}
+	}
+
+	return p_dictionary_to_scan;
+}
+
 bool SceneState::has_local_resource(const Array &p_array) const {
 bool SceneState::has_local_resource(const Array &p_array) const {
 	for (int i = 0; i < p_array.size(); i++) {
 	for (int i = 0; i < p_array.size(); i++) {
 		Ref<Resource> res = p_array[i];
 		Ref<Resource> res = p_array[i];
@@ -810,6 +850,53 @@ Error SceneState::_parse_node(Node *p_owner, Node *p_node, int p_parent_idx, Has
 					value = new_array;
 					value = new_array;
 				}
 				}
 			}
 			}
+		} else if (E.type == Variant::DICTIONARY && E.hint == PROPERTY_HINT_TYPE_STRING) {
+			int key_value_separator = E.hint_string.find(";");
+			if (key_value_separator >= 0) {
+				int key_subtype_separator = E.hint_string.find(":");
+				String key_subtype_string = E.hint_string.substr(0, key_subtype_separator);
+				int key_slash_pos = key_subtype_string.find("/");
+				PropertyHint key_subtype_hint = PropertyHint::PROPERTY_HINT_NONE;
+				if (key_slash_pos >= 0) {
+					key_subtype_hint = PropertyHint(key_subtype_string.get_slice("/", 1).to_int());
+					key_subtype_string = key_subtype_string.substr(0, key_slash_pos);
+				}
+				Variant::Type key_subtype = Variant::Type(key_subtype_string.to_int());
+				bool convert_key = key_subtype == Variant::OBJECT && key_subtype_hint == PROPERTY_HINT_NODE_TYPE;
+
+				int value_subtype_separator = E.hint_string.find(":", key_value_separator) - (key_value_separator + 1);
+				String value_subtype_string = E.hint_string.substr(key_value_separator + 1, value_subtype_separator);
+				int value_slash_pos = value_subtype_string.find("/");
+				PropertyHint value_subtype_hint = PropertyHint::PROPERTY_HINT_NONE;
+				if (value_slash_pos >= 0) {
+					value_subtype_hint = PropertyHint(value_subtype_string.get_slice("/", 1).to_int());
+					value_subtype_string = value_subtype_string.substr(0, value_slash_pos);
+				}
+				Variant::Type value_subtype = Variant::Type(value_subtype_string.to_int());
+				bool convert_value = value_subtype == Variant::OBJECT && value_subtype_hint == PROPERTY_HINT_NODE_TYPE;
+
+				if (convert_key || convert_value) {
+					use_deferred_node_path_bit = true;
+					Dictionary dict = value;
+					Dictionary new_dict;
+					for (int i = 0; i < dict.size(); i++) {
+						Variant new_key = dict.get_key_at_index(i);
+						if (convert_key && new_key.get_type() == Variant::OBJECT) {
+							if (Node *n = Object::cast_to<Node>(new_key)) {
+								new_key = p_node->get_path_to(n);
+							}
+						}
+						Variant new_value = dict.get_value_at_index(i);
+						if (convert_value && new_value.get_type() == Variant::OBJECT) {
+							if (Node *n = Object::cast_to<Node>(new_value)) {
+								new_value = p_node->get_path_to(n);
+							}
+						}
+						new_dict[new_key] = new_value;
+					}
+					value = new_dict;
+				}
+			}
 		}
 		}
 
 
 		if (!pinned_props.has(name)) {
 		if (!pinned_props.has(name)) {

+ 1 - 0
scene/resources/packed_scene.h

@@ -158,6 +158,7 @@ public:
 	Node *instantiate(GenEditState p_edit_state) const;
 	Node *instantiate(GenEditState p_edit_state) const;
 
 
 	Array setup_resources_in_array(Array &array_to_scan, const SceneState::NodeData &n, HashMap<Ref<Resource>, Ref<Resource>> &resources_local_to_sub_scene, Node *node, const StringName sname, HashMap<Ref<Resource>, Ref<Resource>> &resources_local_to_scene, int i, Node **ret_nodes, SceneState::GenEditState p_edit_state) const;
 	Array setup_resources_in_array(Array &array_to_scan, const SceneState::NodeData &n, HashMap<Ref<Resource>, Ref<Resource>> &resources_local_to_sub_scene, Node *node, const StringName sname, HashMap<Ref<Resource>, Ref<Resource>> &resources_local_to_scene, int i, Node **ret_nodes, SceneState::GenEditState p_edit_state) const;
+	Dictionary setup_resources_in_dictionary(Dictionary &p_dictionary_to_scan, const SceneState::NodeData &p_n, HashMap<Ref<Resource>, Ref<Resource>> &p_resources_local_to_sub_scene, Node *p_node, const StringName p_sname, HashMap<Ref<Resource>, Ref<Resource>> &p_resources_local_to_scene, int p_i, Node **p_ret_nodes, SceneState::GenEditState p_edit_state) const;
 	Variant make_local_resource(Variant &value, const SceneState::NodeData &p_node_data, HashMap<Ref<Resource>, Ref<Resource>> &p_resources_local_to_sub_scene, Node *p_node, const StringName p_sname, HashMap<Ref<Resource>, Ref<Resource>> &p_resources_local_to_scene, int p_i, Node **p_ret_nodes, SceneState::GenEditState p_edit_state) const;
 	Variant make_local_resource(Variant &value, const SceneState::NodeData &p_node_data, HashMap<Ref<Resource>, Ref<Resource>> &p_resources_local_to_sub_scene, Node *p_node, const StringName p_sname, HashMap<Ref<Resource>, Ref<Resource>> &p_resources_local_to_scene, int p_i, Node **p_ret_nodes, SceneState::GenEditState p_edit_state) const;
 	bool has_local_resource(const Array &p_array) const;
 	bool has_local_resource(const Array &p_array) const;
 
 

+ 28 - 0
scene/resources/resource_format_text.cpp

@@ -624,6 +624,19 @@ Error ResourceLoaderText::load() {
 						}
 						}
 					}
 					}
 
 
+					if (value.get_type() == Variant::DICTIONARY) {
+						Dictionary set_dict = value;
+						bool is_get_valid = false;
+						Variant get_value = res->get(assign, &is_get_valid);
+						if (is_get_valid && get_value.get_type() == Variant::DICTIONARY) {
+							Dictionary get_dict = get_value;
+							if (!set_dict.is_same_typed(get_dict)) {
+								value = Dictionary(set_dict, get_dict.get_typed_key_builtin(), get_dict.get_typed_key_class_name(), get_dict.get_typed_key_script(),
+										get_dict.get_typed_value_builtin(), get_dict.get_typed_value_class_name(), get_dict.get_typed_value_script());
+							}
+						}
+					}
+
 					if (set_valid) {
 					if (set_valid) {
 						res->set(assign, value);
 						res->set(assign, value);
 					}
 					}
@@ -751,6 +764,19 @@ Error ResourceLoaderText::load() {
 					}
 					}
 				}
 				}
 
 
+				if (value.get_type() == Variant::DICTIONARY) {
+					Dictionary set_dict = value;
+					bool is_get_valid = false;
+					Variant get_value = resource->get(assign, &is_get_valid);
+					if (is_get_valid && get_value.get_type() == Variant::DICTIONARY) {
+						Dictionary get_dict = get_value;
+						if (!set_dict.is_same_typed(get_dict)) {
+							value = Dictionary(set_dict, get_dict.get_typed_key_builtin(), get_dict.get_typed_key_class_name(), get_dict.get_typed_key_script(),
+									get_dict.get_typed_value_builtin(), get_dict.get_typed_value_class_name(), get_dict.get_typed_value_script());
+						}
+					}
+				}
+
 				if (set_valid) {
 				if (set_valid) {
 					resource->set(assign, value);
 					resource->set(assign, value);
 				}
 				}
@@ -1642,6 +1668,8 @@ void ResourceFormatSaverTextInstance::_find_resources(const Variant &p_variant,
 		} break;
 		} break;
 		case Variant::DICTIONARY: {
 		case Variant::DICTIONARY: {
 			Dictionary d = p_variant;
 			Dictionary d = p_variant;
+			_find_resources(d.get_typed_key_script());
+			_find_resources(d.get_typed_value_script());
 			List<Variant> keys;
 			List<Variant> keys;
 			d.get_key_list(&keys);
 			d.get_key_list(&keys);
 			for (const Variant &E : keys) {
 			for (const Variant &E : keys) {

+ 38 - 1
tests/core/variant/test_dictionary.h

@@ -31,7 +31,7 @@
 #ifndef TEST_DICTIONARY_H
 #ifndef TEST_DICTIONARY_H
 #define TEST_DICTIONARY_H
 #define TEST_DICTIONARY_H
 
 
-#include "core/variant/dictionary.h"
+#include "core/variant/typed_dictionary.h"
 #include "tests/test_macros.h"
 #include "tests/test_macros.h"
 
 
 namespace TestDictionary {
 namespace TestDictionary {
@@ -536,6 +536,43 @@ TEST_CASE("[Dictionary] Order and find") {
 	CHECK_EQ(d.find_key("does not exist"), Variant());
 	CHECK_EQ(d.find_key("does not exist"), Variant());
 }
 }
 
 
+TEST_CASE("[Dictionary] Typed copying") {
+	TypedDictionary<int, int> d1;
+	d1[0] = 1;
+
+	TypedDictionary<double, double> d2;
+	d2[0] = 1.0;
+
+	Dictionary d3 = d1;
+	TypedDictionary<int, int> d4 = d3;
+
+	Dictionary d5 = d2;
+	TypedDictionary<int, int> d6 = d5;
+
+	d3[0] = 2;
+	d4[0] = 3;
+
+	// Same typed TypedDictionary should be shared.
+	CHECK_EQ(d1[0], Variant(3));
+	CHECK_EQ(d3[0], Variant(3));
+	CHECK_EQ(d4[0], Variant(3));
+
+	d5[0] = 2.0;
+	d6[0] = 3.0;
+
+	// Different typed TypedDictionary should not be shared.
+	CHECK_EQ(d2[0], Variant(2.0));
+	CHECK_EQ(d5[0], Variant(2.0));
+	CHECK_EQ(d6[0], Variant(3.0));
+
+	d1.clear();
+	d2.clear();
+	d3.clear();
+	d4.clear();
+	d5.clear();
+	d6.clear();
+}
+
 } // namespace TestDictionary
 } // namespace TestDictionary
 
 
 #endif // TEST_DICTIONARY_H
 #endif // TEST_DICTIONARY_H