diff options
Diffstat (limited to 'modules/gdscript/gdscript_parser.cpp')
-rw-r--r-- | modules/gdscript/gdscript_parser.cpp | 675 |
1 files changed, 529 insertions, 146 deletions
diff --git a/modules/gdscript/gdscript_parser.cpp b/modules/gdscript/gdscript_parser.cpp index d706f4e9a3..e30f03afad 100644 --- a/modules/gdscript/gdscript_parser.cpp +++ b/modules/gdscript/gdscript_parser.cpp @@ -101,7 +101,6 @@ GDScriptParser::GDScriptParser() { register_annotation(MethodInfo("@onready"), AnnotationInfo::VARIABLE, &GDScriptParser::onready_annotation); // Export annotations. register_annotation(MethodInfo("@export"), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_NONE, Variant::NIL>); - register_annotation(MethodInfo("@export_storage"), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_NONE, Variant::NIL>); register_annotation(MethodInfo("@export_enum", PropertyInfo(Variant::STRING, "names")), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_ENUM, Variant::NIL>, varray(), true); register_annotation(MethodInfo("@export_file", PropertyInfo(Variant::STRING, "filter")), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_FILE, Variant::STRING>, varray(""), true); register_annotation(MethodInfo("@export_dir"), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_DIR, Variant::STRING>); @@ -121,7 +120,9 @@ GDScriptParser::GDScriptParser() { 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>); register_annotation(MethodInfo("@export_flags_avoidance"), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_LAYERS_AVOIDANCE, Variant::INT>); + register_annotation(MethodInfo("@export_storage"), AnnotationInfo::VARIABLE, &GDScriptParser::export_storage_annotation); register_annotation(MethodInfo("@export_custom", PropertyInfo(Variant::INT, "hint", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_CLASS_IS_ENUM, "PropertyHint"), PropertyInfo(Variant::STRING, "hint_string"), PropertyInfo(Variant::INT, "usage", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_CLASS_IS_BITFIELD, "PropertyUsageFlags")), AnnotationInfo::VARIABLE, &GDScriptParser::export_custom_annotation, varray(PROPERTY_USAGE_DEFAULT)); + register_annotation(MethodInfo("@export_tool_button", PropertyInfo(Variant::STRING, "text"), PropertyInfo(Variant::STRING, "icon")), AnnotationInfo::VARIABLE, &GDScriptParser::export_tool_button_annotation, varray("")); // 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>, varray("")); @@ -137,33 +138,38 @@ GDScriptParser::GDScriptParser() { #endif #ifdef TOOLS_ENABLED - if (theme_color_names.is_empty()) { + if (unlikely(theme_color_names.is_empty())) { + // Vectors. 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"); + + // Color. + theme_color_names.insert("r", "axis_x_color"); + theme_color_names.insert("r8", "axis_x_color"); + theme_color_names.insert("g", "axis_y_color"); + theme_color_names.insert("g8", "axis_y_color"); + theme_color_names.insert("b", "axis_z_color"); + theme_color_names.insert("b8", "axis_z_color"); + theme_color_names.insert("a", "axis_w_color"); + theme_color_names.insert("a8", "axis_w_color"); } #endif } GDScriptParser::~GDScriptParser() { - clear(); -} - -void GDScriptParser::clear() { while (list != nullptr) { Node *element = list; list = list->next; memdelete(element); } +} - head = nullptr; - list = nullptr; - _is_tool = false; - for_completion = false; - errors.clear(); - multiline_stack.clear(); - nodes_in_progress.clear(); +void GDScriptParser::clear() { + GDScriptParser tmp; + tmp = *this; + *this = GDScriptParser(); } void GDScriptParser::push_error(const String &p_message, const Node *p_origin) { @@ -172,7 +178,7 @@ void GDScriptParser::push_error(const String &p_message, const Node *p_origin) { panic_mode = true; // TODO: Improve positional information. if (p_origin == nullptr) { - errors.push_back({ p_message, current.start_line, current.start_column }); + errors.push_back({ p_message, previous.start_line, previous.start_column }); } else { errors.push_back({ p_message, p_origin->start_line, p_origin->leftmost_column }); } @@ -240,6 +246,25 @@ void GDScriptParser::apply_pending_warnings() { } #endif +void GDScriptParser::override_completion_context(const Node *p_for_node, CompletionType p_type, Node *p_node, int p_argument) { + if (!for_completion) { + return; + } + if (p_for_node == nullptr || completion_context.node != p_for_node) { + return; + } + CompletionContext context; + context.type = p_type; + context.current_class = current_class; + context.current_function = current_function; + context.current_suite = current_suite; + context.current_line = tokenizer->get_cursor_line(); + context.current_argument = p_argument; + context.node = p_node; + context.parser = this; + completion_context = context; +} + void GDScriptParser::make_completion_context(CompletionType p_type, Node *p_node, int p_argument, bool p_force) { if (!for_completion || (!p_force && completion_context.type != COMPLETION_NONE)) { return; @@ -255,6 +280,7 @@ void GDScriptParser::make_completion_context(CompletionType p_type, Node *p_node context.current_line = tokenizer->get_cursor_line(); context.current_argument = p_argument; context.node = p_node; + context.parser = this; completion_context = context; } @@ -272,6 +298,7 @@ void GDScriptParser::make_completion_context(CompletionType p_type, Variant::Typ context.current_suite = current_suite; context.current_line = tokenizer->get_cursor_line(); context.builtin_type = p_builtin_type; + context.parser = this; completion_context = context; } @@ -304,13 +331,14 @@ void GDScriptParser::set_last_completion_call_arg(int p_argument) { completion_call_stack.back()->get().argument = p_argument; } -Error GDScriptParser::parse(const String &p_source_code, const String &p_script_path, bool p_for_completion) { +Error GDScriptParser::parse(const String &p_source_code, const String &p_script_path, bool p_for_completion, bool p_parse_body) { clear(); String source = p_source_code; int cursor_line = -1; int cursor_column = -1; for_completion = p_for_completion; + parse_body = p_parse_body; int tab_size = 4; #ifdef TOOLS_ENABLED @@ -407,7 +435,7 @@ Error GDScriptParser::parse_binary(const Vector<uint8_t> &p_binary, const String } tokenizer = buffer_tokenizer; - script_path = p_script_path; + script_path = p_script_path.simplify_path(); current = tokenizer->scan(); // Avoid error or newline as the first token. // The latter can mess with the parser when opening files filled exclusively with comments and newlines. @@ -684,6 +712,12 @@ void GDScriptParser::parse_program() { } } + // When the only thing needed is the class name and the icon, we don't need to parse the hole file. + // It really speed up the call to GDScriptLanguage::get_global_class_name especially for large script. + if (!parse_body) { + return; + } + #undef PUSH_PENDING_ANNOTATIONS_TO_HEAD parse_class_body(true); @@ -709,6 +743,25 @@ void GDScriptParser::parse_program() { clear_unused_annotations(); } +Ref<GDScriptParserRef> GDScriptParser::get_depended_parser_for(const String &p_path) { + Ref<GDScriptParserRef> ref; + if (depended_parsers.has(p_path)) { + ref = depended_parsers[p_path]; + } else { + Error err = OK; + ref = GDScriptCache::get_parser(p_path, GDScriptParserRef::EMPTY, err, script_path); + if (ref.is_valid()) { + depended_parsers[p_path] = ref; + } + } + + return ref; +} + +const HashMap<String, Ref<GDScriptParserRef>> &GDScriptParser::get_depended_parsers() { + return depended_parsers; +} + GDScriptParser::ClassNode *GDScriptParser::find_class(const String &p_qualified_name) const { String first = p_qualified_name.get_slice("::", 0); @@ -1571,15 +1624,17 @@ GDScriptParser::AnnotationNode *GDScriptParser::parse_annotation(uint32_t p_vali valid = false; } - annotation->info = &valid_annotations[annotation->name]; + if (valid) { + annotation->info = &valid_annotations[annotation->name]; - if (!annotation->applies_to(p_valid_targets)) { - if (annotation->applies_to(AnnotationInfo::SCRIPT)) { - push_error(vformat(R"(Annotation "%s" must be at the top of the script, before "extends" and "class_name".)", annotation->name)); - } else { - push_error(vformat(R"(Annotation "%s" is not allowed in this level.)", annotation->name)); + if (!annotation->applies_to(p_valid_targets)) { + if (annotation->applies_to(AnnotationInfo::SCRIPT)) { + push_error(vformat(R"(Annotation "%s" must be at the top of the script, before "extends" and "class_name".)", annotation->name)); + } else { + push_error(vformat(R"(Annotation "%s" is not allowed in this level.)", annotation->name)); + } + valid = false; } - valid = false; } if (check(GDScriptTokenizer::Token::PARENTHESIS_OPEN)) { @@ -1587,23 +1642,29 @@ GDScriptParser::AnnotationNode *GDScriptParser::parse_annotation(uint32_t p_vali advance(); // Arguments. push_completion_call(annotation); - make_completion_context(COMPLETION_ANNOTATION_ARGUMENTS, annotation, 0, true); int argument_index = 0; do { + make_completion_context(COMPLETION_ANNOTATION_ARGUMENTS, annotation, argument_index); + set_last_completion_call_arg(argument_index); if (check(GDScriptTokenizer::Token::PARENTHESIS_CLOSE)) { // Allow for trailing comma. break; } - make_completion_context(COMPLETION_ANNOTATION_ARGUMENTS, annotation, argument_index, true); - set_last_completion_call_arg(argument_index++); ExpressionNode *argument = parse_expression(false); + if (argument == nullptr) { push_error("Expected expression as the annotation argument."); valid = false; - continue; + } else { + annotation->arguments.push_back(argument); + + if (argument->type == Node::LITERAL) { + override_completion_context(argument, COMPLETION_ANNOTATION_ARGUMENTS, annotation, argument_index); + } } - annotation->arguments.push_back(argument); + + argument_index++; } while (match(GDScriptTokenizer::Token::COMMA) && !is_at_end()); pop_multiline(); @@ -1864,6 +1925,10 @@ GDScriptParser::Node *GDScriptParser::parse_statement() { case Node::CALL: // Fine. break; + case Node::PRELOAD: + // `preload` is a function-like keyword. + push_warning(expression, GDScriptWarning::RETURN_VALUE_DISCARDED, "preload"); + break; case Node::LAMBDA: // Standalone lambdas can't be used, so make this an error. push_error("Standalone lambdas cannot be accessed. Consider assigning it to a variable.", expression); @@ -2270,28 +2335,31 @@ GDScriptParser::PatternNode *GDScriptParser::parse_match_pattern(PatternNode *p_ break; case GDScriptTokenizer::Token::BRACKET_OPEN: { // Array. + push_multiline(true); advance(); pattern->pattern_type = PatternNode::PT_ARRAY; - - if (!check(GDScriptTokenizer::Token::BRACKET_CLOSE)) { - do { - PatternNode *sub_pattern = parse_match_pattern(p_root_pattern != nullptr ? p_root_pattern : pattern); - if (sub_pattern == nullptr) { - continue; - } - if (pattern->rest_used) { - push_error(R"(The ".." pattern must be the last element in the pattern array.)"); - } else if (sub_pattern->pattern_type == PatternNode::PT_REST) { - pattern->rest_used = true; - } - pattern->array.push_back(sub_pattern); - } while (match(GDScriptTokenizer::Token::COMMA)); - } + do { + if (is_at_end() || check(GDScriptTokenizer::Token::BRACKET_CLOSE)) { + break; + } + PatternNode *sub_pattern = parse_match_pattern(p_root_pattern != nullptr ? p_root_pattern : pattern); + if (sub_pattern == nullptr) { + continue; + } + if (pattern->rest_used) { + push_error(R"(The ".." pattern must be the last element in the pattern array.)"); + } else if (sub_pattern->pattern_type == PatternNode::PT_REST) { + pattern->rest_used = true; + } + pattern->array.push_back(sub_pattern); + } while (match(GDScriptTokenizer::Token::COMMA)); consume(GDScriptTokenizer::Token::BRACKET_CLOSE, R"(Expected "]" to close the array pattern.)"); + pop_multiline(); break; } case GDScriptTokenizer::Token::BRACE_OPEN: { // Dictionary. + push_multiline(true); advance(); pattern->pattern_type = PatternNode::PT_DICTIONARY; do { @@ -2334,6 +2402,7 @@ GDScriptParser::PatternNode *GDScriptParser::parse_match_pattern(PatternNode *p_ } } while (match(GDScriptTokenizer::Token::COMMA)); consume(GDScriptTokenizer::Token::BRACE_CLOSE, R"(Expected "}" to close the dictionary pattern.)"); + pop_multiline(); break; } default: { @@ -2411,7 +2480,7 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_precedence(Precedence p_pr } // Completion can appear whenever an expression is expected. - make_completion_context(COMPLETION_IDENTIFIER, nullptr); + make_completion_context(COMPLETION_IDENTIFIER, nullptr, -1, false); GDScriptTokenizer::Token token = current; GDScriptTokenizer::Token::Type token_type = token.type; @@ -2428,8 +2497,17 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_precedence(Precedence p_pr advance(); // Only consume the token if there's a valid rule. + // After a token was consumed, update the completion context regardless of a previously set context. + ExpressionNode *previous_operand = (this->*prefix_rule)(nullptr, p_can_assign); +#ifdef TOOLS_ENABLED + // HACK: We can't create a context in parse_identifier since it is used in places were we don't want completion. + if (previous_operand != nullptr && previous_operand->type == GDScriptParser::Node::IDENTIFIER && prefix_rule == static_cast<ParseFunction>(&GDScriptParser::parse_identifier)) { + make_completion_context(COMPLETION_IDENTIFIER, previous_operand); + } +#endif + while (p_precedence <= get_rule(current.type)->precedence) { if (previous_operand == nullptr || (p_stop_on_assign && current.type == GDScriptTokenizer::Token::EQUAL) || lambda_ended) { return previous_operand; @@ -2528,8 +2606,11 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_literal(ExpressionNode *p_ } LiteralNode *literal = alloc_node<LiteralNode>(); - complete_extents(literal); literal->value = previous.literal; + reset_extents(literal, p_previous_operand); + update_extents(literal); + make_completion_context(COMPLETION_NONE, literal, -1); + complete_extents(literal); return literal; } @@ -2769,10 +2850,6 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_assignment(ExpressionNode return parse_expression(false); // Return the following expression. } -#ifdef DEBUG_ENABLED - VariableNode *source_variable = nullptr; -#endif - switch (p_previous_operand->type) { case Node::IDENTIFIER: { #ifdef DEBUG_ENABLED @@ -2781,8 +2858,6 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_assignment(ExpressionNode IdentifierNode *id = static_cast<IdentifierNode *>(p_previous_operand); switch (id->source) { case IdentifierNode::LOCAL_VARIABLE: - - source_variable = id->variable_source; id->variable_source->usages--; break; case IdentifierNode::LOCAL_CONSTANT: @@ -2813,16 +2888,10 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_assignment(ExpressionNode update_extents(assignment); make_completion_context(COMPLETION_ASSIGN, assignment); -#ifdef DEBUG_ENABLED - bool has_operator = true; -#endif switch (previous.type) { case GDScriptTokenizer::Token::EQUAL: assignment->operation = AssignmentNode::OP_NONE; assignment->variant_op = Variant::OP_MAX; -#ifdef DEBUG_ENABLED - has_operator = false; -#endif break; case GDScriptTokenizer::Token::PLUS_EQUAL: assignment->operation = AssignmentNode::OP_ADDITION; @@ -2873,21 +2942,16 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_assignment(ExpressionNode } assignment->assignee = p_previous_operand; assignment->assigned_value = parse_expression(false); +#ifdef TOOLS_ENABLED + if (assignment->assigned_value != nullptr && assignment->assigned_value->type == GDScriptParser::Node::IDENTIFIER) { + override_completion_context(assignment->assigned_value, COMPLETION_ASSIGN, assignment); + } +#endif if (assignment->assigned_value == nullptr) { push_error(R"(Expected an expression after "=".)"); } complete_extents(assignment); -#ifdef DEBUG_ENABLED - if (source_variable != nullptr) { - if (has_operator && source_variable->assignments == 0) { - push_warning(assignment, GDScriptWarning::UNASSIGNED_VARIABLE_OP_ASSIGN, source_variable->identifier->name); - } - - source_variable->assignments += 1; - } -#endif - return assignment; } @@ -3046,12 +3110,12 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_attribute(ExpressionNode * const IdentifierNode *id = static_cast<const IdentifierNode *>(p_previous_operand); Variant::Type builtin_type = get_builtin_type(id->name); if (builtin_type < Variant::VARIANT_MAX) { - make_completion_context(COMPLETION_BUILT_IN_TYPE_CONSTANT_OR_STATIC_METHOD, builtin_type, true); + make_completion_context(COMPLETION_BUILT_IN_TYPE_CONSTANT_OR_STATIC_METHOD, builtin_type); is_builtin = true; } } if (!is_builtin) { - make_completion_context(COMPLETION_ATTRIBUTE, attribute, -1, true); + make_completion_context(COMPLETION_ATTRIBUTE, attribute, -1); } } @@ -3082,6 +3146,12 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_subscript(ExpressionNode * subscript->base = p_previous_operand; subscript->index = parse_expression(false); +#ifdef TOOLS_ENABLED + if (subscript->index != nullptr && subscript->index->type == Node::LITERAL) { + override_completion_context(subscript->index, COMPLETION_SUBSCRIPT, subscript); + } +#endif + if (subscript->index == nullptr) { push_error(R"(Expected expression after "[".)"); } @@ -3176,23 +3246,24 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_call(ExpressionNode *p_pre push_completion_call(call); int argument_index = 0; do { - make_completion_context(ct, call, argument_index++, true); + make_completion_context(ct, call, argument_index); if (check(GDScriptTokenizer::Token::PARENTHESIS_CLOSE)) { // Allow for trailing comma. break; } - bool use_identifier_completion = current.cursor_place == GDScriptTokenizerText::CURSOR_END || current.cursor_place == GDScriptTokenizerText::CURSOR_MIDDLE; ExpressionNode *argument = parse_expression(false); if (argument == nullptr) { push_error(R"(Expected expression as the function argument.)"); } else { call->arguments.push_back(argument); - if (argument->type == Node::IDENTIFIER && use_identifier_completion) { - completion_context.type = COMPLETION_IDENTIFIER; + if (argument->type == Node::LITERAL) { + override_completion_context(argument, ct, call, argument_index); } } + ct = COMPLETION_CALL_ARGUMENTS; + argument_index++; } while (match(GDScriptTokenizer::Token::COMMA)); pop_completion_call(); @@ -3204,6 +3275,9 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_call(ExpressionNode *p_pre } GDScriptParser::ExpressionNode *GDScriptParser::parse_get_node(ExpressionNode *p_previous_operand, bool p_can_assign) { + // We want code completion after a DOLLAR even if the current code is invalid. + make_completion_context(COMPLETION_GET_NODE, nullptr, -1); + if (!current.is_node_name() && !check(GDScriptTokenizer::Token::LITERAL) && !check(GDScriptTokenizer::Token::SLASH) && !check(GDScriptTokenizer::Token::PERCENT)) { push_error(vformat(R"(Expected node path as string or identifier after "%s".)", previous.get_name())); return nullptr; @@ -3503,7 +3577,7 @@ GDScriptParser::TypeNode *GDScriptParser::parse_type(bool p_allow_void) { type->type_chain.push_back(type_element); if (match(GDScriptTokenizer::Token::BRACKET_OPEN)) { - // Typed collection (like Array[int]). + // Typed collection (like Array[int], Dictionary[String, int]). bool first_pass = true; do { TypeNode *container_type = parse_type(false); // Don't allow void for element type. @@ -4042,7 +4116,7 @@ bool GDScriptParser::validate_annotation_arguments(AnnotationNode *p_annotation) return true; } -bool GDScriptParser::tool_annotation(const AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class) { +bool GDScriptParser::tool_annotation(AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class) { #ifdef DEBUG_ENABLED if (_is_tool) { push_error(R"("@tool" annotation can only be used once.)", p_annotation); @@ -4053,7 +4127,7 @@ bool GDScriptParser::tool_annotation(const AnnotationNode *p_annotation, Node *p return true; } -bool GDScriptParser::icon_annotation(const AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class) { +bool GDScriptParser::icon_annotation(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); @@ -4084,11 +4158,12 @@ bool GDScriptParser::icon_annotation(const AnnotationNode *p_annotation, Node *p return true; } -bool GDScriptParser::onready_annotation(const AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class) { +bool GDScriptParser::onready_annotation(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); + return false; } VariableNode *variable = static_cast<VariableNode *>(p_target); @@ -4133,6 +4208,9 @@ static String _get_annotation_error_string(const StringName &p_annotation_name, case Variant::COLOR: types.push_back("PackedColorArray"); break; + case Variant::VECTOR4: + types.push_back("PackedVector4Array"); + break; default: break; } @@ -4154,8 +4232,66 @@ static String _get_annotation_error_string(const StringName &p_annotation_name, return vformat(R"("%s" annotation requires a variable of type %s, but type "%s" was given instead.)", p_annotation_name, string, p_provided_type.to_string()); } +static StringName _find_narrowest_native_or_global_class(const GDScriptParser::DataType &p_type) { + switch (p_type.kind) { + case GDScriptParser::DataType::NATIVE: { + if (p_type.is_meta_type) { + return Object::get_class_static(); // `GDScriptNativeClass` is not an exposed class. + } + return p_type.native_type; + } break; + case GDScriptParser::DataType::SCRIPT: { + Ref<Script> script; + if (p_type.script_type.is_valid()) { + script = p_type.script_type; + } else { + script = ResourceLoader::load(p_type.script_path, SNAME("Script")); + } + + if (p_type.is_meta_type) { + return script.is_valid() ? script->get_class() : Script::get_class_static(); + } + if (script.is_null()) { + return p_type.native_type; + } + if (script->get_global_name() != StringName()) { + return script->get_global_name(); + } + + Ref<Script> base_script = script->get_base_script(); + if (base_script.is_null()) { + return script->get_instance_base_type(); + } + + GDScriptParser::DataType base_type; + base_type.kind = GDScriptParser::DataType::SCRIPT; + base_type.builtin_type = Variant::OBJECT; + base_type.native_type = base_script->get_instance_base_type(); + base_type.script_type = base_script; + base_type.script_path = base_script->get_path(); + + return _find_narrowest_native_or_global_class(base_type); + } break; + case GDScriptParser::DataType::CLASS: { + if (p_type.is_meta_type) { + return GDScript::get_class_static(); + } + if (p_type.class_type == nullptr) { + return p_type.native_type; + } + if (p_type.class_type->get_global_name() != StringName()) { + return p_type.class_type->get_global_name(); + } + return _find_narrowest_native_or_global_class(p_type.class_type->base_type); + } break; + default: { + ERR_FAIL_V(StringName()); + } break; + } +} + template <PropertyHint t_hint, Variant::Type t_type> -bool GDScriptParser::export_annotations(const AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class) { +bool GDScriptParser::export_annotations(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); @@ -4258,6 +4394,14 @@ bool GDScriptParser::export_annotations(const AnnotationNode *p_annotation, Node export_type.type_source = variable->datatype.type_source; } + bool is_dict = false; + if (export_type.builtin_type == Variant::DICTIONARY && export_type.has_container_element_types()) { + is_dict = true; + DataType inner_type = export_type.get_container_element_type_or_variant(1); + export_type = export_type.get_container_element_type_or_variant(0); + export_type.set_container_element_type(0, inner_type); // Store earlier extracted value within key to separately parse after. + } + bool use_default_variable_type_check = true; if (p_annotation->name == SNAME("@export_range")) { @@ -4285,64 +4429,37 @@ bool GDScriptParser::export_annotations(const AnnotationNode *p_annotation, Node } if (export_type.is_variant() || export_type.has_no_type()) { - push_error(R"(Cannot use simple "@export" annotation because the type of the initialized value can't be inferred.)", p_annotation); - return false; + if (is_dict) { + // Dictionary allowed to have a variant key/value. + export_type.kind = GDScriptParser::DataType::BUILTIN; + } else { + push_error(R"(Cannot use simple "@export" annotation because the type of the initialized value can't be inferred.)", p_annotation); + return false; + } } switch (export_type.kind) { case GDScriptParser::DataType::BUILTIN: variable->export_info.type = export_type.builtin_type; variable->export_info.hint = PROPERTY_HINT_NONE; - variable->export_info.hint_string = Variant::get_type_name(export_type.builtin_type); + variable->export_info.hint_string = String(); break; case GDScriptParser::DataType::NATIVE: + case GDScriptParser::DataType::SCRIPT: + case GDScriptParser::DataType::CLASS: { + const StringName class_name = _find_narrowest_native_or_global_class(export_type); if (ClassDB::is_parent_class(export_type.native_type, SNAME("Resource"))) { variable->export_info.type = Variant::OBJECT; variable->export_info.hint = PROPERTY_HINT_RESOURCE_TYPE; - variable->export_info.hint_string = export_type.native_type; - } else if (ClassDB::is_parent_class(export_type.native_type, SNAME("Node"))) { - variable->export_info.type = Variant::OBJECT; - variable->export_info.hint = PROPERTY_HINT_NODE_TYPE; - variable->export_info.hint_string = export_type.native_type; - } else { - push_error(R"(Export type can only be built-in, a resource, a node, or an enum.)", p_annotation); - return false; - } - break; - case GDScriptParser::DataType::CLASS: - if (ClassDB::is_parent_class(export_type.native_type, SNAME("Resource"))) { - variable->export_info.type = Variant::OBJECT; - variable->export_info.hint = PROPERTY_HINT_RESOURCE_TYPE; - variable->export_info.hint_string = export_type.to_string(); + variable->export_info.hint_string = class_name; } else if (ClassDB::is_parent_class(export_type.native_type, SNAME("Node"))) { variable->export_info.type = Variant::OBJECT; variable->export_info.hint = PROPERTY_HINT_NODE_TYPE; - variable->export_info.hint_string = export_type.to_string(); + variable->export_info.hint_string = class_name; } else { push_error(R"(Export type can only be built-in, a resource, a node, or an enum.)", p_annotation); return false; } - - break; - case GDScriptParser::DataType::SCRIPT: { - StringName class_name; - StringName native_base; - if (export_type.script_type.is_valid()) { - class_name = export_type.script_type->get_language()->get_global_class_name(export_type.script_type->get_path()); - native_base = export_type.script_type->get_instance_base_type(); - } - if (class_name == StringName()) { - Ref<Script> script = ResourceLoader::load(export_type.script_path, SNAME("Script")); - if (script.is_valid()) { - class_name = script->get_language()->get_global_class_name(export_type.script_path); - native_base = script->get_instance_base_type(); - } - } - if (class_name != StringName() && native_base != StringName() && ClassDB::is_parent_class(native_base, SNAME("Resource"))) { - variable->export_info.type = Variant::OBJECT; - variable->export_info.hint = PROPERTY_HINT_RESOURCE_TYPE; - variable->export_info.hint_string = class_name; - } } break; case GDScriptParser::DataType::ENUM: { if (export_type.is_meta_type) { @@ -4378,6 +4495,90 @@ bool GDScriptParser::export_annotations(const AnnotationNode *p_annotation, 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()), p_annotation); return false; } + + if (is_dict) { + String key_prefix = itos(variable->export_info.type); + if (variable->export_info.hint) { + key_prefix += "/" + itos(variable->export_info.hint); + } + key_prefix += ":" + variable->export_info.hint_string; + + // Now parse value. + export_type = export_type.get_container_element_type(0); + + if (export_type.is_variant() || export_type.has_no_type()) { + export_type.kind = GDScriptParser::DataType::BUILTIN; + } + switch (export_type.kind) { + case GDScriptParser::DataType::BUILTIN: + variable->export_info.type = export_type.builtin_type; + variable->export_info.hint = PROPERTY_HINT_NONE; + variable->export_info.hint_string = String(); + break; + case GDScriptParser::DataType::NATIVE: + case GDScriptParser::DataType::SCRIPT: + case GDScriptParser::DataType::CLASS: { + const StringName class_name = _find_narrowest_native_or_global_class(export_type); + if (ClassDB::is_parent_class(export_type.native_type, SNAME("Resource"))) { + variable->export_info.type = Variant::OBJECT; + variable->export_info.hint = PROPERTY_HINT_RESOURCE_TYPE; + variable->export_info.hint_string = class_name; + } else if (ClassDB::is_parent_class(export_type.native_type, SNAME("Node"))) { + variable->export_info.type = Variant::OBJECT; + variable->export_info.hint = PROPERTY_HINT_NODE_TYPE; + variable->export_info.hint_string = class_name; + } else { + push_error(R"(Export type can only be built-in, a resource, a node, or an enum.)", p_annotation); + return false; + } + } break; + case GDScriptParser::DataType::ENUM: { + 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(); + } + + 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.)", p_annotation); + 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()), p_annotation); + return false; + } + + String value_prefix = itos(variable->export_info.type); + if (variable->export_info.hint) { + value_prefix += "/" + itos(variable->export_info.hint); + } + value_prefix += ":" + variable->export_info.hint_string; + + variable->export_info.type = Variant::DICTIONARY; + variable->export_info.hint = PROPERTY_HINT_TYPE_STRING; + variable->export_info.hint_string = key_prefix + ";" + value_prefix; + variable->export_info.usage = PROPERTY_USAGE_DEFAULT; + variable->export_info.class_name = StringName(); + } } else if (p_annotation->name == SNAME("@export_enum")) { use_default_variable_type_check = false; @@ -4394,12 +4595,6 @@ bool GDScriptParser::export_annotations(const AnnotationNode *p_annotation, Node push_error(_get_annotation_error_string(p_annotation->name, expected_types, variable->get_datatype()), p_annotation); return false; } - } else if (p_annotation->name == SNAME("@export_storage")) { - use_default_variable_type_check = false; // Can be applied to a variable of any type. - - // Save the info because the compiler uses export info for overwriting member info. - variable->export_info = export_type.to_property_info(variable->identifier->name); - variable->export_info.usage |= PROPERTY_USAGE_STORAGE; } if (use_default_variable_type_check) { @@ -4419,19 +4614,50 @@ bool GDScriptParser::export_annotations(const AnnotationNode *p_annotation, Node if (variable->export_info.hint) { hint_prefix += "/" + itos(variable->export_info.hint); } + variable->export_info.type = original_export_type_builtin; variable->export_info.hint = PROPERTY_HINT_TYPE_STRING; variable->export_info.hint_string = hint_prefix + ":" + variable->export_info.hint_string; - variable->export_info.type = original_export_type_builtin; + variable->export_info.usage = PROPERTY_USAGE_DEFAULT; + variable->export_info.class_name = StringName(); } return true; } -bool GDScriptParser::export_custom_annotation(const AnnotationNode *p_annotation, Node *p_node, ClassNode *p_class) { - ERR_FAIL_COND_V_MSG(p_node->type != Node::VARIABLE, false, vformat(R"("%s" annotation can only be applied to variables.)", p_annotation->name)); +// For `@export_storage` and `@export_custom`, there is no need to check the variable type, argument values, +// or handle array exports in a special way, so they are implemented as separate methods. + +bool GDScriptParser::export_storage_annotation(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)); + + 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; + } + if (variable->exported) { + push_error(vformat(R"(Annotation "%s" cannot be used with another "@export" annotation.)", p_annotation->name), p_annotation); + return false; + } + + variable->exported = true; + + // Save the info because the compiler uses export info for overwriting member info. + variable->export_info = variable->get_datatype().to_property_info(variable->identifier->name); + variable->export_info.usage |= PROPERTY_USAGE_STORAGE; + + return true; +} + +bool GDScriptParser::export_custom_annotation(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_COND_V_MSG(p_annotation->resolved_arguments.size() < 2, false, R"(Annotation "@export_custom" requires 2 arguments.)"); - 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; + } if (variable->exported) { push_error(vformat(R"(Annotation "%s" cannot be used with another "@export" annotation.)", p_annotation->name), p_annotation); return false; @@ -4451,32 +4677,74 @@ bool GDScriptParser::export_custom_annotation(const AnnotationNode *p_annotation return true; } -template <PropertyUsageFlags t_usage> -bool GDScriptParser::export_group_annotations(const AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class) { - AnnotationNode *annotation = const_cast<AnnotationNode *>(p_annotation); +bool GDScriptParser::export_tool_button_annotation(AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class) { +#ifdef TOOLS_ENABLED + 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_COND_V(p_annotation->resolved_arguments.is_empty(), false); + + if (!is_tool()) { + push_error(R"(Tool buttons can only be used in tool scripts (add "@tool" to the top of the script).)", p_annotation); + return false; + } + + VariableNode *variable = static_cast<VariableNode *>(p_target); - if (annotation->resolved_arguments.is_empty()) { + if (variable->is_static) { + push_error(vformat(R"(Annotation "%s" cannot be applied to a static variable.)", p_annotation->name), p_annotation); + return false; + } + if (variable->exported) { + push_error(vformat(R"(Annotation "%s" cannot be used with another "@export" annotation.)", p_annotation->name), p_annotation); return false; } - annotation->export_info.name = annotation->resolved_arguments[0]; + const DataType variable_type = variable->get_datatype(); + if (!variable_type.is_variant() && variable_type.is_hard_type()) { + if (variable_type.kind != DataType::BUILTIN || variable_type.builtin_type != Variant::CALLABLE) { + push_error(vformat(R"("@export_tool_button" annotation requires a variable of type "Callable", but type "%s" was given instead.)", variable_type.to_string()), p_annotation); + return false; + } + } + + variable->exported = true; + + // Build the hint string (format: `<text>[,<icon>]`). + String hint_string = p_annotation->resolved_arguments[0].operator String(); // Button text. + if (p_annotation->resolved_arguments.size() > 1) { + hint_string += "," + p_annotation->resolved_arguments[1].operator String(); // Button icon. + } + + variable->export_info.type = Variant::CALLABLE; + variable->export_info.hint = PROPERTY_HINT_TOOL_BUTTON; + variable->export_info.hint_string = hint_string; + variable->export_info.usage = PROPERTY_USAGE_EDITOR; +#endif // TOOLS_ENABLED + + return true; // Only available in editor. +} + +template <PropertyUsageFlags t_usage> +bool GDScriptParser::export_group_annotations(AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class) { + ERR_FAIL_COND_V(p_annotation->resolved_arguments.is_empty(), false); + + p_annotation->export_info.name = p_annotation->resolved_arguments[0]; switch (t_usage) { case PROPERTY_USAGE_CATEGORY: { - annotation->export_info.usage = t_usage; + p_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]; + p_annotation->export_info.usage = t_usage; + if (p_annotation->resolved_arguments.size() == 2) { + p_annotation->export_info.hint_string = p_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]; + p_annotation->export_info.usage = t_usage; + if (p_annotation->resolved_arguments.size() == 2) { + p_annotation->export_info.hint_string = p_annotation->resolved_arguments[1]; } } break; } @@ -4484,7 +4752,7 @@ bool GDScriptParser::export_group_annotations(const AnnotationNode *p_annotation return true; } -bool GDScriptParser::warning_annotations(const AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class) { +bool GDScriptParser::warning_annotations(AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class) { #ifndef DEBUG_ENABLED // Only available in debug builds. return true; @@ -4559,7 +4827,7 @@ bool GDScriptParser::warning_annotations(const AnnotationNode *p_annotation, Nod #endif // DEBUG_ENABLED } -bool GDScriptParser::rpc_annotation(const AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class) { +bool GDScriptParser::rpc_annotation(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_target); @@ -4620,7 +4888,7 @@ bool GDScriptParser::rpc_annotation(const AnnotationNode *p_annotation, Node *p_ return true; } -bool GDScriptParser::static_unload_annotation(const AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class) { +bool GDScriptParser::static_unload_annotation(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 *class_node = static_cast<ClassNode *>(p_target); if (class_node->annotated_static_unload) { @@ -4676,7 +4944,10 @@ String GDScriptParser::DataType::to_string() const { return "null"; } if (builtin_type == Variant::ARRAY && has_container_element_type(0)) { - return vformat("Array[%s]", container_element_types[0].to_string()); + return vformat("Array[%s]", get_container_element_type(0).to_string()); + } + if (builtin_type == Variant::DICTIONARY && has_container_element_types()) { + return vformat("Dictionary[%s, %s]", get_container_element_type_or_variant(0).to_string(), get_container_element_type_or_variant(1).to_string()); } return Variant::get_type_name(builtin_type); case NATIVE: @@ -4691,9 +4962,9 @@ String GDScriptParser::DataType::to_string() const { return class_type->fqcn; case SCRIPT: { if (is_meta_type) { - return script_type != nullptr ? script_type->get_class_name().operator String() : ""; + return script_type.is_valid() ? script_type->get_class_name().operator String() : ""; } - String name = script_type != nullptr ? script_type->get_name() : ""; + String name = script_type.is_valid() ? script_type->get_name() : ""; if (!name.is_empty()) { return name; } @@ -4765,6 +5036,72 @@ PropertyInfo GDScriptParser::DataType::to_property_info(const String &p_name) co case UNRESOLVED: break; } + } else if (builtin_type == Variant::DICTIONARY && has_container_element_types()) { + const DataType key_type = get_container_element_type_or_variant(0); + const DataType value_type = get_container_element_type_or_variant(1); + if ((key_type.kind == VARIANT && value_type.kind == VARIANT) || key_type.kind == RESOLVING || + key_type.kind == UNRESOLVED || value_type.kind == RESOLVING || value_type.kind == UNRESOLVED) { + break; + } + String key_hint, value_hint; + switch (key_type.kind) { + case BUILTIN: + key_hint = Variant::get_type_name(key_type.builtin_type); + break; + case NATIVE: + key_hint = key_type.native_type; + break; + case SCRIPT: + if (key_type.script_type.is_valid() && key_type.script_type->get_global_name() != StringName()) { + key_hint = key_type.script_type->get_global_name(); + } else { + key_hint = key_type.native_type; + } + break; + case CLASS: + if (key_type.class_type != nullptr && key_type.class_type->get_global_name() != StringName()) { + key_hint = key_type.class_type->get_global_name(); + } else { + key_hint = key_type.native_type; + } + break; + case ENUM: + key_hint = String(key_type.native_type).replace("::", "."); + break; + default: + key_hint = "Variant"; + break; + } + switch (value_type.kind) { + case BUILTIN: + value_hint = Variant::get_type_name(value_type.builtin_type); + break; + case NATIVE: + value_hint = value_type.native_type; + break; + case SCRIPT: + if (value_type.script_type.is_valid() && value_type.script_type->get_global_name() != StringName()) { + value_hint = value_type.script_type->get_global_name(); + } else { + value_hint = value_type.native_type; + } + break; + case CLASS: + if (value_type.class_type != nullptr && value_type.class_type->get_global_name() != StringName()) { + value_hint = value_type.class_type->get_global_name(); + } else { + value_hint = value_type.native_type; + } + break; + case ENUM: + value_hint = String(value_type.native_type).replace("::", "."); + break; + default: + value_hint = "Variant"; + break; + } + result.hint = PROPERTY_HINT_DICTIONARY_TYPE; + result.hint_string = key_hint + ";" + value_hint; } break; case NATIVE: @@ -4831,6 +5168,8 @@ static Variant::Type _variant_type_to_typed_array_element_type(Variant::Type p_t return Variant::VECTOR3; case Variant::PACKED_COLOR_ARRAY: return Variant::COLOR; + case Variant::PACKED_VECTOR4_ARRAY: + return Variant::VECTOR4; default: return Variant::NIL; } @@ -4847,6 +5186,50 @@ GDScriptParser::DataType GDScriptParser::DataType::get_typed_container_type() co return type; } +bool GDScriptParser::DataType::can_reference(const GDScriptParser::DataType &p_other) const { + if (p_other.is_meta_type) { + return false; + } else if (builtin_type != p_other.builtin_type) { + return false; + } else if (builtin_type != Variant::OBJECT) { + return true; + } + + if (native_type == StringName()) { + return true; + } else if (p_other.native_type == StringName()) { + return false; + } else if (native_type != p_other.native_type && !ClassDB::is_parent_class(p_other.native_type, native_type)) { + return false; + } + + Ref<Script> script = script_type; + if (kind == GDScriptParser::DataType::CLASS && script.is_null()) { + Error err = OK; + Ref<GDScript> scr = GDScriptCache::get_shallow_script(script_path, err); + ERR_FAIL_COND_V_MSG(err, false, vformat(R"(Error while getting cache for script "%s".)", script_path)); + script.reference_ptr(scr->find_class(class_type->fqcn)); + } + + Ref<Script> script_other = p_other.script_type; + if (p_other.kind == GDScriptParser::DataType::CLASS && script_other.is_null()) { + Error err = OK; + Ref<GDScript> scr = GDScriptCache::get_shallow_script(p_other.script_path, err); + ERR_FAIL_COND_V_MSG(err, false, vformat(R"(Error while getting cache for script "%s".)", p_other.script_path)); + script_other.reference_ptr(scr->find_class(p_other.class_type->fqcn)); + } + + if (script.is_null()) { + return true; + } else if (script_other.is_null()) { + return false; + } else if (script != script_other && !script_other->inherits_script(script)) { + return false; + } + + return true; +} + void GDScriptParser::complete_extents(Node *p_node) { while (!nodes_in_progress.is_empty() && nodes_in_progress.back()->get() != p_node) { ERR_PRINT("Parser bug: Mismatch in extents tracking stack."); |