@@ -132,6 +132,8 @@ GDScriptParser::GDScriptParser() {
132
132
register_annotation (MethodInfo (" @warning_ignore_restore" , PropertyInfo (Variant::STRING, " warning" )), AnnotationInfo::STANDALONE, &GDScriptParser::warning_ignore_region_annotations, varray (), true );
133
133
// Networking.
134
134
register_annotation (MethodInfo (" @rpc" , PropertyInfo (Variant::STRING, " mode" ), PropertyInfo (Variant::STRING, " sync" ), PropertyInfo (Variant::STRING, " transfer_mode" ), PropertyInfo (Variant::INT, " transfer_channel" )), AnnotationInfo::FUNCTION, &GDScriptParser::rpc_annotation, varray (" authority" , " call_remote" , " unreliable" , 0 ));
135
+ // Preprocessing.
136
+ register_annotation (MethodInfo (" @if_features" , PropertyInfo (Variant::STRING, " feature" )), AnnotationInfo::FUNCTION, &GDScriptParser::if_features_annotation, varray (Variant ()), true );
135
137
}
136
138
137
139
#ifdef DEBUG_ENABLED
@@ -944,11 +946,22 @@ void GDScriptParser::parse_class_member(T *(GDScriptParser::*p_parse_function)(b
944
946
945
947
// Consume annotations.
946
948
List<AnnotationNode *> annotations;
949
+ #ifdef TOOLS_ENABLED
950
+ constexpr bool parsing_function = std::is_same_v<T, FunctionNode>;
951
+ AnnotationNode *if_features = nullptr ;
952
+ #endif
947
953
while (!annotation_stack.is_empty ()) {
948
954
AnnotationNode *last_annotation = annotation_stack.back ()->get ();
949
955
if (last_annotation->applies_to (p_target)) {
950
956
annotations.push_front (last_annotation);
951
957
annotation_stack.pop_back ();
958
+ #ifdef TOOLS_ENABLED
959
+ if constexpr (parsing_function) {
960
+ if (last_annotation->name == StringName (" @if_features" )) {
961
+ if_features = last_annotation;
962
+ }
963
+ }
964
+ #endif
952
965
} else {
953
966
push_error (vformat (R"( Annotation "%s" cannot be applied to a %s.)" , last_annotation->name , p_member_kind));
954
967
clear_unused_annotations ();
@@ -994,6 +1007,32 @@ void GDScriptParser::parse_class_member(T *(GDScriptParser::*p_parse_function)(b
994
1007
}
995
1008
996
1009
min_member_doc_line = member->end_line + 1 ; // Prevent multiple members from using the same doc comment.
1010
+
1011
+ if constexpr (parsing_function) {
1012
+ if (if_features) {
1013
+ // Mark this one as a default implementation if the annotation provides no features.
1014
+ member->if_features .is_default_impl = if_features->arguments .is_empty ();
1015
+
1016
+ // Let the first default one with of same name fully in.
1017
+ // The others are added so they are parsed, but not indexed so name clash error is avoided.
1018
+ HashMap<StringName, int >::Iterator E = current_class->members_indices .find (member->identifier ->name );
1019
+ if (E) {
1020
+ // Member with that name already exists.
1021
+ const ClassNode::Member &existing = current_class->members [E->value ];
1022
+ if (existing.type == ClassNode::Member::FUNCTION) { // Otherwise, an error is raised anyway.
1023
+ // HACK: Compare compatibility of functions via TreePrinter.
1024
+ const String &existing_str = TreePrinter ().strinfigy_function_declaration (existing.function );
1025
+ const String &incoming_str = TreePrinter ().strinfigy_function_declaration (member);
1026
+ if (existing_str != incoming_str) {
1027
+ push_error (vformat (R"( %s "%s" does not match the signature of a previously declared function of the same name.)" , p_member_kind.capitalize (), member->identifier ->name ), member->identifier );
1028
+ } else {
1029
+ current_class->add_if_features_potential_candidate (member);
1030
+ }
1031
+ return ;
1032
+ }
1033
+ }
1034
+ }
1035
+ }
997
1036
#endif // TOOLS_ENABLED
998
1037
999
1038
if (member->identifier != nullptr ) {
@@ -4975,6 +5014,106 @@ bool GDScriptParser::warning_ignore_region_annotations(AnnotationNode *p_annotat
4975
5014
#endif // DEBUG_ENABLED
4976
5015
}
4977
5016
5017
+ bool GDScriptParser::if_features_annotation (AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class) {
5018
+ #if defined(TOOLS_ENABLED)
5019
+ ERR_FAIL_COND_V_MSG (p_target->type != Node::FUNCTION, false , vformat (R"( "%s" annotation can only be applied to functions.)" , p_annotation->name ));
5020
+
5021
+ FunctionNode *function = static_cast <FunctionNode *>(p_target);
5022
+ if (function->if_features .used ) {
5023
+ push_error (" The @if_features annotation can only be used once per function." );
5024
+ return false ;
5025
+ }
5026
+
5027
+ function->if_features .used = true ;
5028
+
5029
+ thread_local LocalVector<String> features;
5030
+ features.clear ();
5031
+ for (const ExpressionNode *arg : p_annotation->arguments ) {
5032
+ DEV_ASSERT (arg->reduced );
5033
+ if (arg->reduced_value .get_type () == Variant::STRING) {
5034
+ features.push_back (arg->reduced_value );
5035
+ } else {
5036
+ push_error (" The arguments to @if_features must be strings." );
5037
+ return false ;
5038
+ }
5039
+ }
5040
+
5041
+ if (Engine::get_singleton ()->is_editor_hint () && !for_export) {
5042
+ // At edit time there's nothing else to process.
5043
+ return true ;
5044
+ }
5045
+
5046
+ // The idea is to keep the first one fitting, with only the default possibly overridden by another one coming later.
5047
+
5048
+ FunctionNode *target_function = static_cast <FunctionNode *>(p_target);
5049
+ const StringName &function_name = target_function->identifier ->name ;
5050
+
5051
+ auto _check_incoming_fits = [&]() -> bool {
5052
+ if (features.is_empty ()) {
5053
+ return false ; // Defaults are considered non-fitting.
5054
+ }
5055
+ bool fitting = true ;
5056
+ for (const String &feature : features) {
5057
+ if (for_export) {
5058
+ // Export time in editor build.
5059
+ if (!export_features.has (feature)) {
5060
+ fitting = false ;
5061
+ break ;
5062
+ }
5063
+ } else {
5064
+ // Runtime in editor build.
5065
+ if (!OS::get_singleton ()->has_feature (feature)) {
5066
+ fitting = false ;
5067
+ break ;
5068
+ }
5069
+ }
5070
+ }
5071
+ return fitting;
5072
+ };
5073
+
5074
+ if (target_function->if_features .potential_candidate_index == -1 ) {
5075
+ // Chosen one at parsing time because it was the first one found. Only keep if fitting.
5076
+ if (!_check_incoming_fits ()) {
5077
+ HashMap<StringName, int >::Iterator E = p_class->members_indices .find (function_name);
5078
+ int64_t current_match_index = E->value ;
5079
+ target_function->if_features .potential_candidate_index = current_match_index;
5080
+ p_class->members_indices .remove (E);
5081
+ }
5082
+ } else {
5083
+ HashMap<StringName, int >::Iterator E = p_class->members_indices .find (function_name);
5084
+ if ((bool )E) {
5085
+ // There's a current one. Override if current is default and incoming fits.
5086
+ bool current_match_is_default = p_class->members [E->value ].function ->if_features .is_default_impl ;
5087
+ if (current_match_is_default && _check_incoming_fits ()) {
5088
+ E->value = target_function->if_features .potential_candidate_index ;
5089
+ target_function->if_features .potential_candidate_index = -1 ;
5090
+ }
5091
+ } else {
5092
+ // Incoming fits and there's no current chosen. Pick if it's default or fits.
5093
+ if (target_function->if_features .is_default_impl || _check_incoming_fits ()) {
5094
+ p_class->members_indices .insert (function_name, target_function->if_features .potential_candidate_index );
5095
+ target_function->if_features .potential_candidate_index = -1 ;
5096
+ }
5097
+ }
5098
+ }
5099
+ #endif
5100
+
5101
+ return true ;
5102
+ }
5103
+
5104
+ #ifdef TOOLS_ENABLED
5105
+ void GDScriptParser::collect_unfitting_functions (ClassNode *p_class, LocalVector<Pair<ClassNode *, FunctionNode *>> &r_functions) {
5106
+ for (const ClassNode::Member &member : p_class->members ) {
5107
+ if (member.type == ClassNode::Member::CLASS) {
5108
+ collect_unfitting_functions (member.m_class , r_functions);
5109
+ }
5110
+ if (member.type == ClassNode::Member::FUNCTION && member.function ->if_features .potential_candidate_index != -1 ) {
5111
+ r_functions.push_back (Pair (p_class, member.function ));
5112
+ }
5113
+ }
5114
+ }
5115
+ #endif
5116
+
4978
5117
bool GDScriptParser::rpc_annotation (AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class) {
4979
5118
ERR_FAIL_COND_V_MSG (p_target->type != Node::FUNCTION, false , vformat (R"( "%s" annotation can only be applied to functions.)" , p_annotation->name ));
4980
5119
@@ -5833,9 +5972,11 @@ void GDScriptParser::TreePrinter::print_for(ForNode *p_for) {
5833
5972
decrease_indent ();
5834
5973
}
5835
5974
5836
- void GDScriptParser::TreePrinter::print_function (FunctionNode *p_function, const String &p_context) {
5837
- for (const AnnotationNode *E : p_function->annotations ) {
5838
- print_annotation (E);
5975
+ void GDScriptParser::TreePrinter::print_function (FunctionNode *p_function, const String &p_context, bool p_signature_only) {
5976
+ if (!p_signature_only) {
5977
+ for (const AnnotationNode *E : p_function->annotations ) {
5978
+ print_annotation (E);
5979
+ }
5839
5980
}
5840
5981
if (p_function->is_static ) {
5841
5982
push_text (" Static " );
@@ -5859,10 +6000,12 @@ void GDScriptParser::TreePrinter::print_function(FunctionNode *p_function, const
5859
6000
push_text (" -> " );
5860
6001
print_type (p_function->return_type );
5861
6002
}
5862
- push_line (" :" );
5863
- increase_indent ();
5864
- print_suite (p_function->body );
5865
- decrease_indent ();
6003
+ if (!p_signature_only) {
6004
+ push_line (" :" );
6005
+ increase_indent ();
6006
+ print_suite (p_function->body );
6007
+ decrease_indent ();
6008
+ }
5866
6009
}
5867
6010
5868
6011
void GDScriptParser::TreePrinter::print_get_node (GetNodeNode *p_get_node) {
@@ -6283,4 +6426,9 @@ void GDScriptParser::TreePrinter::print_tree(const GDScriptParser &p_parser) {
6283
6426
print_line (String (printed));
6284
6427
}
6285
6428
6429
+ String GDScriptParser::TreePrinter::strinfigy_function_declaration (FunctionNode *p_function) {
6430
+ print_function (p_function, " Function" , true );
6431
+ return String (printed);
6432
+ }
6433
+
6286
6434
#endif // DEBUG_ENABLED
0 commit comments