diff --git a/modules/gdscript/gdscript_analyzer.cpp b/modules/gdscript/gdscript_analyzer.cpp index 7ddb8fe05bc8..fbd91b36099d 100644 --- a/modules/gdscript/gdscript_analyzer.cpp +++ b/modules/gdscript/gdscript_analyzer.cpp @@ -1805,7 +1805,8 @@ void GDScriptAnalyzer::resolve_function_signature(GDScriptParser::FunctionNode * int default_par_count = 0; BitField method_flags = {}; StringName native_base; - if (!p_is_lambda && get_function_signature(p_function, false, base_type, function_name, parent_return_type, parameters_types, default_par_count, method_flags, &native_base)) { + GDScriptParser::Node::AccessLevel acceess_level = GDScriptParser::Node::PUBLIC; + if (!p_is_lambda && get_function_signature(p_function, false, base_type, function_name, parent_return_type, parameters_types, default_par_count, method_flags, acceess_level, &native_base)) { bool valid = p_function->is_static == method_flags.has_flag(METHOD_FLAG_STATIC); if (p_function->return_type != nullptr) { @@ -1925,6 +1926,27 @@ void GDScriptAnalyzer::resolve_function_body(GDScriptParser::FunctionNode *p_fun } } + if (p_function->must_call_super) { + if (p_is_lambda) { + push_error(R"(Cannot be used in lambda expressions)", p_function); + } else { + if (p_function->body->statements.size() == 0) { + push_error(R"(Must call the base class function)", p_function); + return; + } + GDScriptParser::Node *node = p_function->body->statements[0]; + bool is_super = false; + if (node->type == GDScriptParser::Node::Type::CALL) { + GDScriptParser::CallNode *call_node = static_cast(node); + if (!call_node->is_super) { + push_error(R"(Must call the base class function)", p_function); + } + } else { + push_error(R"(Must call the base class function)", p_function); + } + } + } + parser->current_function = previous_function; static_context = previous_static_context; } @@ -2277,7 +2299,8 @@ void GDScriptAnalyzer::resolve_for(GDScriptParser::ForNode *p_for) { List par_types; int default_arg_count = 0; BitField method_flags = {}; - if (get_function_signature(p_for->list, false, list_type, CoreStringName(_iter_get), return_type, par_types, default_arg_count, method_flags)) { + GDScriptParser::Node::AccessLevel access_level = GDScriptParser::Node::PUBLIC; + if (get_function_signature(p_for->list, false, list_type, CoreStringName(_iter_get), return_type, par_types, default_arg_count, method_flags, access_level)) { variable_type = return_type; variable_type.type_source = list_type.type_source; } else if (!list_type.is_hard_type()) { @@ -3570,6 +3593,7 @@ void GDScriptAnalyzer::reduce_call(GDScriptParser::CallNode *p_call, bool p_is_a BitField method_flags = {}; GDScriptParser::DataType return_type; List par_types; + GDScriptParser::Node::AccessLevel access_level = GDScriptParser::Node::PUBLIC; bool is_constructor = (base_type.is_meta_type || (p_call->callee && p_call->callee->type == GDScriptParser::Node::IDENTIFIER)) && p_call->function_name == SNAME("new"); @@ -3584,12 +3608,14 @@ void GDScriptAnalyzer::reduce_call(GDScriptParser::CallNode *p_call, bool p_is_a } } - if (get_function_signature(p_call, is_constructor, base_type, p_call->function_name, return_type, par_types, default_arg_count, method_flags)) { + if (get_function_signature(p_call, is_constructor, base_type, p_call->function_name, return_type, par_types, default_arg_count, method_flags, access_level)) { // If the method is implemented in the class hierarchy, the virtual flag will not be set for that MethodInfo and the search stops there. // Virtual check only possible for super() calls because class hierarchy is known. Node/Objects may have scripts attached we don't know of at compile-time. p_call->is_static = method_flags.has_flag(METHOD_FLAG_STATIC); if (p_call->is_super && method_flags.has_flag(METHOD_FLAG_VIRTUAL)) { push_error(vformat(R"*(Cannot call the parent class' virtual function "%s()" because it hasn't been defined.)*", p_call->function_name), p_call); + } else if (p_call->is_super && access_level < GDScriptParser::Node::AccessLevel::PROTECTED) { + push_error(R"(Access restricted.)", p_call); } // If the function requires typed arrays we must make literals be typed. @@ -3630,7 +3656,12 @@ void GDScriptAnalyzer::reduce_call(GDScriptParser::CallNode *p_call, bool p_is_a base_type.is_meta_type = false; // For `to_string()`. push_error(vformat(R"*(Cannot call non-static function "%s()" on the class "%s" directly. Make an instance instead.)*", p_call->function_name, base_type.to_string()), p_call); } else if (is_self && !p_call->is_static) { + if (access_level < GDScriptParser::Node::AccessLevel::PROTECTED) { + push_error(R"(Access restricted.)", p_call); + } mark_lambda_use_self(); + } else if (!is_self && access_level < GDScriptParser::Node::AccessLevel::PUBLIC) { + push_error(R"(Access restricted.)", p_call); } if (!p_is_root && !p_is_await && return_type.is_hard_type() && return_type.kind == GDScriptParser::DataType::BUILTIN && return_type.builtin_type == Variant::NIL) { @@ -4171,6 +4202,7 @@ void GDScriptAnalyzer::reduce_identifier_from_base(GDScriptParser::IdentifierNod if (is_base && (!base.is_meta_type || member.variable->is_static)) { p_identifier->set_datatype(member.get_datatype()); p_identifier->source = member.variable->is_static ? GDScriptParser::IdentifierNode::STATIC_VARIABLE : GDScriptParser::IdentifierNode::MEMBER_VARIABLE; + p_identifier->is_base = is_base; p_identifier->variable_source = member.variable; member.variable->usages += 1; return; @@ -4447,6 +4479,18 @@ void GDScriptAnalyzer::reduce_identifier(GDScriptParser::IdentifierNode *p_ident } else { push_error(vformat(R"*(Cannot access %s "%s" from a static variable initializer.)*", source_type, p_identifier->name), p_identifier); } + } else if (!static_context && source_is_instance_variable) { + switch (p_identifier->variable_source->access_level) { + case GDScriptParser::Node::AccessLevel::PRIVATE: { + if (p_identifier->is_base || p_identifier->source != GDScriptParser::IdentifierNode::MEMBER_VARIABLE) { + push_error(R"(Access restricted.)", p_identifier); + } + } break; + case GDScriptParser::Node::AccessLevel::PROTECTED: { + } break; + default: + break; + } } if (current_lambda != nullptr) { @@ -4784,6 +4828,18 @@ void GDScriptAnalyzer::reduce_subscript(GDScriptParser::SubscriptNode *p_subscri } } + for (GDScriptParser::ClassNode::Member member : base_type.class_type->members) { + if (member.get_name() == p_subscript->attribute->name) { + for (GDScriptParser::AnnotationNode *annotation : member.get_source_node()->annotations) { + if (annotation->name == "@protected" || annotation->name == "@protected") { + push_error(R"(Access restricted.)", p_subscript); + break; + } + } + break; + } + } + if (valid) { // Do nothing. } else if (base_type.is_variant() || !base_type.is_hard_type()) { @@ -5709,7 +5765,7 @@ GDScriptParser::DataType GDScriptAnalyzer::type_from_property(const PropertyInfo return result; } -bool GDScriptAnalyzer::get_function_signature(GDScriptParser::Node *p_source, bool p_is_constructor, GDScriptParser::DataType p_base_type, const StringName &p_function, GDScriptParser::DataType &r_return_type, List &r_par_types, int &r_default_arg_count, BitField &r_method_flags, StringName *r_native_class) { +bool GDScriptAnalyzer::get_function_signature(GDScriptParser::Node *p_source, bool p_is_constructor, GDScriptParser::DataType p_base_type, const StringName &p_function, GDScriptParser::DataType &r_return_type, List &r_par_types, int &r_default_arg_count, BitField &r_method_flags, GDScriptParser::Node::AccessLevel &access_level, StringName *r_native_class) { r_method_flags = METHOD_FLAGS_DEFAULT; r_default_arg_count = 0; if (r_native_class) { @@ -5811,6 +5867,7 @@ bool GDScriptAnalyzer::get_function_signature(GDScriptParser::Node *p_source, bo r_return_type = p_is_constructor ? p_base_type : found_function->get_datatype(); r_return_type.is_meta_type = false; r_return_type.is_coroutine = found_function->is_coroutine; + access_level = found_function->access_level; return true; } diff --git a/modules/gdscript/gdscript_analyzer.h b/modules/gdscript/gdscript_analyzer.h index 576276471d3a..441588521ac0 100644 --- a/modules/gdscript/gdscript_analyzer.h +++ b/modules/gdscript/gdscript_analyzer.h @@ -128,7 +128,7 @@ class GDScriptAnalyzer { GDScriptParser::DataType type_from_variant(const Variant &p_value, const GDScriptParser::Node *p_source); 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 &r_par_types, int &r_default_arg_count, BitField &r_method_flags, StringName *r_native_class = nullptr); + 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 &r_par_types, int &r_default_arg_count, BitField &r_method_flags, GDScriptParser::Node::AccessLevel &access_level, StringName *r_native_class = nullptr); bool function_signature_from_info(const MethodInfo &p_info, GDScriptParser::DataType &r_return_type, List &r_par_types, int &r_default_arg_count, BitField &r_method_flags); void validate_call_arg(const List &p_par_types, int p_default_args_count, bool p_is_vararg, const GDScriptParser::CallNode *p_call); void validate_call_arg(const MethodInfo &p_method, const GDScriptParser::CallNode *p_call); diff --git a/modules/gdscript/gdscript_parser.cpp b/modules/gdscript/gdscript_parser.cpp index 0153f47b40a8..411fc9a1901f 100644 --- a/modules/gdscript/gdscript_parser.cpp +++ b/modules/gdscript/gdscript_parser.cpp @@ -96,6 +96,9 @@ GDScriptParser::GDScriptParser() { register_annotation(MethodInfo("@tool"), AnnotationInfo::SCRIPT, &GDScriptParser::tool_annotation); register_annotation(MethodInfo("@icon", PropertyInfo(Variant::STRING, "icon_path")), AnnotationInfo::SCRIPT, &GDScriptParser::icon_annotation); register_annotation(MethodInfo("@static_unload"), AnnotationInfo::SCRIPT, &GDScriptParser::static_unload_annotation); + register_annotation(MethodInfo("@must_call_super"), AnnotationInfo::FUNCTION, &GDScriptParser::must_call_super_annotation); + register_annotation(MethodInfo("@protected"), AnnotationInfo::CLASS_LEVEL, &GDScriptParser::protected_annotation); + register_annotation(MethodInfo("@private"), AnnotationInfo::CLASS_LEVEL, &GDScriptParser::private_annotation); // Onready annotation. register_annotation(MethodInfo("@onready"), AnnotationInfo::VARIABLE, &GDScriptParser::onready_annotation); // Export annotations. @@ -4349,6 +4352,39 @@ bool GDScriptParser::static_unload_annotation(AnnotationNode *p_annotation, Node return true; } +bool GDScriptParser::must_call_super_annotation(AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class) { + ERR_FAIL_COND_V_MSG(p_target->type != Node::FUNCTION, false, R"("@must_call_super" annotation can only be applied to functions.)"); + FunctionNode *func_node = static_cast(p_target); + if (func_node->must_call_super) { + push_error(vformat(R"("%s" annotation can only be used once per function.)", p_annotation->name), p_annotation); + return false; + } + func_node->must_call_super = true; + return true; +} + +bool GDScriptParser::protected_annotation(AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class) { + ERR_FAIL_COND_V_MSG(p_target->type != Node::CONSTANT && p_target->type != Node::VARIABLE && p_target->type != Node::FUNCTION && p_target->type != Node::CLASS && p_target->type != Node::SIGNAL, + false, R"(@protected can only be applied to constants, variables, signals, functions, and classes.)"); + if (p_target->access_level != GDScriptParser::Node::AccessLevel::PUBLIC) { + push_error(R"(Only one of @protected/@private is allowed, and it can be used only once.)", p_target); + return false; + } + p_target->access_level = GDScriptParser::Node::AccessLevel::PROTECTED; + return true; +} + +bool GDScriptParser::private_annotation(AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class) { + ERR_FAIL_COND_V_MSG(p_target->type != Node::CONSTANT && p_target->type != Node::VARIABLE && p_target->type != Node::FUNCTION && p_target->type != Node::CLASS && p_target->type != Node::SIGNAL, + false, R"(@private can only be applied to constants, variables, signals, functions, and classes.)"); + if (p_target->access_level != GDScriptParser::Node::AccessLevel::PUBLIC) { + push_error(R"(Only one of @protected/@private is allowed, and it can be used only once.)", p_target); + return false; + } + p_target->access_level = GDScriptParser::Node::AccessLevel::PRIVATE; + return true; +} + bool GDScriptParser::onready_annotation(AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class) { ERR_FAIL_COND_V_MSG(p_target->type != Node::VARIABLE, false, R"("@onready" annotation can only be applied to class variables.)"); diff --git a/modules/gdscript/gdscript_parser.h b/modules/gdscript/gdscript_parser.h index a235f0d799da..0c2bf1b0850a 100644 --- a/modules/gdscript/gdscript_parser.h +++ b/modules/gdscript/gdscript_parser.h @@ -336,7 +336,15 @@ class GDScriptParser { WHILE, }; + enum AccessLevel { + PRIVATE, + PROTECTED, + FRIEND, + PUBLIC + }; + Type type = NONE; + AccessLevel access_level = PUBLIC; int start_line = 0, end_line = 0; int start_column = 0, end_column = 0; int leftmost_column = 0, rightmost_column = 0; @@ -856,6 +864,7 @@ class GDScriptParser { SuiteNode *body = nullptr; bool is_static = false; // For lambdas it's determined in the analyzer. bool is_coroutine = false; + bool must_call_super = false; Variant rpc_config; MethodInfo info; LambdaNode *source_lambda = nullptr; @@ -912,6 +921,7 @@ class GDScriptParser { FunctionNode *function_source; }; bool function_source_is_static = false; // For non-GDScript scripts. + bool is_base = false; FunctionNode *source_function = nullptr; // TODO: Rename to disambiguate `function_source`. @@ -1528,6 +1538,9 @@ class GDScriptParser { bool tool_annotation(AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class); bool icon_annotation(AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class); bool static_unload_annotation(AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class); + bool must_call_super_annotation(AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class); + bool protected_annotation(AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class); + bool private_annotation(AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class); bool onready_annotation(AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class); template bool export_annotations(AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class);