diff --git a/doc/classes/ProjectSettings.xml b/doc/classes/ProjectSettings.xml
index 131ac6ea9308..79d5a44e20db 100644
--- a/doc/classes/ProjectSettings.xml
+++ b/doc/classes/ProjectSettings.xml
@@ -469,12 +469,24 @@
Specifies the maximum number of log files allowed (used for rotation). Set to [code]1[/code] to disable log file rotation.
If the [code]--log-file <file>[/code] [url=$DOCS_URL/tutorials/editor/command_line_tutorial.html]command line argument[/url] is used, log rotation is always disabled.
+
+ When set to [code]warn[/code] or [code]error[/code], produces a warning or an error respectively when a member beginning with [code]__[/code] is accessed from outside the class.
+
+
+ When set to [code]warn[/code] or [code]error[/code], produces a warning or an error respectively when a member beginning with [code]_[/code] is accessed from outside the class or the super class where it is defined.
+
When set to [code]warn[/code] or [code]error[/code], produces a warning or an error respectively when an [code]assert[/code] call always evaluates to [code]false[/code].
When set to [code]warn[/code] or [code]error[/code], produces a warning or an error respectively when an [code]assert[/code] call always evaluates to [code]true[/code].
+
+ When set to [code]warn[/code] or [code]error[/code], produces a warning or an error respectively when a method beginning with [code]__[/code] is called from outside the class.
+
+
+ When set to [code]warn[/code] or [code]error[/code], produces a warning or an error respectively when a method beginning with [code]_[/code] is called from outside the class or the super class where it is defined.
+
When set to [code]warn[/code] or [code]error[/code], produces a warning or an error respectively when a local variable captured by a lambda is reassigned, since this does not modify the outer local variable.
@@ -616,6 +628,9 @@
When set to [code]warn[/code] or [code]error[/code], produces a warning or an error respectively when a private member variable is never used.
+
+ When set to [code]warn[/code] or [code]error[/code], produces a warning or an error respectively when a protected member variable is never used.
+
When set to [code]warn[/code] or [code]error[/code], produces a warning or an error respectively when a signal is declared but never explicitly used in the class.
diff --git a/modules/gdscript/gdscript_analyzer.cpp b/modules/gdscript/gdscript_analyzer.cpp
index 60109efc4d5b..4ed2be7730ad 100644
--- a/modules/gdscript/gdscript_analyzer.cpp
+++ b/modules/gdscript/gdscript_analyzer.cpp
@@ -341,6 +341,54 @@ void GDScriptAnalyzer::get_class_node_current_scope_classes(GDScriptParser::Clas
}
}
+#ifdef DEBUG_ENABLED
+void GDScriptAnalyzer::check_access_private_member(GDScriptParser::IdentifierNode *p_identifier, const bool p_is_call) {
+ if (p_identifier == nullptr) {
+ return;
+ }
+ GDScriptParser::IdentifierNode::IdentifierAccess identifier_access = p_identifier->get_access();
+ if (identifier_access == GDScriptParser::IdentifierNode::IdentifierAccess::ACCESS_PUBLIC) {
+ return;
+ }
+ if (parser->current_class->get_datatype().kind != GDScriptParser::DataType::CLASS && parser->current_class->get_datatype().kind != GDScriptParser::DataType::SCRIPT) {
+ return;
+ }
+ if (parser->current_function && parser->current_function->body && parser->current_function->body->has_local(p_identifier->name)) {
+ return;
+ }
+
+ bool has_member = parser->current_class->has_member(p_identifier->name);
+ if (has_member) {
+ return;
+ }
+ if (identifier_access == GDScriptParser::IdentifierNode::IdentifierAccess::ACCESS_PRIVATE) {
+ parser->push_warning(p_identifier, p_is_call ? GDScriptWarning::CALLING_PRIVATE_METHOD : GDScriptWarning::ACCESSING_PRIVATE_MEMBER, p_identifier->name);
+ return;
+ }
+
+ bool access_valid_protected_member = false;
+ GDScriptParser::ClassNode *base_class = parser->current_class->base_type.class_type;
+ while (base_class != nullptr) {
+ if (base_class->has_member(p_identifier->name)) {
+ access_valid_protected_member = true;
+ break;
+ }
+ base_class = base_class->base_type.class_type;
+ }
+ if (!access_valid_protected_member && identifier_access == GDScriptParser::IdentifierNode::IdentifierAccess::ACCESS_PROTECTED) {
+ parser->push_warning(p_identifier, p_is_call ? GDScriptWarning::CALLING_PROTECTED_METHOD : GDScriptWarning::ACCESSING_PROTECTED_MEMBER, p_identifier->name);
+ }
+}
+
+void GDScriptAnalyzer::warn_unused_private_protected_class_variable(const GDScriptParser::Node *p_node, const GDScriptParser::IdentifierNode *p_identifier) {
+ GDScriptParser::IdentifierNode::IdentifierAccess identifier_access = p_identifier->get_access();
+ if (identifier_access == GDScriptParser::IdentifierNode::IdentifierAccess::ACCESS_PUBLIC) {
+ return;
+ }
+ parser->push_warning(p_node, identifier_access == GDScriptParser::IdentifierNode::IdentifierAccess::ACCESS_PRIVATE ? GDScriptWarning::UNUSED_PRIVATE_CLASS_VARIABLE : GDScriptWarning::UNUSED_PROTECTED_CLASS_VARIABLE, p_identifier->name);
+}
+#endif // DEBUG_ENABLED
+
Error GDScriptAnalyzer::resolve_class_inheritance(GDScriptParser::ClassNode *p_class, const GDScriptParser::Node *p_source) {
if (p_source == nullptr && parser->has_class(p_class)) {
p_source = p_class;
@@ -1435,8 +1483,8 @@ void GDScriptAnalyzer::resolve_class_body(GDScriptParser::ClassNode *p_class, co
GDScriptParser::ClassNode::Member member = p_class->members[i];
if (member.type == GDScriptParser::ClassNode::Member::VARIABLE) {
#ifdef DEBUG_ENABLED
- if (member.variable->usages == 0 && String(member.variable->identifier->name).begins_with("_")) {
- parser->push_warning(member.variable->identifier, GDScriptWarning::UNUSED_PRIVATE_CLASS_VARIABLE, member.variable->identifier->name);
+ if (member.variable->usages == 0) {
+ warn_unused_private_protected_class_variable(member.variable->identifier, member.variable->identifier);
}
#endif
@@ -3510,10 +3558,19 @@ void GDScriptAnalyzer::reduce_call(GDScriptParser::CallNode *p_call, bool p_is_a
if (p_call->callee == nullptr && current_lambda != nullptr) {
push_error("Cannot use `super()` inside a lambda.", p_call);
}
+
+#ifdef DEBUG_ENABLED
+ GDScriptParser::IdentifierNode *identifier = static_cast(p_call->callee);
+ check_access_private_member(identifier, true);
+#endif
} else if (callee_type == GDScriptParser::Node::IDENTIFIER) {
base_type = parser->current_class->get_datatype();
base_type.is_meta_type = false;
is_self = true;
+#ifdef DEBUG_ENABLED
+ GDScriptParser::IdentifierNode *identifier = static_cast(p_call->callee);
+ check_access_private_member(identifier, true);
+#endif
} else if (callee_type == GDScriptParser::Node::SUBSCRIPT) {
GDScriptParser::SubscriptNode *subscript = static_cast(p_call->callee);
if (subscript->base == nullptr) {
@@ -3547,6 +3604,9 @@ void GDScriptAnalyzer::reduce_call(GDScriptParser::CallNode *p_call, bool p_is_a
base_type = subscript->base->get_datatype();
is_self = subscript->base->type == GDScriptParser::Node::SELF;
}
+#ifdef DEBUG_ENABLED
+ check_access_private_member(subscript->attribute, true);
+#endif
} else {
// Invalid call. Error already sent in parser.
// TODO: Could check if Callable here too.
@@ -4468,7 +4528,9 @@ void GDScriptAnalyzer::reduce_identifier(GDScriptParser::IdentifierNode *p_ident
function_test = function_test->source_lambda->parent_function;
}
}
-
+#ifdef DEBUG_ENABLED
+ check_access_private_member(p_identifier);
+#endif
return;
}
@@ -4830,6 +4892,9 @@ void GDScriptAnalyzer::reduce_subscript(GDScriptParser::SubscriptNode *p_subscri
}
result_type.kind = GDScriptParser::DataType::VARIANT;
}
+#ifdef DEBUG_ENABLED
+ check_access_private_member(p_subscript->attribute);
+#endif
} else {
if (p_subscript->index == nullptr) {
return;
diff --git a/modules/gdscript/gdscript_analyzer.h b/modules/gdscript/gdscript_analyzer.h
index 6acc34101f24..7fb1200f5ff1 100644
--- a/modules/gdscript/gdscript_analyzer.h
+++ b/modules/gdscript/gdscript_analyzer.h
@@ -66,6 +66,11 @@ class GDScriptAnalyzer {
void get_class_node_current_scope_classes(GDScriptParser::ClassNode *p_node, List *p_list, GDScriptParser::Node *p_source);
+#ifdef DEBUG_ENABLED
+ void check_access_private_member(GDScriptParser::IdentifierNode *p_identifier, const bool p_is_call = false);
+ void warn_unused_private_protected_class_variable(const GDScriptParser::Node *p_node, const GDScriptParser::IdentifierNode *p_identifier);
+#endif
+
Error resolve_class_inheritance(GDScriptParser::ClassNode *p_class, const GDScriptParser::Node *p_source = nullptr);
Error resolve_class_inheritance(GDScriptParser::ClassNode *p_class, bool p_recursive);
GDScriptParser::DataType resolve_datatype(GDScriptParser::TypeNode *p_type);
diff --git a/modules/gdscript/gdscript_parser.cpp b/modules/gdscript/gdscript_parser.cpp
index 646391787173..77e25a15f230 100644
--- a/modules/gdscript/gdscript_parser.cpp
+++ b/modules/gdscript/gdscript_parser.cpp
@@ -98,6 +98,8 @@ GDScriptParser::GDScriptParser() {
register_annotation(MethodInfo("@static_unload"), AnnotationInfo::SCRIPT, &GDScriptParser::static_unload_annotation);
// Onready annotation.
register_annotation(MethodInfo("@onready"), AnnotationInfo::VARIABLE, &GDScriptParser::onready_annotation);
+
+ // register_annotation(MethodInfo("@virtual"), AnnotationInfo::FUNCTION, &GDScriptParser::virtual_annotation);
// Export annotations.
register_annotation(MethodInfo("@export"), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations);
register_annotation(MethodInfo("@export_enum", PropertyInfo(Variant::STRING, "names")), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations, varray(), true);
@@ -4282,6 +4284,16 @@ bool GDScriptParser::onready_annotation(AnnotationNode *p_annotation, Node *p_ta
current_class->onready_used = true;
return true;
}
+// bool GDScriptParser::virtual_annotation(AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class) {
+// ERR_FAIL_COND_V_MSG(p_target->type != Node::FUNCTION, false, R"("@virtual" annotation can only be applied to class methods.)");
+// FunctionNode *method = static_cast(p_target);
+// if (method->is_virtual) {
+// push_error(R"("@virtual" annotation can only be applied to a method once.)", p_annotation);
+// return false;
+// }
+// method->is_virtual = true;
+// return true;
+// }
static String _get_annotation_error_string(const StringName &p_annotation_name, const Vector &p_expected_types, const GDScriptParser::DataType &p_provided_type) {
Vector types;
diff --git a/modules/gdscript/gdscript_parser.h b/modules/gdscript/gdscript_parser.h
index 22b14e8bb5c1..f3abd7a7576c 100644
--- a/modules/gdscript/gdscript_parser.h
+++ b/modules/gdscript/gdscript_parser.h
@@ -336,8 +336,8 @@ class GDScriptParser {
VARIABLE,
WHILE,
};
-
Type type = NONE;
+
int start_line = 0, end_line = 0;
int start_column = 0, end_column = 0;
int leftmost_column = 0, rightmost_column = 0;
@@ -796,6 +796,20 @@ class GDScriptParser {
members.push_back(Member(p_annotation_node));
}
+ Vector get_super_class_fqcns() const {
+ Vector ret;
+
+ const ClassNode *iterated_class = base_type.class_type;
+ while (iterated_class) {
+ if (iterated_class->datatype.kind == DataType::SCRIPT || iterated_class->datatype.kind == DataType::CLASS) {
+ ret.append(iterated_class->fqcn);
+ }
+ iterated_class = iterated_class->base_type.class_type;
+ }
+
+ return ret;
+ }
+
ClassNode() {
type = CLASS;
}
@@ -855,6 +869,7 @@ class GDScriptParser {
SuiteNode *body = nullptr;
bool is_static = false; // For lambdas it's determined in the analyzer.
bool is_coroutine = false;
+ // bool is_virtual = false;
Variant rpc_config;
MethodInfo info;
LambdaNode *source_lambda = nullptr;
@@ -902,6 +917,12 @@ class GDScriptParser {
};
Source source = UNDEFINED_SOURCE;
+ enum IdentifierAccess {
+ ACCESS_PUBLIC,
+ ACCESS_PRIVATE,
+ ACCESS_PROTECTED,
+ };
+
union {
ParameterNode *parameter_source = nullptr;
IdentifierNode *bind_source;
@@ -916,6 +937,17 @@ class GDScriptParser {
int usages = 0; // Useful for binds/iterator variable.
+ IdentifierAccess get_access() const {
+ String str_name = String(name);
+ if (str_name.begins_with("__")) {
+ return ACCESS_PRIVATE;
+ } else if (str_name.begins_with("_")) {
+ return ACCESS_PROTECTED;
+ } else {
+ return ACCESS_PUBLIC;
+ }
+ }
+
IdentifierNode() {
type = IDENTIFIER;
}
@@ -1509,6 +1541,7 @@ class GDScriptParser {
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 onready_annotation(AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class);
+ // bool virtual_annotation(AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class);
template
bool export_annotations(AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class);
bool export_storage_annotation(AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class);
diff --git a/modules/gdscript/gdscript_warning.cpp b/modules/gdscript/gdscript_warning.cpp
index f14b38a1ef24..59bc3820f9d3 100644
--- a/modules/gdscript/gdscript_warning.cpp
+++ b/modules/gdscript/gdscript_warning.cpp
@@ -51,6 +51,7 @@ String GDScriptWarning::get_message() const {
CHECK_SYMBOLS(1);
return vformat(R"(The local constant "%s" is declared but never used in the block. If this is intended, prefix it with an underscore: "_%s".)", symbols[0], symbols[0]);
case UNUSED_PRIVATE_CLASS_VARIABLE:
+ case UNUSED_PROTECTED_CLASS_VARIABLE:
CHECK_SYMBOLS(1);
return vformat(R"(The class variable "%s" is declared but never used in the class.)", symbols[0]);
case UNUSED_PARAMETER:
@@ -162,6 +163,18 @@ String GDScriptWarning::get_message() const {
return vformat(R"*(The default value is using "%s" which won't return nodes in the scene tree before "_ready()" is called. Use the "@onready" annotation to solve this.)*", symbols[0]);
case ONREADY_WITH_EXPORT:
return R"("@onready" will set the default value after "@export" takes effect and will override it.)";
+ case ACCESSING_PRIVATE_MEMBER:
+ CHECK_SYMBOLS(1);
+ return vformat(R"(Trying accessing the member "%s" prefixed with "__", which would be a private member.)", symbols[0]);
+ case CALLING_PRIVATE_METHOD:
+ CHECK_SYMBOLS(1);
+ return vformat(R"*(Trying calling the method "%s()" prefixed with "__", which would be a private method.)*", symbols[0]);
+ case ACCESSING_PROTECTED_MEMBER:
+ CHECK_SYMBOLS(1);
+ return vformat(R"(Trying accessing the member "%s" prefixed with "_", which would be a protected member.)", symbols[0]);
+ case CALLING_PROTECTED_METHOD:
+ CHECK_SYMBOLS(1);
+ return vformat(R"*(Trying calling the method "%s()" prefixed with "_", which would be a protected method.)*", symbols[0]);
#ifndef DISABLE_DEPRECATED
// Never produced. These warnings migrated from 3.x by mistake.
case PROPERTY_USED_AS_FUNCTION: // There is already an error.
@@ -199,6 +212,7 @@ String GDScriptWarning::get_name_from_code(Code p_code) {
"UNUSED_VARIABLE",
"UNUSED_LOCAL_CONSTANT",
"UNUSED_PRIVATE_CLASS_VARIABLE",
+ "UNUSED_PROTECTED_CLASS_VARIABLE",
"UNUSED_PARAMETER",
"UNUSED_SIGNAL",
"SHADOWED_VARIABLE",
@@ -238,6 +252,10 @@ String GDScriptWarning::get_name_from_code(Code p_code) {
"NATIVE_METHOD_OVERRIDE",
"GET_NODE_DEFAULT_WITHOUT_ONREADY",
"ONREADY_WITH_EXPORT",
+ "ACCESSING_PRIVATE_MEMBER",
+ "CALLING_PRIVATE_METHOD",
+ "ACCESSING_PROTECTED_MEMBER",
+ "CALLING_PROTECTED_METHOD",
#ifndef DISABLE_DEPRECATED
"PROPERTY_USED_AS_FUNCTION",
"CONSTANT_USED_AS_FUNCTION",
diff --git a/modules/gdscript/gdscript_warning.h b/modules/gdscript/gdscript_warning.h
index e9ab309cd647..28362de718f8 100644
--- a/modules/gdscript/gdscript_warning.h
+++ b/modules/gdscript/gdscript_warning.h
@@ -50,7 +50,8 @@ class GDScriptWarning {
UNASSIGNED_VARIABLE_OP_ASSIGN, // Variable never assigned but used in an assignment operation (+=, *=, etc).
UNUSED_VARIABLE, // Local variable is declared but never used.
UNUSED_LOCAL_CONSTANT, // Local constant is declared but never used.
- UNUSED_PRIVATE_CLASS_VARIABLE, // Class variable is declared private ("_" prefix) but never used in the class.
+ UNUSED_PRIVATE_CLASS_VARIABLE, // Class variable is declared private ("__" prefix) but never used in the class.
+ UNUSED_PROTECTED_CLASS_VARIABLE, // Class variable is declared private ("__" prefix) but never used in the class.
UNUSED_PARAMETER, // Function parameter is never used.
UNUSED_SIGNAL, // Signal is defined but never explicitly used in the class.
SHADOWED_VARIABLE, // A local variable/constant shadows a current class member.
@@ -90,6 +91,10 @@ class GDScriptWarning {
NATIVE_METHOD_OVERRIDE, // The script method overrides a native one, this may not work as intended.
GET_NODE_DEFAULT_WITHOUT_ONREADY, // A class variable uses `get_node()` (or the `$` notation) as its default value, but does not use the @onready annotation.
ONREADY_WITH_EXPORT, // The `@onready` annotation will set the value after `@export` which is likely not intended.
+ ACCESSING_PRIVATE_MEMBER, // A member prefixed with `__` is being accessed, whether the member exists or not.
+ CALLING_PRIVATE_METHOD, // A method prefixed with `__` is being called, whether the method exists or not.
+ ACCESSING_PROTECTED_MEMBER, // A member prefixed with `_` is being accessed, whether the member exists or not.
+ CALLING_PROTECTED_METHOD, // A method prefixed with `_` is being called, whether the method exists or not.
#ifndef DISABLE_DEPRECATED
PROPERTY_USED_AS_FUNCTION, // Function not found, but there's a property with the same name.
CONSTANT_USED_AS_FUNCTION, // Function not found, but there's a constant with the same name.
@@ -108,6 +113,7 @@ class GDScriptWarning {
WARN, // UNUSED_VARIABLE
WARN, // UNUSED_LOCAL_CONSTANT
WARN, // UNUSED_PRIVATE_CLASS_VARIABLE
+ WARN, // UNUSED_PROTECTED_CLASS_VARIABLE
WARN, // UNUSED_PARAMETER
WARN, // UNUSED_SIGNAL
WARN, // SHADOWED_VARIABLE
@@ -147,6 +153,10 @@ class GDScriptWarning {
ERROR, // NATIVE_METHOD_OVERRIDE // May not work as expected.
ERROR, // GET_NODE_DEFAULT_WITHOUT_ONREADY // May not work as expected.
ERROR, // ONREADY_WITH_EXPORT // May not work as expected.
+ WARN, // ACCESSING_PRIVATE_MEMBER
+ WARN, // CALLING_PRIVATE_METHOD
+ WARN, // ACCESSING_PROTECTED_MEMBER
+ WARN, // CALLING_PROTECTED_METHOD
#ifndef DISABLE_DEPRECATED
WARN, // PROPERTY_USED_AS_FUNCTION
WARN, // CONSTANT_USED_AS_FUNCTION
diff --git a/modules/gdscript/tests/scripts/analyzer/features/enum_function_typecheck.gd b/modules/gdscript/tests/scripts/analyzer/features/enum_function_typecheck.gd
index 3a1863a1f409..88f32d3653c8 100644
--- a/modules/gdscript/tests/scripts/analyzer/features/enum_function_typecheck.gd
+++ b/modules/gdscript/tests/scripts/analyzer/features/enum_function_typecheck.gd
@@ -26,7 +26,6 @@ class InnerClass:
print("Inner")
var o := EnumFunctionTypecheckOuterClass.new()
-
_d = o.outer_outer_no_class(EnumFunctionTypecheckOuterClass.MyEnum.V1)
print()
_d = o.outer_outer_class(EnumFunctionTypecheckOuterClass.MyEnum.V1)
diff --git a/modules/gdscript/tests/scripts/analyzer/features/virtual_method_implemented.gd b/modules/gdscript/tests/scripts/analyzer/features/virtual_method_implemented.gd
index a8641e4f3b8c..0b1c4191fd03 100644
--- a/modules/gdscript/tests/scripts/analyzer/features/virtual_method_implemented.gd
+++ b/modules/gdscript/tests/scripts/analyzer/features/virtual_method_implemented.gd
@@ -5,6 +5,7 @@ class BaseClass:
class SuperClassMethodsRecognized extends BaseClass:
func _init():
# Recognizes super class methods.
+ @warning_ignore("calling_protected_method")
var _x = _get_property_list()
class SuperMethodsRecognized extends BaseClass:
@@ -16,6 +17,8 @@ class SuperMethodsRecognized extends BaseClass:
func test():
var test1 = SuperClassMethodsRecognized.new()
+ @warning_ignore("calling_protected_method")
print(test1._get_property_list()) # Calls base class's method.
var test2 = SuperMethodsRecognized.new()
+ @warning_ignore("calling_protected_method")
print(test2._get_property_list())
diff --git a/modules/gdscript/tests/scripts/analyzer/features/warning_ignore_warnings.gd b/modules/gdscript/tests/scripts/analyzer/features/warning_ignore_warnings.gd
index 333950d64e98..3a77e12c68be 100644
--- a/modules/gdscript/tests/scripts/analyzer/features/warning_ignore_warnings.gd
+++ b/modules/gdscript/tests/scripts/analyzer/features/warning_ignore_warnings.gd
@@ -10,7 +10,9 @@ class A extends Node:
var get_node_default_without_onready = $Node
@warning_ignore("unused_private_class_variable")
-var _unused_private_class_variable
+var __unused_private_class_variable
+@warning_ignore("unused_protected_class_variable")
+var _unused_protected_class_variable
@warning_ignore("onready_with_export")
@onready @export var onready_with_export = 1
@@ -36,7 +38,7 @@ func test_warnings(unused_private_class_variable):
print(unassigned_variable)
var _unassigned_variable_op_assign
- @warning_ignore("unassigned_variable_op_assign")
+ @warning_ignore("unassigned_variable_op_assign", "accessing_protected_member")
_unassigned_variable_op_assign += t
@warning_ignore("unused_variable")
@@ -151,6 +153,86 @@ func test_unsafe_void_return() -> void:
func get_class():
pass
+class AccessTestA:
+ @warning_ignore("unused_protected_class_variable")
+ static var _static_a = null
+ @warning_ignore("unused_private_class_variable")
+ static var __static_b = null
+
+ @warning_ignore("unused_protected_class_variable")
+ var _a = null
+ @warning_ignore("unused_private_class_variable")
+ var __b = null
+
+ func _call_a():
+ pass
+
+ func __call_b():
+ pass
+
+ func _call_a_ret():
+ return null
+
+ func __call_b_ret():
+ return null
+
+ static func _static_call_a():
+ pass
+
+ static func __static_call_b():
+ pass
+
+ static func _static_call_a_ret():
+ return null
+
+ static func __static_call_b_ret():
+ return null
+
+class AccessTestB:
+ func _init():
+ var cls_a = AccessTestA.new()
+ @warning_ignore("accessing_protected_member")
+ AccessTestA._static_a = null
+ @warning_ignore("accessing_private_member")
+ AccessTestA.__static_b = null
+ @warning_ignore("accessing_protected_member")
+ cls_a._a = null
+ @warning_ignore("accessing_private_member")
+ cls_a.__b = null
+ @warning_ignore("calling_protected_method")
+ cls_a._call_a()
+ @warning_ignore("calling_private_method")
+ cls_a.__call_b()
+
+ @warning_ignore("unused_variable", "calling_protected_method")
+ var t1 = cls_a._call_a_ret()
+ @warning_ignore("unused_variable", "calling_private_method")
+ var t2 = cls_a.__call_b_ret()
+ @warning_ignore("calling_protected_method")
+ AccessTestA._static_call_a()
+ @warning_ignore("calling_private_method")
+ AccessTestA.__static_call_b()
+ @warning_ignore("unused_variable", "calling_protected_method")
+ var t3 = AccessTestA._static_call_a_ret()
+ @warning_ignore("unused_variable", "calling_private_method")
+ var t4 = AccessTestA.__static_call_b_ret()
+
+class AccessTestC extends AccessTestA:
+ func _init():
+ @warning_ignore("accessing_private_member")
+ AccessTestA.__static_b = null
+ @warning_ignore("accessing_private_member")
+ __b = null
+ @warning_ignore("calling_private_method")
+ __call_b()
+
+ @warning_ignore("unused_variable", "calling_private_method")
+ var t1 = __call_b_ret()
+ @warning_ignore("calling_private_method")
+ AccessTestA.__static_call_b()
+ @warning_ignore("unused_variable", "calling_private_method")
+ var t2 = AccessTestA.__static_call_b_ret()
+
# We don't want to execute it because of errors, just analyze.
func test():
pass
diff --git a/modules/gdscript/tests/scripts/analyzer/warnings/private_protected_members.gd b/modules/gdscript/tests/scripts/analyzer/warnings/private_protected_members.gd
new file mode 100644
index 000000000000..bdd1d3b270dc
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/warnings/private_protected_members.gd
@@ -0,0 +1,80 @@
+class A:
+ @warning_ignore("unused_protected_class_variable")
+ static var _static_a = null
+ @warning_ignore("unused_private_class_variable")
+ static var __static_b = null
+
+ @warning_ignore("unused_protected_class_variable")
+ var _a = null
+ @warning_ignore("unused_private_class_variable")
+ var __b = null
+
+ func _call_a():
+ pass
+
+ func __call_b():
+ pass
+
+ func _call_a_ret():
+ return null
+
+ func __call_b_ret():
+ return null
+
+ static func _static_call_a():
+ pass
+
+ static func __static_call_b():
+ pass
+
+ static func _static_call_a_ret():
+ return null
+
+ static func __static_call_b_ret():
+ return null
+
+class B:
+ func _init():
+ var cls_a = A.new()
+ A._static_a = null
+ A.__static_b = null
+ cls_a._a = null
+ cls_a.__b = null
+ cls_a._call_a()
+ cls_a.__call_b()
+
+ @warning_ignore("unused_variable")
+ var t1 = cls_a._call_a_ret()
+ @warning_ignore("unused_variable")
+ var t2 = cls_a.__call_b_ret()
+
+ A._static_call_a()
+ A.__static_call_b()
+ @warning_ignore("unused_variable")
+ var t3 = A._static_call_a_ret()
+ @warning_ignore("unused_variable")
+ var t4 = A.__static_call_b_ret()
+
+class C extends A:
+ func _init():
+ A._static_a = null
+ A.__static_b = null
+ _a = null
+ __b = null
+ _call_a()
+ __call_b()
+
+ @warning_ignore("unused_variable")
+ var t1 = _call_a_ret()
+ @warning_ignore("unused_variable")
+ var t2 = __call_b_ret()
+
+ _static_call_a()
+ __static_call_b()
+ @warning_ignore("unused_variable")
+ var t3 = _static_call_a_ret()
+ @warning_ignore("unused_variable")
+ var t4 = __static_call_b_ret()
+
+func test():
+ pass
diff --git a/modules/gdscript/tests/scripts/analyzer/warnings/private_protected_members.out b/modules/gdscript/tests/scripts/analyzer/warnings/private_protected_members.out
new file mode 100644
index 000000000000..77332a726bd6
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/warnings/private_protected_members.out
@@ -0,0 +1,19 @@
+GDTEST_OK
+~~ WARNING at line 39: (ACCESSING_PROTECTED_MEMBER) Trying accessing the member "_static_a" prefixed with "_", which would be a protected member.
+~~ WARNING at line 40: (ACCESSING_PRIVATE_MEMBER) Trying accessing the member "__static_b" prefixed with "__", which would be a private member.
+~~ WARNING at line 41: (ACCESSING_PROTECTED_MEMBER) Trying accessing the member "_a" prefixed with "_", which would be a protected member.
+~~ WARNING at line 42: (ACCESSING_PRIVATE_MEMBER) Trying accessing the member "__b" prefixed with "__", which would be a private member.
+~~ WARNING at line 43: (CALLING_PROTECTED_METHOD) Trying calling the method "_call_a()" prefixed with "_", which would be a protected method.
+~~ WARNING at line 44: (CALLING_PRIVATE_METHOD) Trying calling the method "__call_b()" prefixed with "__", which would be a private method.
+~~ WARNING at line 47: (CALLING_PROTECTED_METHOD) Trying calling the method "_call_a_ret()" prefixed with "_", which would be a protected method.
+~~ WARNING at line 49: (CALLING_PRIVATE_METHOD) Trying calling the method "__call_b_ret()" prefixed with "__", which would be a private method.
+~~ WARNING at line 51: (CALLING_PROTECTED_METHOD) Trying calling the method "_static_call_a()" prefixed with "_", which would be a protected method.
+~~ WARNING at line 52: (CALLING_PRIVATE_METHOD) Trying calling the method "__static_call_b()" prefixed with "__", which would be a private method.
+~~ WARNING at line 54: (CALLING_PROTECTED_METHOD) Trying calling the method "_static_call_a_ret()" prefixed with "_", which would be a protected method.
+~~ WARNING at line 56: (CALLING_PRIVATE_METHOD) Trying calling the method "__static_call_b_ret()" prefixed with "__", which would be a private method.
+~~ WARNING at line 61: (ACCESSING_PRIVATE_MEMBER) Trying accessing the member "__static_b" prefixed with "__", which would be a private member.
+~~ WARNING at line 63: (ACCESSING_PRIVATE_MEMBER) Trying accessing the member "__b" prefixed with "__", which would be a private member.
+~~ WARNING at line 65: (CALLING_PRIVATE_METHOD) Trying calling the method "__call_b()" prefixed with "__", which would be a private method.
+~~ WARNING at line 70: (CALLING_PRIVATE_METHOD) Trying calling the method "__call_b_ret()" prefixed with "__", which would be a private method.
+~~ WARNING at line 73: (CALLING_PRIVATE_METHOD) Trying calling the method "__static_call_b()" prefixed with "__", which would be a private method.
+~~ WARNING at line 77: (CALLING_PRIVATE_METHOD) Trying calling the method "__static_call_b_ret()" prefixed with "__", which would be a private method.
diff --git a/modules/gdscript/tests/scripts/analyzer/warnings/unused_private_class_variable.gd b/modules/gdscript/tests/scripts/analyzer/warnings/unused_private_class_variable.gd
index 5ca8ceffddc5..acff6e2bbb7e 100644
--- a/modules/gdscript/tests/scripts/analyzer/warnings/unused_private_class_variable.gd
+++ b/modules/gdscript/tests/scripts/analyzer/warnings/unused_private_class_variable.gd
@@ -1,10 +1,16 @@
# GH-72135
var _a
-@warning_ignore("unused_private_class_variable")
+@warning_ignore("unused_protected_class_variable")
var _b
-@warning_ignore("unused_private_class_variable") var _c
+@warning_ignore("unused_protected_class_variable") var _c
var _d
+var __e
+@warning_ignore("unused_private_class_variable")
+var __f
+@warning_ignore("unused_private_class_variable") var __g
+var __h
+
func test():
pass
diff --git a/modules/gdscript/tests/scripts/analyzer/warnings/unused_private_class_variable.out b/modules/gdscript/tests/scripts/analyzer/warnings/unused_private_class_variable.out
index 692ec589def8..82d671fa32b4 100644
--- a/modules/gdscript/tests/scripts/analyzer/warnings/unused_private_class_variable.out
+++ b/modules/gdscript/tests/scripts/analyzer/warnings/unused_private_class_variable.out
@@ -1,3 +1,5 @@
GDTEST_OK
-~~ WARNING at line 3: (UNUSED_PRIVATE_CLASS_VARIABLE) The class variable "_a" is declared but never used in the class.
-~~ WARNING at line 7: (UNUSED_PRIVATE_CLASS_VARIABLE) The class variable "_d" is declared but never used in the class.
+~~ WARNING at line 3: (UNUSED_PROTECTED_CLASS_VARIABLE) The class variable "_a" is declared but never used in the class.
+~~ WARNING at line 7: (UNUSED_PROTECTED_CLASS_VARIABLE) The class variable "_d" is declared but never used in the class.
+~~ WARNING at line 9: (UNUSED_PRIVATE_CLASS_VARIABLE) The class variable "__e" is declared but never used in the class.
+~~ WARNING at line 13: (UNUSED_PRIVATE_CLASS_VARIABLE) The class variable "__h" is declared but never used in the class.
diff --git a/modules/gdscript/tests/scripts/parser/features/warning_ignore_regions.gd b/modules/gdscript/tests/scripts/parser/features/warning_ignore_regions.gd
index ffdeae1763ad..50832ed280e1 100644
--- a/modules/gdscript/tests/scripts/parser/features/warning_ignore_regions.gd
+++ b/modules/gdscript/tests/scripts/parser/features/warning_ignore_regions.gd
@@ -1,12 +1,19 @@
@warning_ignore_start("unreachable_code", "narrowing_conversion")
var _a = 1
-@warning_ignore_start("unused_private_class_variable")
+@warning_ignore_start("unused_protected_class_variable")
var _b = 2
var _c = 3
-@warning_ignore_restore("unused_private_class_variable")
+@warning_ignore_restore("unused_protected_class_variable")
var _d = 4
+var __e = 5
+@warning_ignore_start("unused_private_class_variable")
+var __f = 6
+var __g = 7
+@warning_ignore_restore("unused_private_class_variable")
+var __h = 8
+
func test():
return
diff --git a/modules/gdscript/tests/scripts/parser/features/warning_ignore_regions.out b/modules/gdscript/tests/scripts/parser/features/warning_ignore_regions.out
index 11f7263ed8e4..121fe8ea208f 100644
--- a/modules/gdscript/tests/scripts/parser/features/warning_ignore_regions.out
+++ b/modules/gdscript/tests/scripts/parser/features/warning_ignore_regions.out
@@ -1,6 +1,8 @@
GDTEST_OK
-~~ WARNING at line 3: (UNUSED_PRIVATE_CLASS_VARIABLE) The class variable "_a" is declared but never used in the class.
-~~ WARNING at line 8: (UNUSED_PRIVATE_CLASS_VARIABLE) The class variable "_d" is declared but never used in the class.
-~~ WARNING at line 13: (UNUSED_VARIABLE) The local variable "a" is declared but never used in the block. If this is intended, prefix it with an underscore: "_a".
-~~ WARNING at line 18: (UNUSED_VARIABLE) The local variable "d" is declared but never used in the block. If this is intended, prefix it with an underscore: "_d".
-~~ WARNING at line 22: (NARROWING_CONVERSION) Narrowing conversion (float is converted to int and loses precision).
+~~ WARNING at line 3: (UNUSED_PROTECTED_CLASS_VARIABLE) The class variable "_a" is declared but never used in the class.
+~~ WARNING at line 8: (UNUSED_PROTECTED_CLASS_VARIABLE) The class variable "_d" is declared but never used in the class.
+~~ WARNING at line 10: (UNUSED_PRIVATE_CLASS_VARIABLE) The class variable "__e" is declared but never used in the class.
+~~ WARNING at line 15: (UNUSED_PRIVATE_CLASS_VARIABLE) The class variable "__h" is declared but never used in the class.
+~~ WARNING at line 20: (UNUSED_VARIABLE) The local variable "a" is declared but never used in the block. If this is intended, prefix it with an underscore: "_a".
+~~ WARNING at line 25: (UNUSED_VARIABLE) The local variable "d" is declared but never used in the block. If this is intended, prefix it with an underscore: "_d".
+~~ WARNING at line 29: (NARROWING_CONVERSION) Narrowing conversion (float is converted to int and loses precision).
diff --git a/modules/gdscript/tests/scripts/runtime/features/onready_base_before_subclass.gd b/modules/gdscript/tests/scripts/runtime/features/onready_base_before_subclass.gd
index 99156adb2865..668b7f05bcaf 100644
--- a/modules/gdscript/tests/scripts/runtime/features/onready_base_before_subclass.gd
+++ b/modules/gdscript/tests/scripts/runtime/features/onready_base_before_subclass.gd
@@ -14,5 +14,6 @@ class B extends A:
func test():
var node := B.new()
+ @warning_ignore("calling_protected_method")
node._ready()
node.free()
diff --git a/modules/gdscript/tests/scripts/runtime/features/self_destruction.gd b/modules/gdscript/tests/scripts/runtime/features/self_destruction.gd
index 442335faeba0..971031951da2 100644
--- a/modules/gdscript/tests/scripts/runtime/features/self_destruction.gd
+++ b/modules/gdscript/tests/scripts/runtime/features/self_destruction.gd
@@ -41,7 +41,7 @@ func test():
# Signal handling.
obj = MyObj.new(nullify_obj)
- @warning_ignore("return_value_discarded")
+ @warning_ignore("return_value_discarded", "accessing_protected_member")
some_signal.connect(obj._on_some_signal)
some_signal.emit()
print(obj)