diff options
Diffstat (limited to 'modules/gdscript')
-rw-r--r-- | modules/gdscript/doc_classes/@GDScript.xml | 141 | ||||
-rw-r--r-- | modules/gdscript/gdscript.h | 1 | ||||
-rw-r--r-- | modules/gdscript/gdscript_analyzer.cpp | 3 | ||||
-rw-r--r-- | modules/gdscript/gdscript_compiler.cpp | 19 | ||||
-rw-r--r-- | modules/gdscript/gdscript_editor.cpp | 23 | ||||
-rw-r--r-- | modules/gdscript/gdscript_parser.cpp | 85 | ||||
-rw-r--r-- | modules/gdscript/gdscript_parser.h | 22 | ||||
-rw-r--r-- | modules/gdscript/language_server/gdscript_extend_parser.cpp | 4 | ||||
-rw-r--r-- | modules/gdscript/tests/scripts/parser/errors/class_name_after_annotation.gd | 4 | ||||
-rw-r--r-- | modules/gdscript/tests/scripts/parser/errors/class_name_after_annotation.out | 2 |
10 files changed, 289 insertions, 15 deletions
diff --git a/modules/gdscript/doc_classes/@GDScript.xml b/modules/gdscript/doc_classes/@GDScript.xml index 70151c4d21..e672ea6454 100644 --- a/modules/gdscript/doc_classes/@GDScript.xml +++ b/modules/gdscript/doc_classes/@GDScript.xml @@ -257,4 +257,145 @@ [b]Note:[/b] "Not a Number" is only a concept with floating-point numbers, and has no equivalent for integers. Dividing an integer [code]0[/code] by [code]0[/code] will not result in [constant NAN] and will result in a run-time error instead. </constant> </constants> + <annotations> + <annotation name="@export"> + <return type="void" /> + <description> + </description> + </annotation> + <annotation name="@export_color_no_alpha"> + <return type="void" /> + <description> + </description> + </annotation> + <annotation name="@export_dir"> + <return type="void" /> + <description> + </description> + </annotation> + <annotation name="@export_enum" qualifiers="vararg"> + <return type="void" /> + <argument index="0" name="names" type="String" /> + <description> + </description> + </annotation> + <annotation name="@export_exp_easing"> + <return type="void" /> + <argument index="0" name="hint1" type="String" default="null" /> + <argument index="1" name="hint2" type="String" default="null" /> + <description> + </description> + </annotation> + <annotation name="@export_file" qualifiers="vararg"> + <return type="void" /> + <argument index="0" name="filter" type="String" default="null" /> + <description> + </description> + </annotation> + <annotation name="@export_flags" qualifiers="vararg"> + <return type="void" /> + <argument index="0" name="names" type="String" /> + <description> + </description> + </annotation> + <annotation name="@export_flags_2d_navigation"> + <return type="void" /> + <description> + </description> + </annotation> + <annotation name="@export_flags_2d_physics"> + <return type="void" /> + <description> + </description> + </annotation> + <annotation name="@export_flags_2d_render"> + <return type="void" /> + <description> + </description> + </annotation> + <annotation name="@export_flags_3d_navigation"> + <return type="void" /> + <description> + </description> + </annotation> + <annotation name="@export_flags_3d_physics"> + <return type="void" /> + <description> + </description> + </annotation> + <annotation name="@export_flags_3d_render"> + <return type="void" /> + <description> + </description> + </annotation> + <annotation name="@export_global_dir"> + <return type="void" /> + <description> + </description> + </annotation> + <annotation name="@export_global_file" qualifiers="vararg"> + <return type="void" /> + <argument index="0" name="filter" type="String" default="null" /> + <description> + </description> + </annotation> + <annotation name="@export_multiline"> + <return type="void" /> + <description> + </description> + </annotation> + <annotation name="@export_node_path" qualifiers="vararg"> + <return type="void" /> + <argument index="0" name="type" type="String" default="null" /> + <description> + </description> + </annotation> + <annotation name="@export_placeholder"> + <return type="void" /> + <description> + </description> + </annotation> + <annotation name="@export_range"> + <return type="void" /> + <argument index="0" name="min" type="float" /> + <argument index="1" name="max" type="float" /> + <argument index="2" name="step" type="float" default="null" /> + <argument index="3" name="slider1" type="String" default="null" /> + <argument index="4" name="slider2" type="String" default="null" /> + <argument index="5" name="slider3" type="String" default="null" /> + <description> + </description> + </annotation> + <annotation name="@icon"> + <return type="void" /> + <argument index="0" name="icon_path" type="String" /> + <description> + </description> + </annotation> + <annotation name="@onready"> + <return type="void" /> + <description> + </description> + </annotation> + <annotation name="@rpc" qualifiers="vararg"> + <return type="void" /> + <argument index="0" name="mode" type="String" default="null" /> + <argument index="1" name="sync" type="String" default="null" /> + <argument index="2" name="transfer_mode" type="String" default="null" /> + <argument index="3" name="transfer_channel" type="int" default="null" /> + <description> + </description> + </annotation> + <annotation name="@tool"> + <return type="void" /> + <description> + </description> + </annotation> + <annotation name="@warning_ignore" qualifiers="vararg"> + <return type="void" /> + <argument index="0" name="warning" type="String" /> + <description> + </description> + </annotation> + </annotations> </class> diff --git a/modules/gdscript/gdscript.h b/modules/gdscript/gdscript.h index feb0a237df..e9a206f48b 100644 --- a/modules/gdscript/gdscript.h +++ b/modules/gdscript/gdscript.h @@ -489,6 +489,7 @@ public: virtual void get_public_functions(List<MethodInfo> *p_functions) const override; virtual void get_public_constants(List<Pair<String, Variant>> *p_constants) const override; + virtual void get_public_annotations(List<MethodInfo> *p_annotations) const override; virtual void profiling_start() override; virtual void profiling_stop() override; diff --git a/modules/gdscript/gdscript_analyzer.cpp b/modules/gdscript/gdscript_analyzer.cpp index ea994654bf..2e8f962db7 100644 --- a/modules/gdscript/gdscript_analyzer.cpp +++ b/modules/gdscript/gdscript_analyzer.cpp @@ -860,6 +860,9 @@ void GDScriptAnalyzer::resolve_class_interface(GDScriptParser::ClassNode *p_clas case GDScriptParser::ClassNode::Member::CLASS: check_class_member_name_conflict(p_class, member.m_class->identifier->name, member.m_class); break; + case GDScriptParser::ClassNode::Member::GROUP: + // No-op, but needed to silence warnings. + break; case GDScriptParser::ClassNode::Member::UNDEFINED: ERR_PRINT("Trying to resolve undefined member."); break; diff --git a/modules/gdscript/gdscript_compiler.cpp b/modules/gdscript/gdscript_compiler.cpp index 6055d3df33..af8e4b3746 100644 --- a/modules/gdscript/gdscript_compiler.cpp +++ b/modules/gdscript/gdscript_compiler.cpp @@ -2452,6 +2452,25 @@ Error GDScriptCompiler::_parse_class_level(GDScript *p_script, const GDScriptPar } #endif } break; + + case GDScriptParser::ClassNode::Member::GROUP: { + const GDScriptParser::AnnotationNode *annotation = member.annotation; + StringName name = annotation->export_info.name; + + // This is not a normal member, but we need this to keep indices in order. + GDScript::MemberInfo minfo; + minfo.index = p_script->member_indices.size(); + + PropertyInfo prop_info; + prop_info.name = name; + prop_info.usage = annotation->export_info.usage; + prop_info.hint_string = annotation->export_info.hint_string; + + p_script->member_info[name] = prop_info; + p_script->member_indices[name] = minfo; + p_script->members.insert(name); + } break; + default: break; // Nothing to do here. } diff --git a/modules/gdscript/gdscript_editor.cpp b/modules/gdscript/gdscript_editor.cpp index e419493324..71ee437744 100644 --- a/modules/gdscript/gdscript_editor.cpp +++ b/modules/gdscript/gdscript_editor.cpp @@ -445,6 +445,16 @@ void GDScriptLanguage::get_public_constants(List<Pair<String, Variant>> *p_const p_constants->push_back(nan); } +void GDScriptLanguage::get_public_annotations(List<MethodInfo> *p_annotations) const { + GDScriptParser parser; + List<MethodInfo> annotations; + parser.get_annotation_list(&annotations); + + for (const MethodInfo &E : annotations) { + p_annotations->push_back(E); + } +} + String GDScriptLanguage::make_function(const String &p_class, const String &p_name, const PackedStringArray &p_args) const { #ifdef TOOLS_ENABLED bool th = EditorSettings::get_singleton()->get_setting("text_editor/completion/add_type_hints"); @@ -950,6 +960,8 @@ static void _find_identifiers_in_class(const GDScriptParser::ClassNode *p_class, } option = ScriptLanguage::CodeCompletionOption(member.signal->identifier->name, ScriptLanguage::CODE_COMPLETION_KIND_SIGNAL, location); break; + case GDScriptParser::ClassNode::Member::GROUP: + break; // No-op, but silences warnings. case GDScriptParser::ClassNode::Member::UNDEFINED: break; } @@ -2082,6 +2094,8 @@ static bool _guess_identifier_type_from_base(GDScriptParser::CompletionContext & r_type.type.kind = GDScriptParser::DataType::CLASS; r_type.type.class_type = member.m_class; return true; + case GDScriptParser::ClassNode::Member::GROUP: + return false; // No-op, but silences warnings. case GDScriptParser::ClassNode::Member::UNDEFINED: return false; // Unreachable. } @@ -3376,6 +3390,15 @@ static Error _lookup_symbol_from_base(const GDScriptParser::DataType &p_base, co return OK; } } break; + case GDScriptParser::COMPLETION_ANNOTATION: { + const String annotation_symbol = "@" + p_symbol; + if (parser.annotation_exists(annotation_symbol)) { + r_result.type = ScriptLanguage::LOOKUP_RESULT_CLASS_ANNOTATION; + r_result.class_name = "@GDScript"; + r_result.class_member = annotation_symbol; + return OK; + } + } break; default: { } } diff --git a/modules/gdscript/gdscript_parser.cpp b/modules/gdscript/gdscript_parser.cpp index ca430b0f72..11fa7575d8 100644 --- a/modules/gdscript/gdscript_parser.cpp +++ b/modules/gdscript/gdscript_parser.cpp @@ -105,6 +105,10 @@ void GDScriptParser::get_annotation_list(List<MethodInfo> *r_annotations) const } } +bool GDScriptParser::annotation_exists(const String &p_annotation_name) const { + return valid_annotations.has(p_annotation_name); +} + GDScriptParser::GDScriptParser() { // Register valid annotations. // TODO: Should this be static? @@ -131,6 +135,11 @@ GDScriptParser::GDScriptParser() { register_annotation(MethodInfo("@export_flags_3d_render"), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_LAYERS_3D_RENDER, Variant::INT>); register_annotation(MethodInfo("@export_flags_3d_physics"), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_LAYERS_3D_PHYSICS, Variant::INT>); register_annotation(MethodInfo("@export_flags_3d_navigation"), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_LAYERS_3D_NAVIGATION, Variant::INT>); + // Export grouping annotations. + register_annotation(MethodInfo("@export_category", PropertyInfo(Variant::STRING, "name")), AnnotationInfo::STANDALONE, &GDScriptParser::export_group_annotations<PROPERTY_USAGE_CATEGORY>); + register_annotation(MethodInfo("@export_group", PropertyInfo(Variant::STRING, "name"), PropertyInfo(Variant::STRING, "prefix")), AnnotationInfo::STANDALONE, &GDScriptParser::export_group_annotations<PROPERTY_USAGE_GROUP>, 1); + register_annotation(MethodInfo("@export_subgroup", PropertyInfo(Variant::STRING, "name"), PropertyInfo(Variant::STRING, "prefix")), AnnotationInfo::STANDALONE, &GDScriptParser::export_group_annotations<PROPERTY_USAGE_SUBGROUP>, 1); + // Warning annotations. register_annotation(MethodInfo("@warning_ignore", PropertyInfo(Variant::STRING, "warning")), AnnotationInfo::CLASS | AnnotationInfo::VARIABLE | AnnotationInfo::SIGNAL | AnnotationInfo::CONSTANT | AnnotationInfo::FUNCTION | AnnotationInfo::STATEMENT, &GDScriptParser::warning_annotations, 0, true); // Networking. 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::network_annotations<Multiplayer::RPC_MODE_AUTHORITY>, 4, true); @@ -519,9 +528,13 @@ void GDScriptParser::parse_program() { head = alloc_node<ClassNode>(); current_class = head; + // If we happen to parse an annotation before extends or class_name keywords, track it. + // @tool is allowed, but others should fail. + AnnotationNode *premature_annotation = nullptr; + if (match(GDScriptTokenizer::Token::ANNOTATION)) { - // Check for @tool annotation. - AnnotationNode *annotation = parse_annotation(AnnotationInfo::SCRIPT | AnnotationInfo::CLASS_LEVEL); + // Check for @tool, script-level, or standalone annotation. + AnnotationNode *annotation = parse_annotation(AnnotationInfo::SCRIPT | AnnotationInfo::STANDALONE | AnnotationInfo::CLASS_LEVEL); if (annotation != nullptr) { if (annotation->name == SNAME("@tool")) { // TODO: don't allow @tool anywhere else. (Should all script annotations be the first thing?). @@ -531,7 +544,14 @@ void GDScriptParser::parse_program() { } // @tool annotation has no specific target. annotation->apply(this, nullptr); + } else if (annotation->applies_to(AnnotationInfo::SCRIPT | AnnotationInfo::STANDALONE)) { + premature_annotation = annotation; + if (previous.type != GDScriptTokenizer::Token::NEWLINE) { + push_error(R"(Expected newline after a standalone annotation.)"); + } + annotation->apply(this, head); } else { + premature_annotation = annotation; annotation_stack.push_back(annotation); } } @@ -541,8 +561,8 @@ void GDScriptParser::parse_program() { // Order here doesn't matter, but there should be only one of each at most. switch (current.type) { case GDScriptTokenizer::Token::CLASS_NAME: - if (!annotation_stack.is_empty()) { - push_error(R"("class_name" should be used before annotations.)"); + if (premature_annotation != nullptr) { + push_error(R"("class_name" should be used before annotations (except @tool).)"); } advance(); if (head->identifier != nullptr) { @@ -552,8 +572,8 @@ void GDScriptParser::parse_program() { } break; case GDScriptTokenizer::Token::EXTENDS: - if (!annotation_stack.is_empty()) { - push_error(R"("extends" should be used before annotations.)"); + if (premature_annotation != nullptr) { + push_error(R"("extends" should be used before annotations (except @tool).)"); } advance(); if (head->extends_used) { @@ -574,12 +594,12 @@ void GDScriptParser::parse_program() { } if (match(GDScriptTokenizer::Token::ANNOTATION)) { - // Check for @icon annotation. - AnnotationNode *annotation = parse_annotation(AnnotationInfo::SCRIPT | AnnotationInfo::CLASS_LEVEL); + // Check for a script-level, or standalone annotation. + AnnotationNode *annotation = parse_annotation(AnnotationInfo::SCRIPT | AnnotationInfo::STANDALONE | AnnotationInfo::CLASS_LEVEL); if (annotation != nullptr) { - if (annotation->name == SNAME("@icon")) { + if (annotation->applies_to(AnnotationInfo::SCRIPT | AnnotationInfo::STANDALONE)) { if (previous.type != GDScriptTokenizer::Token::NEWLINE) { - push_error(R"(Expected newline after "@icon" annotation.)"); + push_error(R"(Expected newline after a standalone annotation.)"); } annotation->apply(this, head); } else { @@ -807,9 +827,18 @@ void GDScriptParser::parse_class_body(bool p_is_multiline) { break; case GDScriptTokenizer::Token::ANNOTATION: { advance(); - AnnotationNode *annotation = parse_annotation(AnnotationInfo::CLASS_LEVEL); + + // Check for class-level annotations. + AnnotationNode *annotation = parse_annotation(AnnotationInfo::STANDALONE | AnnotationInfo::CLASS_LEVEL); if (annotation != nullptr) { - annotation_stack.push_back(annotation); + if (annotation->applies_to(AnnotationInfo::STANDALONE)) { + if (previous.type != GDScriptTokenizer::Token::NEWLINE) { + push_error(R"(Expected newline after a standalone annotation.)"); + } + annotation->apply(this, head); + } else { + annotation_stack.push_back(annotation); + } } break; } @@ -3662,6 +3691,36 @@ bool GDScriptParser::export_annotations(const AnnotationNode *p_annotation, Node return true; } +template <PropertyUsageFlags t_usage> +bool GDScriptParser::export_group_annotations(const AnnotationNode *p_annotation, Node *p_node) { + AnnotationNode *annotation = const_cast<AnnotationNode *>(p_annotation); + + annotation->export_info.name = annotation->resolved_arguments[0]; + + switch (t_usage) { + case PROPERTY_USAGE_CATEGORY: { + annotation->export_info.usage = t_usage; + } break; + + case PROPERTY_USAGE_GROUP: { + annotation->export_info.usage = t_usage; + if (annotation->resolved_arguments.size() == 2) { + annotation->export_info.hint_string = annotation->resolved_arguments[1]; + } + } break; + + case PROPERTY_USAGE_SUBGROUP: { + annotation->export_info.usage = t_usage; + if (annotation->resolved_arguments.size() == 2) { + annotation->export_info.hint_string = annotation->resolved_arguments[1]; + } + } break; + } + + current_class->add_member_group(annotation); + return true; +} + bool GDScriptParser::warning_annotations(const AnnotationNode *p_annotation, Node *p_node) { #ifdef DEBUG_ENABLED bool has_error = false; @@ -4145,6 +4204,8 @@ void GDScriptParser::TreePrinter::print_class(ClassNode *p_class) { break; case ClassNode::Member::ENUM_VALUE: break; // Nothing. Will be printed by enum. + case ClassNode::Member::GROUP: + break; // Nothing. Groups are only used by inspector. case ClassNode::Member::UNDEFINED: push_line("<unknown member>"); break; diff --git a/modules/gdscript/gdscript_parser.h b/modules/gdscript/gdscript_parser.h index e3f8d4b8ba..8d3295f25b 100644 --- a/modules/gdscript/gdscript_parser.h +++ b/modules/gdscript/gdscript_parser.h @@ -325,6 +325,7 @@ public: Vector<Variant> resolved_arguments; AnnotationInfo *info = nullptr; + PropertyInfo export_info; bool apply(GDScriptParser *p_this, Node *p_target) const; bool applies_to(uint32_t p_target_kinds) const; @@ -500,6 +501,7 @@ public: VARIABLE, ENUM, ENUM_VALUE, // For unnamed enums. + GROUP, // For member grouping. }; Type type = UNDEFINED; @@ -511,6 +513,7 @@ public: SignalNode *signal; VariableNode *variable; EnumNode *m_enum; + AnnotationNode *annotation; }; EnumNode::Value enum_value; @@ -532,6 +535,8 @@ public: return "enum"; case ENUM_VALUE: return "enum value"; + case GROUP: + return "group"; } return ""; } @@ -552,6 +557,8 @@ public: return m_enum->start_line; case SIGNAL: return signal->start_line; + case GROUP: + return annotation->start_line; case UNDEFINED: ERR_FAIL_V_MSG(-1, "Reaching undefined member type."); } @@ -586,6 +593,9 @@ public: // TODO: Add parameter info. return type; } + case GROUP: { + return DataType(); + } case UNDEFINED: return DataType(); } @@ -622,6 +632,10 @@ public: type = ENUM_VALUE; enum_value = p_enum_value; } + Member(AnnotationNode *p_annotation) { + type = GROUP; + annotation = p_annotation; + } }; IdentifierNode *identifier = nullptr; @@ -668,6 +682,10 @@ public: members_indices[p_enum_value.identifier->name] = members.size(); members.push_back(Member(p_enum_value)); } + void add_member_group(AnnotationNode *p_annotation_node) { + members_indices[p_annotation_node->export_info.name] = members.size(); + members.push_back(Member(p_annotation_node)); + } ClassNode() { type = CLASS; @@ -1238,6 +1256,7 @@ private: SIGNAL = 1 << 4, FUNCTION = 1 << 5, STATEMENT = 1 << 6, + STANDALONE = 1 << 7, CLASS_LEVEL = CLASS | VARIABLE | FUNCTION, }; uint32_t target_kind = 0; // Flags. @@ -1348,6 +1367,8 @@ private: bool onready_annotation(const AnnotationNode *p_annotation, Node *p_target); template <PropertyHint t_hint, Variant::Type t_type> bool export_annotations(const AnnotationNode *p_annotation, Node *p_target); + template <PropertyUsageFlags t_usage> + bool export_group_annotations(const AnnotationNode *p_annotation, Node *p_target); bool warning_annotations(const AnnotationNode *p_annotation, Node *p_target); template <Multiplayer::RPCMode t_mode> bool network_annotations(const AnnotationNode *p_annotation, Node *p_target); @@ -1413,6 +1434,7 @@ public: CompletionContext get_completion_context() const { return completion_context; } CompletionCall get_completion_call() const { return completion_call; } void get_annotation_list(List<MethodInfo> *r_annotations) const; + bool annotation_exists(const String &p_annotation_name) const; const List<ParserError> &get_errors() const { return errors; } const List<String> get_dependencies() const { diff --git a/modules/gdscript/language_server/gdscript_extend_parser.cpp b/modules/gdscript/language_server/gdscript_extend_parser.cpp index d3c5fed95a..03e93821c7 100644 --- a/modules/gdscript/language_server/gdscript_extend_parser.cpp +++ b/modules/gdscript/language_server/gdscript_extend_parser.cpp @@ -307,6 +307,8 @@ void ExtendGDScriptParser::parse_class_symbol(const GDScriptParser::ClassNode *p parse_class_symbol(m.m_class, symbol); r_symbol.children.push_back(symbol); } break; + case ClassNode::Member::GROUP: + break; // No-op, but silences warnings. case ClassNode::Member::UNDEFINED: break; // Unreachable. } @@ -815,6 +817,8 @@ Dictionary ExtendGDScriptParser::dump_class_api(const GDScriptParser::ClassNode methods.append(dump_function_api(m.function)); } } break; + case ClassNode::Member::GROUP: + break; // No-op, but silences warnings. case ClassNode::Member::UNDEFINED: break; // Unreachable. } diff --git a/modules/gdscript/tests/scripts/parser/errors/class_name_after_annotation.gd b/modules/gdscript/tests/scripts/parser/errors/class_name_after_annotation.gd index d13d713454..ada6030132 100644 --- a/modules/gdscript/tests/scripts/parser/errors/class_name_after_annotation.gd +++ b/modules/gdscript/tests/scripts/parser/errors/class_name_after_annotation.gd @@ -1,6 +1,6 @@ -# Error here. `class_name` should be used *before* annotations, not after. +# Error here. `class_name` should be used *before* annotations, not after (except @tool). @icon("res://path/to/optional/icon.svg") class_name HelloWorld func test(): - pass + pass diff --git a/modules/gdscript/tests/scripts/parser/errors/class_name_after_annotation.out b/modules/gdscript/tests/scripts/parser/errors/class_name_after_annotation.out index 0bcc8acc55..02b33c8692 100644 --- a/modules/gdscript/tests/scripts/parser/errors/class_name_after_annotation.out +++ b/modules/gdscript/tests/scripts/parser/errors/class_name_after_annotation.out @@ -1,2 +1,2 @@ GDTEST_PARSER_ERROR -"class_name" should be used before annotations. +"class_name" should be used before annotations (except @tool). |