@@ -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,19 @@ 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
+ // Let the first default one with of same name fully in.
1016
+ // The others are added so they are parsed, but not indexed so name clash error is avoided.
1017
+ if (!member->if_features .is_default_impl || current_class->members_indices .has (member->identifier ->name )) {
1018
+ current_class->add_if_features_potential_candidate (member);
1019
+ return ;
1020
+ }
1021
+ }
1022
+ }
997
1023
#endif // TOOLS_ENABLED
998
1024
999
1025
if (member->identifier != nullptr ) {
@@ -4975,6 +5001,95 @@ bool GDScriptParser::warning_ignore_region_annotations(AnnotationNode *p_annotat
4975
5001
#endif // DEBUG_ENABLED
4976
5002
}
4977
5003
5004
+ bool GDScriptParser::if_features_annotation (AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class) {
5005
+ #if defined(TOOLS_ENABLED)
5006
+ ERR_FAIL_COND_V_MSG (p_target->type != Node::FUNCTION, false , vformat (R"( "%s" annotation can only be applied to functions.)" , p_annotation->name ));
5007
+
5008
+ FunctionNode *function = static_cast <FunctionNode *>(p_target);
5009
+ if (function->if_features .used ) {
5010
+ push_error (" The @if_features annotation can only be used once per function." );
5011
+ return false ;
5012
+ }
5013
+
5014
+ function->if_features .used = true ;
5015
+
5016
+ thread_local LocalVector<String> features;
5017
+ features.clear ();
5018
+ for (const ExpressionNode *arg : p_annotation->arguments ) {
5019
+ DEV_ASSERT (arg->reduced );
5020
+ if (arg->reduced_value .get_type () == Variant::STRING) {
5021
+ features.push_back (arg->reduced_value );
5022
+ } else {
5023
+ push_error (" The arguments to @if_features must be strings." );
5024
+ return false ;
5025
+ }
5026
+ }
5027
+
5028
+ if (Engine::get_singleton ()->is_editor_hint () && !for_export) {
5029
+ // At edit time there's nothing else to process.
5030
+ return true ;
5031
+ }
5032
+
5033
+ // The idea is to keep the first one fitting, with only the default possibly overridden by another one coming later.
5034
+
5035
+ FunctionNode *target_function = static_cast <FunctionNode *>(p_target);
5036
+ const StringName &function_name = target_function->identifier ->name ;
5037
+
5038
+ bool current_match_overridable = false ;
5039
+ if (target_function->if_features .potential_candidate_index != -1 ) { // Otherwise, it's already the chosen one when parsing the function.
5040
+ if (p_class->has_function (function_name)) {
5041
+ bool current_match_is_default = p_class->get_member (function_name).function ->if_features .is_default_impl ;
5042
+ bool incoming_candidate_is_default = target_function->if_features .is_default_impl ;
5043
+ current_match_overridable = current_match_is_default && !incoming_candidate_is_default;
5044
+ }
5045
+ }
5046
+
5047
+ if (current_match_overridable) {
5048
+ bool fitting = false ;
5049
+ if (features.size ()) {
5050
+ fitting = true ;
5051
+ for (const String &feature : features) {
5052
+ if (for_export) {
5053
+ // Export time in editor build.
5054
+ if (!export_features.has (feature)) {
5055
+ fitting = false ;
5056
+ break ;
5057
+ }
5058
+ } else {
5059
+ // Runtime in editor build.
5060
+ if (!OS::get_singleton ()->has_feature (feature)) {
5061
+ fitting = false ;
5062
+ break ;
5063
+ }
5064
+ }
5065
+ }
5066
+ }
5067
+ if (fitting) {
5068
+ // If fits, replace the current match.
5069
+ int64_t current_match_index = p_class->members_indices [function_name];
5070
+ p_class->members [current_match_index].function ->if_features .potential_candidate_index = current_match_index;
5071
+ p_class->members_indices [function_name] = target_function->if_features .potential_candidate_index ;
5072
+ target_function->if_features .potential_candidate_index = -1 ;
5073
+ }
5074
+ }
5075
+ #endif
5076
+
5077
+ return true ;
5078
+ }
5079
+
5080
+ #ifdef TOOLS_ENABLED
5081
+ void GDScriptParser::collect_unfitting_functions (ClassNode *p_class, LocalVector<Pair<ClassNode *, FunctionNode *>> &r_functions) {
5082
+ for (const ClassNode::Member &member : p_class->members ) {
5083
+ if (member.type == ClassNode::Member::CLASS) {
5084
+ collect_unfitting_functions (member.m_class , r_functions);
5085
+ }
5086
+ if (member.type == ClassNode::Member::FUNCTION && member.function ->if_features .potential_candidate_index != -1 ) {
5087
+ r_functions.push_back (Pair (p_class, member.function ));
5088
+ }
5089
+ }
5090
+ }
5091
+ #endif
5092
+
4978
5093
bool GDScriptParser::rpc_annotation (AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class) {
4979
5094
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
5095
0 commit comments