Skip to content

Commit 1e2346b

Browse files
added @override to help with explicitly overriding the base method
1 parent ddd763e commit 1e2346b

20 files changed

+93
-38
lines changed

doc/classes/ProjectSettings.xml

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -546,7 +546,10 @@
546546
When set to [code]warn[/code] or [code]error[/code], produces a warning or an error respectively when the [code]@onready[/code] annotation is used together with the [code]@export[/code] annotation, since it may not behave as expected.
547547
</member>
548548
<member name="debug/gdscript/warnings/override_non_virtual_method" type="int" setter="" getter="" default="1">
549-
When set to [code]warn[/code] or [code]error[/code], produces a warning or an error respectively when a method tries to override the one that is not annotated by [code]@virtual[/code] in the super class, since such behavior will lead to unexpected and unsafe behaviors, unless you know what you are doing.
549+
When set to [code]warn[/code] or [code]error[/code], produces a warning or an error respectively when a method tries to override the one that is not annotated by [code]@virtual[/code] from the base class, since such behavior will lead to unexpected and unsafe behaviors, unless you know what you are doing.
550+
</member>
551+
<member name="debug/gdscript/warnings/override_without_override_annotation" type="int" setter="" getter="" default="1">
552+
When set to [code]warn[/code] or [code]error[/code], produces a warning or an error respectively when a method tries to override the one annotated by [code]@virtual[/code] from the base class, without an explicit annotation [code]@override[/code].
550553
</member>
551554
<member name="debug/gdscript/warnings/property_used_as_function" type="int" setter="" getter="" default="1" deprecated="This warning is never produced. Instead, an error is generated if the expression type is known at compile time.">
552555
When set to [code]warn[/code] or [code]error[/code], produces a warning or an error respectively when using a property as if it is a function.

modules/gdscript/doc_classes/@GDScript.xml

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -739,6 +739,27 @@
739739
[/codeblock]
740740
</description>
741741
</annotation>
742+
<annotation name="@override">
743+
<return type="void" />
744+
<description>
745+
Mark that the following method overrides the one annotated by [annotation @virtual] from the base class.
746+
If you override a method from the base class without this annotation, you will get a warning (or an error).
747+
[codeblock]
748+
func _init():
749+
B.new().test() # Prints B
750+
751+
752+
class A:
753+
@virtual func test():
754+
print("A")
755+
756+
class B extends A:
757+
func test():
758+
print("B") # Warning (or an error) about overriding a virtual method without explicit overriding.
759+
[/codeblock]
760+
You can ignore the warning as long as you know what you are doing.
761+
</description>
762+
</annotation>
742763
<annotation name="@rpc">
743764
<return type="void" />
744765
<param index="0" name="mode" type="String" default="&quot;authority&quot;" />
@@ -787,6 +808,7 @@
787808
<return type="void" />
788809
<description>
789810
Mark a method as a virtual method. A virtual method is a method that can be overridden by subclasses.
811+
To override a virtual method, it is better to use an [annotation @override] annotation for the method in the subclass.
790812
[codeblock]
791813
func _init():
792814
B.new().test() # Prints B.
@@ -797,13 +819,13 @@
797819
print("A")
798820

799821
class B extends A:
800-
func test():
822+
@override func test():
801823
print("B")
802824
[/codeblock]
803825
If a method is trying to override a non-virtual method, a warning (or an error) will be thrown.
804826
[codeblock]
805827
func _init():
806-
B.new().test() # Prints B, with a warning (or an error) about overriding a non-virtual method.
828+
B.new().test() # Prints B
807829

808830

809831
class A:
@@ -812,7 +834,7 @@
812834

813835
class B extends A:
814836
func test():
815-
print("B")
837+
print("B") # Warning (or an error) about overriding a non-virtual method.
816838
[/codeblock]
817839
You can ignore the warning as long as you know what you are doing.
818840
</description>

modules/gdscript/gdscript_analyzer.cpp

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1870,11 +1870,13 @@ void GDScriptAnalyzer::resolve_function_signature(GDScriptParser::FunctionNode *
18701870
push_error(vformat(R"(The function signature doesn't match the parent. Parent signature is "%s".)", parent_signature), p_function);
18711871
}
18721872

1873-
// Non-virtual methods cannot be overridden.
1873+
// (Non-)virtual methods overriding warnings
18741874
if (base_type.class_type != nullptr && base_type.class_type->has_function(p_function->identifier->name)) {
18751875
GDScriptParser::FunctionNode *parent_func = base_type.class_type->get_member(p_function->identifier->name).function;
18761876
if (!parent_func->is_annotated_virtual) {
18771877
parser->push_warning(p_function, GDScriptWarning::OVERRIDE_NON_VIRTUAL_METHOD, function_name);
1878+
} else if (!p_function->is_annotated_overriding) {
1879+
parser->push_warning(p_function, GDScriptWarning::OVERRIDE_WITHOUT_OVERRIDE_ANNOATION, function_name);
18781880
}
18791881
}
18801882

modules/gdscript/gdscript_parser.cpp

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,7 @@ GDScriptParser::GDScriptParser() {
9999
// Onready annotation.
100100
register_annotation(MethodInfo("@onready"), AnnotationInfo::VARIABLE, &GDScriptParser::onready_annotation);
101101
// Virtual annotation
102+
register_annotation(MethodInfo("@override"), AnnotationInfo::FUNCTION, &GDScriptParser::override_annotation);
102103
register_annotation(MethodInfo("@virtual"), AnnotationInfo::FUNCTION, &GDScriptParser::virtual_annotation);
103104
// Export annotations.
104105
register_annotation(MethodInfo("@export"), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_NONE, Variant::NIL>);
@@ -4293,14 +4294,28 @@ bool GDScriptParser::virtual_annotation(AnnotationNode *p_annotation, Node *p_ta
42934294

42944295
FunctionNode *method = static_cast<FunctionNode *>(p_target);
42954296
if (method->is_annotated_virtual) {
4296-
push_error(R"("@virtual" annotation can only be used once per variable.)", p_annotation);
4297+
push_error(R"("@virtual" annotation can only be used once per method.)", p_annotation);
42974298
return false;
42984299
}
42994300

43004301
method->is_annotated_virtual = true;
43014302
return true;
43024303
}
43034304

4305+
bool GDScriptParser::override_annotation(AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class) {
4306+
ERR_FAIL_COND_V_MSG(p_target->type != Node::FUNCTION, false, R"("@override" annotation can only be applied to class methods.)");
4307+
ERR_FAIL_COND_V_MSG(p_target->type == Node::LAMBDA, false, R"("@override" annotation cannot be applied to lambdas.)");
4308+
4309+
FunctionNode *method = static_cast<FunctionNode *>(p_target);
4310+
if (method->is_annotated_overriding) {
4311+
push_error(R"("@override" annotation can only be used once per method.)", p_annotation);
4312+
return false;
4313+
}
4314+
4315+
method->is_annotated_overriding = true;
4316+
return true;
4317+
}
4318+
43044319
static String _get_annotation_error_string(const StringName &p_annotation_name, const Vector<Variant::Type> &p_expected_types, const GDScriptParser::DataType &p_provided_type) {
43054320
Vector<String> types;
43064321
for (int i = 0; i < p_expected_types.size(); i++) {

modules/gdscript/gdscript_parser.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -856,6 +856,7 @@ class GDScriptParser {
856856
bool is_static = false; // For lambdas it's determined in the analyzer.
857857
bool is_coroutine = false;
858858
bool is_annotated_virtual = false;
859+
bool is_annotated_overriding = false;
859860
Variant rpc_config;
860861
MethodInfo info;
861862
LambdaNode *source_lambda = nullptr;
@@ -1511,6 +1512,7 @@ class GDScriptParser {
15111512
bool static_unload_annotation(AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class);
15121513
bool onready_annotation(AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class);
15131514
bool virtual_annotation(AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class);
1515+
bool override_annotation(AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class);
15141516
template <PropertyHint t_hint, Variant::Type t_type>
15151517
bool export_annotations(AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class);
15161518
bool export_storage_annotation(AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class);

modules/gdscript/gdscript_warning.cpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -165,6 +165,9 @@ String GDScriptWarning::get_message() const {
165165
case OVERRIDE_NON_VIRTUAL_METHOD:
166166
CHECK_SYMBOLS(1);
167167
return vformat(R"*(The method "%s()" overrides a non-virtual method from the base class. This may cause unexpected and unsafe behaviors.)*", symbols[0]);
168+
case OVERRIDE_WITHOUT_OVERRIDE_ANNOATION:
169+
CHECK_SYMBOLS(1);
170+
return vformat(R"*(The method "%s()" overrides a virtual method from the base class without the "@override" annotation. Annotating the method with the annotation can better help you understand and clarify the code structure.)*", symbols[0]);
168171
#ifndef DISABLE_DEPRECATED
169172
// Never produced. These warnings migrated from 3.x by mistake.
170173
case PROPERTY_USED_AS_FUNCTION: // There is already an error.
@@ -242,6 +245,7 @@ String GDScriptWarning::get_name_from_code(Code p_code) {
242245
"GET_NODE_DEFAULT_WITHOUT_ONREADY",
243246
"ONREADY_WITH_EXPORT",
244247
"OVERRIDE_NON_VIRTUAL_METHOD",
248+
"OVERRIDE_WITHOUT_OVERRIDE_ANNOATION",
245249
#ifndef DISABLE_DEPRECATED
246250
"PROPERTY_USED_AS_FUNCTION",
247251
"CONSTANT_USED_AS_FUNCTION",

modules/gdscript/gdscript_warning.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,7 @@ class GDScriptWarning {
9090
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.
9191
ONREADY_WITH_EXPORT, // The `@onready` annotation will set the value after `@export` which is likely not intended.
9292
OVERRIDE_NON_VIRTUAL_METHOD, // The class method overrides a non-virtual one, which would cause potential problems.
93+
OVERRIDE_WITHOUT_OVERRIDE_ANNOATION, // The class method overrides a virtual one without the `@override` annotation.
9394
#ifndef DISABLE_DEPRECATED
9495
PROPERTY_USED_AS_FUNCTION, // Function not found, but there's a property with the same name.
9596
CONSTANT_USED_AS_FUNCTION, // Function not found, but there's a constant with the same name.
@@ -148,6 +149,7 @@ class GDScriptWarning {
148149
ERROR, // GET_NODE_DEFAULT_WITHOUT_ONREADY // May not work as expected.
149150
ERROR, // ONREADY_WITH_EXPORT // May not work as expected.
150151
WARN, // OVERRIDE_NON_VIRTUAL_METHOD
152+
WARN, // OVERRIDE_WITHOUT_OVERRIDE_ANNOATION
151153
#ifndef DISABLE_DEPRECATED
152154
WARN, // PROPERTY_USED_AS_FUNCTION
153155
WARN, // CONSTANT_USED_AS_FUNCTION
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
extends "inner_base.gd".InnerA.InnerAB
22

3-
func test():
3+
@override func test():
44
super.test()

modules/gdscript/tests/scripts/analyzer/features/function_match_parent_signature_with_default_dict_void.gd

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,5 +10,5 @@ class Parent:
1010
pass
1111

1212
class Child extends Parent:
13-
func my_function(_par1: Dictionary = {}) -> void:
13+
@override func my_function(_par1: Dictionary = {}) -> void:
1414
pass

modules/gdscript/tests/scripts/analyzer/features/function_match_parent_signature_with_extra_parameters.gd

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,5 +13,5 @@ class Parent:
1313
return par1
1414

1515
class Child extends Parent:
16-
func my_function(_par1: int, par2: int = 0) -> int:
16+
@override func my_function(_par1: int, par2: int = 0) -> int:
1717
return par2

0 commit comments

Comments
 (0)