diff options
Diffstat (limited to 'modules/gdscript')
17 files changed, 121 insertions, 59 deletions
diff --git a/modules/gdscript/doc_classes/@GDScript.xml b/modules/gdscript/doc_classes/@GDScript.xml index cd837b3a91..32acad76aa 100644 --- a/modules/gdscript/doc_classes/@GDScript.xml +++ b/modules/gdscript/doc_classes/@GDScript.xml @@ -227,18 +227,6 @@ [/codeblock] </description> </method> - <method name="str" qualifiers="vararg"> - <return type="String" /> - <description> - Converts one or more arguments to a [String] in the best way possible. - [codeblock] - var a = [10, 20, 30] - var b = str(a); - len(a) # Returns 3 - len(b) # Returns 12 - [/codeblock] - </description> - </method> <method name="type_exists"> <return type="bool" /> <param index="0" name="type" type="StringName" /> diff --git a/modules/gdscript/gdscript_analyzer.cpp b/modules/gdscript/gdscript_analyzer.cpp index 1c2b743909..fd04d3c660 100644 --- a/modules/gdscript/gdscript_analyzer.cpp +++ b/modules/gdscript/gdscript_analyzer.cpp @@ -2240,6 +2240,28 @@ void GDScriptAnalyzer::reduce_assignment(GDScriptParser::AssignmentNode *p_assig if (assignee_type.is_constant || (p_assignment->assignee->type == GDScriptParser::Node::SUBSCRIPT && static_cast<GDScriptParser::SubscriptNode *>(p_assignment->assignee)->base->is_constant)) { push_error("Cannot assign a new value to a constant.", p_assignment->assignee); + return; + } else if (assignee_type.is_read_only) { + push_error("Cannot assign a new value to a read-only property.", p_assignment->assignee); + return; + } else if (p_assignment->assignee->type == GDScriptParser::Node::SUBSCRIPT) { + GDScriptParser::SubscriptNode *sub = static_cast<GDScriptParser::SubscriptNode *>(p_assignment->assignee); + while (sub) { + const GDScriptParser::DataType &base_type = sub->base->datatype; + if (base_type.is_hard_type() && base_type.is_read_only) { + if (base_type.kind == GDScriptParser::DataType::BUILTIN && !Variant::is_type_shared(base_type.builtin_type)) { + push_error("Cannot assign a new value to a read-only property.", p_assignment->assignee); + return; + } + } else { + break; + } + if (sub->base->type == GDScriptParser::Node::SUBSCRIPT) { + sub = static_cast<GDScriptParser::SubscriptNode *>(sub->base); + } else { + sub = nullptr; + } + } } // Check if assigned value is an array literal, so we can make it a typed array too if appropriate. @@ -3329,7 +3351,8 @@ void GDScriptAnalyzer::reduce_identifier_from_base(GDScriptParser::IdentifierNod StringName getter_name = ClassDB::get_property_getter(native, name); MethodBind *getter = ClassDB::get_method(native, getter_name); if (getter != nullptr) { - p_identifier->set_datatype(type_from_property(getter->get_return_info())); + bool has_setter = ClassDB::get_property_setter(native, name) != StringName(); + p_identifier->set_datatype(type_from_property(getter->get_return_info(), false, !has_setter)); p_identifier->source = GDScriptParser::IdentifierNode::INHERITED_VARIABLE; } return; @@ -4037,6 +4060,10 @@ void GDScriptAnalyzer::reduce_unary_op(GDScriptParser::UnaryOpNode *p_unary_op) Variant GDScriptAnalyzer::make_expression_reduced_value(GDScriptParser::ExpressionNode *p_expression, bool &is_reduced) { Variant value; + if (p_expression == nullptr) { + return value; + } + if (p_expression->is_constant) { is_reduced = true; value = p_expression->reduced_value; @@ -4101,6 +4128,10 @@ Variant GDScriptAnalyzer::make_dictionary_reduced_value(GDScriptParser::Dictiona } Variant GDScriptAnalyzer::make_subscript_reduced_value(GDScriptParser::SubscriptNode *p_subscript, bool &is_reduced) { + if (p_subscript->base == nullptr || p_subscript->index == nullptr) { + return Variant(); + } + bool is_base_value_reduced = false; Variant base_value = make_expression_reduced_value(p_subscript->base, is_base_value_reduced); if (!is_base_value_reduced) { @@ -4260,8 +4291,9 @@ GDScriptParser::DataType GDScriptAnalyzer::type_from_metatype(const GDScriptPars return result; } -GDScriptParser::DataType GDScriptAnalyzer::type_from_property(const PropertyInfo &p_property, bool p_is_arg) const { +GDScriptParser::DataType GDScriptAnalyzer::type_from_property(const PropertyInfo &p_property, bool p_is_arg, bool p_is_readonly) const { GDScriptParser::DataType result; + result.is_read_only = p_is_readonly; result.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT; if (p_property.type == Variant::NIL && (p_is_arg || (p_property.usage & PROPERTY_USAGE_NIL_IS_VARIANT))) { // Variant diff --git a/modules/gdscript/gdscript_analyzer.h b/modules/gdscript/gdscript_analyzer.h index b51564fb0a..75d52509a4 100644 --- a/modules/gdscript/gdscript_analyzer.h +++ b/modules/gdscript/gdscript_analyzer.h @@ -115,7 +115,7 @@ class GDScriptAnalyzer { Array make_array_from_element_datatype(const GDScriptParser::DataType &p_element_datatype, const GDScriptParser::Node *p_source_node = nullptr); GDScriptParser::DataType type_from_variant(const Variant &p_value, const GDScriptParser::Node *p_source); static GDScriptParser::DataType type_from_metatype(const GDScriptParser::DataType &p_meta_type); - GDScriptParser::DataType type_from_property(const PropertyInfo &p_property, bool p_is_arg = false) const; + GDScriptParser::DataType type_from_property(const PropertyInfo &p_property, bool p_is_arg = false, bool p_is_readonly = false) const; GDScriptParser::DataType make_global_class_meta_type(const StringName &p_class_name, const GDScriptParser::Node *p_source); bool get_function_signature(GDScriptParser::Node *p_source, bool p_is_constructor, GDScriptParser::DataType base_type, const StringName &p_function, GDScriptParser::DataType &r_return_type, List<GDScriptParser::DataType> &r_par_types, int &r_default_arg_count, bool &r_static, bool &r_vararg); bool function_signature_from_info(const MethodInfo &p_info, GDScriptParser::DataType &r_return_type, List<GDScriptParser::DataType> &r_par_types, int &r_default_arg_count, bool &r_static, bool &r_vararg); diff --git a/modules/gdscript/gdscript_disassembler.cpp b/modules/gdscript/gdscript_disassembler.cpp index b5c8a6f478..d4f4358ac1 100644 --- a/modules/gdscript/gdscript_disassembler.cpp +++ b/modules/gdscript/gdscript_disassembler.cpp @@ -317,7 +317,7 @@ void GDScriptFunction::disassemble(const Vector<String> &p_code_lines) const { text += " = "; text += DADDR(2); - incr += 3; + incr += 6; } break; case OPCODE_ASSIGN_TYPED_NATIVE: { text += "assign typed native ("; @@ -434,7 +434,7 @@ void GDScriptFunction::disassemble(const Vector<String> &p_code_lines) const { int instr_var_args = _code_ptr[++ip]; int argc = _code_ptr[ip + 1 + instr_var_args]; - Ref<Script> script_type = get_constant(_code_ptr[ip + argc + 2]); + Ref<Script> script_type = get_constant(_code_ptr[ip + argc + 2] & GDScriptFunction::ADDR_MASK); Variant::Type builtin_type = (Variant::Type)_code_ptr[ip + argc + 4]; StringName native_type = get_global_name(_code_ptr[ip + argc + 5]); @@ -463,7 +463,7 @@ void GDScriptFunction::disassemble(const Vector<String> &p_code_lines) const { text += "]"; - incr += 3 + argc; + incr += 6 + argc; } break; case OPCODE_CONSTRUCT_DICTIONARY: { int instr_var_args = _code_ptr[++ip]; diff --git a/modules/gdscript/gdscript_editor.cpp b/modules/gdscript/gdscript_editor.cpp index f88ac581ca..12c10642ec 100644 --- a/modules/gdscript/gdscript_editor.cpp +++ b/modules/gdscript/gdscript_editor.cpp @@ -1953,17 +1953,19 @@ static bool _guess_identifier_type(GDScriptParser::CompletionContext &p_context, case GDScriptParser::DataType::CLASS: if (base_type.class_type->has_function(p_context.current_function->identifier->name)) { GDScriptParser::FunctionNode *parent_function = base_type.class_type->get_member(p_context.current_function->identifier->name).function; - const GDScriptParser::ParameterNode *parameter = parent_function->parameters[parent_function->parameters_indices[p_identifier]]; - if ((!id_type.is_set() || id_type.is_variant()) && parameter->get_datatype().is_hard_type()) { - id_type = parameter->get_datatype(); - } - if (parameter->initializer) { - GDScriptParser::CompletionContext c = p_context; - c.current_function = parent_function; - c.current_class = base_type.class_type; - c.base = nullptr; - if (_guess_expression_type(c, parameter->initializer, r_type)) { - return true; + if (parent_function->parameters_indices.has(p_identifier)) { + const GDScriptParser::ParameterNode *parameter = parent_function->parameters[parent_function->parameters_indices[p_identifier]]; + if ((!id_type.is_set() || id_type.is_variant()) && parameter->get_datatype().is_hard_type()) { + id_type = parameter->get_datatype(); + } + if (parameter->initializer) { + GDScriptParser::CompletionContext c = p_context; + c.current_function = parent_function; + c.current_class = base_type.class_type; + c.base = nullptr; + if (_guess_expression_type(c, parameter->initializer, r_type)) { + return true; + } } } } diff --git a/modules/gdscript/gdscript_parser.cpp b/modules/gdscript/gdscript_parser.cpp index 713ad3ed17..ed2dce471f 100644 --- a/modules/gdscript/gdscript_parser.cpp +++ b/modules/gdscript/gdscript_parser.cpp @@ -3602,6 +3602,7 @@ bool GDScriptParser::tool_annotation(const AnnotationNode *p_annotation, Node *p bool GDScriptParser::icon_annotation(const AnnotationNode *p_annotation, Node *p_node) { ERR_FAIL_COND_V_MSG(p_node->type != Node::CLASS, false, R"("@icon" annotation can only be applied to classes.)"); + ERR_FAIL_COND_V(p_annotation->resolved_arguments.is_empty(), false); ClassNode *p_class = static_cast<ClassNode *>(p_node); p_class->icon_path = p_annotation->resolved_arguments[0]; return true; @@ -3830,6 +3831,10 @@ template <PropertyUsageFlags t_usage> bool GDScriptParser::export_group_annotations(const AnnotationNode *p_annotation, Node *p_node) { AnnotationNode *annotation = const_cast<AnnotationNode *>(p_annotation); + if (annotation->resolved_arguments.is_empty()) { + return false; + } + annotation->export_info.name = annotation->resolved_arguments[0]; switch (t_usage) { @@ -3887,7 +3892,7 @@ bool GDScriptParser::rpc_annotation(const AnnotationNode *p_annotation, Node *p_ Dictionary rpc_config; rpc_config["rpc_mode"] = MultiplayerAPI::RPC_MODE_AUTHORITY; - if (p_annotation->resolved_arguments.size()) { + if (!p_annotation->resolved_arguments.is_empty()) { int last = p_annotation->resolved_arguments.size() - 1; if (p_annotation->resolved_arguments[last].get_type() == Variant::INT) { rpc_config["channel"] = p_annotation->resolved_arguments[last].operator int(); diff --git a/modules/gdscript/gdscript_parser.h b/modules/gdscript/gdscript_parser.h index 07dac25ec5..bc0fe58fa7 100644 --- a/modules/gdscript/gdscript_parser.h +++ b/modules/gdscript/gdscript_parser.h @@ -122,6 +122,7 @@ public: TypeSource type_source = UNDETECTED; bool is_constant = false; + bool is_read_only = false; bool is_meta_type = false; bool is_coroutine = false; // For function calls. @@ -206,6 +207,7 @@ public: void operator=(const DataType &p_other) { kind = p_other.kind; type_source = p_other.type_source; + is_read_only = p_other.is_read_only; is_constant = p_other.is_constant; is_meta_type = p_other.is_meta_type; is_coroutine = p_other.is_coroutine; diff --git a/modules/gdscript/gdscript_utility_functions.cpp b/modules/gdscript/gdscript_utility_functions.cpp index 10d83dcfe5..758b61bb31 100644 --- a/modules/gdscript/gdscript_utility_functions.cpp +++ b/modules/gdscript/gdscript_utility_functions.cpp @@ -112,28 +112,6 @@ struct GDScriptUtilityFunctionsDefinitions { *r_ret = String(result); } - static inline void str(Variant *r_ret, const Variant **p_args, int p_arg_count, Callable::CallError &r_error) { - if (p_arg_count < 1) { - r_error.error = Callable::CallError::CALL_ERROR_TOO_FEW_ARGUMENTS; - r_error.argument = 1; - r_error.expected = 1; - *r_ret = Variant(); - return; - } - - String str; - for (int i = 0; i < p_arg_count; i++) { - String os = p_args[i]->operator String(); - - if (i == 0) { - str = os; - } else { - str += os; - } - } - *r_ret = str; - } - static inline void range(Variant *r_ret, const Variant **p_args, int p_arg_count, Callable::CallError &r_error) { switch (p_arg_count) { case 0: { @@ -651,7 +629,6 @@ void GDScriptUtilityFunctions::register_functions() { REGISTER_VARIANT_FUNC(convert, true, VARARG("what"), ARG("type", Variant::INT)); REGISTER_FUNC(type_exists, true, Variant::BOOL, ARG("type", Variant::STRING_NAME)); REGISTER_FUNC(_char, true, Variant::STRING, ARG("char", Variant::INT)); - REGISTER_VARARG_FUNC(str, true, Variant::STRING); REGISTER_VARARG_FUNC(range, false, Variant::ARRAY); REGISTER_CLASS_FUNC(load, false, "Resource", ARG("path", Variant::STRING)); REGISTER_FUNC(inst_to_dict, false, Variant::DICTIONARY, ARG("instance", Variant::OBJECT)); diff --git a/modules/gdscript/gdscript_vm.cpp b/modules/gdscript/gdscript_vm.cpp index e18a4a6190..b99f5d2685 100644 --- a/modules/gdscript/gdscript_vm.cpp +++ b/modules/gdscript/gdscript_vm.cpp @@ -811,13 +811,22 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a #ifdef DEBUG_ENABLED if (!valid) { + Object *obj = dst->get_validated_object(); String v = index->operator String(); - if (!v.is_empty()) { - v = "'" + v + "'"; + bool read_only_property = false; + if (obj) { + read_only_property = ClassDB::has_property(obj->get_class_name(), v) && (ClassDB::get_property_setter(obj->get_class_name(), v) == StringName()); + } + if (read_only_property) { + err_text = vformat(R"(Cannot set value into property "%s" (on base "%s") because it is read-only.)", v, _get_var_type(dst)); } else { - v = "of type '" + _get_var_type(index) + "'"; + if (!v.is_empty()) { + v = "'" + v + "'"; + } else { + v = "of type '" + _get_var_type(index) + "'"; + } + err_text = "Invalid set index " + v + " (on base: '" + _get_var_type(dst) + "') with value of type '" + _get_var_type(value) + "'"; } - err_text = "Invalid set index " + v + " (on base: '" + _get_var_type(dst) + "') with value of type '" + _get_var_type(value) + "'"; OPCODE_BREAK; } #endif @@ -1003,8 +1012,16 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a #ifdef DEBUG_ENABLED if (!valid) { - String err_type; - err_text = "Invalid set index '" + String(*index) + "' (on base: '" + _get_var_type(dst) + "') with value of type '" + _get_var_type(value) + "'."; + Object *obj = dst->get_validated_object(); + bool read_only_property = false; + if (obj) { + read_only_property = ClassDB::has_property(obj->get_class_name(), *index) && (ClassDB::get_property_setter(obj->get_class_name(), *index) == StringName()); + } + if (read_only_property) { + err_text = vformat(R"(Cannot set value into property "%s" (on base "%s") because it is read-only.)", String(*index), _get_var_type(dst)); + } else { + err_text = "Invalid set index '" + String(*index) + "' (on base: '" + _get_var_type(dst) + "') with value of type '" + _get_var_type(value) + "'."; + } OPCODE_BREAK; } #endif diff --git a/modules/gdscript/tests/scripts/analyzer/errors/assign_to_read_only_property.gd b/modules/gdscript/tests/scripts/analyzer/errors/assign_to_read_only_property.gd new file mode 100644 index 0000000000..2b1c4c9594 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/assign_to_read_only_property.gd @@ -0,0 +1,4 @@ +func test(): + var tree := SceneTree.new() + tree.root = Window.new() + tree.free() diff --git a/modules/gdscript/tests/scripts/analyzer/errors/assign_to_read_only_property.out b/modules/gdscript/tests/scripts/analyzer/errors/assign_to_read_only_property.out new file mode 100644 index 0000000000..b236d70ec8 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/assign_to_read_only_property.out @@ -0,0 +1,2 @@ +GDTEST_ANALYZER_ERROR +Cannot assign a new value to a read-only property. diff --git a/modules/gdscript/tests/scripts/analyzer/errors/assign_to_read_only_property_indirectly.gd b/modules/gdscript/tests/scripts/analyzer/errors/assign_to_read_only_property_indirectly.gd new file mode 100644 index 0000000000..c97ee0ea69 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/assign_to_read_only_property_indirectly.gd @@ -0,0 +1,4 @@ +func test(): + var state := PhysicsDirectBodyState3DExtension.new() + state.center_of_mass.x += 1.0 + state.free() diff --git a/modules/gdscript/tests/scripts/analyzer/errors/assign_to_read_only_property_indirectly.out b/modules/gdscript/tests/scripts/analyzer/errors/assign_to_read_only_property_indirectly.out new file mode 100644 index 0000000000..b236d70ec8 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/assign_to_read_only_property_indirectly.out @@ -0,0 +1,2 @@ +GDTEST_ANALYZER_ERROR +Cannot assign a new value to a read-only property. diff --git a/modules/gdscript/tests/scripts/runtime/assign_to_read_only_property.gd b/modules/gdscript/tests/scripts/runtime/assign_to_read_only_property.gd new file mode 100644 index 0000000000..19c4186622 --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/assign_to_read_only_property.gd @@ -0,0 +1,7 @@ +func test(): + var state = PhysicsDirectBodyState3DExtension.new() + assign(state) + state.free() + +func assign(state): + state.center_of_mass.x -= 1.0 diff --git a/modules/gdscript/tests/scripts/runtime/assign_to_read_only_property.out b/modules/gdscript/tests/scripts/runtime/assign_to_read_only_property.out new file mode 100644 index 0000000000..c181c5dd02 --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/assign_to_read_only_property.out @@ -0,0 +1,6 @@ +GDTEST_RUNTIME_ERROR +>> SCRIPT ERROR +>> on function: assign() +>> runtime/assign_to_read_only_property.gd +>> 7 +>> Cannot set value into property "center_of_mass" (on base "PhysicsDirectBodyState3DExtension") because it is read-only. diff --git a/modules/gdscript/tests/scripts/runtime/assign_to_read_only_property_with_variable_index.gd b/modules/gdscript/tests/scripts/runtime/assign_to_read_only_property_with_variable_index.gd new file mode 100644 index 0000000000..f15f580272 --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/assign_to_read_only_property_with_variable_index.gd @@ -0,0 +1,8 @@ +func test(): + var state = PhysicsDirectBodyState3DExtension.new() + var prop = &"center_of_mass" + assign(state, prop) + state.free() + +func assign(state, prop): + state[prop].x = 1.0 diff --git a/modules/gdscript/tests/scripts/runtime/assign_to_read_only_property_with_variable_index.out b/modules/gdscript/tests/scripts/runtime/assign_to_read_only_property_with_variable_index.out new file mode 100644 index 0000000000..2cdc81aacc --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/assign_to_read_only_property_with_variable_index.out @@ -0,0 +1,6 @@ +GDTEST_RUNTIME_ERROR +>> SCRIPT ERROR +>> on function: assign() +>> runtime/assign_to_read_only_property_with_variable_index.gd +>> 8 +>> Cannot set value into property "center_of_mass" (on base "PhysicsDirectBodyState3DExtension") because it is read-only. |