From 4405e9ddbc9e12619d475b76aa670144755059b5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Pedro=20Braz?= Date: Wed, 3 May 2023 19:19:30 -0300 Subject: [PATCH] [Nullable] Implement Nullable Types for GDScript This PR aims to add a feature-set to GDScript tailored towards the safe usage of potentially-null expressions. It was initially based off godotengine/godot-proposals#162 and through several discussions over RocketChat it grew to be what it is currently. --- core/core_constants.cpp | 1 + core/object/object.h | 1 + core/variant/array.cpp | 27 +- core/variant/array.h | 5 +- core/variant/container_type_validate.h | 12 +- core/variant/variant_op.cpp | 4 +- doc/classes/@GlobalScope.xml | 3 + modules/gdscript/gdscript_analyzer.cpp | 978 +++++++++++++++++- modules/gdscript/gdscript_analyzer.h | 55 +- modules/gdscript/gdscript_byte_codegen.cpp | 126 ++- modules/gdscript/gdscript_byte_codegen.h | 27 +- modules/gdscript/gdscript_codegen.h | 21 +- modules/gdscript/gdscript_compiler.cpp | 52 +- modules/gdscript/gdscript_disassembler.cpp | 30 +- modules/gdscript/gdscript_function.h | 9 + modules/gdscript/gdscript_parser.cpp | 151 ++- modules/gdscript/gdscript_parser.h | 57 +- modules/gdscript/gdscript_tokenizer.cpp | 13 +- modules/gdscript/gdscript_tokenizer.h | 7 + modules/gdscript/gdscript_vm.cpp | 95 +- .../errors/nullable_access_operator.gd | 5 + .../errors/nullable_access_operator.out | 2 + .../errors/nullable_access_operator_index.gd | 8 + .../errors/nullable_access_operator_index.out | 2 + .../nullable_access_operator_method_call.gd | 7 + .../nullable_access_operator_method_call.out | 2 + .../nullable_assignment_to_non_nullable.gd | 3 + .../nullable_assignment_to_non_nullable.out | 2 + ...ment_to_non_nullable_from_method_return.gd | 6 + ...ent_to_non_nullable_from_method_return.out | 2 + .../errors/nullable_attribute_access.gd | 6 + .../errors/nullable_attribute_access.out | 2 + .../nullable_attribute_access_nested.gd | 9 + .../nullable_attribute_access_nested.out | 2 + .../errors/nullable_binary_operator.gd | 3 + .../errors/nullable_binary_operator.out | 2 + .../nullable_binary_operator_both_operands.gd | 4 + ...nullable_binary_operator_both_operands.out | 2 + .../errors/nullable_cast_attribute_access.gd | 6 + .../errors/nullable_cast_attribute_access.out | 2 + .../errors/nullable_cast_method_call.gd | 4 + .../errors/nullable_cast_method_call.out | 2 + .../analyzer/errors/nullable_class_member.gd | 5 + .../analyzer/errors/nullable_class_member.out | 2 + .../errors/nullable_elif_narrowing.gd | 8 + .../errors/nullable_elif_narrowing.out | 2 + .../analyzer/errors/nullable_for_on_call.gd | 6 + .../analyzer/errors/nullable_for_on_call.out | 2 + .../errors/nullable_for_on_identifier.gd | 4 + .../errors/nullable_for_on_identifier.out | 2 + .../errors/nullable_for_on_subscript.gd | 6 + .../errors/nullable_for_on_subscript.out | 2 + ...assigment_with_nullable_access_operator.gd | 5 + ...ssigment_with_nullable_access_operator.out | 2 + .../analyzer/errors/nullable_index_access.gd | 3 + .../analyzer/errors/nullable_index_access.out | 2 + .../errors/nullable_index_access_nested.gd | 9 + .../errors/nullable_index_access_nested.out | 2 + ..._index_access_nested_on_nullable_return.gd | 8 + ...index_access_nested_on_nullable_return.out | 2 + .../nullable_index_access_with_nullable.gd | 4 + .../nullable_index_access_with_nullable.out | 2 + .../errors/nullable_is_instance_of.gd | 5 + .../errors/nullable_is_instance_of.out | 2 + .../errors/nullable_match_narrowing.gd | 7 + .../errors/nullable_match_narrowing.out | 2 + .../analyzer/errors/nullable_method_call.gd | 7 + .../analyzer/errors/nullable_method_call.out | 2 + .../errors/nullable_method_call_nested.gd | 10 + .../errors/nullable_method_call_nested.out | 2 + .../nullable_method_call_nested_index.gd | 10 + .../nullable_method_call_nested_index.out | 2 + ...e_method_call_nested_on_nullable_return.gd | 9 + ..._method_call_nested_on_nullable_return.out | 2 + .../analyzer/errors/nullable_method_return.gd | 7 + .../errors/nullable_method_return.out | 2 + .../errors/nullable_narrowed_assignment.gd | 6 + .../errors/nullable_narrowed_assignment.out | 1 + .../analyzer/errors/nullable_narrowing.gd | 4 + .../analyzer/errors/nullable_narrowing.out | 2 + .../analyzer/errors/nullable_parameters.gd | 7 + .../analyzer/errors/nullable_parameters.out | 2 + ...le_qualifier_forbidden_for_native_types.gd | 2 + ...e_qualifier_forbidden_for_native_types.out | 2 + .../analyzer/errors/nullable_return.gd | 6 + .../analyzer/errors/nullable_return.out | 2 + .../nullable_return_on_non_nullable_method.gd | 8 + ...nullable_return_on_non_nullable_method.out | 2 + .../errors/nullable_subscript_as_index.gd | 8 + .../errors/nullable_subscript_as_index.out | 2 + .../analyzer/errors/nullable_ternary.gd | 3 + .../analyzer/errors/nullable_ternary.out | 2 + .../errors/nullable_type_inference.gd | 7 + .../errors/nullable_type_inference.out | 2 + .../errors/nullable_while_narrowing.gd | 4 + .../errors/nullable_while_narrowing.out | 2 + .../nullable_and_binary_operator_narrowing.gd | 5 + ...nullable_and_binary_operator_narrowing.out | 2 + .../analyzer/features/nullable_cast.gd | 5 + .../analyzer/features/nullable_cast.out | 3 + .../features/nullable_cast_narrowing.gd | 4 + .../features/nullable_cast_narrowing.out | 2 + .../analyzer/features/nullable_coalesce.gd | 3 + .../analyzer/features/nullable_coalesce.out | 2 + .../features/nullable_coalesce_narrowing.gd | 3 + .../features/nullable_coalesce_narrowing.out | 2 + .../features/nullable_complex_narrowing.gd | 9 + .../features/nullable_complex_narrowing.out | 1 + .../features/nullable_is_instance_of.gd | 6 + .../features/nullable_is_instance_of.out | 2 + .../features/nullable_is_instance_valid.gd | 4 + .../features/nullable_is_instance_valid.out | 1 + .../analyzer/features/nullable_is_same.gd | 4 + .../analyzer/features/nullable_is_same.out | 1 + .../features/nullable_match_narrowing.gd | 13 + .../features/nullable_match_narrowing.out | 2 + ...lable_method_return_with_container_type.gd | 8 + ...able_method_return_with_container_type.out | 2 + .../nullable_method_return_with_narrowing.gd | 9 + .../nullable_method_return_with_narrowing.out | 2 + .../features/nullable_nested_narrowing.gd | 5 + .../features/nullable_nested_narrowing.out | 6 + .../features/nullable_setter_getter.gd | 11 + .../features/nullable_setter_getter.out | 4 + .../features/nullable_ternary_narrowing.gd | 4 + .../features/nullable_ternary_narrowing.out | 2 + .../analyzer/features/nullable_type_test.gd | 5 + .../analyzer/features/nullable_type_test.out | 2 + .../features/nullable_unary_operator.gd | 14 + .../features/nullable_unary_operator.out | 4 + .../features/nullable_variant_parameter.gd | 4 + .../features/nullable_variant_parameter.out | 2 + .../features/nullable_while_narrowing.gd | 4 + .../features/nullable_while_narrowing.out | 1 + ...lable_qualifier_forbidden_for_constants.gd | 2 + ...able_qualifier_forbidden_for_constants.out | 2 + .../nullable_access_on_freed_instance.gd | 4 + .../nullable_access_on_freed_instance.out | 2 + .../nullable_assignment_with_object.gd | 10 + .../nullable_assignment_with_object.out | 2 + .../nullable_binary_op_short_circuit.gd | 5 + .../nullable_binary_op_short_circuit.out | 1 + .../nullable_coalescing_on_freed_instance.gd | 6 + .../nullable_coalescing_on_freed_instance.out | 2 + ...licit_narrowing_with_function_parameter.gd | 7 + ...icit_narrowing_with_function_parameter.out | 1 + 146 files changed, 2014 insertions(+), 171 deletions(-) create mode 100644 modules/gdscript/tests/scripts/analyzer/errors/nullable_access_operator.gd create mode 100644 modules/gdscript/tests/scripts/analyzer/errors/nullable_access_operator.out create mode 100644 modules/gdscript/tests/scripts/analyzer/errors/nullable_access_operator_index.gd create mode 100644 modules/gdscript/tests/scripts/analyzer/errors/nullable_access_operator_index.out create mode 100644 modules/gdscript/tests/scripts/analyzer/errors/nullable_access_operator_method_call.gd create mode 100644 modules/gdscript/tests/scripts/analyzer/errors/nullable_access_operator_method_call.out create mode 100644 modules/gdscript/tests/scripts/analyzer/errors/nullable_assignment_to_non_nullable.gd create mode 100644 modules/gdscript/tests/scripts/analyzer/errors/nullable_assignment_to_non_nullable.out create mode 100644 modules/gdscript/tests/scripts/analyzer/errors/nullable_assignment_to_non_nullable_from_method_return.gd create mode 100644 modules/gdscript/tests/scripts/analyzer/errors/nullable_assignment_to_non_nullable_from_method_return.out create mode 100644 modules/gdscript/tests/scripts/analyzer/errors/nullable_attribute_access.gd create mode 100644 modules/gdscript/tests/scripts/analyzer/errors/nullable_attribute_access.out create mode 100644 modules/gdscript/tests/scripts/analyzer/errors/nullable_attribute_access_nested.gd create mode 100644 modules/gdscript/tests/scripts/analyzer/errors/nullable_attribute_access_nested.out create mode 100644 modules/gdscript/tests/scripts/analyzer/errors/nullable_binary_operator.gd create mode 100644 modules/gdscript/tests/scripts/analyzer/errors/nullable_binary_operator.out create mode 100644 modules/gdscript/tests/scripts/analyzer/errors/nullable_binary_operator_both_operands.gd create mode 100644 modules/gdscript/tests/scripts/analyzer/errors/nullable_binary_operator_both_operands.out create mode 100644 modules/gdscript/tests/scripts/analyzer/errors/nullable_cast_attribute_access.gd create mode 100644 modules/gdscript/tests/scripts/analyzer/errors/nullable_cast_attribute_access.out create mode 100644 modules/gdscript/tests/scripts/analyzer/errors/nullable_cast_method_call.gd create mode 100644 modules/gdscript/tests/scripts/analyzer/errors/nullable_cast_method_call.out create mode 100644 modules/gdscript/tests/scripts/analyzer/errors/nullable_class_member.gd create mode 100644 modules/gdscript/tests/scripts/analyzer/errors/nullable_class_member.out create mode 100644 modules/gdscript/tests/scripts/analyzer/errors/nullable_elif_narrowing.gd create mode 100644 modules/gdscript/tests/scripts/analyzer/errors/nullable_elif_narrowing.out create mode 100644 modules/gdscript/tests/scripts/analyzer/errors/nullable_for_on_call.gd create mode 100644 modules/gdscript/tests/scripts/analyzer/errors/nullable_for_on_call.out create mode 100644 modules/gdscript/tests/scripts/analyzer/errors/nullable_for_on_identifier.gd create mode 100644 modules/gdscript/tests/scripts/analyzer/errors/nullable_for_on_identifier.out create mode 100644 modules/gdscript/tests/scripts/analyzer/errors/nullable_for_on_subscript.gd create mode 100644 modules/gdscript/tests/scripts/analyzer/errors/nullable_for_on_subscript.out create mode 100644 modules/gdscript/tests/scripts/analyzer/errors/nullable_forbid_assigment_with_nullable_access_operator.gd create mode 100644 modules/gdscript/tests/scripts/analyzer/errors/nullable_forbid_assigment_with_nullable_access_operator.out create mode 100644 modules/gdscript/tests/scripts/analyzer/errors/nullable_index_access.gd create mode 100644 modules/gdscript/tests/scripts/analyzer/errors/nullable_index_access.out create mode 100644 modules/gdscript/tests/scripts/analyzer/errors/nullable_index_access_nested.gd create mode 100644 modules/gdscript/tests/scripts/analyzer/errors/nullable_index_access_nested.out create mode 100644 modules/gdscript/tests/scripts/analyzer/errors/nullable_index_access_nested_on_nullable_return.gd create mode 100644 modules/gdscript/tests/scripts/analyzer/errors/nullable_index_access_nested_on_nullable_return.out create mode 100644 modules/gdscript/tests/scripts/analyzer/errors/nullable_index_access_with_nullable.gd create mode 100644 modules/gdscript/tests/scripts/analyzer/errors/nullable_index_access_with_nullable.out create mode 100644 modules/gdscript/tests/scripts/analyzer/errors/nullable_is_instance_of.gd create mode 100644 modules/gdscript/tests/scripts/analyzer/errors/nullable_is_instance_of.out create mode 100644 modules/gdscript/tests/scripts/analyzer/errors/nullable_match_narrowing.gd create mode 100644 modules/gdscript/tests/scripts/analyzer/errors/nullable_match_narrowing.out create mode 100644 modules/gdscript/tests/scripts/analyzer/errors/nullable_method_call.gd create mode 100644 modules/gdscript/tests/scripts/analyzer/errors/nullable_method_call.out create mode 100644 modules/gdscript/tests/scripts/analyzer/errors/nullable_method_call_nested.gd create mode 100644 modules/gdscript/tests/scripts/analyzer/errors/nullable_method_call_nested.out create mode 100644 modules/gdscript/tests/scripts/analyzer/errors/nullable_method_call_nested_index.gd create mode 100644 modules/gdscript/tests/scripts/analyzer/errors/nullable_method_call_nested_index.out create mode 100644 modules/gdscript/tests/scripts/analyzer/errors/nullable_method_call_nested_on_nullable_return.gd create mode 100644 modules/gdscript/tests/scripts/analyzer/errors/nullable_method_call_nested_on_nullable_return.out create mode 100644 modules/gdscript/tests/scripts/analyzer/errors/nullable_method_return.gd create mode 100644 modules/gdscript/tests/scripts/analyzer/errors/nullable_method_return.out create mode 100644 modules/gdscript/tests/scripts/analyzer/errors/nullable_narrowed_assignment.gd create mode 100644 modules/gdscript/tests/scripts/analyzer/errors/nullable_narrowed_assignment.out create mode 100644 modules/gdscript/tests/scripts/analyzer/errors/nullable_narrowing.gd create mode 100644 modules/gdscript/tests/scripts/analyzer/errors/nullable_narrowing.out create mode 100644 modules/gdscript/tests/scripts/analyzer/errors/nullable_parameters.gd create mode 100644 modules/gdscript/tests/scripts/analyzer/errors/nullable_parameters.out create mode 100644 modules/gdscript/tests/scripts/analyzer/errors/nullable_qualifier_forbidden_for_native_types.gd create mode 100644 modules/gdscript/tests/scripts/analyzer/errors/nullable_qualifier_forbidden_for_native_types.out create mode 100644 modules/gdscript/tests/scripts/analyzer/errors/nullable_return.gd create mode 100644 modules/gdscript/tests/scripts/analyzer/errors/nullable_return.out create mode 100644 modules/gdscript/tests/scripts/analyzer/errors/nullable_return_on_non_nullable_method.gd create mode 100644 modules/gdscript/tests/scripts/analyzer/errors/nullable_return_on_non_nullable_method.out create mode 100644 modules/gdscript/tests/scripts/analyzer/errors/nullable_subscript_as_index.gd create mode 100644 modules/gdscript/tests/scripts/analyzer/errors/nullable_subscript_as_index.out create mode 100644 modules/gdscript/tests/scripts/analyzer/errors/nullable_ternary.gd create mode 100644 modules/gdscript/tests/scripts/analyzer/errors/nullable_ternary.out create mode 100644 modules/gdscript/tests/scripts/analyzer/errors/nullable_type_inference.gd create mode 100644 modules/gdscript/tests/scripts/analyzer/errors/nullable_type_inference.out create mode 100644 modules/gdscript/tests/scripts/analyzer/errors/nullable_while_narrowing.gd create mode 100644 modules/gdscript/tests/scripts/analyzer/errors/nullable_while_narrowing.out create mode 100644 modules/gdscript/tests/scripts/analyzer/features/nullable_and_binary_operator_narrowing.gd create mode 100644 modules/gdscript/tests/scripts/analyzer/features/nullable_and_binary_operator_narrowing.out create mode 100644 modules/gdscript/tests/scripts/analyzer/features/nullable_cast.gd create mode 100644 modules/gdscript/tests/scripts/analyzer/features/nullable_cast.out create mode 100644 modules/gdscript/tests/scripts/analyzer/features/nullable_cast_narrowing.gd create mode 100644 modules/gdscript/tests/scripts/analyzer/features/nullable_cast_narrowing.out create mode 100644 modules/gdscript/tests/scripts/analyzer/features/nullable_coalesce.gd create mode 100644 modules/gdscript/tests/scripts/analyzer/features/nullable_coalesce.out create mode 100644 modules/gdscript/tests/scripts/analyzer/features/nullable_coalesce_narrowing.gd create mode 100644 modules/gdscript/tests/scripts/analyzer/features/nullable_coalesce_narrowing.out create mode 100644 modules/gdscript/tests/scripts/analyzer/features/nullable_complex_narrowing.gd create mode 100644 modules/gdscript/tests/scripts/analyzer/features/nullable_complex_narrowing.out create mode 100644 modules/gdscript/tests/scripts/analyzer/features/nullable_is_instance_of.gd create mode 100644 modules/gdscript/tests/scripts/analyzer/features/nullable_is_instance_of.out create mode 100644 modules/gdscript/tests/scripts/analyzer/features/nullable_is_instance_valid.gd create mode 100644 modules/gdscript/tests/scripts/analyzer/features/nullable_is_instance_valid.out create mode 100644 modules/gdscript/tests/scripts/analyzer/features/nullable_is_same.gd create mode 100644 modules/gdscript/tests/scripts/analyzer/features/nullable_is_same.out create mode 100644 modules/gdscript/tests/scripts/analyzer/features/nullable_match_narrowing.gd create mode 100644 modules/gdscript/tests/scripts/analyzer/features/nullable_match_narrowing.out create mode 100644 modules/gdscript/tests/scripts/analyzer/features/nullable_method_return_with_container_type.gd create mode 100644 modules/gdscript/tests/scripts/analyzer/features/nullable_method_return_with_container_type.out create mode 100644 modules/gdscript/tests/scripts/analyzer/features/nullable_method_return_with_narrowing.gd create mode 100644 modules/gdscript/tests/scripts/analyzer/features/nullable_method_return_with_narrowing.out create mode 100644 modules/gdscript/tests/scripts/analyzer/features/nullable_nested_narrowing.gd create mode 100644 modules/gdscript/tests/scripts/analyzer/features/nullable_nested_narrowing.out create mode 100644 modules/gdscript/tests/scripts/analyzer/features/nullable_setter_getter.gd create mode 100644 modules/gdscript/tests/scripts/analyzer/features/nullable_setter_getter.out create mode 100644 modules/gdscript/tests/scripts/analyzer/features/nullable_ternary_narrowing.gd create mode 100644 modules/gdscript/tests/scripts/analyzer/features/nullable_ternary_narrowing.out create mode 100644 modules/gdscript/tests/scripts/analyzer/features/nullable_type_test.gd create mode 100644 modules/gdscript/tests/scripts/analyzer/features/nullable_type_test.out create mode 100644 modules/gdscript/tests/scripts/analyzer/features/nullable_unary_operator.gd create mode 100644 modules/gdscript/tests/scripts/analyzer/features/nullable_unary_operator.out create mode 100644 modules/gdscript/tests/scripts/analyzer/features/nullable_variant_parameter.gd create mode 100644 modules/gdscript/tests/scripts/analyzer/features/nullable_variant_parameter.out create mode 100644 modules/gdscript/tests/scripts/analyzer/features/nullable_while_narrowing.gd create mode 100644 modules/gdscript/tests/scripts/analyzer/features/nullable_while_narrowing.out create mode 100644 modules/gdscript/tests/scripts/parser/errors/nullable_qualifier_forbidden_for_constants.gd create mode 100644 modules/gdscript/tests/scripts/parser/errors/nullable_qualifier_forbidden_for_constants.out create mode 100644 modules/gdscript/tests/scripts/runtime/features/nullable_access_on_freed_instance.gd create mode 100644 modules/gdscript/tests/scripts/runtime/features/nullable_access_on_freed_instance.out create mode 100644 modules/gdscript/tests/scripts/runtime/features/nullable_assignment_with_object.gd create mode 100644 modules/gdscript/tests/scripts/runtime/features/nullable_assignment_with_object.out create mode 100644 modules/gdscript/tests/scripts/runtime/features/nullable_binary_op_short_circuit.gd create mode 100644 modules/gdscript/tests/scripts/runtime/features/nullable_binary_op_short_circuit.out create mode 100644 modules/gdscript/tests/scripts/runtime/features/nullable_coalescing_on_freed_instance.gd create mode 100644 modules/gdscript/tests/scripts/runtime/features/nullable_coalescing_on_freed_instance.out create mode 100644 modules/gdscript/tests/scripts/runtime/features/nullable_implicit_narrowing_with_function_parameter.gd create mode 100644 modules/gdscript/tests/scripts/runtime/features/nullable_implicit_narrowing_with_function_parameter.out diff --git a/core/core_constants.cpp b/core/core_constants.cpp index 5322e39ec0de..1c60dbda11f5 100644 --- a/core/core_constants.cpp +++ b/core/core_constants.cpp @@ -710,6 +710,7 @@ void register_global_constants() { BIND_CORE_BITFIELD_FLAG(PROPERTY_USAGE_EDITOR_BASIC_SETTING); BIND_CORE_BITFIELD_FLAG(PROPERTY_USAGE_READ_ONLY); BIND_CORE_BITFIELD_FLAG(PROPERTY_USAGE_SECRET); + BIND_CORE_BITFIELD_FLAG(PROPERTY_USAGE_NULLABLE); BIND_CORE_BITFIELD_FLAG(PROPERTY_USAGE_DEFAULT); BIND_CORE_BITFIELD_FLAG(PROPERTY_USAGE_NO_EDITOR); diff --git a/core/object/object.h b/core/object/object.h index adb50268d2ad..626c27d6e9f0 100644 --- a/core/object/object.h +++ b/core/object/object.h @@ -120,6 +120,7 @@ enum PropertyUsageFlags { PROPERTY_USAGE_EDITOR_BASIC_SETTING = 1 << 27, //for project or editor settings, show when basic settings are selected. PROPERTY_USAGE_READ_ONLY = 1 << 28, // Mark a property as read-only in the inspector. PROPERTY_USAGE_SECRET = 1 << 29, // Export preset credentials that should be stored separately from the rest of the export config. + PROPERTY_USAGE_NULLABLE = 1 << 30, // When the property's type has the nullable qualifier. PROPERTY_USAGE_DEFAULT = PROPERTY_USAGE_STORAGE | PROPERTY_USAGE_EDITOR, PROPERTY_USAGE_NO_EDITOR = PROPERTY_USAGE_STORAGE, diff --git a/core/variant/array.cpp b/core/variant/array.cpp index 3685515db5b0..f04b75b863a8 100644 --- a/core/variant/array.cpp +++ b/core/variant/array.cpp @@ -234,7 +234,7 @@ void Array::assign(const Array &p_array) { // from base classes to subclasses for (int i = 0; i < size; i++) { const Variant &element = source[i]; - if (element.get_type() != Variant::NIL && (element.get_type() != Variant::OBJECT || !typed.validate_object(element, "assign"))) { + if ((element.get_type() != Variant::NIL && !_p->typed.is_nullable) && (element.get_type() != Variant::OBJECT || !typed.validate_object(element, "assign"))) { ERR_FAIL_MSG(vformat(R"(Unable to convert array index %i from "%s" to "%s".)", i, Variant::get_type_name(element.get_type()), Variant::get_type_name(typed.type))); } } @@ -253,7 +253,8 @@ void Array::assign(const Array &p_array) { // from variants to primitives for (int i = 0; i < size; i++) { const Variant *value = source + i; - if (value->get_type() == typed.type) { + // Force-assign the current value if it's null, and we're nullable + if (value->get_type() == typed.type || (value->get_type() == Variant::NIL && _p->typed.is_nullable)) { data[i] = *value; continue; } @@ -264,13 +265,16 @@ void Array::assign(const Array &p_array) { Variant::construct(typed.type, data[i], &value, 1, ce); ERR_FAIL_COND_MSG(ce.error, vformat(R"(Unable to convert array index %i from "%s" to "%s".)", i, Variant::get_type_name(value->get_type()), Variant::get_type_name(typed.type))); } - } else if (Variant::can_convert_strict(source_typed.type, typed.type)) { + } else if (Variant::can_convert_strict(source_typed.type, typed.type) && (source_typed.type != Variant::NIL || _p->typed.is_nullable)) { // from primitives to different convertible primitives for (int i = 0; i < size; i++) { const Variant *value = source + i; - Callable::CallError ce; - Variant::construct(typed.type, data[i], &value, 1, ce); - ERR_FAIL_COND_MSG(ce.error, vformat(R"(Unable to convert array index %i from "%s" to "%s".)", i, Variant::get_type_name(value->get_type()), Variant::get_type_name(typed.type))); + // Avoid constructing the variant if it's null, and we're nullable + if (value->get_type() != Variant::NIL && !_p->typed.is_nullable) { + Callable::CallError ce; + Variant::construct(typed.type, data[i], &value, 1, ce); + ERR_FAIL_COND_MSG(ce.error, vformat(R"(Unable to convert array index %i from "%s" to "%s".)", i, Variant::get_type_name(value->get_type()), Variant::get_type_name(typed.type))); + } } } else { ERR_FAIL_MSG(vformat(R"(Cannot assign contents of "Array[%s]" to "Array[%s]".)", Variant::get_type_name(source_typed.type), Variant::get_type_name(typed.type))); @@ -773,14 +777,14 @@ const void *Array::id() const { return _p; } -Array::Array(const Array &p_from, uint32_t p_type, const StringName &p_class_name, const Variant &p_script) { +Array::Array(const Array &p_from, uint32_t p_type, const StringName &p_class_name, const Variant &p_script, bool p_is_nullable) { _p = memnew(ArrayPrivate); _p->refcount.init(); - set_typed(p_type, p_class_name, p_script); + set_typed(p_type, p_class_name, p_script, p_is_nullable); assign(p_from); } -void Array::set_typed(uint32_t p_type, const StringName &p_class_name, const Variant &p_script) { +void Array::set_typed(uint32_t p_type, const StringName &p_class_name, const Variant &p_script, bool p_is_nullable) { ERR_FAIL_COND_MSG(_p->read_only, "Array is in read-only state."); ERR_FAIL_COND_MSG(_p->array.size() > 0, "Type can only be set when array is empty."); ERR_FAIL_COND_MSG(_p->refcount.get() > 1, "Type can only be set when array has no more than one user."); @@ -792,6 +796,7 @@ void Array::set_typed(uint32_t p_type, const StringName &p_class_name, const Var _p->typed.type = Variant::Type(p_type); _p->typed.class_name = p_class_name; _p->typed.script = script; + _p->typed.is_nullable = p_is_nullable; _p->typed.where = "TypedArray"; } @@ -815,6 +820,10 @@ Variant Array::get_typed_script() const { return _p->typed.script; } +bool Array::is_nullable() const { + return _p->typed.is_nullable; +} + void Array::make_read_only() { if (_p->read_only == nullptr) { _p->read_only = memnew(Variant); diff --git a/core/variant/array.h b/core/variant/array.h index 3aa957b31268..e2b4477d2a9a 100644 --- a/core/variant/array.h +++ b/core/variant/array.h @@ -183,17 +183,18 @@ class Array { const void *id() const; - void set_typed(uint32_t p_type, const StringName &p_class_name, const Variant &p_script); + void set_typed(uint32_t p_type, const StringName &p_class_name, const Variant &p_script, bool p_is_nullable = false); bool is_typed() const; bool is_same_typed(const Array &p_other) const; uint32_t get_typed_builtin() const; StringName get_typed_class_name() const; Variant get_typed_script() const; + bool is_nullable() const; void make_read_only(); bool is_read_only() const; - Array(const Array &p_base, uint32_t p_type, const StringName &p_class_name, const Variant &p_script); + Array(const Array &p_base, uint32_t p_type, const StringName &p_class_name, const Variant &p_script, bool p_is_nullable = false); Array(const Array &p_from); Array(); ~Array(); diff --git a/core/variant/container_type_validate.h b/core/variant/container_type_validate.h index 0a23c69cb4bd..d4f9f35546ad 100644 --- a/core/variant/container_type_validate.h +++ b/core/variant/container_type_validate.h @@ -38,13 +38,15 @@ struct ContainerTypeValidate { Variant::Type type = Variant::NIL; StringName class_name; Ref