diff options
Diffstat (limited to 'modules/gdscript/gdscript_parser.cpp')
-rw-r--r-- | modules/gdscript/gdscript_parser.cpp | 636 |
1 files changed, 443 insertions, 193 deletions
diff --git a/modules/gdscript/gdscript_parser.cpp b/modules/gdscript/gdscript_parser.cpp index b61b469fbc..67f6b61f14 100644 --- a/modules/gdscript/gdscript_parser.cpp +++ b/modules/gdscript/gdscript_parser.cpp @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ @@ -31,14 +31,15 @@ #include "gdscript_parser.h" #include "core/config/project_settings.h" +#include "core/io/file_access.h" #include "core/io/resource_loader.h" #include "core/math/math_defs.h" -#include "core/os/file_access.h" #include "gdscript.h" #ifdef DEBUG_ENABLED #include "core/os/os.h" #include "core/string/string_builder.h" +#include "gdscript_warning.h" #endif // DEBUG_ENABLED #ifdef TOOLS_ENABLED @@ -61,9 +62,9 @@ Variant::Type GDScriptParser::get_builtin_type(const StringName &p_type) { builtin_types["Vector3i"] = Variant::VECTOR3I; builtin_types["AABB"] = Variant::AABB; builtin_types["Plane"] = Variant::PLANE; - builtin_types["Quat"] = Variant::QUAT; + builtin_types["Quaternion"] = Variant::QUATERNION; builtin_types["Basis"] = Variant::BASIS; - builtin_types["Transform"] = Variant::TRANSFORM; + builtin_types["Transform3D"] = Variant::TRANSFORM3D; builtin_types["Color"] = Variant::COLOR; builtin_types["RID"] = Variant::RID; builtin_types["Object"] = Variant::OBJECT; @@ -94,50 +95,15 @@ Variant::Type GDScriptParser::get_builtin_type(const StringName &p_type) { return Variant::VARIANT_MAX; } -// TODO: Move this to a central location (maybe core?). -static HashMap<StringName, StringName> underscore_map; -static const char *underscore_classes[] = { - "ClassDB", - "Directory", - "Engine", - "File", - "Geometry", - "GodotSharp", - "JSON", - "Marshalls", - "Mutex", - "OS", - "ResourceLoader", - "ResourceSaver", - "Semaphore", - "Thread", - "VisualScriptEditor", - nullptr, -}; -StringName GDScriptParser::get_real_class_name(const StringName &p_source) { - if (underscore_map.is_empty()) { - const char **class_name = underscore_classes; - while (*class_name != nullptr) { - underscore_map[*class_name] = String("_") + *class_name; - class_name++; - } - } - if (underscore_map.has(p_source)) { - return underscore_map[p_source]; - } - return p_source; -} - void GDScriptParser::cleanup() { builtin_types.clear(); - underscore_map.clear(); } void GDScriptParser::get_annotation_list(List<MethodInfo> *r_annotations) const { List<StringName> keys; valid_annotations.get_key_list(&keys); - for (const List<StringName>::Element *E = keys.front(); E != nullptr; E = E->next()) { - r_annotations->push_back(valid_annotations[E->get()].info); + for (const StringName &E : keys) { + r_annotations->push_back(valid_annotations[E].info); } } @@ -157,7 +123,6 @@ GDScriptParser::GDScriptParser() { register_annotation(MethodInfo("@export_multiline"), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_MULTILINE_TEXT, Variant::STRING>); register_annotation(MethodInfo("@export_placeholder"), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_PLACEHOLDER_TEXT, Variant::STRING>); register_annotation(MethodInfo("@export_range", { Variant::FLOAT, "min" }, { Variant::FLOAT, "max" }, { Variant::FLOAT, "step" }, { Variant::STRING, "slider1" }, { Variant::STRING, "slider2" }), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_RANGE, Variant::FLOAT>, 3); - register_annotation(MethodInfo("@export_exp_range", { Variant::FLOAT, "min" }, { Variant::FLOAT, "max" }, { Variant::FLOAT, "step" }, { Variant::STRING, "slider1" }, { Variant::STRING, "slider2" }), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_EXP_RANGE, Variant::FLOAT>, 3); register_annotation(MethodInfo("@export_exp_easing", { Variant::STRING, "hint1" }, { Variant::STRING, "hint2" }), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_EXP_EASING, Variant::FLOAT>, 2); register_annotation(MethodInfo("@export_color_no_alpha"), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_COLOR_NO_ALPHA, Variant::COLOR>); register_annotation(MethodInfo("@export_node_path", { Variant::STRING, "type" }), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_NODE_PATH_VALID_TYPES, Variant::NODE_PATH>, 1, true); @@ -168,14 +133,9 @@ GDScriptParser::GDScriptParser() { register_annotation(MethodInfo("@export_flags_3d_render"), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_LAYERS_3D_RENDER, Variant::INT>); register_annotation(MethodInfo("@export_flags_3d_physics"), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_LAYERS_3D_PHYSICS, Variant::INT>); register_annotation(MethodInfo("@export_flags_3d_navigation"), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_LAYERS_3D_NAVIGATION, Variant::INT>); + register_annotation(MethodInfo("@warning_ignore", { Variant::STRING, "warning" }), AnnotationInfo::CLASS | AnnotationInfo::VARIABLE | AnnotationInfo::SIGNAL | AnnotationInfo::CONSTANT | AnnotationInfo::FUNCTION | AnnotationInfo::STATEMENT, &GDScriptParser::warning_annotations, 0, true); // Networking. - register_annotation(MethodInfo("@remote"), AnnotationInfo::VARIABLE | AnnotationInfo::FUNCTION, &GDScriptParser::network_annotations<MultiplayerAPI::RPC_MODE_REMOTE>); - register_annotation(MethodInfo("@master"), AnnotationInfo::VARIABLE | AnnotationInfo::FUNCTION, &GDScriptParser::network_annotations<MultiplayerAPI::RPC_MODE_MASTER>); - register_annotation(MethodInfo("@puppet"), AnnotationInfo::VARIABLE | AnnotationInfo::FUNCTION, &GDScriptParser::network_annotations<MultiplayerAPI::RPC_MODE_PUPPET>); - register_annotation(MethodInfo("@remotesync"), AnnotationInfo::VARIABLE | AnnotationInfo::FUNCTION, &GDScriptParser::network_annotations<MultiplayerAPI::RPC_MODE_REMOTESYNC>); - register_annotation(MethodInfo("@mastersync"), AnnotationInfo::VARIABLE | AnnotationInfo::FUNCTION, &GDScriptParser::network_annotations<MultiplayerAPI::RPC_MODE_MASTERSYNC>); - register_annotation(MethodInfo("@puppetsync"), AnnotationInfo::VARIABLE | AnnotationInfo::FUNCTION, &GDScriptParser::network_annotations<MultiplayerAPI::RPC_MODE_PUPPETSYNC>); - // TODO: Warning annotations. + register_annotation(MethodInfo("@rpc", { Variant::STRING, "mode" }, { Variant::STRING, "sync" }, { Variant::STRING, "transfer_mode" }, { Variant::INT, "transfer_channel" }), AnnotationInfo::FUNCTION, &GDScriptParser::network_annotations<Multiplayer::RPC_MODE_AUTHORITY>, 4, true); } GDScriptParser::~GDScriptParser() { @@ -237,6 +197,10 @@ void GDScriptParser::push_warning(const Node *p_source, GDScriptWarning::Code p_ return; } + if (ignored_warning_codes.has(p_code)) { + return; + } + String warn_name = GDScriptWarning::get_name_from_code((GDScriptWarning::Code)p_code).to_lower(); if (ignored_warnings.has(warn_name)) { return; @@ -254,7 +218,7 @@ void GDScriptParser::push_warning(const Node *p_source, GDScriptWarning::Code p_ warning.rightmost_column = p_source->rightmost_column; List<GDScriptWarning>::Element *before = nullptr; - for (List<GDScriptWarning>::Element *E = warnings.front(); E != nullptr; E = E->next()) { + for (List<GDScriptWarning>::Element *E = warnings.front(); E; E = E->next()) { if (E->get().start_line > warning.start_line) { break; } @@ -343,7 +307,7 @@ Error GDScriptParser::parse(const String &p_source_code, const String &p_script_ int tab_size = 4; #ifdef TOOLS_ENABLED if (EditorSettings::get_singleton()) { - tab_size = EditorSettings::get_singleton()->get_setting("text_editor/indent/size"); + tab_size = EditorSettings::get_singleton()->get_setting("text_editor/behavior/indent/size"); } #endif // TOOLS_ENABLED @@ -378,12 +342,29 @@ Error GDScriptParser::parse(const String &p_source_code, const String &p_script_ tokenizer.set_cursor_position(cursor_line, cursor_column); script_path = p_script_path; current = tokenizer.scan(); - // Avoid error as the first token. - while (current.type == GDScriptTokenizer::Token::ERROR) { - push_error(current.literal); + // Avoid error or newline as the first token. + // The latter can mess with the parser when opening files filled exclusively with comments and newlines. + while (current.type == GDScriptTokenizer::Token::ERROR || current.type == GDScriptTokenizer::Token::NEWLINE) { + if (current.type == GDScriptTokenizer::Token::ERROR) { + push_error(current.literal); + } current = tokenizer.scan(); } +#ifdef DEBUG_ENABLED + // Warn about parsing an empty script file: + if (current.type == GDScriptTokenizer::Token::TK_EOF) { + // Create a dummy Node for the warning, pointing to the very beginning of the file + Node *nd = alloc_node<PassNode>(); + nd->start_line = 1; + nd->start_column = 0; + nd->end_line = 1; + nd->leftmost_column = 0; + nd->rightmost_column = 0; + push_warning(nd, GDScriptWarning::EMPTY_FILE); + } +#endif + push_multiline(false); // Keep one for the whole parsing. parse_program(); pop_multiline(); @@ -538,7 +519,7 @@ void GDScriptParser::parse_program() { // Check for @tool annotation. AnnotationNode *annotation = parse_annotation(AnnotationInfo::SCRIPT | AnnotationInfo::CLASS_LEVEL); if (annotation != nullptr) { - if (annotation->name == "@tool") { + if (annotation->name == SNAME("@tool")) { // TODO: don't allow @tool anywhere else. (Should all script annotations be the first thing?). _is_tool = true; if (previous.type != GDScriptTokenizer::Token::NEWLINE) { @@ -592,7 +573,7 @@ void GDScriptParser::parse_program() { // Check for @icon annotation. AnnotationNode *annotation = parse_annotation(AnnotationInfo::SCRIPT | AnnotationInfo::CLASS_LEVEL); if (annotation != nullptr) { - if (annotation->name == "@icon") { + if (annotation->name == SNAME("@icon")) { if (previous.type != GDScriptTokenizer::Token::NEWLINE) { push_error(R"(Expected newline after "@icon" annotation.)"); } @@ -603,12 +584,12 @@ void GDScriptParser::parse_program() { } } - parse_class_body(); + parse_class_body(true); #ifdef TOOLS_ENABLED - for (Map<int, GDScriptTokenizer::CommentData>::Element *E = tokenizer.get_comments().front(); E; E = E->next()) { - if (E->get().new_line && E->get().comment.begins_with("##")) { - class_doc_line = MIN(class_doc_line, E->key()); + 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); } } if (has_comment(class_doc_line)) { @@ -639,9 +620,10 @@ GDScriptParser::ClassNode *GDScriptParser::parse_class() { } consume(GDScriptTokenizer::Token::COLON, R"(Expected ":" after class declaration.)"); - consume(GDScriptTokenizer::Token::NEWLINE, R"(Expected newline after class declaration.)"); - if (!consume(GDScriptTokenizer::Token::INDENT, R"(Expected indented block after class declaration.)")) { + bool multiline = match(GDScriptTokenizer::Token::NEWLINE); + + if (multiline && !consume(GDScriptTokenizer::Token::INDENT, R"(Expected indented block after class declaration.)")) { current_class = previous_class; return n_class; } @@ -654,9 +636,11 @@ GDScriptParser::ClassNode *GDScriptParser::parse_class() { end_statement("superclass"); } - parse_class_body(); + parse_class_body(multiline); - consume(GDScriptTokenizer::Token::DEDENT, R"(Missing unindent at the end of the class body.)"); + if (multiline) { + consume(GDScriptTokenizer::Token::DEDENT, R"(Missing unindent at the end of the class body.)"); + } current_class = previous_class; return n_class; @@ -722,24 +706,21 @@ void GDScriptParser::parse_extends() { template <class T> void GDScriptParser::parse_class_member(T *(GDScriptParser::*p_parse_function)(), AnnotationInfo::TargetKind p_target, const String &p_member_kind) { advance(); - T *member = (this->*p_parse_function)(); - if (member == nullptr) { - return; - } + #ifdef TOOLS_ENABLED - int doc_comment_line = member->start_line - 1; + int doc_comment_line = previous.start_line - 1; #endif // TOOLS_ENABLED // Consume annotations. + List<AnnotationNode *> annotations; while (!annotation_stack.is_empty()) { AnnotationNode *last_annotation = annotation_stack.back()->get(); if (last_annotation->applies_to(p_target)) { - member->annotations.push_front(last_annotation); + annotations.push_front(last_annotation); annotation_stack.pop_back(); } else { push_error(vformat(R"(Annotation "%s" cannot be applied to a %s.)", last_annotation->name, p_member_kind)); clear_unused_annotations(); - return; } #ifdef TOOLS_ENABLED if (last_annotation->start_line == doc_comment_line) { @@ -748,6 +729,16 @@ void GDScriptParser::parse_class_member(T *(GDScriptParser::*p_parse_function)() #endif // TOOLS_ENABLED } + T *member = (this->*p_parse_function)(); + if (member == nullptr) { + return; + } + + // Apply annotations. + for (AnnotationNode *&annotation : annotations) { + member->annotations.push_back(annotation); + } + #ifdef TOOLS_ENABLED // Consume doc comments. class_doc_line = MIN(class_doc_line, doc_comment_line - 1); @@ -761,17 +752,33 @@ void GDScriptParser::parse_class_member(T *(GDScriptParser::*p_parse_function)() #endif // TOOLS_ENABLED if (member->identifier != nullptr) { - // Enums may be unnamed. - // TODO: Consider names in outer scope too, for constants and classes (and static functions?) - if (current_class->members_indices.has(member->identifier->name)) { - push_error(vformat(R"(%s "%s" has the same name as a previously declared %s.)", p_member_kind.capitalize(), member->identifier->name, current_class->get_member(member->identifier->name).get_type_name()), member->identifier); + if (!((String)member->identifier->name).is_empty()) { // Enums may be unnamed. + +#ifdef DEBUG_ENABLED + List<MethodInfo> gdscript_funcs; + GDScriptLanguage::get_singleton()->get_public_functions(&gdscript_funcs); + for (MethodInfo &info : gdscript_funcs) { + if (info.name == member->identifier->name) { + push_warning(member->identifier, GDScriptWarning::SHADOWED_GLOBAL_IDENTIFIER, p_member_kind, member->identifier->name, "built-in function"); + } + } + if (Variant::has_utility_function(member->identifier->name)) { + push_warning(member->identifier, GDScriptWarning::SHADOWED_GLOBAL_IDENTIFIER, p_member_kind, member->identifier->name, "built-in function"); + } +#endif + + if (current_class->members_indices.has(member->identifier->name)) { + push_error(vformat(R"(%s "%s" has the same name as a previously declared %s.)", p_member_kind.capitalize(), member->identifier->name, current_class->get_member(member->identifier->name).get_type_name()), member->identifier); + } else { + current_class->add_member(member); + } } else { current_class->add_member(member); } } } -void GDScriptParser::parse_class_body() { +void GDScriptParser::parse_class_body(bool p_is_multiline) { bool class_end = false; while (!class_end && !is_at_end()) { switch (current.type) { @@ -810,6 +817,8 @@ void GDScriptParser::parse_class_body() { class_end = true; break; default: + // Display a completion with identifiers. + make_completion_context(COMPLETION_IDENTIFIER, nullptr); push_error(vformat(R"(Unexpected "%s" in class body.)", current.get_name())); advance(); break; @@ -817,6 +826,9 @@ void GDScriptParser::parse_class_body() { if (panic_mode) { synchronize(); } + if (!p_is_multiline) { + class_end = true; + } } } @@ -965,7 +977,7 @@ GDScriptParser::VariableNode *GDScriptParser::parse_property(VariableNode *p_var void GDScriptParser::parse_property_setter(VariableNode *p_variable) { switch (p_variable->property) { - case VariableNode::PROP_INLINE: + case VariableNode::PROP_INLINE: { consume(GDScriptTokenizer::Token::PARENTHESIS_OPEN, R"(Expected "(" after "set".)"); if (consume(GDScriptTokenizer::Token::IDENTIFIER, R"(Expected parameter name after "(".)")) { p_variable->setter_parameter = parse_identifier(); @@ -973,9 +985,32 @@ void GDScriptParser::parse_property_setter(VariableNode *p_variable) { consume(GDScriptTokenizer::Token::PARENTHESIS_CLOSE, R"*(Expected ")" after parameter name.)*"); consume(GDScriptTokenizer::Token::COLON, R"*(Expected ":" after ")".)*"); - p_variable->setter = parse_suite("setter definition"); - break; + IdentifierNode *identifier = alloc_node<IdentifierNode>(); + identifier->name = "@" + p_variable->identifier->name + "_setter"; + + FunctionNode *function = alloc_node<FunctionNode>(); + function->identifier = identifier; + + FunctionNode *previous_function = current_function; + current_function = function; + + ParameterNode *parameter = alloc_node<ParameterNode>(); + parameter->identifier = p_variable->setter_parameter; + + if (parameter->identifier != nullptr) { + function->parameters_indices[parameter->identifier->name] = 0; + function->parameters.push_back(parameter); + SuiteNode *body = alloc_node<SuiteNode>(); + body->add_local(parameter, function); + + function->body = parse_suite("setter declaration", body); + p_variable->setter = function; + } + + current_function = previous_function; + break; + } case VariableNode::PROP_SETGET: consume(GDScriptTokenizer::Token::EQUAL, R"(Expected "=" after "set")"); make_completion_context(COMPLETION_PROPERTY_METHOD, p_variable); @@ -990,11 +1025,25 @@ void GDScriptParser::parse_property_setter(VariableNode *p_variable) { void GDScriptParser::parse_property_getter(VariableNode *p_variable) { switch (p_variable->property) { - case VariableNode::PROP_INLINE: + case VariableNode::PROP_INLINE: { consume(GDScriptTokenizer::Token::COLON, R"(Expected ":" after "get".)"); - p_variable->getter = parse_suite("getter definition"); + IdentifierNode *identifier = alloc_node<IdentifierNode>(); + identifier->name = "@" + p_variable->identifier->name + "_getter"; + + FunctionNode *function = alloc_node<FunctionNode>(); + function->identifier = identifier; + + FunctionNode *previous_function = current_function; + current_function = function; + + SuiteNode *body = alloc_node<SuiteNode>(); + function->body = parse_suite("getter declaration", body); + + p_variable->getter = function; + current_function = previous_function; break; + } case VariableNode::PROP_SETGET: consume(GDScriptTokenizer::Token::EQUAL, R"(Expected "=" after "get")"); make_completion_context(COMPLETION_PROPERTY_METHOD, p_variable); @@ -1077,7 +1126,9 @@ GDScriptParser::SignalNode *GDScriptParser::parse_signal() { SignalNode *signal = alloc_node<SignalNode>(); signal->identifier = parse_identifier(); - if (match(GDScriptTokenizer::Token::PARENTHESIS_OPEN)) { + if (check(GDScriptTokenizer::Token::PARENTHESIS_OPEN)) { + push_multiline(true); + advance(); do { if (check(GDScriptTokenizer::Token::PARENTHESIS_CLOSE)) { // Allow for trailing comma. @@ -1100,6 +1151,7 @@ GDScriptParser::SignalNode *GDScriptParser::parse_signal() { } } while (match(GDScriptTokenizer::Token::COMMA) && !is_at_end()); + pop_multiline(); consume(GDScriptTokenizer::Token::PARENTHESIS_CLOSE, R"*(Expected closing ")" after signal parameters.)*"); } @@ -1123,13 +1175,31 @@ GDScriptParser::EnumNode *GDScriptParser::parse_enum() { HashMap<StringName, int> elements; +#ifdef DEBUG_ENABLED + List<MethodInfo> gdscript_funcs; + GDScriptLanguage::get_singleton()->get_public_functions(&gdscript_funcs); +#endif + do { if (check(GDScriptTokenizer::Token::BRACE_CLOSE)) { break; // Allow trailing comma. } if (consume(GDScriptTokenizer::Token::IDENTIFIER, R"(Expected identifier for enum key.)")) { EnumNode::Value item; - item.identifier = parse_identifier(); + GDScriptParser::IdentifierNode *identifier = parse_identifier(); +#ifdef DEBUG_ENABLED + for (MethodInfo &info : gdscript_funcs) { + if (info.name == identifier->name) { + push_warning(identifier, GDScriptWarning::SHADOWED_GLOBAL_IDENTIFIER, "enum member", identifier->name, "built-in function"); + } + } + if (Variant::has_utility_function(identifier->name)) { + push_warning(identifier, GDScriptWarning::SHADOWED_GLOBAL_IDENTIFIER, "enum member", identifier->name, "built-in function"); + } else if (ClassDB::class_exists(identifier->name)) { + push_warning(identifier, GDScriptWarning::SHADOWED_GLOBAL_IDENTIFIER, "enum member", identifier->name, "global class"); + } +#endif + item.identifier = identifier; item.parent_enum = enum_node; item.line = previous.start_line; item.leftmost_column = previous.leftmost_column; @@ -1158,7 +1228,6 @@ GDScriptParser::EnumNode *GDScriptParser::parse_enum() { } item.custom_value = value; } - item.rightmost_column = previous.rightmost_column; item.index = enum_node->values.size(); enum_node->values.push_back(item); @@ -1311,6 +1380,7 @@ GDScriptParser::AnnotationNode *GDScriptParser::parse_annotation(uint32_t p_vali push_completion_call(annotation); make_completion_context(COMPLETION_ANNOTATION_ARGUMENTS, annotation, 0, true); if (!check(GDScriptTokenizer::Token::PARENTHESIS_CLOSE) && !is_at_end()) { + push_multiline(true); int argument_index = 0; do { make_completion_context(COMPLETION_ANNOTATION_ARGUMENTS, annotation, argument_index, true); @@ -1322,6 +1392,7 @@ GDScriptParser::AnnotationNode *GDScriptParser::parse_annotation(uint32_t p_vali } annotation->arguments.push_back(argument); } while (match(GDScriptTokenizer::Token::COMMA)); + pop_multiline(); consume(GDScriptTokenizer::Token::PARENTHESIS_CLOSE, R"*(Expected ")" after annotation arguments.)*"); } @@ -1338,8 +1409,7 @@ GDScriptParser::AnnotationNode *GDScriptParser::parse_annotation(uint32_t p_vali } void GDScriptParser::clear_unused_annotations() { - for (const List<AnnotationNode *>::Element *E = annotation_stack.front(); E != nullptr; E = E->next()) { - AnnotationNode *annotation = E->get(); + for (const AnnotationNode *annotation : annotation_stack) { push_error(vformat(R"(Annotation "%s" does not precedes a valid target, so it will have no effect.)", annotation->name), annotation); } @@ -1384,6 +1454,9 @@ GDScriptParser::SuiteNode *GDScriptParser::parse_suite(const String &p_context, int error_count = 0; do { + if (!multiline && previous.type == GDScriptTokenizer::Token::SEMICOLON && check(GDScriptTokenizer::Token::NEWLINE)) { + break; + } Node *statement = parse_statement(); if (statement == nullptr) { if (error_count++ > 100) { @@ -1424,7 +1497,7 @@ GDScriptParser::SuiteNode *GDScriptParser::parse_suite(const String &p_context, break; } - } while (multiline && !check(GDScriptTokenizer::Token::DEDENT) && !lambda_ended && !is_at_end()); + } while ((multiline || previous.type == GDScriptTokenizer::Token::SEMICOLON) && !check(GDScriptTokenizer::Token::DEDENT) && !lambda_ended && !is_at_end()); if (multiline) { if (!lambda_ended) { @@ -1450,6 +1523,8 @@ GDScriptParser::Node *GDScriptParser::parse_statement() { bool unreachable = current_suite->has_return && !current_suite->has_unreachable_code; #endif + bool is_annotation = false; + switch (current.type) { case GDScriptTokenizer::Token::PASS: advance(); @@ -1519,6 +1594,7 @@ GDScriptParser::Node *GDScriptParser::parse_statement() { break; case GDScriptTokenizer::Token::ANNOTATION: { advance(); + is_annotation = true; AnnotationNode *annotation = parse_annotation(AnnotationInfo::STATEMENT); if (annotation != nullptr) { annotation_stack.push_back(annotation); @@ -1559,6 +1635,18 @@ GDScriptParser::Node *GDScriptParser::parse_statement() { } } + // Apply annotations to statement. + while (!is_annotation && result != nullptr && !annotation_stack.is_empty()) { + AnnotationNode *last_annotation = annotation_stack.back()->get(); + if (last_annotation->applies_to(AnnotationInfo::STATEMENT)) { + result->annotations.push_front(last_annotation); + annotation_stack.pop_back(); + } else { + push_error(vformat(R"(Annotation "%s" cannot be applied to a statement.)", last_annotation->name)); + clear_unused_annotations(); + } + } + #ifdef DEBUG_ENABLED if (unreachable && result != nullptr) { current_suite->has_unreachable_code = true; @@ -1634,6 +1722,10 @@ GDScriptParser::ForNode *GDScriptParser::parse_for() { n_for->list = parse_expression(false); + if (!n_for->list) { + push_error(R"(Expected a list or range after "in".)"); + } + consume(GDScriptTokenizer::Token::COLON, R"(Expected ":" after "for" condition.)"); // Save break/continue state. @@ -1725,6 +1817,7 @@ GDScriptParser::MatchNode *GDScriptParser::parse_match() { while (!check(GDScriptTokenizer::Token::DEDENT) && !is_at_end()) { MatchBranchNode *branch = parse_match_branch(); if (branch == nullptr) { + advance(); continue; } @@ -1788,7 +1881,9 @@ GDScriptParser::MatchBranchNode *GDScriptParser::parse_match_branch() { push_error(R"(No pattern found for "match" branch.)"); } - consume(GDScriptTokenizer::Token::COLON, R"(Expected ":" after "match" patterns.)"); + if (!consume(GDScriptTokenizer::Token::COLON, R"(Expected ":" after "match" patterns.)")) { + return nullptr; + } // Save continue state. bool could_continue = can_continue; @@ -1802,8 +1897,8 @@ GDScriptParser::MatchBranchNode *GDScriptParser::parse_match_branch() { List<StringName> binds; branch->patterns[0]->binds.get_key_list(&binds); - for (List<StringName>::Element *E = binds.front(); E != nullptr; E = E->next()) { - SuiteNode::Local local(branch->patterns[0]->binds[E->get()], current_function); + for (const StringName &E : binds) { + SuiteNode::Local local(branch->patterns[0]->binds[E], current_function); suite->add_local(local); } } @@ -1821,15 +1916,6 @@ GDScriptParser::PatternNode *GDScriptParser::parse_match_pattern(PatternNode *p_ PatternNode *pattern = alloc_node<PatternNode>(); switch (current.type) { - case GDScriptTokenizer::Token::LITERAL: - advance(); - pattern->pattern_type = PatternNode::PT_LITERAL; - pattern->literal = parse_literal(); - if (pattern->literal == nullptr) { - // Error happened. - return nullptr; - } - break; case GDScriptTokenizer::Token::VAR: { // Bind. advance(); @@ -1892,44 +1978,44 @@ GDScriptParser::PatternNode *GDScriptParser::parse_match_pattern(PatternNode *p_ // Dictionary. advance(); pattern->pattern_type = PatternNode::PT_DICTIONARY; - - if (!check(GDScriptTokenizer::Token::BRACE_CLOSE) && !is_at_end()) { - do { - if (match(GDScriptTokenizer::Token::PERIOD_PERIOD)) { - // Rest. + do { + if (check(GDScriptTokenizer::Token::BRACE_CLOSE) || is_at_end()) { + break; + } + if (match(GDScriptTokenizer::Token::PERIOD_PERIOD)) { + // Rest. + if (pattern->rest_used) { + push_error(R"(The ".." pattern must be the last element in the pattern dictionary.)"); + } else { + PatternNode *sub_pattern = alloc_node<PatternNode>(); + sub_pattern->pattern_type = PatternNode::PT_REST; + pattern->dictionary.push_back({ nullptr, sub_pattern }); + pattern->rest_used = true; + } + } else { + ExpressionNode *key = parse_expression(false); + if (key == nullptr) { + push_error(R"(Expected expression as key for dictionary pattern.)"); + } + if (match(GDScriptTokenizer::Token::COLON)) { + // Value pattern. + 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 dictionary.)"); + } else if (sub_pattern->pattern_type == PatternNode::PT_REST) { + push_error(R"(The ".." pattern cannot be used as a value.)"); } else { - PatternNode *sub_pattern = alloc_node<PatternNode>(); - sub_pattern->pattern_type = PatternNode::PT_REST; - pattern->dictionary.push_back({ nullptr, sub_pattern }); - pattern->rest_used = true; + pattern->dictionary.push_back({ key, sub_pattern }); } } else { - ExpressionNode *key = parse_expression(false); - if (key == nullptr) { - push_error(R"(Expected expression as key for dictionary pattern.)"); - } - if (match(GDScriptTokenizer::Token::COLON)) { - // Value pattern. - 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 dictionary.)"); - } else if (sub_pattern->pattern_type == PatternNode::PT_REST) { - push_error(R"(The ".." pattern cannot be used as a value.)"); - } else { - pattern->dictionary.push_back({ key, sub_pattern }); - } - } else { - // Key match only. - pattern->dictionary.push_back({ key, nullptr }); - } + // Key match only. + pattern->dictionary.push_back({ key, nullptr }); } - } while (match(GDScriptTokenizer::Token::COMMA)); - } + } + } while (match(GDScriptTokenizer::Token::COMMA)); consume(GDScriptTokenizer::Token::BRACE_CLOSE, R"(Expected "}" to close the dictionary pattern.)"); break; } @@ -1938,8 +2024,13 @@ GDScriptParser::PatternNode *GDScriptParser::parse_match_pattern(PatternNode *p_ ExpressionNode *expression = parse_expression(false); if (expression == nullptr) { push_error(R"(Expected expression for match pattern.)"); + return nullptr; } else { - pattern->pattern_type = PatternNode::PT_EXPRESSION; + if (expression->type == GDScriptParser::Node::LITERAL) { + pattern->pattern_type = PatternNode::PT_LITERAL; + } else { + pattern->pattern_type = PatternNode::PT_EXPRESSION; + } pattern->expression = expression; } break; @@ -2016,7 +2107,7 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_precedence(Precedence p_pr ExpressionNode *previous_operand = (this->*prefix_rule)(nullptr, p_can_assign); while (p_precedence <= get_rule(current.type)->precedence) { - if (p_stop_on_assign && current.type == GDScriptTokenizer::Token::EQUAL) { + if (previous_operand == nullptr || (p_stop_on_assign && current.type == GDScriptTokenizer::Token::EQUAL)) { return previous_operand; } // Also switch multiline mode on here for infix operators. @@ -2109,9 +2200,6 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_self(ExpressionNode *p_pre if (current_function && current_function->is_static) { push_error(R"(Cannot use "self" inside a static function.)"); } - if (in_lambda) { - push_error(R"(Cannot use "self" inside a lambda.)"); - } SelfNode *self = alloc_node<SelfNode>(); self->current_class = current_class; return self; @@ -2129,10 +2217,10 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_builtin_constant(Expressio constant->value = Math_TAU; break; case GDScriptTokenizer::Token::CONST_INF: - constant->value = Math_INF; + constant->value = INFINITY; break; case GDScriptTokenizer::Token::CONST_NAN: - constant->value = Math_NAN; + constant->value = NAN; break; default: return nullptr; // Unreachable. @@ -2150,22 +2238,34 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_unary_operator(ExpressionN operation->operation = UnaryOpNode::OP_NEGATIVE; operation->variant_op = Variant::OP_NEGATE; operation->operand = parse_precedence(PREC_SIGN, false); + if (operation->operand == nullptr) { + push_error(R"(Expected expression after "-" operator.)"); + } break; case GDScriptTokenizer::Token::PLUS: operation->operation = UnaryOpNode::OP_POSITIVE; operation->variant_op = Variant::OP_POSITIVE; operation->operand = parse_precedence(PREC_SIGN, false); + if (operation->operand == nullptr) { + push_error(R"(Expected expression after "+" operator.)"); + } break; case GDScriptTokenizer::Token::TILDE: operation->operation = UnaryOpNode::OP_COMPLEMENT; operation->variant_op = Variant::OP_BIT_NEGATE; operation->operand = parse_precedence(PREC_BIT_NOT, false); + if (operation->operand == nullptr) { + push_error(R"(Expected expression after "~" operator.)"); + } break; case GDScriptTokenizer::Token::NOT: case GDScriptTokenizer::Token::BANG: operation->operation = UnaryOpNode::OP_LOGIC_NOT; operation->variant_op = Variant::OP_NOT; operation->operand = parse_precedence(PREC_LOGIC_NOT, false); + if (operation->operand == nullptr) { + push_error(vformat(R"(Expected expression after "%s" operator.)", op_type == GDScriptTokenizer::Token::NOT ? "not" : "!")); + } break; default: return nullptr; // Unreachable. @@ -2302,6 +2402,10 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_ternary_operator(Expressio operation->false_expr = parse_precedence(PREC_TERNARY, false); + if (operation->false_expr == nullptr) { + push_error(R"(Expected expression after "else".)"); + } + return operation; } @@ -2310,6 +2414,9 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_assignment(ExpressionNode push_error("Assignment is not allowed inside an expression."); return parse_expression(false); // Return the following expression. } + if (p_previous_operand == nullptr) { + return parse_expression(false); // Return the following expression. + } #ifdef DEBUG_ENABLED VariableNode *source_variable = nullptr; @@ -2408,10 +2515,17 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_assignment(ExpressionNode } assignment->assignee = p_previous_operand; assignment->assigned_value = parse_expression(false); + if (assignment->assigned_value == nullptr) { + push_error(R"(Expected an expression after "=".)"); + } #ifdef DEBUG_ENABLED - if (has_operator && source_variable != nullptr && source_variable->assignments == 0) { - push_warning(assignment, GDScriptWarning::UNASSIGNED_VARIABLE_OP_ASSIGN, source_variable->identifier->name); + 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 @@ -2420,9 +2534,15 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_assignment(ExpressionNode GDScriptParser::ExpressionNode *GDScriptParser::parse_await(ExpressionNode *p_previous_operand, bool p_can_assign) { AwaitNode *await = alloc_node<AwaitNode>(); - await->to_await = parse_precedence(PREC_AWAIT, false); + ExpressionNode *element = parse_precedence(PREC_AWAIT, false); + if (element == nullptr) { + push_error(R"(Expected signal or coroutine after "await".)"); + } + await->to_await = element; - current_function->is_coroutine = true; + if (current_function) { // Might be null in a getter or setter. + current_function->is_coroutine = true; + } return await; } @@ -2486,8 +2606,15 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_dictionary(ExpressionNode switch (dictionary->style) { case DictionaryNode::LUA_TABLE: - if (key != nullptr && key->type != Node::IDENTIFIER) { - push_error("Expected identifier as dictionary key."); + if (key != nullptr && key->type != Node::IDENTIFIER && key->type != Node::LITERAL) { + push_error("Expected identifier or string as LUA-style dictionary key."); + advance(); + break; + } + if (key != nullptr && key->type == Node::LITERAL && static_cast<LiteralNode *>(key)->value.get_type() != Variant::STRING) { + push_error("Expected identifier or string as LUA-style dictionary key."); + advance(); + break; } if (!match(GDScriptTokenizer::Token::EQUAL)) { if (match(GDScriptTokenizer::Token::COLON)) { @@ -2497,8 +2624,14 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_dictionary(ExpressionNode push_error(R"(Expected "=" after dictionary key.)"); } } - key->is_constant = true; - key->reduced_value = static_cast<IdentifierNode *>(key)->name; + if (key != nullptr) { + key->is_constant = true; + if (key->type == Node::IDENTIFIER) { + key->reduced_value = static_cast<IdentifierNode *>(key)->name; + } else if (key->type == Node::LITERAL) { + key->reduced_value = StringName(static_cast<LiteralNode *>(key)->value.operator String()); + } + } break; case DictionaryNode::PYTHON_DICT: if (!match(GDScriptTokenizer::Token::COLON)) { @@ -2549,7 +2682,7 @@ 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, builtin_type, true); + make_completion_context(COMPLETION_BUILT_IN_TYPE_CONSTANT_OR_STATIC_METHOD, builtin_type, true); is_builtin = true; } } @@ -2558,12 +2691,13 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_attribute(ExpressionNode * } } - attribute->is_attribute = true; attribute->base = p_previous_operand; if (!consume(GDScriptTokenizer::Token::IDENTIFIER, R"(Expected identifier after "." for attribute access.)")) { return attribute; } + + attribute->is_attribute = true; attribute->attribute = parse_identifier(); return attribute; @@ -2577,6 +2711,10 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_subscript(ExpressionNode * subscript->base = p_previous_operand; subscript->index = parse_expression(false); + if (subscript->index == nullptr) { + push_error(R"(Expected expression after "[".)"); + } + pop_multiline(); consume(GDScriptTokenizer::Token::BRACKET_CLOSE, R"(Expected "]" after subscription index.)"); @@ -2611,7 +2749,11 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_call(ExpressionNode *p_pre pop_multiline(); return nullptr; } - call->function_name = current_function->identifier->name; + if (current_function->identifier) { + call->function_name = current_function->identifier->name; + } else { + call->function_name = SNAME("<anonymous>"); + } } else { consume(GDScriptTokenizer::Token::PERIOD, R"(Expected "." or "(" after "super".)"); make_completion_context(COMPLETION_SUPER_METHOD, call, true); @@ -2702,6 +2844,23 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_get_node(ExpressionNode *p get_node->chain.push_back(identifier); } while (match(GDScriptTokenizer::Token::SLASH)); return get_node; + } else if (match(GDScriptTokenizer::Token::SLASH)) { + GetNodeNode *get_node = alloc_node<GetNodeNode>(); + IdentifierNode *identifier_root = alloc_node<IdentifierNode>(); + get_node->chain.push_back(identifier_root); + int chain_position = 0; + do { + make_completion_context(COMPLETION_GET_NODE, get_node, chain_position++); + if (!current.is_node_name()) { + push_error(R"(Expect node path after "/".)"); + return nullptr; + } + advance(); + IdentifierNode *identifier = alloc_node<IdentifierNode>(); + identifier->name = previous.get_identifier(); + get_node->chain.push_back(identifier); + } while (match(GDScriptTokenizer::Token::SLASH)); + return get_node; } else { push_error(R"(Expect node path as string or identifier after "$".)"); return nullptr; @@ -2791,6 +2950,11 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_lambda(ExpressionNode *p_p return lambda; } +GDScriptParser::ExpressionNode *GDScriptParser::parse_yield(ExpressionNode *p_previous_operand, bool p_can_assign) { + push_error(R"("yield" was removed in Godot 4.0. Use "await" instead.)"); + return nullptr; +} + GDScriptParser::ExpressionNode *GDScriptParser::parse_invalid_token(ExpressionNode *p_previous_operand, bool p_can_assign) { // Just for better error messages. GDScriptTokenizer::Token::Type invalid = previous.type; @@ -2941,7 +3105,7 @@ void GDScriptParser::get_class_doc_comment(int p_line, String &p_brief, String & if (!comments.has(p_line)) { return; } - ERR_FAIL_COND(p_brief != "" || p_desc != "" || p_tutorials.size() != 0); + ERR_FAIL_COND(!p_brief.is_empty() || !p_desc.is_empty() || p_tutorials.size() != 0); int line = p_line; bool in_codeblock = false; @@ -2973,7 +3137,7 @@ void GDScriptParser::get_class_doc_comment(int p_line, String &p_brief, String & String striped_line = doc_line.strip_edges(); // Set the read mode. - if (striped_line.begins_with("@desc:") && p_desc == "") { + if (striped_line.begins_with("@desc:") && p_desc.is_empty()) { mode = DESC; striped_line = striped_line.trim_prefix("@desc:"); in_codeblock = _in_codeblock(doc_line, in_codeblock); @@ -2991,9 +3155,9 @@ void GDScriptParser::get_class_doc_comment(int p_line, String &p_brief, String & } else { /* Syntax: - @tutorial ( The Title Here ) : http://the.url/ - ^ open ^ close ^ colon ^ url - */ + * @tutorial ( The Title Here ) : https://the.url/ + * ^ open ^ close ^ colon ^ url + */ int open_bracket_pos = begin_scan, close_bracket_pos = 0; while (open_bracket_pos < striped_line.length() && (striped_line[open_bracket_pos] == ' ' || striped_line[open_bracket_pos] == '\t')) { open_bracket_pos++; @@ -3147,7 +3311,7 @@ GDScriptParser::ParseRule *GDScriptParser::get_rule(GDScriptTokenizer::Token::Ty { nullptr, nullptr, PREC_NONE }, // TRAIT, { nullptr, nullptr, PREC_NONE }, // VAR, { nullptr, nullptr, PREC_NONE }, // VOID, - { nullptr, nullptr, PREC_NONE }, // YIELD, + { &GDScriptParser::parse_yield, nullptr, PREC_NONE }, // YIELD, // Punctuation { &GDScriptParser::parse_array, &GDScriptParser::parse_subscript, PREC_SUBSCRIPT }, // BRACKET_OPEN, { nullptr, nullptr, PREC_NONE }, // BRACKET_CLOSE, @@ -3254,8 +3418,8 @@ bool GDScriptParser::validate_annotation_arguments(AnnotationNode *p_annotation) Variant::construct(parameter.type, r, &(name), 1, error); p_annotation->resolved_arguments.push_back(r); if (error.error != Callable::CallError::CALL_OK) { - push_error(vformat(R"(Expected %s as argument %d of annotation "%s").)", Variant::get_type_name(parameter.type), i + 1, p_annotation->name)); - p_annotation->resolved_arguments.remove(p_annotation->resolved_arguments.size() - 1); + push_error(vformat(R"(Expected %s as argument %d of annotation "%s".)", Variant::get_type_name(parameter.type), i + 1, p_annotation->name)); + p_annotation->resolved_arguments.remove_at(p_annotation->resolved_arguments.size() - 1); return false; } break; @@ -3263,13 +3427,13 @@ bool GDScriptParser::validate_annotation_arguments(AnnotationNode *p_annotation) [[fallthrough]]; default: { if (argument->type != Node::LITERAL) { - push_error(vformat(R"(Expected %s as argument %d of annotation "%s").)", Variant::get_type_name(parameter.type), i + 1, p_annotation->name)); + push_error(vformat(R"(Expected %s as argument %d of annotation "%s".)", Variant::get_type_name(parameter.type), i + 1, p_annotation->name)); return false; } Variant value = static_cast<LiteralNode *>(argument)->value; if (!Variant::can_convert_strict(value.get_type(), parameter.type)) { - push_error(vformat(R"(Expected %s as argument %d of annotation "%s").)", Variant::get_type_name(parameter.type), i + 1, p_annotation->name)); + push_error(vformat(R"(Expected %s as argument %d of annotation "%s".)", Variant::get_type_name(parameter.type), i + 1, p_annotation->name)); return false; } Callable::CallError error; @@ -3278,8 +3442,8 @@ bool GDScriptParser::validate_annotation_arguments(AnnotationNode *p_annotation) Variant::construct(parameter.type, r, &(args), 1, error); p_annotation->resolved_arguments.push_back(r); if (error.error != Callable::CallError::CALL_OK) { - push_error(vformat(R"(Expected %s as argument %d of annotation "%s").)", Variant::get_type_name(parameter.type), i + 1, p_annotation->name)); - p_annotation->resolved_arguments.remove(p_annotation->resolved_arguments.size() - 1); + push_error(vformat(R"(Expected %s as argument %d of annotation "%s".)", Variant::get_type_name(parameter.type), i + 1, p_annotation->name)); + p_annotation->resolved_arguments.remove_at(p_annotation->resolved_arguments.size() - 1); return false; } break; @@ -3319,6 +3483,15 @@ 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)); + { + const int max_flags = 32; + + if (t_hint == PropertyHint::PROPERTY_HINT_FLAGS && p_annotation->resolved_arguments.size() > max_flags) { + push_error(vformat(R"(The argument count limit for "@export_flags" is exceeded (%d/%d).)", p_annotation->resolved_arguments.size(), max_flags), p_annotation); + return false; + } + } + VariableNode *variable = static_cast<VariableNode *>(p_node); if (variable->exported) { push_error(vformat(R"(Annotation "%s" cannot be used with another "@export" annotation.)", p_annotation->name), p_annotation); @@ -3340,10 +3513,10 @@ bool GDScriptParser::export_annotations(const AnnotationNode *p_annotation, Node variable->export_info.hint_string = hint_string; - // This is called after tne analyzer is done finding the type, so this should be set here. + // This is called after the analyzer is done finding the type, so this should be set here. DataType export_type = variable->get_datatype(); - if (p_annotation->name == "@export") { + if (p_annotation->name == SNAME("@export")) { if (variable->datatype_specifier == nullptr && variable->initializer == nullptr) { push_error(R"(Cannot use simple "@export" annotation with variable without type or initializer, since type can't be inferred.)", p_annotation); return false; @@ -3368,10 +3541,10 @@ bool GDScriptParser::export_annotations(const AnnotationNode *p_annotation, Node variable->export_info.hint_string = Variant::get_type_name(export_type.builtin_type); break; case GDScriptParser::DataType::NATIVE: - if (ClassDB::is_parent_class(get_real_class_name(export_type.native_type), "Resource")) { + 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 = get_real_class_name(export_type.native_type); + variable->export_info.hint_string = export_type.native_type; } else { push_error(R"(Export type can only be built-in, a resource, or an enum.)", variable); return false; @@ -3382,12 +3555,12 @@ bool GDScriptParser::export_annotations(const AnnotationNode *p_annotation, Node variable->export_info.hint = PROPERTY_HINT_ENUM; String enum_hint_string; - for (const Map<StringName, int>::Element *E = export_type.enum_values.front(); E; E = E->next()) { - enum_hint_string += E->key().operator String().camelcase_to_underscore(true).capitalize().xml_escape(); + for (OrderedHashMap<StringName, int>::Element E = export_type.enum_values.front(); E; E = E.next()) { + enum_hint_string += E.key().operator String().capitalize().xml_escape(); enum_hint_string += ":"; - enum_hint_string += String::num_int64(E->get()).xml_escape(); + enum_hint_string += String::num_int64(E.value()).xml_escape(); - if (E->next()) { + if (E.next()) { enum_hint_string += ","; } } @@ -3424,34 +3597,76 @@ bool GDScriptParser::export_annotations(const AnnotationNode *p_annotation, Node } bool GDScriptParser::warning_annotations(const AnnotationNode *p_annotation, Node *p_node) { - ERR_FAIL_V_MSG(false, "Not implemented."); +#ifdef DEBUG_ENABLED + bool has_error = false; + for (const Variant &warning_name : p_annotation->resolved_arguments) { + GDScriptWarning::Code warning = GDScriptWarning::get_code_from_name(String(warning_name).to_upper()); + if (warning == GDScriptWarning::WARNING_MAX) { + push_error(vformat(R"(Invalid warning name: "%s".)", warning_name), p_annotation); + has_error = true; + } else { + p_node->ignored_warnings.push_back(warning); + } + } + + return !has_error; + +#else // ! DEBUG_ENABLED + // Only available in debug builds. + return true; +#endif // DEBUG_ENABLED } -template <MultiplayerAPI::RPCMode t_mode> +template <Multiplayer::RPCMode t_mode> bool GDScriptParser::network_annotations(const AnnotationNode *p_annotation, Node *p_node) { ERR_FAIL_COND_V_MSG(p_node->type != Node::VARIABLE && p_node->type != Node::FUNCTION, false, vformat(R"("%s" annotation can only be applied to variables and functions.)", p_annotation->name)); - switch (p_node->type) { - case Node::VARIABLE: { - VariableNode *variable = static_cast<VariableNode *>(p_node); - if (variable->rpc_mode != MultiplayerAPI::RPC_MODE_DISABLED) { - push_error(R"(RPC annotations can only be used once per variable.)", p_annotation); + Multiplayer::RPCConfig rpc_config; + rpc_config.rpc_mode = t_mode; + if (p_annotation->resolved_arguments.size()) { + int last = p_annotation->resolved_arguments.size() - 1; + if (p_annotation->resolved_arguments[last].get_type() == Variant::INT) { + rpc_config.channel = p_annotation->resolved_arguments[last].operator int(); + last -= 1; + } + if (last > 3) { + push_error(R"(Invalid RPC arguments. At most 4 arguments are allowed, where only the last argument can be an integer to specify the channel.')", p_annotation); + return false; + } + for (int i = last; i >= 0; i--) { + String mode = p_annotation->resolved_arguments[i].operator String(); + if (mode == "any_peer") { + rpc_config.rpc_mode = Multiplayer::RPC_MODE_ANY_PEER; + } else if (mode == "authority") { + rpc_config.rpc_mode = Multiplayer::RPC_MODE_AUTHORITY; + } else if (mode == "call_local") { + rpc_config.call_local = true; + } else if (mode == "call_remote") { + rpc_config.call_local = false; + } else if (mode == "reliable") { + rpc_config.transfer_mode = Multiplayer::TRANSFER_MODE_RELIABLE; + } else if (mode == "unreliable") { + rpc_config.transfer_mode = Multiplayer::TRANSFER_MODE_UNRELIABLE; + } else if (mode == "unreliable_ordered") { + rpc_config.transfer_mode = Multiplayer::TRANSFER_MODE_UNRELIABLE_ORDERED; + } else { + push_error(R"(Invalid RPC argument. Must be one of: 'call_local'/'call_remote' (local calls), 'any_peer'/'authority' (permission), 'reliable'/'unreliable'/'unreliable_ordered' (transfer mode).)", p_annotation); } - variable->rpc_mode = t_mode; - break; } + } + switch (p_node->type) { case Node::FUNCTION: { FunctionNode *function = static_cast<FunctionNode *>(p_node); - if (function->rpc_mode != MultiplayerAPI::RPC_MODE_DISABLED) { + if (function->rpc_config.rpc_mode != Multiplayer::RPC_MODE_DISABLED) { push_error(R"(RPC annotations can only be used once per function.)", p_annotation); + return false; } - function->rpc_mode = t_mode; + function->rpc_config = rpc_config; break; } default: return false; // Unreachable. } - return true; } @@ -3538,8 +3753,6 @@ String GDScriptParser::DataType::to_string() const { } case ENUM: return enum_type.operator String() + " (enum)"; - case ENUM_VALUE: - return enum_type.operator String() + " (enum value)"; case UNRESOLVED: return "<unresolved type>"; } @@ -3547,6 +3760,39 @@ String GDScriptParser::DataType::to_string() const { ERR_FAIL_V_MSG("<unresolved type", "Kind set outside the enum range."); } +static Variant::Type _variant_type_to_typed_array_element_type(Variant::Type p_type) { + switch (p_type) { + case Variant::PACKED_BYTE_ARRAY: + case Variant::PACKED_INT32_ARRAY: + case Variant::PACKED_INT64_ARRAY: + return Variant::INT; + case Variant::PACKED_FLOAT32_ARRAY: + case Variant::PACKED_FLOAT64_ARRAY: + return Variant::FLOAT; + case Variant::PACKED_STRING_ARRAY: + return Variant::STRING; + case Variant::PACKED_VECTOR2_ARRAY: + return Variant::VECTOR2; + case Variant::PACKED_VECTOR3_ARRAY: + return Variant::VECTOR3; + case Variant::PACKED_COLOR_ARRAY: + return Variant::COLOR; + default: + return Variant::NIL; + } +} + +bool GDScriptParser::DataType::is_typed_container_type() const { + return kind == GDScriptParser::DataType::BUILTIN && _variant_type_to_typed_array_element_type(builtin_type) != Variant::NIL; +} + +GDScriptParser::DataType GDScriptParser::DataType::get_typed_container_type() const { + GDScriptParser::DataType type; + type.kind = GDScriptParser::DataType::BUILTIN; + type.builtin_type = _variant_type_to_typed_array_element_type(builtin_type); + return type; +} + /*---------- PRETTY PRINT FOR DEBUG ----------*/ #ifdef DEBUG_ENABLED @@ -3591,7 +3837,7 @@ void GDScriptParser::TreePrinter::push_text(const String &p_text) { printed += p_text; } -void GDScriptParser::TreePrinter::print_annotation(AnnotationNode *p_annotation) { +void GDScriptParser::TreePrinter::print_annotation(const AnnotationNode *p_annotation) { push_text(p_annotation->name); push_text(" ("); for (int i = 0; i < p_annotation->arguments.size(); i++) { @@ -3966,8 +4212,8 @@ void GDScriptParser::TreePrinter::print_for(ForNode *p_for) { } void GDScriptParser::TreePrinter::print_function(FunctionNode *p_function, const String &p_context) { - for (const List<AnnotationNode *>::Element *E = p_function->annotations.front(); E != nullptr; E = E->next()) { - print_annotation(E->get()); + for (const AnnotationNode *E : p_function->annotations) { + print_annotation(E); } push_text(p_context); push_text(" "); @@ -4004,7 +4250,11 @@ void GDScriptParser::TreePrinter::print_get_node(GetNodeNode *p_get_node) { } void GDScriptParser::TreePrinter::print_identifier(IdentifierNode *p_identifier) { - push_text(p_identifier->name); + if (p_identifier != nullptr) { + push_text(p_identifier->name); + } else { + push_text("<invalid identifier>"); + } } void GDScriptParser::TreePrinter::print_if(IfNode *p_if, bool p_is_elif) { @@ -4306,8 +4556,8 @@ void GDScriptParser::TreePrinter::print_unary_op(UnaryOpNode *p_unary_op) { } void GDScriptParser::TreePrinter::print_variable(VariableNode *p_variable) { - for (const List<AnnotationNode *>::Element *E = p_variable->annotations.front(); E != nullptr; E = E->next()) { - print_annotation(E->get()); + for (const AnnotationNode *E : p_variable->annotations) { + print_annotation(E); } push_text("Variable "); @@ -4339,7 +4589,7 @@ void GDScriptParser::TreePrinter::print_variable(VariableNode *p_variable) { if (p_variable->property == VariableNode::PROP_INLINE) { push_line(":"); increase_indent(); - print_suite(p_variable->getter); + print_suite(p_variable->getter->body); decrease_indent(); } else { push_line(" ="); @@ -4359,7 +4609,7 @@ void GDScriptParser::TreePrinter::print_variable(VariableNode *p_variable) { } push_line("):"); increase_indent(); - print_suite(p_variable->setter); + print_suite(p_variable->setter->body); decrease_indent(); } else { push_line(" ="); @@ -4398,7 +4648,7 @@ void GDScriptParser::TreePrinter::print_tree(const GDScriptParser &p_parser) { } print_class(p_parser.get_tree()); - print_line(printed); + print_line(String(printed)); } #endif // DEBUG_ENABLED |