diff options
Diffstat (limited to 'modules/gdscript/gdscript_parser.cpp')
-rw-r--r-- | modules/gdscript/gdscript_parser.cpp | 175 |
1 files changed, 31 insertions, 144 deletions
diff --git a/modules/gdscript/gdscript_parser.cpp b/modules/gdscript/gdscript_parser.cpp index 97d5c1d8b2..1a744d59ba 100644 --- a/modules/gdscript/gdscript_parser.cpp +++ b/modules/gdscript/gdscript_parser.cpp @@ -41,6 +41,7 @@ #include "core/os/os.h" #include "core/string/string_builder.h" #include "gdscript_warning.h" +#include "servers/text_server.h" #endif // DEBUG_ENABLED #ifdef TOOLS_ENABLED @@ -186,24 +187,6 @@ void GDScriptParser::push_error(const String &p_message, const Node *p_origin) { } #ifdef DEBUG_ENABLED -void GDScriptParser::push_warning(const Node *p_source, GDScriptWarning::Code p_code, const String &p_symbol1, const String &p_symbol2, const String &p_symbol3, const String &p_symbol4) { - ERR_FAIL_COND(p_source == nullptr); - Vector<String> symbols; - if (!p_symbol1.is_empty()) { - symbols.push_back(p_symbol1); - } - if (!p_symbol2.is_empty()) { - symbols.push_back(p_symbol2); - } - if (!p_symbol3.is_empty()) { - symbols.push_back(p_symbol3); - } - if (!p_symbol4.is_empty()) { - symbols.push_back(p_symbol4); - } - push_warning(p_source, p_code, symbols); -} - void GDScriptParser::push_warning(const Node *p_source, GDScriptWarning::Code p_code, const Vector<String> &p_symbols) { ERR_FAIL_COND(p_source == nullptr); if (is_ignoring_warnings) { @@ -540,43 +523,28 @@ void GDScriptParser::parse_program() { head = alloc_node<ClassNode>(); head->fqcn = script_path; current_class = head; + bool can_have_class_or_extends = true; - // If we happen to parse an annotation before extends or class_name keywords, track it. - // @tool is allowed, but others should fail. - AnnotationNode *premature_annotation = nullptr; - - if (match(GDScriptTokenizer::Token::ANNOTATION)) { - // Check for @tool, script-level, or standalone annotation. + while (match(GDScriptTokenizer::Token::ANNOTATION)) { AnnotationNode *annotation = parse_annotation(AnnotationInfo::SCRIPT | AnnotationInfo::STANDALONE | AnnotationInfo::CLASS_LEVEL); if (annotation != nullptr) { - if (annotation->name == SNAME("@tool")) { - // TODO: don't allow @tool anywhere else. (Should all script annotations be the first thing?). - _is_tool = true; - if (previous.type != GDScriptTokenizer::Token::NEWLINE) { - push_error(R"(Expected newline after "@tool" annotation.)"); - } - // @tool annotation has no specific target. - annotation->apply(this, nullptr); - } else if (annotation->applies_to(AnnotationInfo::SCRIPT | AnnotationInfo::STANDALONE)) { - premature_annotation = annotation; - if (previous.type != GDScriptTokenizer::Token::NEWLINE) { - push_error(R"(Expected newline after a standalone annotation.)"); - } - annotation->apply(this, head); + if (annotation->applies_to(AnnotationInfo::SCRIPT)) { + head->annotations.push_back(annotation); } else { - premature_annotation = annotation; annotation_stack.push_back(annotation); + // This annotation must appear after script-level annotations + // and class_name/extends (ex: could be @onready or @export), + // so we stop looking for script-level stuff. + can_have_class_or_extends = false; + break; } } } - for (bool should_break = false; !should_break;) { + while (can_have_class_or_extends) { // Order here doesn't matter, but there should be only one of each at most. switch (current.type) { case GDScriptTokenizer::Token::CLASS_NAME: - if (premature_annotation != nullptr) { - push_error(R"("class_name" should be used before annotations (except @tool).)"); - } advance(); if (head->identifier != nullptr) { push_error(R"("class_name" can only be used once.)"); @@ -585,9 +553,6 @@ void GDScriptParser::parse_program() { } break; case GDScriptTokenizer::Token::EXTENDS: - if (premature_annotation != nullptr) { - push_error(R"("extends" should be used before annotations (except @tool).)"); - } advance(); if (head->extends_used) { push_error(R"("extends" can only be used once.)"); @@ -597,7 +562,8 @@ void GDScriptParser::parse_program() { } break; default: - should_break = true; + // No tokens are allowed between script annotations and class/extends. + can_have_class_or_extends = false; break; } @@ -606,21 +572,6 @@ void GDScriptParser::parse_program() { } } - if (match(GDScriptTokenizer::Token::ANNOTATION)) { - // Check for a script-level, or standalone annotation. - AnnotationNode *annotation = parse_annotation(AnnotationInfo::SCRIPT | AnnotationInfo::STANDALONE | AnnotationInfo::CLASS_LEVEL); - if (annotation != nullptr) { - if (annotation->applies_to(AnnotationInfo::SCRIPT | AnnotationInfo::STANDALONE)) { - if (previous.type != GDScriptTokenizer::Token::NEWLINE) { - push_error(R"(Expected newline after a standalone annotation.)"); - } - annotation->apply(this, head); - } else { - annotation_stack.push_back(annotation); - } - } - } - parse_class_body(true); complete_extents(head); @@ -820,7 +771,6 @@ void GDScriptParser::parse_class_member(T *(GDScriptParser::*p_parse_function)() return; } - // Apply annotations. for (AnnotationNode *&annotation : annotations) { member->annotations.push_back(annotation); } @@ -897,7 +847,7 @@ void GDScriptParser::parse_class_body(bool p_is_multiline) { if (previous.type != GDScriptTokenizer::Token::NEWLINE) { push_error(R"(Expected newline after a standalone annotation.)"); } - annotation->apply(this, head); + head->annotations.push_back(annotation); } else { annotation_stack.push_back(annotation); } @@ -1483,7 +1433,11 @@ GDScriptParser::AnnotationNode *GDScriptParser::parse_annotation(uint32_t p_vali annotation->info = &valid_annotations[annotation->name]; if (!annotation->applies_to(p_valid_targets)) { - push_error(vformat(R"(Annotation "%s" is not allowed in this level.)", annotation->name)); + 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; } @@ -1515,7 +1469,7 @@ GDScriptParser::AnnotationNode *GDScriptParser::parse_annotation(uint32_t p_vali match(GDScriptTokenizer::Token::NEWLINE); // Newline after annotation is optional. if (valid) { - valid = validate_annotation_arguments(annotation); + valid = validate_annotation_argument_count(annotation); } return valid ? annotation : nullptr; @@ -1746,6 +1700,7 @@ GDScriptParser::Node *GDScriptParser::parse_statement() { case Node::CALL: case Node::ASSIGNMENT: case Node::AWAIT: + case Node::TERNARY_OPERATOR: // Fine. break; case Node::LAMBDA: @@ -1761,7 +1716,6 @@ 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)) { @@ -1838,11 +1792,10 @@ GDScriptParser::BreakNode *GDScriptParser::parse_break() { GDScriptParser::ContinueNode *GDScriptParser::parse_continue() { if (!can_continue) { - push_error(R"(Cannot use "continue" outside of a loop or pattern matching block.)"); + push_error(R"(Cannot use "continue" outside of a loop.)"); } current_suite->has_continue = true; ContinueNode *cont = alloc_node<ContinueNode>(); - cont->is_for_match = is_continue_match; complete_extents(cont); end_statement(R"("continue")"); return cont; @@ -1868,12 +1821,10 @@ GDScriptParser::ForNode *GDScriptParser::parse_for() { // Save break/continue state. bool could_break = can_break; bool could_continue = can_continue; - bool was_continue_match = is_continue_match; // Allow break/continue. can_break = true; can_continue = true; - is_continue_match = false; SuiteNode *suite = alloc_node<SuiteNode>(); if (n_for->variable) { @@ -1891,7 +1842,6 @@ GDScriptParser::ForNode *GDScriptParser::parse_for() { // Reset break/continue state. can_break = could_break; can_continue = could_continue; - is_continue_match = was_continue_match; return n_for; } @@ -2028,13 +1978,6 @@ GDScriptParser::MatchBranchNode *GDScriptParser::parse_match_branch() { return nullptr; } - // Save continue state. - bool could_continue = can_continue; - bool was_continue_match = is_continue_match; - // Allow continue for match. - can_continue = true; - is_continue_match = true; - SuiteNode *suite = alloc_node<SuiteNode>(); if (branch->patterns.size() > 0) { for (const KeyValue<StringName, IdentifierNode *> &E : branch->patterns[0]->binds) { @@ -2047,10 +1990,6 @@ GDScriptParser::MatchBranchNode *GDScriptParser::parse_match_branch() { branch->block = parse_suite("match pattern block", suite); complete_extents(branch); - // Restore continue state. - can_continue = could_continue; - is_continue_match = was_continue_match; - return branch; } @@ -2209,12 +2148,10 @@ GDScriptParser::WhileNode *GDScriptParser::parse_while() { // Save break/continue state. bool could_break = can_break; bool could_continue = can_continue; - bool was_continue_match = is_continue_match; // Allow break/continue. can_break = true; can_continue = true; - is_continue_match = false; n_while->loop = parse_suite(R"("while" block)"); n_while->loop->is_loop = true; @@ -2223,7 +2160,6 @@ GDScriptParser::WhileNode *GDScriptParser::parse_while() { // Reset break/continue state. can_break = could_break; can_continue = could_continue; - is_continue_match = was_continue_match; return n_while; } @@ -2283,7 +2219,14 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_expression(bool p_can_assi } GDScriptParser::IdentifierNode *GDScriptParser::parse_identifier() { - return static_cast<IdentifierNode *>(parse_identifier(nullptr, false)); + IdentifierNode *identifier = static_cast<IdentifierNode *>(parse_identifier(nullptr, false)); +#ifdef DEBUG_ENABLED + // Check for spoofing here (if available in TextServer) since this isn't called inside expressions. This is only relevant for declarations. + if (identifier && TS->has_feature(TextServer::FEATURE_UNICODE_SECURITY) && TS->spoof_check(identifier->name.operator String())) { + push_warning(identifier, GDScriptWarning::CONFUSABLE_IDENTIFIER, identifier->name.operator String()); + } +#endif + return identifier; } GDScriptParser::ExpressionNode *GDScriptParser::parse_identifier(ExpressionNode *p_previous_operand, bool p_can_assign) { @@ -3653,7 +3596,7 @@ bool GDScriptParser::AnnotationNode::applies_to(uint32_t p_target_kinds) const { return (info->target_kind & p_target_kinds) > 0; } -bool GDScriptParser::validate_annotation_arguments(AnnotationNode *p_annotation) { +bool GDScriptParser::validate_annotation_argument_count(AnnotationNode *p_annotation) { ERR_FAIL_COND_V_MSG(!valid_annotations.has(p_annotation->name), false, vformat(R"(Annotation "%s" not found to validate.)", p_annotation->name)); const MethodInfo &info = valid_annotations[p_annotation->name].info; @@ -3668,62 +3611,6 @@ bool GDScriptParser::validate_annotation_arguments(AnnotationNode *p_annotation) return false; } - const List<PropertyInfo>::Element *E = info.arguments.front(); - for (int i = 0; i < p_annotation->arguments.size(); i++) { - ExpressionNode *argument = p_annotation->arguments[i]; - const PropertyInfo ¶meter = E->get(); - - if (E->next() != nullptr) { - E = E->next(); - } - - switch (parameter.type) { - case Variant::STRING: - case Variant::STRING_NAME: - case Variant::NODE_PATH: - // Allow "quote-less strings", as long as they are recognized as identifiers. - if (argument->type == Node::IDENTIFIER) { - IdentifierNode *string = static_cast<IdentifierNode *>(argument); - Callable::CallError error; - Vector<Variant> args = varray(string->name); - const Variant *name = args.ptr(); - Variant r; - 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_at(p_annotation->resolved_arguments.size() - 1); - return false; - } - break; - } - [[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)); - 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)); - return false; - } - Callable::CallError error; - const Variant *args = &value; - Variant r; - 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_at(p_annotation->resolved_arguments.size() - 1); - return false; - } - break; - } - } - } - return true; } |