diff options
Diffstat (limited to 'modules/gdscript/gdscript_parser.cpp')
-rw-r--r-- | modules/gdscript/gdscript_parser.cpp | 295 |
1 files changed, 262 insertions, 33 deletions
diff --git a/modules/gdscript/gdscript_parser.cpp b/modules/gdscript/gdscript_parser.cpp index ecef852b4b..65aa150be3 100644 --- a/modules/gdscript/gdscript_parser.cpp +++ b/modules/gdscript/gdscript_parser.cpp @@ -249,7 +249,7 @@ void GDScriptParser::override_completion_context(const Node *p_for_node, Complet if (!for_completion) { return; } - if (completion_context.node != p_for_node) { + if (p_for_node == nullptr || completion_context.node != p_for_node) { return; } CompletionContext context; @@ -260,11 +260,12 @@ void GDScriptParser::override_completion_context(const Node *p_for_node, Complet 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) { - if (!for_completion) { +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; } if (previous.cursor_place != GDScriptTokenizerText::CURSOR_MIDDLE && previous.cursor_place != GDScriptTokenizerText::CURSOR_END && current.cursor_place == GDScriptTokenizerText::CURSOR_NONE) { @@ -282,8 +283,8 @@ void GDScriptParser::make_completion_context(CompletionType p_type, Node *p_node completion_context = context; } -void GDScriptParser::make_completion_context(CompletionType p_type, Variant::Type p_builtin_type) { - if (!for_completion) { +void GDScriptParser::make_completion_context(CompletionType p_type, Variant::Type p_builtin_type, bool p_force) { + if (!for_completion || (!p_force && completion_context.type != COMPLETION_NONE)) { return; } if (previous.cursor_place != GDScriptTokenizerText::CURSOR_MIDDLE && previous.cursor_place != GDScriptTokenizerText::CURSOR_END && current.cursor_place == GDScriptTokenizerText::CURSOR_NONE) { @@ -2470,7 +2471,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; @@ -2487,8 +2488,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; @@ -2923,6 +2933,11 @@ 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 "=".)"); } @@ -3122,6 +3137,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 "[".)"); } @@ -3547,7 +3568,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. @@ -4086,7 +4107,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); @@ -4097,7 +4118,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); @@ -4128,7 +4149,7 @@ 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"))) { @@ -4261,7 +4282,7 @@ static StringName _find_narrowest_native_or_global_class(const GDScriptParser::D } 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); @@ -4364,6 +4385,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")) { @@ -4391,8 +4420,13 @@ 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) { @@ -4452,6 +4486,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; @@ -4500,7 +4618,7 @@ bool GDScriptParser::export_annotations(const AnnotationNode *p_annotation, Node // 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(const AnnotationNode *p_annotation, Node *p_node, ClassNode *p_class) { +bool GDScriptParser::export_storage_annotation(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)); VariableNode *variable = static_cast<VariableNode *>(p_node); @@ -4522,7 +4640,7 @@ bool GDScriptParser::export_storage_annotation(const AnnotationNode *p_annotatio return true; } -bool GDScriptParser::export_custom_annotation(const AnnotationNode *p_annotation, Node *p_node, ClassNode *p_class) { +bool GDScriptParser::export_custom_annotation(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)); ERR_FAIL_COND_V_MSG(p_annotation->resolved_arguments.size() < 2, false, R"(Annotation "@export_custom" requires 2 arguments.)"); @@ -4551,31 +4669,29 @@ bool GDScriptParser::export_custom_annotation(const AnnotationNode *p_annotation } 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); - - if (annotation->resolved_arguments.is_empty()) { +bool GDScriptParser::export_group_annotations(AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class) { + if (p_annotation->resolved_arguments.is_empty()) { return false; } - annotation->export_info.name = annotation->resolved_arguments[0]; + 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; } @@ -4583,7 +4699,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; @@ -4658,7 +4774,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); @@ -4719,7 +4835,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) { @@ -4775,7 +4891,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: @@ -4790,9 +4909,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; } @@ -4864,6 +4983,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: @@ -4948,6 +5133,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."); |