@@ -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,108 @@ 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
+ // There's nothing to process unless we're exporting or running in the editor.
5042
+ // Sadly, we can't tell for sure if the script is being run in the editor or just being edited.
5043
+ // We only know for sure it's the latter if this parsing is done for completion reasons.
5044
+ if (for_edition) {
5045
+ return true;
5046
+ }
5047
+
5048
+ // The idea is to keep the first one fitting, with only the default possibly overridden by another one coming later.
5049
+
5050
+ FunctionNode *target_function = static_cast<FunctionNode *>(p_target);
5051
+ const StringName &function_name = target_function->identifier->name;
5052
+
5053
+ auto _check_incoming_fits = [&]() -> bool {
5054
+ if (features.is_empty()) {
5055
+ return false; // Defaults are considered non-fitting.
5056
+ }
5057
+ bool fitting = true;
5058
+ for (const String &feature : features) {
5059
+ if (for_export) {
5060
+ // Export time in editor build.
5061
+ if (!export_features.has(feature)) {
5062
+ fitting = false;
5063
+ break;
5064
+ }
5065
+ } else {
5066
+ // Run-on-editor.
5067
+ if (!OS::get_singleton()->has_feature(feature)) {
5068
+ fitting = false;
5069
+ break;
5070
+ }
5071
+ }
5072
+ }
5073
+ return fitting;
5074
+ };
5075
+
5076
+ if (target_function->if_features.potential_candidate_index == -1) {
5077
+ // Chosen one at parsing time because it was the first one found. Only keep if fitting.
5078
+ if (!_check_incoming_fits()) {
5079
+ HashMap<StringName, int>::Iterator E = p_class->members_indices.find(function_name);
5080
+ int64_t current_match_index = E->value;
5081
+ target_function->if_features.potential_candidate_index = current_match_index;
5082
+ p_class->members_indices.remove(E);
5083
+ }
5084
+ } else {
5085
+ HashMap<StringName, int>::Iterator E = p_class->members_indices.find(function_name);
5086
+ if ((bool)E) {
5087
+ // There's a current one. Override if current is default and incoming fits.
5088
+ bool current_match_is_default = p_class->members[E->value].function->if_features.is_default_impl;
5089
+ if (current_match_is_default && _check_incoming_fits()) {
5090
+ E->value = target_function->if_features.potential_candidate_index;
5091
+ target_function->if_features.potential_candidate_index = -1;
5092
+ }
5093
+ } else {
5094
+ // Incoming fits and there's no current chosen. Pick if it's default or fits.
5095
+ if (target_function->if_features.is_default_impl || _check_incoming_fits()) {
5096
+ p_class->members_indices.insert(function_name, target_function->if_features.potential_candidate_index);
5097
+ target_function->if_features.potential_candidate_index = -1;
5098
+ }
5099
+ }
5100
+ }
5101
+ #endif
5102
+
5103
+ return true;
5104
+ }
5105
+
5106
+ #ifdef TOOLS_ENABLED
5107
+ void GDScriptParser::collect_unfitting_functions(ClassNode *p_class, LocalVector<Pair<ClassNode *, FunctionNode *>> &r_functions) {
5108
+ for (const ClassNode::Member &member : p_class->members) {
5109
+ if (member.type == ClassNode::Member::CLASS) {
5110
+ collect_unfitting_functions(member.m_class, r_functions);
5111
+ }
5112
+ if (member.type == ClassNode::Member::FUNCTION && member.function->if_features.potential_candidate_index != -1) {
5113
+ r_functions.push_back(Pair(p_class, member.function));
5114
+ }
5115
+ }
5116
+ }
5117
+ #endif
5118
+
4978
5119
bool GDScriptParser::rpc_annotation(AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class) {
4979
5120
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
5121
@@ -5833,9 +5974,11 @@ void GDScriptParser::TreePrinter::print_for(ForNode *p_for) {
5833
5974
decrease_indent();
5834
5975
}
5835
5976
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);
5977
+ void GDScriptParser::TreePrinter::print_function(FunctionNode *p_function, const String &p_context, bool p_signature_only) {
5978
+ if (!p_signature_only) {
5979
+ for (const AnnotationNode *E : p_function->annotations) {
5980
+ print_annotation(E);
5981
+ }
5839
5982
}
5840
5983
if (p_function->is_static) {
5841
5984
push_text("Static ");
@@ -5859,10 +6002,12 @@ void GDScriptParser::TreePrinter::print_function(FunctionNode *p_function, const
5859
6002
push_text("-> ");
5860
6003
print_type(p_function->return_type);
5861
6004
}
5862
- push_line (" :" );
5863
- increase_indent ();
5864
- print_suite (p_function->body );
5865
- decrease_indent ();
6005
+ if (!p_signature_only) {
6006
+ push_line(" :");
6007
+ increase_indent();
6008
+ print_suite(p_function->body);
6009
+ decrease_indent();
6010
+ }
5866
6011
}
5867
6012
5868
6013
void GDScriptParser::TreePrinter::print_get_node(GetNodeNode *p_get_node) {
@@ -6283,4 +6428,9 @@ void GDScriptParser::TreePrinter::print_tree(const GDScriptParser &p_parser) {
6283
6428
print_line(String(printed));
6284
6429
}
6285
6430
6431
+ String GDScriptParser::TreePrinter::strinfigy_function_declaration(FunctionNode *p_function) {
6432
+ print_function(p_function, "Function", true);
6433
+ return String(printed);
6434
+ }
6435
+
6286
6436
#endif // DEBUG_ENABLED
0 commit comments