diff options
Diffstat (limited to 'modules/gdscript/gdscript_parser.cpp')
-rw-r--r-- | modules/gdscript/gdscript_parser.cpp | 459 |
1 files changed, 299 insertions, 160 deletions
diff --git a/modules/gdscript/gdscript_parser.cpp b/modules/gdscript/gdscript_parser.cpp index 0f7166c101..db7b3e7ace 100644 --- a/modules/gdscript/gdscript_parser.cpp +++ b/modules/gdscript/gdscript_parser.cpp @@ -32,10 +32,6 @@ #include "gdscript.h" -#ifdef DEBUG_ENABLED -#include "gdscript_warning.h" -#endif - #include "core/config/project_settings.h" #include "core/io/file_access.h" #include "core/io/resource_loader.h" @@ -52,11 +48,18 @@ #include "editor/editor_settings.h" #endif +// This function is used to determine that a type is "built-in" as opposed to native +// and custom classes. So `Variant::NIL` and `Variant::OBJECT` are excluded: +// `Variant::NIL` - `null` is literal, not a type. +// `Variant::OBJECT` - `Object` should be treated as a class, not as a built-in type. static HashMap<StringName, Variant::Type> builtin_types; Variant::Type GDScriptParser::get_builtin_type(const StringName &p_type) { - if (builtin_types.is_empty()) { - for (int i = 1; i < Variant::VARIANT_MAX; i++) { - builtin_types[Variant::get_type_name((Variant::Type)i)] = (Variant::Type)i; + if (unlikely(builtin_types.is_empty())) { + for (int i = 0; i < Variant::VARIANT_MAX; i++) { + Variant::Type type = (Variant::Type)i; + if (type != Variant::NIL && type != Variant::OBJECT) { + builtin_types[Variant::get_type_name(type)] = type; + } } } @@ -66,6 +69,10 @@ Variant::Type GDScriptParser::get_builtin_type(const StringName &p_type) { return Variant::VARIANT_MAX; } +#ifdef TOOLS_ENABLED +HashMap<String, String> GDScriptParser::theme_color_names; +#endif + void GDScriptParser::cleanup() { builtin_types.clear(); } @@ -121,6 +128,15 @@ GDScriptParser::GDScriptParser() { #ifdef DEBUG_ENABLED is_ignoring_warnings = !(bool)GLOBAL_GET("debug/gdscript/warnings/enable"); #endif + +#ifdef TOOLS_ENABLED + if (theme_color_names.is_empty()) { + theme_color_names.insert("x", "axis_x_color"); + theme_color_names.insert("y", "axis_y_color"); + theme_color_names.insert("z", "axis_z_color"); + theme_color_names.insert("w", "axis_w_color"); + } +#endif } GDScriptParser::~GDScriptParser() { @@ -157,7 +173,7 @@ void GDScriptParser::push_error(const String &p_message, const Node *p_origin) { #ifdef DEBUG_ENABLED void GDScriptParser::push_warning(const Node *p_source, GDScriptWarning::Code p_code, const Vector<String> &p_symbols) { - ERR_FAIL_COND(p_source == nullptr); + ERR_FAIL_NULL(p_source); if (is_ignoring_warnings) { return; } @@ -370,8 +386,10 @@ GDScriptTokenizer::Token GDScriptParser::advance() { push_error(current.literal); current = tokenizer.scan(); } - for (Node *n : nodes_in_progress) { - update_extents(n); + if (previous.type != GDScriptTokenizer::Token::DEDENT) { // `DEDENT` belongs to the next non-empty line. + for (Node *n : nodes_in_progress) { + update_extents(n); + } } return previous; } @@ -497,7 +515,7 @@ void GDScriptParser::parse_program() { if (annotation->applies_to(AnnotationInfo::SCRIPT)) { // `@icon` needs to be applied in the parser. See GH-72444. if (annotation->name == SNAME("@icon")) { - annotation->apply(this, head); + annotation->apply(this, head, nullptr); } else { head->annotations.push_back(annotation); } @@ -566,13 +584,14 @@ void GDScriptParser::parse_program() { complete_extents(head); #ifdef TOOLS_ENABLED - for (const KeyValue<int, GDScriptTokenizer::CommentData> &E : tokenizer.get_comments()) { - if (E.value.new_line && E.value.comment.begins_with("##")) { - class_doc_line = MIN(class_doc_line, E.key); + const HashMap<int, GDScriptTokenizer::CommentData> &comments = tokenizer.get_comments(); + int line = MIN(max_script_doc_line, head->end_line); + while (line > 0) { + if (comments.has(line) && comments[line].new_line && comments[line].comment.begins_with("##")) { + head->doc_data = parse_class_doc_comment(line); + break; } - } - if (has_comment(class_doc_line, true)) { - head->doc_data = parse_class_doc_comment(class_doc_line, false); + line--; } #endif // TOOLS_ENABLED @@ -734,10 +753,6 @@ template <class T> void GDScriptParser::parse_class_member(T *(GDScriptParser::*p_parse_function)(bool), AnnotationInfo::TargetKind p_target, const String &p_member_kind, bool p_is_static) { advance(); -#ifdef TOOLS_ENABLED - int doc_comment_line = previous.start_line - 1; -#endif // TOOLS_ENABLED - // Consume annotations. List<AnnotationNode *> annotations; while (!annotation_stack.is_empty()) { @@ -749,11 +764,6 @@ void GDScriptParser::parse_class_member(T *(GDScriptParser::*p_parse_function)(b push_error(vformat(R"(Annotation "%s" cannot be applied to a %s.)", last_annotation->name, p_member_kind)); clear_unused_annotations(); } -#ifdef TOOLS_ENABLED - if (last_annotation->start_line == doc_comment_line) { - doc_comment_line--; - } -#endif // TOOLS_ENABLED } T *member = (this->*p_parse_function)(p_is_static); @@ -761,28 +771,40 @@ void GDScriptParser::parse_class_member(T *(GDScriptParser::*p_parse_function)(b return; } +#ifdef TOOLS_ENABLED + int doc_comment_line = member->start_line - 1; +#endif // TOOLS_ENABLED + for (AnnotationNode *&annotation : annotations) { member->annotations.push_back(annotation); +#ifdef TOOLS_ENABLED + if (annotation->start_line <= doc_comment_line) { + doc_comment_line = annotation->start_line - 1; + } +#endif // TOOLS_ENABLED } #ifdef TOOLS_ENABLED - // Consume doc comments. - class_doc_line = MIN(class_doc_line, doc_comment_line - 1); - - // Check whether current line has a doc comment - if (has_comment(previous.start_line, true)) { - if constexpr (std::is_same_v<T, ClassNode>) { - member->doc_data = parse_class_doc_comment(previous.start_line, true, true); - } else { - member->doc_data = parse_doc_comment(previous.start_line, true); + if constexpr (std::is_same_v<T, ClassNode>) { + if (has_comment(member->start_line, true)) { + // Inline doc comment. + member->doc_data = parse_class_doc_comment(member->start_line, true); + } else if (has_comment(doc_comment_line, true) && tokenizer.get_comments()[doc_comment_line].new_line) { + // Normal doc comment. Don't check `min_member_doc_line` because a class ends parsing after its members. + // This may not work correctly for cases like `var a; class B`, but it doesn't matter in practice. + member->doc_data = parse_class_doc_comment(doc_comment_line); } - } else if (has_comment(doc_comment_line, true)) { - if constexpr (std::is_same_v<T, ClassNode>) { - member->doc_data = parse_class_doc_comment(doc_comment_line, true); - } else { + } else { + if (has_comment(member->start_line, true)) { + // Inline doc comment. + member->doc_data = parse_doc_comment(member->start_line, true); + } else if (doc_comment_line >= min_member_doc_line && has_comment(doc_comment_line, true) && tokenizer.get_comments()[doc_comment_line].new_line) { + // Normal doc comment. member->doc_data = parse_doc_comment(doc_comment_line); } } + + min_member_doc_line = member->end_line + 1; // Prevent multiple members from using the same doc comment. #endif // TOOLS_ENABLED if (member->identifier != nullptr) { @@ -1250,6 +1272,9 @@ GDScriptParser::EnumNode *GDScriptParser::parse_enum(bool p_is_static) { push_multiline(true); consume(GDScriptTokenizer::Token::BRACE_OPEN, vformat(R"(Expected "{" after %s.)", named ? "enum name" : R"("enum")")); +#ifdef TOOLS_ENABLED + int min_enum_value_doc_line = previous.end_line + 1; +#endif HashMap<StringName, int> elements; @@ -1312,43 +1337,35 @@ GDScriptParser::EnumNode *GDScriptParser::parse_enum(bool p_is_static) { } } while (match(GDScriptTokenizer::Token::COMMA)); - pop_multiline(); - consume(GDScriptTokenizer::Token::BRACE_CLOSE, R"(Expected closing "}" for enum.)"); - #ifdef TOOLS_ENABLED // Enum values documentation. for (int i = 0; i < enum_node->values.size(); i++) { - int doc_comment_line = enum_node->values[i].line; - bool single_line = false; - - if (has_comment(doc_comment_line, true)) { - single_line = true; - } else if (has_comment(doc_comment_line - 1, true)) { - doc_comment_line--; - } else { - continue; - } - - if (i == enum_node->values.size() - 1) { - // If close bracket is same line as last value. - if (doc_comment_line == previous.start_line) { - break; - } - } else { - // If two values are same line. - if (doc_comment_line == enum_node->values[i + 1].line) { - continue; + int enum_value_line = enum_node->values[i].line; + int doc_comment_line = enum_value_line - 1; + + MemberDocData doc_data; + if (has_comment(enum_value_line, true)) { + // Inline doc comment. + if (i == enum_node->values.size() - 1 || enum_node->values[i + 1].line > enum_value_line) { + doc_data = parse_doc_comment(enum_value_line, true); } + } else if (doc_comment_line >= min_enum_value_doc_line && has_comment(doc_comment_line, true) && tokenizer.get_comments()[doc_comment_line].new_line) { + // Normal doc comment. + doc_data = parse_doc_comment(doc_comment_line); } if (named) { - enum_node->values.write[i].doc_data = parse_doc_comment(doc_comment_line, single_line); + enum_node->values.write[i].doc_data = doc_data; } else { - current_class->set_enum_value_doc_data(enum_node->values[i].identifier->name, parse_doc_comment(doc_comment_line, single_line)); + current_class->set_enum_value_doc_data(enum_node->values[i].identifier->name, doc_data); } + + min_enum_value_doc_line = enum_value_line + 1; // Prevent multiple enum values from using the same doc comment. } #endif // TOOLS_ENABLED + pop_multiline(); + consume(GDScriptTokenizer::Token::BRACE_CLOSE, R"(Expected closing "}" for enum.)"); complete_extents(enum_node); end_statement("enum"); @@ -2021,7 +2038,37 @@ GDScriptParser::MatchBranchNode *GDScriptParser::parse_match_branch() { push_error(R"(No pattern found for "match" branch.)"); } - if (!consume(GDScriptTokenizer::Token::COLON, R"(Expected ":" after "match" patterns.)")) { + bool has_guard = false; + if (match(GDScriptTokenizer::Token::WHEN)) { + // Pattern guard. + // Create block for guard because it also needs to access the bound variables from patterns, and we don't want to add them to the outer scope. + branch->guard_body = alloc_node<SuiteNode>(); + if (branch->patterns.size() > 0) { + for (const KeyValue<StringName, IdentifierNode *> &E : branch->patterns[0]->binds) { + SuiteNode::Local local(E.value, current_function); + local.type = SuiteNode::Local::PATTERN_BIND; + branch->guard_body->add_local(local); + } + } + + SuiteNode *parent_block = current_suite; + branch->guard_body->parent_block = parent_block; + current_suite = branch->guard_body; + + ExpressionNode *guard = parse_expression(false); + if (guard == nullptr) { + push_error(R"(Expected expression for pattern guard after "when".)"); + } else { + branch->guard_body->statements.append(guard); + } + current_suite = parent_block; + complete_extents(branch->guard_body); + + has_guard = true; + branch->has_wildcard = false; // If it has a guard, the wildcard might still not match. + } + + if (!consume(GDScriptTokenizer::Token::COLON, vformat(R"(Expected ":"%s after "match" %s.)", has_guard ? "" : R"( or "when")", has_guard ? "pattern guard" : "patterns"))) { complete_extents(branch); return nullptr; } @@ -3441,31 +3488,21 @@ bool GDScriptParser::has_comment(int p_line, bool p_must_be_doc) { } GDScriptParser::MemberDocData GDScriptParser::parse_doc_comment(int p_line, bool p_single_line) { - MemberDocData result; + ERR_FAIL_COND_V(!has_comment(p_line, true), MemberDocData()); const HashMap<int, GDScriptTokenizer::CommentData> &comments = tokenizer.get_comments(); - ERR_FAIL_COND_V(!comments.has(p_line), result); - - if (p_single_line) { - if (comments[p_line].comment.begins_with("##")) { - result.description = comments[p_line].comment.trim_prefix("##").strip_edges(); - return result; - } - return result; - } - int line = p_line; - DocLineState state = DOC_LINE_NORMAL; - while (comments.has(line - 1)) { - if (!comments[line - 1].new_line || !comments[line - 1].comment.begins_with("##")) { - break; + if (!p_single_line) { + while (comments.has(line - 1) && comments[line - 1].new_line && comments[line - 1].comment.begins_with("##")) { + line--; } - line--; } + max_script_doc_line = MIN(max_script_doc_line, line - 1); + String space_prefix; - if (comments.has(line) && comments[line].comment.begins_with("##")) { + { int i = 2; for (; i < comments[line].comment.length(); i++) { if (comments[line].comment[i] != ' ') { @@ -3475,11 +3512,10 @@ GDScriptParser::MemberDocData GDScriptParser::parse_doc_comment(int p_line, bool space_prefix = String(" ").repeat(i - 2); } - while (comments.has(line)) { - if (!comments[line].new_line || !comments[line].comment.begins_with("##")) { - break; - } + DocLineState state = DOC_LINE_NORMAL; + MemberDocData result; + while (line <= p_line) { String doc_line = comments[line].comment.trim_prefix("##"); line++; @@ -3500,35 +3536,22 @@ GDScriptParser::MemberDocData GDScriptParser::parse_doc_comment(int p_line, bool return result; } -GDScriptParser::ClassDocData GDScriptParser::parse_class_doc_comment(int p_line, bool p_inner_class, bool p_single_line) { - ClassDocData result; +GDScriptParser::ClassDocData GDScriptParser::parse_class_doc_comment(int p_line, bool p_single_line) { + ERR_FAIL_COND_V(!has_comment(p_line, true), ClassDocData()); const HashMap<int, GDScriptTokenizer::CommentData> &comments = tokenizer.get_comments(); - ERR_FAIL_COND_V(!comments.has(p_line), result); - - if (p_single_line) { - if (comments[p_line].comment.begins_with("##")) { - result.brief = comments[p_line].comment.trim_prefix("##").strip_edges(); - return result; - } - return result; - } - int line = p_line; - DocLineState state = DOC_LINE_NORMAL; - bool is_in_brief = true; - if (p_inner_class) { - while (comments.has(line - 1)) { - if (!comments[line - 1].new_line || !comments[line - 1].comment.begins_with("##")) { - break; - } + if (!p_single_line) { + while (comments.has(line - 1) && comments[line - 1].new_line && comments[line - 1].comment.begins_with("##")) { line--; } } + max_script_doc_line = MIN(max_script_doc_line, line - 1); + String space_prefix; - if (comments.has(line) && comments[line].comment.begins_with("##")) { + { int i = 2; for (; i < comments[line].comment.length(); i++) { if (comments[line].comment[i] != ' ') { @@ -3538,11 +3561,11 @@ GDScriptParser::ClassDocData GDScriptParser::parse_class_doc_comment(int p_line, space_prefix = String(" ").repeat(i - 2); } - while (comments.has(line)) { - if (!comments[line].new_line || !comments[line].comment.begins_with("##")) { - break; - } + DocLineState state = DOC_LINE_NORMAL; + bool is_in_brief = true; + ClassDocData result; + while (line <= p_line) { String doc_line = comments[line].comment.trim_prefix("##"); line++; @@ -3617,14 +3640,6 @@ GDScriptParser::ClassDocData GDScriptParser::parse_class_doc_comment(int p_line, } } - if (current_class->members.size() > 0) { - const ClassNode::Member &m = current_class->members[0]; - int first_member_line = m.get_line(); - if (first_member_line == line) { - result = ClassDocData(); // Clear result. - } - } - return result; } #endif // TOOLS_ENABLED @@ -3692,6 +3707,7 @@ GDScriptParser::ParseRule *GDScriptParser::get_rule(GDScriptTokenizer::Token::Ty { nullptr, nullptr, PREC_NONE }, // PASS, { nullptr, nullptr, PREC_NONE }, // RETURN, { nullptr, nullptr, PREC_NONE }, // MATCH, + { nullptr, nullptr, PREC_NONE }, // WHEN, // Keywords { nullptr, &GDScriptParser::parse_cast, PREC_CAST }, // AS, { nullptr, nullptr, PREC_NONE }, // ASSERT, @@ -3775,12 +3791,12 @@ const GDScriptParser::SuiteNode::Local &GDScriptParser::SuiteNode::get_local(con return empty; } -bool GDScriptParser::AnnotationNode::apply(GDScriptParser *p_this, Node *p_target) { +bool GDScriptParser::AnnotationNode::apply(GDScriptParser *p_this, Node *p_target, ClassNode *p_class) { if (is_applied) { return true; } is_applied = true; - return (p_this->*(p_this->valid_annotations[name].apply))(this, p_target); + return (p_this->*(p_this->valid_annotations[name].apply))(this, p_target, p_class); } bool GDScriptParser::AnnotationNode::applies_to(uint32_t p_target_kinds) const { @@ -3826,7 +3842,7 @@ bool GDScriptParser::validate_annotation_arguments(AnnotationNode *p_annotation) return true; } -bool GDScriptParser::tool_annotation(const AnnotationNode *p_annotation, Node *p_node) { +bool GDScriptParser::tool_annotation(const AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class) { #ifdef DEBUG_ENABLED if (this->_is_tool) { push_error(R"("@tool" annotation can only be used once.)", p_annotation); @@ -3837,32 +3853,45 @@ bool GDScriptParser::tool_annotation(const AnnotationNode *p_annotation, Node *p return true; } -bool GDScriptParser::icon_annotation(const AnnotationNode *p_annotation, Node *p_node) { - ERR_FAIL_COND_V_MSG(p_node->type != Node::CLASS, false, R"("@icon" annotation can only be applied to classes.)"); +bool GDScriptParser::icon_annotation(const AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class) { + ERR_FAIL_COND_V_MSG(p_target->type != Node::CLASS, false, R"("@icon" annotation can only be applied to classes.)"); ERR_FAIL_COND_V(p_annotation->resolved_arguments.is_empty(), false); - ClassNode *p_class = static_cast<ClassNode *>(p_node); + + ClassNode *class_node = static_cast<ClassNode *>(p_target); + String path = p_annotation->resolved_arguments[0]; + #ifdef DEBUG_ENABLED - if (!p_class->icon_path.is_empty()) { + if (!class_node->icon_path.is_empty()) { push_error(R"("@icon" annotation can only be used once.)", p_annotation); return false; } - if (String(p_annotation->resolved_arguments[0]).is_empty()) { + if (path.is_empty()) { push_error(R"("@icon" annotation argument must contain the path to the icon.)", p_annotation->arguments[0]); return false; } #endif // DEBUG_ENABLED - p_class->icon_path = p_annotation->resolved_arguments[0]; + + class_node->icon_path = path; + + if (path.is_empty() || path.is_absolute_path()) { + class_node->simplified_icon_path = path.simplify_path(); + } else if (path.is_relative_path()) { + class_node->simplified_icon_path = script_path.get_base_dir().path_join(path).simplify_path(); + } else { + class_node->simplified_icon_path = path; + } + return true; } -bool GDScriptParser::onready_annotation(const AnnotationNode *p_annotation, Node *p_node) { - ERR_FAIL_COND_V_MSG(p_node->type != Node::VARIABLE, false, R"("@onready" annotation can only be applied to class variables.)"); +bool GDScriptParser::onready_annotation(const AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class) { + ERR_FAIL_COND_V_MSG(p_target->type != Node::VARIABLE, false, R"("@onready" annotation can only be applied to class variables.)"); if (current_class && !ClassDB::is_parent_class(current_class->get_datatype().native_type, SNAME("Node"))) { push_error(R"("@onready" can only be used in classes that inherit "Node".)", p_annotation); } - VariableNode *variable = static_cast<VariableNode *>(p_node); + VariableNode *variable = static_cast<VariableNode *>(p_target); if (variable->is_static) { push_error(R"("@onready" annotation cannot be applied to a static variable.)", p_annotation); return false; @@ -3877,10 +3906,11 @@ bool GDScriptParser::onready_annotation(const AnnotationNode *p_annotation, Node } template <PropertyHint t_hint, Variant::Type t_type> -bool GDScriptParser::export_annotations(const AnnotationNode *p_annotation, Node *p_node) { - ERR_FAIL_COND_V_MSG(p_node->type != Node::VARIABLE, false, vformat(R"("%s" annotation can only be applied to variables.)", p_annotation->name)); +bool GDScriptParser::export_annotations(const AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class) { + ERR_FAIL_COND_V_MSG(p_target->type != Node::VARIABLE, false, vformat(R"("%s" annotation can only be applied to variables.)", p_annotation->name)); + ERR_FAIL_NULL_V(p_class, false); - VariableNode *variable = static_cast<VariableNode *>(p_node); + VariableNode *variable = static_cast<VariableNode *>(p_target); if (variable->is_static) { push_error(vformat(R"(Annotation "%s" cannot be applied to a static variable.)", p_annotation->name), p_annotation); return false; @@ -4069,27 +4099,38 @@ bool GDScriptParser::export_annotations(const AnnotationNode *p_annotation, Node } } break; case GDScriptParser::DataType::ENUM: { - variable->export_info.type = Variant::INT; - variable->export_info.hint = PROPERTY_HINT_ENUM; - - String enum_hint_string; - bool first = true; - for (const KeyValue<StringName, int64_t> &E : export_type.enum_values) { - if (!first) { - enum_hint_string += ","; - } else { - first = false; + if (export_type.is_meta_type) { + variable->export_info.type = Variant::DICTIONARY; + } else { + variable->export_info.type = Variant::INT; + variable->export_info.hint = PROPERTY_HINT_ENUM; + + String enum_hint_string; + bool first = true; + for (const KeyValue<StringName, int64_t> &E : export_type.enum_values) { + if (!first) { + enum_hint_string += ","; + } else { + first = false; + } + enum_hint_string += E.key.operator String().capitalize().xml_escape(); + enum_hint_string += ":"; + enum_hint_string += String::num_int64(E.value).xml_escape(); } - enum_hint_string += E.key.operator String().capitalize().xml_escape(); - enum_hint_string += ":"; - enum_hint_string += String::num_int64(E.value).xml_escape(); - } - variable->export_info.hint_string = enum_hint_string; + variable->export_info.hint_string = enum_hint_string; + variable->export_info.usage |= PROPERTY_USAGE_CLASS_IS_ENUM; + variable->export_info.class_name = String(export_type.native_type).replace("::", "."); + } } break; default: push_error(R"(Export type can only be built-in, a resource, a node, or an enum.)", variable); - break; + return false; + } + + if (variable->export_info.hint == PROPERTY_HINT_NODE_TYPE && !ClassDB::is_parent_class(p_class->base_type.native_type, SNAME("Node"))) { + push_error(vformat(R"(Node export is only supported in Node-derived classes, but the current class inherits "%s".)", p_class->base_type.to_string()), variable); + return false; } if (is_array) { @@ -4134,7 +4175,7 @@ bool GDScriptParser::export_annotations(const AnnotationNode *p_annotation, Node } template <PropertyUsageFlags t_usage> -bool GDScriptParser::export_group_annotations(const AnnotationNode *p_annotation, Node *p_node) { +bool GDScriptParser::export_group_annotations(const AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class) { AnnotationNode *annotation = const_cast<AnnotationNode *>(p_annotation); if (annotation->resolved_arguments.is_empty()) { @@ -4166,7 +4207,7 @@ bool GDScriptParser::export_group_annotations(const AnnotationNode *p_annotation return true; } -bool GDScriptParser::warning_annotations(const AnnotationNode *p_annotation, Node *p_node) { +bool GDScriptParser::warning_annotations(const AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class) { #ifdef DEBUG_ENABLED bool has_error = false; for (const Variant &warning_name : p_annotation->resolved_arguments) { @@ -4175,7 +4216,7 @@ bool GDScriptParser::warning_annotations(const AnnotationNode *p_annotation, Nod push_error(vformat(R"(Invalid warning name: "%s".)", warning_name), p_annotation); has_error = true; } else { - p_node->ignored_warnings.push_back(warning); + p_target->ignored_warnings.push_back(warning); } } @@ -4187,10 +4228,10 @@ bool GDScriptParser::warning_annotations(const AnnotationNode *p_annotation, Nod #endif // DEBUG_ENABLED } -bool GDScriptParser::rpc_annotation(const AnnotationNode *p_annotation, Node *p_node) { - ERR_FAIL_COND_V_MSG(p_node->type != Node::FUNCTION, false, vformat(R"("%s" annotation can only be applied to functions.)", p_annotation->name)); +bool GDScriptParser::rpc_annotation(const AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class) { + ERR_FAIL_COND_V_MSG(p_target->type != Node::FUNCTION, false, vformat(R"("%s" annotation can only be applied to functions.)", p_annotation->name)); - FunctionNode *function = static_cast<FunctionNode *>(p_node); + FunctionNode *function = static_cast<FunctionNode *>(p_target); if (function->rpc_config.get_type() != Variant::NIL) { push_error(R"(RPC annotations can only be used once per function.)", p_annotation); return false; @@ -4248,14 +4289,14 @@ bool GDScriptParser::rpc_annotation(const AnnotationNode *p_annotation, Node *p_ return true; } -bool GDScriptParser::static_unload_annotation(const AnnotationNode *p_annotation, Node *p_target) { +bool GDScriptParser::static_unload_annotation(const AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class) { ERR_FAIL_COND_V_MSG(p_target->type != Node::CLASS, false, vformat(R"("%s" annotation can only be applied to classes.)", p_annotation->name)); - ClassNode *p_class = static_cast<ClassNode *>(p_target); - if (p_class->annotated_static_unload) { + ClassNode *class_node = static_cast<ClassNode *>(p_target); + if (class_node->annotated_static_unload) { push_error(vformat(R"("%s" annotation can only be used once per script.)", p_annotation->name), p_annotation); return false; } - p_class->annotated_static_unload = true; + class_node->annotated_static_unload = true; return true; } @@ -4344,6 +4385,104 @@ String GDScriptParser::DataType::to_string() const { ERR_FAIL_V_MSG("<unresolved type>", "Kind set outside the enum range."); } +PropertyInfo GDScriptParser::DataType::to_property_info(const String &p_name) const { + PropertyInfo result; + result.name = p_name; + result.usage = PROPERTY_USAGE_NONE; + + if (!is_hard_type()) { + result.usage |= PROPERTY_USAGE_NIL_IS_VARIANT; + return result; + } + + switch (kind) { + case BUILTIN: + result.type = builtin_type; + if (builtin_type == Variant::ARRAY && has_container_element_type()) { + const DataType *elem_type = container_element_type; + switch (elem_type->kind) { + case BUILTIN: + result.hint = PROPERTY_HINT_ARRAY_TYPE; + result.hint_string = Variant::get_type_name(elem_type->builtin_type); + break; + case NATIVE: + result.hint = PROPERTY_HINT_ARRAY_TYPE; + result.hint_string = elem_type->native_type; + break; + case SCRIPT: + result.hint = PROPERTY_HINT_ARRAY_TYPE; + if (elem_type->script_type.is_valid() && elem_type->script_type->get_global_name() != StringName()) { + result.hint_string = elem_type->script_type->get_global_name(); + } else { + result.hint_string = elem_type->native_type; + } + break; + case CLASS: + result.hint = PROPERTY_HINT_ARRAY_TYPE; + if (elem_type->class_type != nullptr && elem_type->class_type->get_global_name() != StringName()) { + result.hint_string = elem_type->class_type->get_global_name(); + } else { + result.hint_string = elem_type->native_type; + } + break; + case ENUM: + result.hint = PROPERTY_HINT_ARRAY_TYPE; + result.hint_string = String(elem_type->native_type).replace("::", "."); + break; + case VARIANT: + case RESOLVING: + case UNRESOLVED: + break; + } + } + break; + case NATIVE: + result.type = Variant::OBJECT; + if (is_meta_type) { + result.class_name = GDScriptNativeClass::get_class_static(); + } else { + result.class_name = native_type; + } + break; + case SCRIPT: + result.type = Variant::OBJECT; + if (is_meta_type) { + result.class_name = script_type.is_valid() ? script_type->get_class() : Script::get_class_static(); + } else if (script_type.is_valid() && script_type->get_global_name() != StringName()) { + result.class_name = script_type->get_global_name(); + } else { + result.class_name = native_type; + } + break; + case CLASS: + result.type = Variant::OBJECT; + if (is_meta_type) { + result.class_name = GDScript::get_class_static(); + } else if (class_type != nullptr && class_type->get_global_name() != StringName()) { + result.class_name = class_type->get_global_name(); + } else { + result.class_name = native_type; + } + break; + case ENUM: + if (is_meta_type) { + result.type = Variant::DICTIONARY; + } else { + result.type = Variant::INT; + result.usage |= PROPERTY_USAGE_CLASS_IS_ENUM; + result.class_name = String(native_type).replace("::", "."); + } + break; + case VARIANT: + case RESOLVING: + case UNRESOLVED: + result.usage |= PROPERTY_USAGE_NIL_IS_VARIANT; + break; + } + + return result; +} + static Variant::Type _variant_type_to_typed_array_element_type(Variant::Type p_type) { switch (p_type) { case Variant::PACKED_BYTE_ARRAY: @@ -5267,7 +5406,7 @@ void GDScriptParser::TreePrinter::print_while(WhileNode *p_while) { } void GDScriptParser::TreePrinter::print_tree(const GDScriptParser &p_parser) { - ERR_FAIL_COND_MSG(p_parser.get_tree() == nullptr, "Parse the code before printing the parse tree."); + ERR_FAIL_NULL_MSG(p_parser.get_tree(), "Parse the code before printing the parse tree."); if (p_parser.is_tool()) { push_line("@tool"); |