Path: blob/master/modules/gdscript/gdscript_parser.cpp
20854 views
/**************************************************************************/1/* gdscript_parser.cpp */2/**************************************************************************/3/* This file is part of: */4/* GODOT ENGINE */5/* https://godotengine.org */6/**************************************************************************/7/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */8/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */9/* */10/* Permission is hereby granted, free of charge, to any person obtaining */11/* a copy of this software and associated documentation files (the */12/* "Software"), to deal in the Software without restriction, including */13/* without limitation the rights to use, copy, modify, merge, publish, */14/* distribute, sublicense, and/or sell copies of the Software, and to */15/* permit persons to whom the Software is furnished to do so, subject to */16/* the following conditions: */17/* */18/* The above copyright notice and this permission notice shall be */19/* included in all copies or substantial portions of the Software. */20/* */21/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */22/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */23/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */24/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */25/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */26/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */27/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */28/**************************************************************************/2930#include "gdscript_parser.h"3132#include "gdscript.h"33#include "gdscript_tokenizer_buffer.h"3435#include "core/config/project_settings.h"36#include "core/io/resource_loader.h"37#include "core/math/math_defs.h"38#include "scene/main/multiplayer_api.h"3940#ifdef DEBUG_ENABLED41#include "core/string/string_builder.h"42#include "servers/text/text_server.h"43#endif4445#ifdef TOOLS_ENABLED46#include "editor/settings/editor_settings.h"47#endif4849// This function is used to determine that a type is "built-in" as opposed to native50// and custom classes. So `Variant::NIL` and `Variant::OBJECT` are excluded:51// `Variant::NIL` - `null` is literal, not a type.52// `Variant::OBJECT` - `Object` should be treated as a class, not as a built-in type.53static HashMap<StringName, Variant::Type> builtin_types;54Variant::Type GDScriptParser::get_builtin_type(const StringName &p_type) {55if (unlikely(builtin_types.is_empty())) {56for (int i = 0; i < Variant::VARIANT_MAX; i++) {57Variant::Type type = (Variant::Type)i;58if (type != Variant::NIL && type != Variant::OBJECT) {59builtin_types[Variant::get_type_name(type)] = type;60}61}62}6364if (builtin_types.has(p_type)) {65return builtin_types[p_type];66}67return Variant::VARIANT_MAX;68}6970#ifdef DEBUG_ENABLED71bool GDScriptParser::is_project_ignoring_warnings = false;72GDScriptWarning::WarnLevel GDScriptParser::warning_levels[GDScriptWarning::WARNING_MAX];73LocalVector<GDScriptParser::WarningDirectoryRule> GDScriptParser::warning_directory_rules;74#endif // DEBUG_ENABLED7576#ifdef TOOLS_ENABLED77HashMap<String, String> GDScriptParser::theme_color_names;78#endif // TOOLS_ENABLED7980HashMap<StringName, GDScriptParser::AnnotationInfo> GDScriptParser::valid_annotations;8182void GDScriptParser::cleanup() {83builtin_types.clear();84valid_annotations.clear();85}8687void GDScriptParser::get_annotation_list(List<MethodInfo> *r_annotations) const {88for (const KeyValue<StringName, AnnotationInfo> &E : valid_annotations) {89r_annotations->push_back(E.value.info);90}91}9293bool GDScriptParser::annotation_exists(const String &p_annotation_name) const {94return valid_annotations.has(p_annotation_name);95}9697#ifdef DEBUG_ENABLED98void GDScriptParser::update_project_settings() {99is_project_ignoring_warnings = !GLOBAL_GET("debug/gdscript/warnings/enable").booleanize();100101for (int i = 0; i < GDScriptWarning::WARNING_MAX; i++) {102const String setting_path = GDScriptWarning::get_setting_path_from_code((GDScriptWarning::Code)i);103warning_levels[i] = (GDScriptWarning::WarnLevel)(int)GLOBAL_GET(setting_path);104}105106#ifndef DISABLE_DEPRECATED107// We do not use `GLOBAL_GET`, since we check without taking overrides into account. We leave the migration of non-trivial configurations to the user.108if (unlikely(ProjectSettings::get_singleton()->has_setting("debug/gdscript/warnings/exclude_addons"))) {109const bool is_excluding_addons = ProjectSettings::get_singleton()->get_setting("debug/gdscript/warnings/exclude_addons", true).booleanize();110ProjectSettings::get_singleton()->clear("debug/gdscript/warnings/exclude_addons");111112Dictionary rules = ProjectSettings::get_singleton()->get_setting("debug/gdscript/warnings/directory_rules");113rules["res://addons"] = is_excluding_addons ? WarningDirectoryRule::DECISION_EXCLUDE : WarningDirectoryRule::DECISION_INCLUDE;114ProjectSettings::get_singleton()->set_setting("debug/gdscript/warnings/directory_rules", rules);115}116#endif // DISABLE_DEPRECATED117118warning_directory_rules.clear();119120const Dictionary rules = GLOBAL_GET("debug/gdscript/warnings/directory_rules");121for (const KeyValue<Variant, Variant> &kv : rules) {122String dir = kv.key.operator String().simplify_path();123ERR_CONTINUE_MSG(!dir.begins_with("res://"), R"(Paths in the project setting "debug/gdscript/warnings/directory_rules" keys must start with the "res://" prefix.)");124if (!dir.ends_with("/")) {125dir += '/';126}127128const int decision = kv.value;129ERR_CONTINUE(decision < 0 || decision >= WarningDirectoryRule::DECISION_MAX);130131warning_directory_rules.push_back({ dir, (WarningDirectoryRule::Decision)decision });132}133134struct RuleSort {135bool operator()(const WarningDirectoryRule &p_a, const WarningDirectoryRule &p_b) const {136return p_a.directory_path.count("/") > p_b.directory_path.count("/");137}138};139140warning_directory_rules.sort_custom<RuleSort>();141}142#endif // DEBUG_ENABLED143144GDScriptParser::GDScriptParser() {145// Register valid annotations.146if (unlikely(valid_annotations.is_empty())) {147// Script annotations.148register_annotation(MethodInfo("@tool"), AnnotationInfo::SCRIPT, &GDScriptParser::tool_annotation);149register_annotation(MethodInfo("@icon", PropertyInfo(Variant::STRING, "icon_path")), AnnotationInfo::SCRIPT, &GDScriptParser::icon_annotation);150register_annotation(MethodInfo("@static_unload"), AnnotationInfo::SCRIPT, &GDScriptParser::static_unload_annotation);151register_annotation(MethodInfo("@abstract"), AnnotationInfo::SCRIPT | AnnotationInfo::CLASS | AnnotationInfo::FUNCTION, &GDScriptParser::abstract_annotation);152// Onready annotation.153register_annotation(MethodInfo("@onready"), AnnotationInfo::VARIABLE, &GDScriptParser::onready_annotation);154// Export annotations.155register_annotation(MethodInfo("@export"), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_NONE, Variant::NIL>);156register_annotation(MethodInfo("@export_enum", PropertyInfo(Variant::STRING, "names")), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_ENUM, Variant::NIL>, varray(), true);157register_annotation(MethodInfo("@export_file", PropertyInfo(Variant::STRING, "filter")), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_FILE, Variant::STRING>, varray(""), true);158register_annotation(MethodInfo("@export_file_path", PropertyInfo(Variant::STRING, "filter")), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_FILE_PATH, Variant::STRING>, varray(""), true);159register_annotation(MethodInfo("@export_dir"), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_DIR, Variant::STRING>);160register_annotation(MethodInfo("@export_global_file", PropertyInfo(Variant::STRING, "filter")), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_GLOBAL_FILE, Variant::STRING>, varray(""), true);161register_annotation(MethodInfo("@export_global_dir"), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_GLOBAL_DIR, Variant::STRING>);162register_annotation(MethodInfo("@export_multiline", PropertyInfo(Variant::STRING, "hint")), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_MULTILINE_TEXT, Variant::STRING>, varray(""), true);163register_annotation(MethodInfo("@export_placeholder", PropertyInfo(Variant::STRING, "placeholder")), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_PLACEHOLDER_TEXT, Variant::STRING>);164register_annotation(MethodInfo("@export_range", PropertyInfo(Variant::FLOAT, "min"), PropertyInfo(Variant::FLOAT, "max"), PropertyInfo(Variant::FLOAT, "step"), PropertyInfo(Variant::STRING, "extra_hints")), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_RANGE, Variant::FLOAT>, varray(1.0, ""), true);165register_annotation(MethodInfo("@export_exp_easing", PropertyInfo(Variant::STRING, "hints")), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_EXP_EASING, Variant::FLOAT>, varray(""), true);166register_annotation(MethodInfo("@export_color_no_alpha"), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_COLOR_NO_ALPHA, Variant::COLOR>);167register_annotation(MethodInfo("@export_node_path", PropertyInfo(Variant::STRING, "type")), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_NODE_PATH_VALID_TYPES, Variant::NODE_PATH>, varray(""), true);168register_annotation(MethodInfo("@export_flags", PropertyInfo(Variant::STRING, "names")), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_FLAGS, Variant::INT>, varray(), true);169register_annotation(MethodInfo("@export_flags_2d_render"), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_LAYERS_2D_RENDER, Variant::INT>);170register_annotation(MethodInfo("@export_flags_2d_physics"), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_LAYERS_2D_PHYSICS, Variant::INT>);171register_annotation(MethodInfo("@export_flags_2d_navigation"), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_LAYERS_2D_NAVIGATION, Variant::INT>);172register_annotation(MethodInfo("@export_flags_3d_render"), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_LAYERS_3D_RENDER, Variant::INT>);173register_annotation(MethodInfo("@export_flags_3d_physics"), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_LAYERS_3D_PHYSICS, Variant::INT>);174register_annotation(MethodInfo("@export_flags_3d_navigation"), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_LAYERS_3D_NAVIGATION, Variant::INT>);175register_annotation(MethodInfo("@export_flags_avoidance"), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_LAYERS_AVOIDANCE, Variant::INT>);176register_annotation(MethodInfo("@export_storage"), AnnotationInfo::VARIABLE, &GDScriptParser::export_storage_annotation);177register_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));178register_annotation(MethodInfo("@export_tool_button", PropertyInfo(Variant::STRING, "text"), PropertyInfo(Variant::STRING, "icon")), AnnotationInfo::VARIABLE, &GDScriptParser::export_tool_button_annotation, varray(""));179// Export grouping annotations.180register_annotation(MethodInfo("@export_category", PropertyInfo(Variant::STRING, "name")), AnnotationInfo::STANDALONE, &GDScriptParser::export_group_annotations<PROPERTY_USAGE_CATEGORY>);181register_annotation(MethodInfo("@export_group", PropertyInfo(Variant::STRING, "name"), PropertyInfo(Variant::STRING, "prefix")), AnnotationInfo::STANDALONE, &GDScriptParser::export_group_annotations<PROPERTY_USAGE_GROUP>, varray(""));182register_annotation(MethodInfo("@export_subgroup", PropertyInfo(Variant::STRING, "name"), PropertyInfo(Variant::STRING, "prefix")), AnnotationInfo::STANDALONE, &GDScriptParser::export_group_annotations<PROPERTY_USAGE_SUBGROUP>, varray(""));183// Warning annotations.184register_annotation(MethodInfo("@warning_ignore", PropertyInfo(Variant::STRING, "warning")), AnnotationInfo::CLASS_LEVEL | AnnotationInfo::STATEMENT, &GDScriptParser::warning_ignore_annotation, varray(), true);185register_annotation(MethodInfo("@warning_ignore_start", PropertyInfo(Variant::STRING, "warning")), AnnotationInfo::STANDALONE, &GDScriptParser::warning_ignore_region_annotations, varray(), true);186register_annotation(MethodInfo("@warning_ignore_restore", PropertyInfo(Variant::STRING, "warning")), AnnotationInfo::STANDALONE, &GDScriptParser::warning_ignore_region_annotations, varray(), true);187// Networking.188// Keep in sync with `rpc_annotation()` and `SceneRPCInterface::_parse_rpc_config()`.189register_annotation(MethodInfo("@rpc", PropertyInfo(Variant::STRING, "mode"), PropertyInfo(Variant::STRING, "sync"), PropertyInfo(Variant::STRING, "transfer_mode"), PropertyInfo(Variant::INT, "transfer_channel")), AnnotationInfo::FUNCTION, &GDScriptParser::rpc_annotation, varray("authority", "call_remote", "reliable", 0));190}191192#ifdef DEBUG_ENABLED193for (int i = 0; i < GDScriptWarning::WARNING_MAX; i++) {194warning_ignore_start_lines[i] = INT_MAX;195}196#endif // DEBUG_ENABLED197198#ifdef TOOLS_ENABLED199if (unlikely(theme_color_names.is_empty())) {200// Vectors.201theme_color_names.insert("x", "axis_x_color");202theme_color_names.insert("y", "axis_y_color");203theme_color_names.insert("z", "axis_z_color");204theme_color_names.insert("w", "axis_w_color");205206// Color.207theme_color_names.insert("r", "axis_x_color");208theme_color_names.insert("r8", "axis_x_color");209theme_color_names.insert("g", "axis_y_color");210theme_color_names.insert("g8", "axis_y_color");211theme_color_names.insert("b", "axis_z_color");212theme_color_names.insert("b8", "axis_z_color");213theme_color_names.insert("a", "axis_w_color");214theme_color_names.insert("a8", "axis_w_color");215}216#endif // TOOLS_ENABLED217}218219GDScriptParser::~GDScriptParser() {220while (list != nullptr) {221Node *element = list;222list = list->next;223memdelete(element);224}225}226227void GDScriptParser::clear() {228GDScriptParser tmp;229tmp = *this;230*this = GDScriptParser();231}232233void GDScriptParser::push_error(const String &p_message, const Node *p_origin) {234// TODO: Improve error reporting by pointing at source code.235// TODO: Errors might point at more than one place at once (e.g. show previous declaration).236panic_mode = true;237ParserError err;238err.message = p_message;239240if (p_origin == nullptr) {241err.start_line = previous.start_line;242err.start_column = previous.start_column;243err.end_line = previous.end_line;244err.end_column = previous.end_column;245} else {246err.start_line = p_origin->start_line;247err.start_column = p_origin->start_column;248err.end_line = p_origin->end_line;249err.end_column = p_origin->end_column;250}251252errors.push_back(err);253}254255#ifdef DEBUG_ENABLED256void GDScriptParser::push_warning(const Node *p_source, GDScriptWarning::Code p_code, const Vector<String> &p_symbols) {257ERR_FAIL_NULL(p_source);258ERR_FAIL_INDEX(p_code, GDScriptWarning::WARNING_MAX);259260if (is_project_ignoring_warnings || is_script_ignoring_warnings) {261return;262}263264const GDScriptWarning::WarnLevel warn_level = warning_levels[p_code];265if (warn_level == GDScriptWarning::IGNORE) {266return;267}268269PendingWarning pw;270pw.source = p_source;271pw.code = p_code;272pw.treated_as_error = warn_level == GDScriptWarning::ERROR;273pw.symbols = p_symbols;274275pending_warnings.push_back(pw);276}277278void GDScriptParser::apply_pending_warnings() {279for (const PendingWarning &pw : pending_warnings) {280if (warning_ignored_lines[pw.code].has(pw.source->start_line)) {281continue;282}283if (warning_ignore_start_lines[pw.code] <= pw.source->start_line) {284continue;285}286287GDScriptWarning warning;288warning.code = pw.code;289warning.symbols = pw.symbols;290warning.start_line = pw.source->start_line;291warning.start_column = pw.source->start_column;292warning.end_line = pw.source->end_line;293warning.end_column = pw.source->end_column;294295if (pw.treated_as_error) {296push_error(warning.get_message() + String(" (Warning treated as error.)"), pw.source);297continue;298}299300List<GDScriptWarning>::Element *before = nullptr;301for (List<GDScriptWarning>::Element *E = warnings.front(); E; E = E->next()) {302if (E->get().start_line > warning.start_line) {303break;304}305before = E;306}307if (before) {308warnings.insert_after(before, warning);309} else {310warnings.push_front(warning);311}312}313314pending_warnings.clear();315}316317void GDScriptParser::evaluate_warning_directory_rules_for_script_path() {318is_script_ignoring_warnings = false;319for (const WarningDirectoryRule &rule : warning_directory_rules) {320if (script_path.begins_with(rule.directory_path)) {321switch (rule.decision) {322case WarningDirectoryRule::DECISION_EXCLUDE:323is_script_ignoring_warnings = true;324return; // Stop checking rules.325case WarningDirectoryRule::DECISION_INCLUDE:326is_script_ignoring_warnings = false;327return; // Stop checking rules.328case WarningDirectoryRule::DECISION_MAX:329return; // Unreachable.330}331}332}333}334#endif // DEBUG_ENABLED335336void GDScriptParser::override_completion_context(const Node *p_for_node, CompletionType p_type, Node *p_node, int p_argument) {337if (!for_completion) {338return;339}340if (p_for_node == nullptr || completion_context.node != p_for_node) {341return;342}343CompletionContext context;344context.type = p_type;345context.current_class = current_class;346context.current_function = current_function;347context.current_suite = current_suite;348context.current_line = tokenizer->get_cursor_line();349context.current_argument = p_argument;350context.node = p_node;351context.parser = this;352if (!completion_call_stack.is_empty()) {353context.call = completion_call_stack.back()->get();354}355completion_context = context;356}357358void GDScriptParser::make_completion_context(CompletionType p_type, Node *p_node, int p_argument, bool p_force) {359if (!for_completion || (!p_force && completion_context.type != COMPLETION_NONE)) {360return;361}362if (previous.cursor_place != GDScriptTokenizerText::CURSOR_MIDDLE && previous.cursor_place != GDScriptTokenizerText::CURSOR_END && current.cursor_place == GDScriptTokenizerText::CURSOR_NONE) {363return;364}365CompletionContext context;366context.type = p_type;367context.current_class = current_class;368context.current_function = current_function;369context.current_suite = current_suite;370context.current_line = tokenizer->get_cursor_line();371context.current_argument = p_argument;372context.node = p_node;373context.parser = this;374if (!completion_call_stack.is_empty()) {375context.call = completion_call_stack.back()->get();376}377completion_context = context;378}379380void GDScriptParser::make_completion_context(CompletionType p_type, Variant::Type p_builtin_type, bool p_force) {381if (!for_completion || (!p_force && completion_context.type != COMPLETION_NONE)) {382return;383}384if (previous.cursor_place != GDScriptTokenizerText::CURSOR_MIDDLE && previous.cursor_place != GDScriptTokenizerText::CURSOR_END && current.cursor_place == GDScriptTokenizerText::CURSOR_NONE) {385return;386}387CompletionContext context;388context.type = p_type;389context.current_class = current_class;390context.current_function = current_function;391context.current_suite = current_suite;392context.current_line = tokenizer->get_cursor_line();393context.builtin_type = p_builtin_type;394context.parser = this;395if (!completion_call_stack.is_empty()) {396context.call = completion_call_stack.back()->get();397}398completion_context = context;399}400401void GDScriptParser::push_completion_call(Node *p_call) {402if (!for_completion) {403return;404}405CompletionCall call;406call.call = p_call;407call.argument = 0;408completion_call_stack.push_back(call);409}410411void GDScriptParser::pop_completion_call() {412if (!for_completion) {413return;414}415ERR_FAIL_COND_MSG(completion_call_stack.is_empty(), "Trying to pop empty completion call stack");416completion_call_stack.pop_back();417}418419void GDScriptParser::set_last_completion_call_arg(int p_argument) {420if (!for_completion) {421return;422}423ERR_FAIL_COND_MSG(completion_call_stack.is_empty(), "Trying to set argument on empty completion call stack");424completion_call_stack.back()->get().argument = p_argument;425}426427Error GDScriptParser::parse(const String &p_source_code, const String &p_script_path, bool p_for_completion, bool p_parse_body) {428clear();429430String source = p_source_code;431int cursor_line = -1;432int cursor_column = -1;433for_completion = p_for_completion;434parse_body = p_parse_body;435436int tab_size = 4;437#ifdef TOOLS_ENABLED438if (EditorSettings::get_singleton()) {439tab_size = EditorSettings::get_singleton()->get_setting("text_editor/behavior/indent/size");440}441#endif // TOOLS_ENABLED442443if (p_for_completion) {444// Remove cursor sentinel char.445const Vector<String> lines = p_source_code.split("\n");446cursor_line = 1;447cursor_column = 1;448for (int i = 0; i < lines.size(); i++) {449bool found = false;450const String &line = lines[i];451for (int j = 0; j < line.size(); j++) {452if (line[j] == char32_t(0xFFFF)) {453found = true;454break;455} else if (line[j] == '\t') {456cursor_column += tab_size - 1;457}458cursor_column++;459}460if (found) {461break;462}463cursor_line++;464cursor_column = 1;465}466467source = source.replace_first(String::chr(0xFFFF), String());468}469470GDScriptTokenizerText *text_tokenizer = memnew(GDScriptTokenizerText);471text_tokenizer->set_source_code(source);472473tokenizer = text_tokenizer;474tokenizer->set_cursor_position(cursor_line, cursor_column);475476script_path = p_script_path.simplify_path();477478#ifdef DEBUG_ENABLED479evaluate_warning_directory_rules_for_script_path();480#endif // DEBUG_ENABLED481482current = tokenizer->scan();483// Avoid error or newline as the first token.484// The latter can mess with the parser when opening files filled exclusively with comments and newlines.485while (current.type == GDScriptTokenizer::Token::ERROR || current.type == GDScriptTokenizer::Token::NEWLINE) {486if (current.type == GDScriptTokenizer::Token::ERROR) {487push_error(current.literal);488}489current = tokenizer->scan();490}491492#ifdef DEBUG_ENABLED493// Warn about parsing an empty script file:494if (current.type == GDScriptTokenizer::Token::TK_EOF) {495// Create a dummy Node for the warning, pointing to the very beginning of the file496Node *nd = alloc_node<PassNode>();497nd->start_line = 1;498nd->start_column = 0;499nd->end_line = 1;500push_warning(nd, GDScriptWarning::EMPTY_FILE);501}502#endif // DEBUG_ENABLED503504push_multiline(false); // Keep one for the whole parsing.505parse_program();506pop_multiline();507508#ifdef TOOLS_ENABLED509comment_data = tokenizer->get_comments();510#endif // TOOLS_ENABLED511512memdelete(text_tokenizer);513tokenizer = nullptr;514515#ifdef DEBUG_ENABLED516if (multiline_stack.size() > 0) {517ERR_PRINT("Parser bug: Imbalanced multiline stack.");518}519#endif // DEBUG_ENABLED520521if (errors.is_empty()) {522return OK;523} else {524return ERR_PARSE_ERROR;525}526}527528Error GDScriptParser::parse_binary(const Vector<uint8_t> &p_binary, const String &p_script_path) {529GDScriptTokenizerBuffer *buffer_tokenizer = memnew(GDScriptTokenizerBuffer);530Error err = buffer_tokenizer->set_code_buffer(p_binary);531532if (err) {533memdelete(buffer_tokenizer);534return err;535}536537tokenizer = buffer_tokenizer;538539script_path = p_script_path.simplify_path();540541#ifdef DEBUG_ENABLED542evaluate_warning_directory_rules_for_script_path();543#endif // DEBUG_ENABLED544545current = tokenizer->scan();546// Avoid error or newline as the first token.547// The latter can mess with the parser when opening files filled exclusively with comments and newlines.548while (current.type == GDScriptTokenizer::Token::ERROR || current.type == GDScriptTokenizer::Token::NEWLINE) {549if (current.type == GDScriptTokenizer::Token::ERROR) {550push_error(current.literal);551}552current = tokenizer->scan();553}554555push_multiline(false); // Keep one for the whole parsing.556parse_program();557pop_multiline();558559memdelete(buffer_tokenizer);560tokenizer = nullptr;561562if (errors.is_empty()) {563return OK;564} else {565return ERR_PARSE_ERROR;566}567}568569GDScriptTokenizer::Token GDScriptParser::advance() {570lambda_ended = false; // Empty marker since we're past the end in any case.571572if (current.type == GDScriptTokenizer::Token::TK_EOF) {573ERR_FAIL_COND_V_MSG(current.type == GDScriptTokenizer::Token::TK_EOF, current, "GDScript parser bug: Trying to advance past the end of stream.");574}575previous = current;576current = tokenizer->scan();577while (current.type == GDScriptTokenizer::Token::ERROR) {578push_error(current.literal);579current = tokenizer->scan();580}581if (previous.type != GDScriptTokenizer::Token::DEDENT) { // `DEDENT` belongs to the next non-empty line.582for (Node *n : nodes_in_progress) {583update_extents(n);584}585}586return previous;587}588589bool GDScriptParser::match(GDScriptTokenizer::Token::Type p_token_type) {590if (!check(p_token_type)) {591return false;592}593advance();594return true;595}596597bool GDScriptParser::check(GDScriptTokenizer::Token::Type p_token_type) const {598if (p_token_type == GDScriptTokenizer::Token::IDENTIFIER) {599return current.is_identifier();600}601return current.type == p_token_type;602}603604bool GDScriptParser::consume(GDScriptTokenizer::Token::Type p_token_type, const String &p_error_message) {605if (match(p_token_type)) {606return true;607}608push_error(p_error_message);609return false;610}611612bool GDScriptParser::is_at_end() const {613return check(GDScriptTokenizer::Token::TK_EOF);614}615616void GDScriptParser::synchronize() {617panic_mode = false;618while (!is_at_end()) {619if (previous.type == GDScriptTokenizer::Token::NEWLINE || previous.type == GDScriptTokenizer::Token::SEMICOLON) {620return;621}622623switch (current.type) {624case GDScriptTokenizer::Token::CLASS:625case GDScriptTokenizer::Token::FUNC:626case GDScriptTokenizer::Token::STATIC:627case GDScriptTokenizer::Token::VAR:628case GDScriptTokenizer::Token::TK_CONST:629case GDScriptTokenizer::Token::SIGNAL:630//case GDScriptTokenizer::Token::IF: // Can also be inside expressions.631case GDScriptTokenizer::Token::FOR:632case GDScriptTokenizer::Token::WHILE:633case GDScriptTokenizer::Token::MATCH:634case GDScriptTokenizer::Token::RETURN:635case GDScriptTokenizer::Token::ANNOTATION:636return;637default:638// Do nothing.639break;640}641642advance();643}644}645646void GDScriptParser::push_multiline(bool p_state) {647multiline_stack.push_back(p_state);648tokenizer->set_multiline_mode(p_state);649if (p_state) {650// Consume potential whitespace tokens already waiting in line.651while (current.type == GDScriptTokenizer::Token::NEWLINE || current.type == GDScriptTokenizer::Token::INDENT || current.type == GDScriptTokenizer::Token::DEDENT) {652current = tokenizer->scan(); // Don't call advance() here, as we don't want to change the previous token.653}654}655}656657void GDScriptParser::pop_multiline() {658ERR_FAIL_COND_MSG(multiline_stack.is_empty(), "Parser bug: trying to pop from multiline stack without available value.");659multiline_stack.pop_back();660tokenizer->set_multiline_mode(multiline_stack.size() > 0 ? multiline_stack.back()->get() : false);661}662663bool GDScriptParser::is_statement_end_token() const {664return check(GDScriptTokenizer::Token::NEWLINE) || check(GDScriptTokenizer::Token::SEMICOLON) || check(GDScriptTokenizer::Token::TK_EOF);665}666667bool GDScriptParser::is_statement_end() const {668return lambda_ended || in_lambda || is_statement_end_token();669}670671void GDScriptParser::end_statement(const String &p_context) {672bool found = false;673while (is_statement_end() && !is_at_end()) {674// Remove sequential newlines/semicolons.675if (is_statement_end_token()) {676// Only consume if this is an actual token.677advance();678} else if (lambda_ended) {679lambda_ended = false; // Consume this "token".680found = true;681break;682} else {683if (!found) {684lambda_ended = true; // Mark the lambda as done since we found something else to end the statement.685found = true;686}687break;688}689690found = true;691}692if (!found && !is_at_end()) {693push_error(vformat(R"(Expected end of statement after %s, found "%s" instead.)", p_context, current.get_name()));694}695}696697void GDScriptParser::parse_program() {698head = alloc_node<ClassNode>();699head->start_line = 1;700head->end_line = 1;701head->fqcn = GDScript::canonicalize_path(script_path);702current_class = head;703bool can_have_class_or_extends = true;704705#define PUSH_PENDING_ANNOTATIONS_TO_HEAD \706if (!annotation_stack.is_empty()) { \707for (AnnotationNode *annot : annotation_stack) { \708head->annotations.push_back(annot); \709} \710annotation_stack.clear(); \711}712713while (!check(GDScriptTokenizer::Token::TK_EOF)) {714if (match(GDScriptTokenizer::Token::ANNOTATION)) {715AnnotationNode *annotation = parse_annotation(AnnotationInfo::SCRIPT | AnnotationInfo::CLASS_LEVEL | AnnotationInfo::STANDALONE);716if (annotation != nullptr) {717if (annotation->applies_to(AnnotationInfo::CLASS)) {718// We do not know in advance what the annotation will be applied to: the `head` class or the subsequent inner class.719// If we encounter `class_name`, `extends` or pure `SCRIPT` annotation, then it's `head`, otherwise it's an inner class.720annotation_stack.push_back(annotation);721} else if (annotation->applies_to(AnnotationInfo::SCRIPT)) {722PUSH_PENDING_ANNOTATIONS_TO_HEAD;723if (annotation->name == SNAME("@tool") || annotation->name == SNAME("@icon") || annotation->name == SNAME("@static_unload")) {724// Some annotations need to be resolved and applied in the parser.725// The root class is not in any class, so `head->outer == nullptr`.726annotation->apply(this, head, nullptr);727} else {728head->annotations.push_back(annotation);729}730} else if (annotation->applies_to(AnnotationInfo::STANDALONE)) {731if (previous.type != GDScriptTokenizer::Token::NEWLINE) {732push_error(R"(Expected newline after a standalone annotation.)");733}734if (annotation->name == SNAME("@export_category") || annotation->name == SNAME("@export_group") || annotation->name == SNAME("@export_subgroup")) {735head->add_member_group(annotation);736// This annotation must appear after script-level annotations and `class_name`/`extends`,737// so we stop looking for script-level stuff.738can_have_class_or_extends = false;739break;740} else if (annotation->name == SNAME("@warning_ignore_start") || annotation->name == SNAME("@warning_ignore_restore")) {741// Some annotations need to be resolved and applied in the parser.742annotation->apply(this, nullptr, nullptr);743} else {744push_error(R"(Unexpected standalone annotation.)");745}746} else {747annotation_stack.push_back(annotation);748// This annotation must appear after script-level annotations and `class_name`/`extends`,749// so we stop looking for script-level stuff.750can_have_class_or_extends = false;751break;752}753}754} else if (check(GDScriptTokenizer::Token::LITERAL) && current.literal.get_type() == Variant::STRING) {755// Allow strings in class body as multiline comments.756advance();757if (!match(GDScriptTokenizer::Token::NEWLINE)) {758push_error("Expected newline after comment string.");759}760} else {761break;762}763}764765if (current.type == GDScriptTokenizer::Token::CLASS_NAME || current.type == GDScriptTokenizer::Token::EXTENDS) {766// Set range of the class to only start at extends or class_name if present.767reset_extents(head, current);768}769770while (can_have_class_or_extends) {771// Order here doesn't matter, but there should be only one of each at most.772switch (current.type) {773case GDScriptTokenizer::Token::CLASS_NAME:774PUSH_PENDING_ANNOTATIONS_TO_HEAD;775advance();776if (head->identifier != nullptr) {777push_error(R"("class_name" can only be used once.)");778} else {779parse_class_name();780}781break;782case GDScriptTokenizer::Token::EXTENDS:783PUSH_PENDING_ANNOTATIONS_TO_HEAD;784advance();785if (head->extends_used) {786push_error(R"("extends" can only be used once.)");787} else {788parse_extends();789end_statement("superclass");790}791break;792case GDScriptTokenizer::Token::TK_EOF:793PUSH_PENDING_ANNOTATIONS_TO_HEAD;794can_have_class_or_extends = false;795break;796case GDScriptTokenizer::Token::LITERAL:797if (current.literal.get_type() == Variant::STRING) {798// Allow strings in class body as multiline comments.799advance();800if (!match(GDScriptTokenizer::Token::NEWLINE)) {801push_error("Expected newline after comment string.");802}803break;804}805[[fallthrough]];806default:807// No tokens are allowed between script annotations and class/extends.808can_have_class_or_extends = false;809break;810}811812if (panic_mode) {813synchronize();814}815}816817#undef PUSH_PENDING_ANNOTATIONS_TO_HEAD818819for (AnnotationNode *&annotation : head->annotations) {820if (annotation->name == SNAME("@abstract")) {821// Some annotations need to be resolved and applied in the parser.822// The root class is not in any class, so `head->outer == nullptr`.823annotation->apply(this, head, nullptr);824}825}826827// When the only thing needed is the class name, icon, and abstractness; we don't need to parse the whole file.828// It really speed up the call to `GDScriptLanguage::get_global_class_name()` especially for large script.829if (!parse_body) {830return;831}832833parse_class_body(true);834835head->end_line = current.end_line;836head->end_column = current.end_column;837838complete_extents(head);839840#ifdef TOOLS_ENABLED841const HashMap<int, GDScriptTokenizer::CommentData> &comments = tokenizer->get_comments();842843int max_line = head->end_line;844if (!head->members.is_empty()) {845max_line = MIN(max_script_doc_line, head->members[0].get_line() - 1);846}847848int line = 0;849while (line <= max_line) {850// Find the start.851if (comments.has(line) && comments[line].new_line && comments[line].comment.begins_with("##")) {852// Find the end.853while (line + 1 <= max_line && comments.has(line + 1) && comments[line + 1].new_line && comments[line + 1].comment.begins_with("##")) {854line++;855}856head->doc_data = parse_class_doc_comment(line);857break;858}859line++;860}861#endif // TOOLS_ENABLED862863if (!check(GDScriptTokenizer::Token::TK_EOF)) {864push_error("Expected end of file.");865}866867clear_unused_annotations();868}869870Ref<GDScriptParserRef> GDScriptParser::get_depended_parser_for(const String &p_path) {871Ref<GDScriptParserRef> ref;872if (depended_parsers.has(p_path)) {873ref = depended_parsers[p_path];874} else {875Error err = OK;876ref = GDScriptCache::get_parser(p_path, GDScriptParserRef::EMPTY, err, script_path);877if (ref.is_valid()) {878depended_parsers[p_path] = ref;879}880}881882return ref;883}884885const HashMap<String, Ref<GDScriptParserRef>> &GDScriptParser::get_depended_parsers() {886return depended_parsers;887}888889GDScriptParser::ClassNode *GDScriptParser::find_class(const String &p_qualified_name) const {890String first = p_qualified_name.get_slice("::", 0);891892Vector<String> class_names;893GDScriptParser::ClassNode *result = nullptr;894// Empty initial name means start at the head.895if (first.is_empty() || (head->identifier && first == head->identifier->name)) {896class_names = p_qualified_name.split("::");897result = head;898} else if (p_qualified_name.begins_with(script_path)) {899// Script path could have a class path separator("::") in it.900class_names = p_qualified_name.trim_prefix(script_path).split("::");901result = head;902} else if (head->has_member(first)) {903class_names = p_qualified_name.split("::");904GDScriptParser::ClassNode::Member member = head->get_member(first);905if (member.type == GDScriptParser::ClassNode::Member::CLASS) {906result = member.m_class;907}908}909910// Starts at index 1 because index 0 was handled above.911for (int i = 1; result != nullptr && i < class_names.size(); i++) {912const String ¤t_name = class_names[i];913GDScriptParser::ClassNode *next = nullptr;914if (result->has_member(current_name)) {915GDScriptParser::ClassNode::Member member = result->get_member(current_name);916if (member.type == GDScriptParser::ClassNode::Member::CLASS) {917next = member.m_class;918}919}920result = next;921}922923return result;924}925926bool GDScriptParser::has_class(const GDScriptParser::ClassNode *p_class) const {927if (head->fqcn.is_empty() && p_class->fqcn.get_slice("::", 0).is_empty()) {928return p_class == head;929} else if (p_class->fqcn.begins_with(head->fqcn)) {930return find_class(p_class->fqcn.trim_prefix(head->fqcn)) == p_class;931}932933return false;934}935936GDScriptParser::ClassNode *GDScriptParser::parse_class(bool p_is_static) {937ClassNode *n_class = alloc_node<ClassNode>();938939ClassNode *previous_class = current_class;940current_class = n_class;941n_class->outer = previous_class;942943if (consume(GDScriptTokenizer::Token::IDENTIFIER, R"(Expected identifier for the class name after "class".)")) {944n_class->identifier = parse_identifier();945if (n_class->outer) {946String fqcn = n_class->outer->fqcn;947if (fqcn.is_empty()) {948fqcn = GDScript::canonicalize_path(script_path);949}950n_class->fqcn = fqcn + "::" + n_class->identifier->name;951} else {952n_class->fqcn = n_class->identifier->name;953}954}955956if (match(GDScriptTokenizer::Token::EXTENDS)) {957parse_extends();958}959960consume(GDScriptTokenizer::Token::COLON, R"(Expected ":" after class declaration.)");961962bool multiline = match(GDScriptTokenizer::Token::NEWLINE);963964if (multiline && !consume(GDScriptTokenizer::Token::INDENT, R"(Expected indented block after class declaration.)")) {965current_class = previous_class;966complete_extents(n_class);967return n_class;968}969970if (match(GDScriptTokenizer::Token::EXTENDS)) {971if (n_class->extends_used) {972push_error(R"(Cannot use "extends" more than once in the same class.)");973}974parse_extends();975end_statement("superclass");976}977978parse_class_body(multiline);979complete_extents(n_class);980981if (multiline) {982consume(GDScriptTokenizer::Token::DEDENT, R"(Missing unindent at the end of the class body.)");983}984985current_class = previous_class;986return n_class;987}988989void GDScriptParser::parse_class_name() {990if (consume(GDScriptTokenizer::Token::IDENTIFIER, R"(Expected identifier for the global class name after "class_name".)")) {991current_class->identifier = parse_identifier();992current_class->fqcn = String(current_class->identifier->name);993}994995if (match(GDScriptTokenizer::Token::EXTENDS)) {996// Allow extends on the same line.997parse_extends();998end_statement("superclass");999} else {1000end_statement("class_name statement");1001}1002}10031004void GDScriptParser::parse_extends() {1005current_class->extends_used = true;10061007int chain_index = 0;10081009if (match(GDScriptTokenizer::Token::LITERAL)) {1010if (previous.literal.get_type() != Variant::STRING) {1011push_error(vformat(R"(Only strings or identifiers can be used after "extends", found "%s" instead.)", Variant::get_type_name(previous.literal.get_type())));1012}1013current_class->extends_path = previous.literal;10141015if (!match(GDScriptTokenizer::Token::PERIOD)) {1016return;1017}1018}10191020make_completion_context(COMPLETION_INHERIT_TYPE, current_class, chain_index++);10211022if (!consume(GDScriptTokenizer::Token::IDENTIFIER, R"(Expected superclass name after "extends".)")) {1023return;1024}1025current_class->extends.push_back(parse_identifier());10261027while (match(GDScriptTokenizer::Token::PERIOD)) {1028make_completion_context(COMPLETION_INHERIT_TYPE, current_class, chain_index++);1029if (!consume(GDScriptTokenizer::Token::IDENTIFIER, R"(Expected superclass name after ".".)")) {1030return;1031}1032current_class->extends.push_back(parse_identifier());1033}1034}10351036template <typename T>1037void GDScriptParser::parse_class_member(T *(GDScriptParser::*p_parse_function)(bool), AnnotationInfo::TargetKind p_target, const String &p_member_kind, bool p_is_static) {1038advance();10391040// Consume annotations.1041List<AnnotationNode *> annotations;1042while (!annotation_stack.is_empty()) {1043AnnotationNode *last_annotation = annotation_stack.back()->get();1044if (last_annotation->applies_to(p_target)) {1045annotations.push_front(last_annotation);1046annotation_stack.pop_back();1047} else {1048push_error(vformat(R"(Annotation "%s" cannot be applied to a %s.)", last_annotation->name, p_member_kind));1049clear_unused_annotations();1050}1051}10521053T *member = (this->*p_parse_function)(p_is_static);1054if (member == nullptr) {1055return;1056}10571058#ifdef TOOLS_ENABLED1059int doc_comment_line = member->start_line - 1;1060#endif // TOOLS_ENABLED10611062for (AnnotationNode *&annotation : annotations) {1063member->annotations.push_back(annotation);1064#ifdef TOOLS_ENABLED1065if (annotation->start_line <= doc_comment_line) {1066doc_comment_line = annotation->start_line - 1;1067}1068#endif // TOOLS_ENABLED1069}10701071#ifdef TOOLS_ENABLED1072if constexpr (std::is_same_v<T, ClassNode>) {1073if (has_comment(member->start_line, true)) {1074// Inline doc comment.1075member->doc_data = parse_class_doc_comment(member->start_line, true);1076} else if (has_comment(doc_comment_line, true) && tokenizer->get_comments()[doc_comment_line].new_line) {1077// Normal doc comment. Don't check `min_member_doc_line` because a class ends parsing after its members.1078// This may not work correctly for cases like `var a; class B`, but it doesn't matter in practice.1079member->doc_data = parse_class_doc_comment(doc_comment_line);1080}1081} else {1082if (has_comment(member->start_line, true)) {1083// Inline doc comment.1084member->doc_data = parse_doc_comment(member->start_line, true);1085} else if (doc_comment_line >= min_member_doc_line && has_comment(doc_comment_line, true) && tokenizer->get_comments()[doc_comment_line].new_line) {1086// Normal doc comment.1087member->doc_data = parse_doc_comment(doc_comment_line);1088}1089}10901091min_member_doc_line = member->end_line + 1; // Prevent multiple members from using the same doc comment.1092#endif // TOOLS_ENABLED10931094if (member->identifier != nullptr) {1095if (!((String)member->identifier->name).is_empty()) { // Enums may be unnamed.1096if (current_class->members_indices.has(member->identifier->name)) {1097push_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);1098} else {1099current_class->add_member(member);1100}1101} else {1102current_class->add_member(member);1103}1104}1105}11061107void GDScriptParser::parse_class_body(bool p_is_multiline) {1108bool class_end = false;1109bool next_is_static = false;1110while (!class_end && !is_at_end()) {1111GDScriptTokenizer::Token token = current;1112switch (token.type) {1113case GDScriptTokenizer::Token::VAR:1114parse_class_member(&GDScriptParser::parse_variable, AnnotationInfo::VARIABLE, "variable", next_is_static);1115if (next_is_static) {1116current_class->has_static_data = true;1117}1118break;1119case GDScriptTokenizer::Token::TK_CONST:1120parse_class_member(&GDScriptParser::parse_constant, AnnotationInfo::CONSTANT, "constant");1121break;1122case GDScriptTokenizer::Token::SIGNAL:1123parse_class_member(&GDScriptParser::parse_signal, AnnotationInfo::SIGNAL, "signal");1124break;1125case GDScriptTokenizer::Token::FUNC:1126parse_class_member(&GDScriptParser::parse_function, AnnotationInfo::FUNCTION, "function", next_is_static);1127break;1128case GDScriptTokenizer::Token::CLASS:1129parse_class_member(&GDScriptParser::parse_class, AnnotationInfo::CLASS, "class");1130break;1131case GDScriptTokenizer::Token::ENUM:1132parse_class_member(&GDScriptParser::parse_enum, AnnotationInfo::NONE, "enum");1133break;1134case GDScriptTokenizer::Token::STATIC: {1135advance();1136next_is_static = true;1137if (!check(GDScriptTokenizer::Token::FUNC) && !check(GDScriptTokenizer::Token::VAR)) {1138push_error(R"(Expected "func" or "var" after "static".)");1139}1140} break;1141case GDScriptTokenizer::Token::ANNOTATION: {1142advance();11431144// Check for class-level and standalone annotations.1145AnnotationNode *annotation = parse_annotation(AnnotationInfo::CLASS_LEVEL | AnnotationInfo::STANDALONE);1146if (annotation != nullptr) {1147if (annotation->applies_to(AnnotationInfo::STANDALONE)) {1148if (previous.type != GDScriptTokenizer::Token::NEWLINE) {1149push_error(R"(Expected newline after a standalone annotation.)");1150}1151if (annotation->name == SNAME("@export_category") || annotation->name == SNAME("@export_group") || annotation->name == SNAME("@export_subgroup")) {1152current_class->add_member_group(annotation);1153} else if (annotation->name == SNAME("@warning_ignore_start") || annotation->name == SNAME("@warning_ignore_restore")) {1154// Some annotations need to be resolved and applied in the parser.1155annotation->apply(this, nullptr, nullptr);1156} else {1157push_error(R"(Unexpected standalone annotation.)");1158}1159} else { // `AnnotationInfo::CLASS_LEVEL`.1160annotation_stack.push_back(annotation);1161}1162}1163break;1164}1165case GDScriptTokenizer::Token::PASS:1166advance();1167end_statement(R"("pass")");1168break;1169case GDScriptTokenizer::Token::DEDENT:1170class_end = true;1171break;1172case GDScriptTokenizer::Token::LITERAL:1173if (current.literal.get_type() == Variant::STRING) {1174// Allow strings in class body as multiline comments.1175advance();1176if (!match(GDScriptTokenizer::Token::NEWLINE)) {1177push_error("Expected newline after comment string.");1178}1179break;1180}1181[[fallthrough]];1182default:1183// Display a completion with identifiers.1184make_completion_context(COMPLETION_IDENTIFIER, nullptr);1185advance();1186if (previous.get_identifier() == "export") {1187push_error(R"(The "export" keyword was removed in Godot 4. Use an export annotation ("@export", "@export_range", etc.) instead.)");1188} else if (previous.get_identifier() == "tool") {1189push_error(R"(The "tool" keyword was removed in Godot 4. Use the "@tool" annotation instead.)");1190} else if (previous.get_identifier() == "onready") {1191push_error(R"(The "onready" keyword was removed in Godot 4. Use the "@onready" annotation instead.)");1192} else if (previous.get_identifier() == "remote") {1193push_error(R"(The "remote" keyword was removed in Godot 4. Use the "@rpc" annotation with "any_peer" instead.)");1194} else if (previous.get_identifier() == "remotesync") {1195push_error(R"(The "remotesync" keyword was removed in Godot 4. Use the "@rpc" annotation with "any_peer" and "call_local" instead.)");1196} else if (previous.get_identifier() == "puppet") {1197push_error(R"(The "puppet" keyword was removed in Godot 4. Use the "@rpc" annotation with "authority" instead.)");1198} else if (previous.get_identifier() == "puppetsync") {1199push_error(R"(The "puppetsync" keyword was removed in Godot 4. Use the "@rpc" annotation with "authority" and "call_local" instead.)");1200} else if (previous.get_identifier() == "master") {1201push_error(R"(The "master" keyword was removed in Godot 4. Use the "@rpc" annotation with "any_peer" and perform a check inside the function instead.)");1202} else if (previous.get_identifier() == "mastersync") {1203push_error(R"(The "mastersync" keyword was removed in Godot 4. Use the "@rpc" annotation with "any_peer" and "call_local", and perform a check inside the function instead.)");1204} else {1205push_error(vformat(R"(Unexpected %s in class body.)", previous.get_debug_name()));1206}1207break;1208}1209if (token.type != GDScriptTokenizer::Token::STATIC) {1210next_is_static = false;1211}1212if (panic_mode) {1213synchronize();1214}1215if (!p_is_multiline) {1216class_end = true;1217}1218}1219}12201221GDScriptParser::VariableNode *GDScriptParser::parse_variable(bool p_is_static) {1222return parse_variable(p_is_static, true);1223}12241225GDScriptParser::VariableNode *GDScriptParser::parse_variable(bool p_is_static, bool p_allow_property) {1226VariableNode *variable = alloc_node<VariableNode>();12271228if (!consume(GDScriptTokenizer::Token::IDENTIFIER, R"(Expected variable name after "var".)")) {1229complete_extents(variable);1230return nullptr;1231}12321233variable->identifier = parse_identifier();1234variable->export_info.name = variable->identifier->name;1235variable->is_static = p_is_static;12361237if (match(GDScriptTokenizer::Token::COLON)) {1238if (check(GDScriptTokenizer::Token::NEWLINE)) {1239if (p_allow_property) {1240advance();1241return parse_property(variable, true);1242} else {1243push_error(R"(Expected type after ":")");1244complete_extents(variable);1245return nullptr;1246}1247} else if (check((GDScriptTokenizer::Token::EQUAL))) {1248// Infer type.1249variable->infer_datatype = true;1250} else {1251if (p_allow_property) {1252make_completion_context(COMPLETION_PROPERTY_DECLARATION_OR_TYPE, variable);1253if (check(GDScriptTokenizer::Token::IDENTIFIER)) {1254// Check if get or set.1255if (current.get_identifier() == "get" || current.get_identifier() == "set") {1256return parse_property(variable, false);1257}1258}1259}12601261// Parse type.1262variable->datatype_specifier = parse_type();1263}1264}12651266if (match(GDScriptTokenizer::Token::EQUAL)) {1267// Initializer.1268variable->initializer = parse_expression(false);1269if (variable->initializer == nullptr) {1270push_error(R"(Expected expression for variable initial value after "=".)");1271}1272variable->assignments++;1273}12741275if (p_allow_property && match(GDScriptTokenizer::Token::COLON)) {1276if (match(GDScriptTokenizer::Token::NEWLINE)) {1277return parse_property(variable, true);1278} else {1279return parse_property(variable, false);1280}1281}12821283complete_extents(variable);1284end_statement("variable declaration");12851286return variable;1287}12881289GDScriptParser::VariableNode *GDScriptParser::parse_property(VariableNode *p_variable, bool p_need_indent) {1290if (p_need_indent) {1291if (!consume(GDScriptTokenizer::Token::INDENT, R"(Expected indented block for property after ":".)")) {1292complete_extents(p_variable);1293return nullptr;1294}1295}12961297VariableNode *property = p_variable;12981299make_completion_context(COMPLETION_PROPERTY_DECLARATION, property);13001301if (!consume(GDScriptTokenizer::Token::IDENTIFIER, R"(Expected "get" or "set" for property declaration.)")) {1302complete_extents(p_variable);1303return nullptr;1304}13051306IdentifierNode *function = parse_identifier();13071308if (check(GDScriptTokenizer::Token::EQUAL)) {1309p_variable->property = VariableNode::PROP_SETGET;1310} else {1311p_variable->property = VariableNode::PROP_INLINE;1312if (!p_need_indent) {1313push_error("Property with inline code must go to an indented block.");1314}1315}13161317bool getter_used = false;1318bool setter_used = false;13191320// Run with a loop because order doesn't matter.1321for (int i = 0; i < 2; i++) {1322if (function->name == SNAME("set")) {1323if (setter_used) {1324push_error(R"(Properties can only have one setter.)");1325} else {1326parse_property_setter(property);1327setter_used = true;1328}1329} else if (function->name == SNAME("get")) {1330if (getter_used) {1331push_error(R"(Properties can only have one getter.)");1332} else {1333parse_property_getter(property);1334getter_used = true;1335}1336} else {1337// TODO: Update message to only have the missing one if it's the case.1338push_error(R"(Expected "get" or "set" for property declaration.)");1339}13401341if (i == 0 && p_variable->property == VariableNode::PROP_SETGET) {1342if (match(GDScriptTokenizer::Token::COMMA)) {1343// Consume potential newline.1344if (match(GDScriptTokenizer::Token::NEWLINE)) {1345if (!p_need_indent) {1346push_error(R"(Inline setter/getter setting cannot span across multiple lines (use "\\"" if needed).)");1347}1348}1349} else {1350break;1351}1352}13531354if (!match(GDScriptTokenizer::Token::IDENTIFIER)) {1355break;1356}1357function = parse_identifier();1358}1359complete_extents(p_variable);13601361if (p_variable->property == VariableNode::PROP_SETGET) {1362end_statement("property declaration");1363}13641365if (p_need_indent) {1366consume(GDScriptTokenizer::Token::DEDENT, R"(Expected end of indented block for property.)");1367}1368return property;1369}13701371void GDScriptParser::parse_property_setter(VariableNode *p_variable) {1372switch (p_variable->property) {1373case VariableNode::PROP_INLINE: {1374FunctionNode *function = alloc_node<FunctionNode>();1375IdentifierNode *identifier = alloc_node<IdentifierNode>();1376complete_extents(identifier);1377identifier->name = "@" + p_variable->identifier->name + "_setter";1378function->identifier = identifier;1379function->is_static = p_variable->is_static;13801381consume(GDScriptTokenizer::Token::PARENTHESIS_OPEN, R"(Expected "(" after "set".)");13821383ParameterNode *parameter = alloc_node<ParameterNode>();1384if (consume(GDScriptTokenizer::Token::IDENTIFIER, R"(Expected parameter name after "(".)")) {1385reset_extents(parameter, previous);1386p_variable->setter_parameter = parse_identifier();1387parameter->identifier = p_variable->setter_parameter;1388function->parameters_indices[parameter->identifier->name] = 0;1389function->parameters.push_back(parameter);1390}1391complete_extents(parameter);13921393consume(GDScriptTokenizer::Token::PARENTHESIS_CLOSE, R"*(Expected ")" after parameter name.)*");1394consume(GDScriptTokenizer::Token::COLON, R"*(Expected ":" after ")".)*");13951396FunctionNode *previous_function = current_function;1397current_function = function;1398if (p_variable->setter_parameter != nullptr) {1399SuiteNode *body = alloc_node<SuiteNode>();1400body->add_local(parameter, function);1401function->body = parse_suite("setter declaration", body);1402p_variable->setter = function;1403}1404current_function = previous_function;1405complete_extents(function);1406break;1407}1408case VariableNode::PROP_SETGET:1409consume(GDScriptTokenizer::Token::EQUAL, R"(Expected "=" after "set")");1410make_completion_context(COMPLETION_PROPERTY_METHOD, p_variable);1411if (consume(GDScriptTokenizer::Token::IDENTIFIER, R"(Expected setter function name after "=".)")) {1412p_variable->setter_pointer = parse_identifier();1413}1414break;1415case VariableNode::PROP_NONE:1416break; // Unreachable.1417}1418}14191420void GDScriptParser::parse_property_getter(VariableNode *p_variable) {1421switch (p_variable->property) {1422case VariableNode::PROP_INLINE: {1423FunctionNode *function = alloc_node<FunctionNode>();14241425if (match(GDScriptTokenizer::Token::PARENTHESIS_OPEN)) {1426consume(GDScriptTokenizer::Token::PARENTHESIS_CLOSE, R"*(Expected ")" after "get(".)*");1427consume(GDScriptTokenizer::Token::COLON, R"*(Expected ":" after "get()".)*");1428} else {1429consume(GDScriptTokenizer::Token::COLON, R"(Expected ":" or "(" after "get".)");1430}14311432IdentifierNode *identifier = alloc_node<IdentifierNode>();1433complete_extents(identifier);1434identifier->name = "@" + p_variable->identifier->name + "_getter";1435function->identifier = identifier;1436function->is_static = p_variable->is_static;14371438FunctionNode *previous_function = current_function;1439current_function = function;14401441SuiteNode *body = alloc_node<SuiteNode>();1442function->body = parse_suite("getter declaration", body);1443p_variable->getter = function;14441445current_function = previous_function;1446complete_extents(function);1447break;1448}1449case VariableNode::PROP_SETGET:1450consume(GDScriptTokenizer::Token::EQUAL, R"(Expected "=" after "get")");1451make_completion_context(COMPLETION_PROPERTY_METHOD, p_variable);1452if (consume(GDScriptTokenizer::Token::IDENTIFIER, R"(Expected getter function name after "=".)")) {1453p_variable->getter_pointer = parse_identifier();1454}1455break;1456case VariableNode::PROP_NONE:1457break; // Unreachable.1458}1459}14601461GDScriptParser::ConstantNode *GDScriptParser::parse_constant(bool p_is_static) {1462ConstantNode *constant = alloc_node<ConstantNode>();14631464if (!consume(GDScriptTokenizer::Token::IDENTIFIER, R"(Expected constant name after "const".)")) {1465complete_extents(constant);1466return nullptr;1467}14681469constant->identifier = parse_identifier();14701471if (match(GDScriptTokenizer::Token::COLON)) {1472if (check((GDScriptTokenizer::Token::EQUAL))) {1473// Infer type.1474constant->infer_datatype = true;1475} else {1476// Parse type.1477constant->datatype_specifier = parse_type();1478}1479}14801481if (consume(GDScriptTokenizer::Token::EQUAL, R"(Expected initializer after constant name.)")) {1482// Initializer.1483constant->initializer = parse_expression(false);14841485if (constant->initializer == nullptr) {1486push_error(R"(Expected initializer expression for constant.)");1487complete_extents(constant);1488return nullptr;1489}1490} else {1491complete_extents(constant);1492return nullptr;1493}14941495complete_extents(constant);1496end_statement("constant declaration");14971498return constant;1499}15001501GDScriptParser::ParameterNode *GDScriptParser::parse_parameter() {1502if (!consume(GDScriptTokenizer::Token::IDENTIFIER, R"(Expected parameter name.)")) {1503return nullptr;1504}15051506ParameterNode *parameter = alloc_node<ParameterNode>();1507parameter->identifier = parse_identifier();15081509if (match(GDScriptTokenizer::Token::COLON)) {1510if (check((GDScriptTokenizer::Token::EQUAL))) {1511// Infer type.1512parameter->infer_datatype = true;1513} else {1514// Parse type.1515make_completion_context(COMPLETION_TYPE_NAME, parameter);1516parameter->datatype_specifier = parse_type();1517}1518}15191520if (match(GDScriptTokenizer::Token::EQUAL)) {1521// Default value.1522parameter->initializer = parse_expression(false);1523}15241525complete_extents(parameter);1526return parameter;1527}15281529GDScriptParser::SignalNode *GDScriptParser::parse_signal(bool p_is_static) {1530SignalNode *signal = alloc_node<SignalNode>();15311532if (!consume(GDScriptTokenizer::Token::IDENTIFIER, R"(Expected signal name after "signal".)")) {1533complete_extents(signal);1534return nullptr;1535}15361537signal->identifier = parse_identifier();15381539if (check(GDScriptTokenizer::Token::PARENTHESIS_OPEN)) {1540push_multiline(true);1541advance();1542do {1543if (check(GDScriptTokenizer::Token::PARENTHESIS_CLOSE)) {1544// Allow for trailing comma.1545break;1546}15471548ParameterNode *parameter = parse_parameter();1549if (parameter == nullptr) {1550push_error("Expected signal parameter name.");1551break;1552}1553if (parameter->initializer != nullptr) {1554push_error(R"(Signal parameters cannot have a default value.)");1555}1556if (signal->parameters_indices.has(parameter->identifier->name)) {1557push_error(vformat(R"(Parameter with name "%s" was already declared for this signal.)", parameter->identifier->name));1558} else {1559signal->parameters_indices[parameter->identifier->name] = signal->parameters.size();1560signal->parameters.push_back(parameter);1561}1562} while (match(GDScriptTokenizer::Token::COMMA) && !is_at_end());15631564pop_multiline();1565consume(GDScriptTokenizer::Token::PARENTHESIS_CLOSE, R"*(Expected closing ")" after signal parameters.)*");1566}15671568complete_extents(signal);1569end_statement("signal declaration");15701571return signal;1572}15731574GDScriptParser::EnumNode *GDScriptParser::parse_enum(bool p_is_static) {1575EnumNode *enum_node = alloc_node<EnumNode>();1576bool named = false;15771578if (match(GDScriptTokenizer::Token::IDENTIFIER)) {1579enum_node->identifier = parse_identifier();1580named = true;1581}15821583push_multiline(true);1584consume(GDScriptTokenizer::Token::BRACE_OPEN, vformat(R"(Expected "{" after %s.)", named ? "enum name" : R"("enum")"));1585#ifdef TOOLS_ENABLED1586int min_enum_value_doc_line = previous.end_line + 1;1587#endif15881589HashMap<StringName, int> elements;15901591#ifdef DEBUG_ENABLED1592List<MethodInfo> gdscript_funcs;1593GDScriptLanguage::get_singleton()->get_public_functions(&gdscript_funcs);1594#endif15951596do {1597if (check(GDScriptTokenizer::Token::BRACE_CLOSE)) {1598break; // Allow trailing comma.1599}1600if (consume(GDScriptTokenizer::Token::IDENTIFIER, R"(Expected identifier for enum key.)")) {1601GDScriptParser::IdentifierNode *identifier = parse_identifier();16021603EnumNode::Value item;1604item.identifier = identifier;1605item.parent_enum = enum_node;1606item.line = previous.start_line;1607item.start_column = previous.start_column;1608item.end_column = previous.end_column;16091610if (elements.has(item.identifier->name)) {1611push_error(vformat(R"(Name "%s" was already in this enum (at line %d).)", item.identifier->name, elements[item.identifier->name]), item.identifier);1612} else if (!named) {1613if (current_class->members_indices.has(item.identifier->name)) {1614push_error(vformat(R"(Name "%s" is already used as a class %s.)", item.identifier->name, current_class->get_member(item.identifier->name).get_type_name()));1615}1616}16171618elements[item.identifier->name] = item.line;16191620if (match(GDScriptTokenizer::Token::EQUAL)) {1621ExpressionNode *value = parse_expression(false);1622if (value == nullptr) {1623push_error(R"(Expected expression value after "=".)");1624}1625item.custom_value = value;1626}16271628item.index = enum_node->values.size();1629enum_node->values.push_back(item);1630if (!named) {1631// Add as member of current class.1632current_class->add_member(item);1633}1634}1635} while (match(GDScriptTokenizer::Token::COMMA));16361637#ifdef TOOLS_ENABLED1638// Enum values documentation.1639for (int i = 0; i < enum_node->values.size(); i++) {1640int enum_value_line = enum_node->values[i].line;1641int doc_comment_line = enum_value_line - 1;16421643MemberDocData doc_data;1644if (has_comment(enum_value_line, true)) {1645// Inline doc comment.1646if (i == enum_node->values.size() - 1 || enum_node->values[i + 1].line > enum_value_line) {1647doc_data = parse_doc_comment(enum_value_line, true);1648}1649} else if (doc_comment_line >= min_enum_value_doc_line && has_comment(doc_comment_line, true) && tokenizer->get_comments()[doc_comment_line].new_line) {1650// Normal doc comment.1651doc_data = parse_doc_comment(doc_comment_line);1652}16531654if (named) {1655enum_node->values.write[i].doc_data = doc_data;1656} else {1657current_class->set_enum_value_doc_data(enum_node->values[i].identifier->name, doc_data);1658}16591660min_enum_value_doc_line = enum_value_line + 1; // Prevent multiple enum values from using the same doc comment.1661}1662#endif // TOOLS_ENABLED16631664pop_multiline();1665consume(GDScriptTokenizer::Token::BRACE_CLOSE, R"(Expected closing "}" for enum.)");1666complete_extents(enum_node);1667end_statement("enum");16681669return enum_node;1670}16711672bool GDScriptParser::parse_function_signature(FunctionNode *p_function, SuiteNode *p_body, const String &p_type, int p_signature_start) {1673if (!check(GDScriptTokenizer::Token::PARENTHESIS_CLOSE) && !is_at_end()) {1674bool default_used = false;1675do {1676if (check(GDScriptTokenizer::Token::PARENTHESIS_CLOSE)) {1677// Allow for trailing comma.1678break;1679}16801681bool is_rest = false;1682if (match(GDScriptTokenizer::Token::PERIOD_PERIOD_PERIOD)) {1683is_rest = true;1684}16851686ParameterNode *parameter = parse_parameter();1687if (parameter == nullptr) {1688break;1689}16901691if (p_function->is_vararg()) {1692push_error("Cannot have parameters after the rest parameter.");1693continue;1694}16951696if (parameter->initializer != nullptr) {1697if (is_rest) {1698push_error("The rest parameter cannot have a default value.");1699continue;1700}1701default_used = true;1702} else {1703if (default_used && !is_rest) {1704push_error("Cannot have mandatory parameters after optional parameters.");1705continue;1706}1707}17081709if (p_function->parameters_indices.has(parameter->identifier->name)) {1710push_error(vformat(R"(Parameter with name "%s" was already declared for this %s.)", parameter->identifier->name, p_type));1711} else if (is_rest) {1712p_function->rest_parameter = parameter;1713p_body->add_local(parameter, current_function);1714} else {1715p_function->parameters_indices[parameter->identifier->name] = p_function->parameters.size();1716p_function->parameters.push_back(parameter);1717p_body->add_local(parameter, current_function);1718}1719} while (match(GDScriptTokenizer::Token::COMMA));1720}17211722pop_multiline();1723consume(GDScriptTokenizer::Token::PARENTHESIS_CLOSE, vformat(R"*(Expected closing ")" after %s parameters.)*", p_type));17241725if (match(GDScriptTokenizer::Token::FORWARD_ARROW)) {1726make_completion_context(COMPLETION_TYPE_NAME_OR_VOID, p_function);1727p_function->return_type = parse_type(true);1728if (p_function->return_type == nullptr) {1729push_error(R"(Expected return type or "void" after "->".)");1730}1731}17321733if (!p_function->source_lambda && p_function->identifier && p_function->identifier->name == GDScriptLanguage::get_singleton()->strings._static_init) {1734if (!p_function->is_static) {1735push_error(R"(Static constructor must be declared static.)");1736}1737if (!p_function->parameters.is_empty() || p_function->is_vararg()) {1738push_error(R"(Static constructor cannot have parameters.)");1739}1740current_class->has_static_data = true;1741}17421743#ifdef TOOLS_ENABLED1744if (p_type == "function" && p_signature_start != -1) {1745const int signature_end_pos = tokenizer->get_current_position() - 1;1746const String source_code = tokenizer->get_source_code();1747p_function->signature = source_code.substr(p_signature_start, signature_end_pos - p_signature_start).strip_edges(false, true);1748}1749#endif // TOOLS_ENABLED17501751// TODO: Improve token consumption so it synchronizes to a statement boundary. This way we can get into the function body with unrecognized tokens.1752if (p_type == "lambda") {1753return consume(GDScriptTokenizer::Token::COLON, R"(Expected ":" after lambda declaration.)");1754}1755// The colon may not be present in the case of abstract functions.1756return match(GDScriptTokenizer::Token::COLON);1757}17581759GDScriptParser::FunctionNode *GDScriptParser::parse_function(bool p_is_static) {1760FunctionNode *function = alloc_node<FunctionNode>();1761function->is_static = p_is_static;17621763make_completion_context(COMPLETION_OVERRIDE_METHOD, function);17641765#ifdef TOOLS_ENABLED1766// The signature is something like `(a: int, b: int = 0) -> void`.1767// We start one token earlier, since the parser looks one token ahead.1768const int signature_start_pos = tokenizer->get_current_position();1769#endif // TOOLS_ENABLED17701771if (!consume(GDScriptTokenizer::Token::IDENTIFIER, R"(Expected function name after "func".)")) {1772complete_extents(function);1773return nullptr;1774}17751776FunctionNode *previous_function = current_function;1777current_function = function;17781779function->identifier = parse_identifier();17801781SuiteNode *body = alloc_node<SuiteNode>();17821783SuiteNode *previous_suite = current_suite;1784current_suite = body;17851786push_multiline(true);1787consume(GDScriptTokenizer::Token::PARENTHESIS_OPEN, R"(Expected opening "(" after function name.)");17881789#ifdef TOOLS_ENABLED1790const bool has_body = parse_function_signature(function, body, "function", signature_start_pos);1791#else // !TOOLS_ENABLED1792const bool has_body = parse_function_signature(function, body, "function", -1);1793#endif // TOOLS_ENABLED17941795current_suite = previous_suite;17961797#ifdef TOOLS_ENABLED1798function->min_local_doc_line = previous.end_line + 1;1799#endif // TOOLS_ENABLED18001801if (!has_body) {1802// Abstract functions do not have a body.1803end_statement("bodyless function declaration");1804reset_extents(body, current);1805complete_extents(body);1806function->body = body;1807} else {1808function->body = parse_suite("function declaration", body);1809}18101811current_function = previous_function;1812complete_extents(function);1813return function;1814}18151816GDScriptParser::AnnotationNode *GDScriptParser::parse_annotation(uint32_t p_valid_targets) {1817AnnotationNode *annotation = alloc_node<AnnotationNode>();18181819annotation->name = previous.literal;18201821make_completion_context(COMPLETION_ANNOTATION, annotation);18221823bool valid = true;18241825if (!valid_annotations.has(annotation->name)) {1826if (annotation->name == "@deprecated") {1827push_error(R"("@deprecated" annotation does not exist. Use "## @deprecated: Reason here." instead.)");1828} else if (annotation->name == "@experimental") {1829push_error(R"("@experimental" annotation does not exist. Use "## @experimental: Reason here." instead.)");1830} else if (annotation->name == "@tutorial") {1831push_error(R"("@tutorial" annotation does not exist. Use "## @tutorial(Title): https://example.com" instead.)");1832} else {1833push_error(vformat(R"(Unrecognized annotation: "%s".)", annotation->name));1834}1835valid = false;1836}18371838if (valid) {1839annotation->info = &valid_annotations[annotation->name];18401841if (!annotation->applies_to(p_valid_targets)) {1842if (annotation->applies_to(AnnotationInfo::SCRIPT)) {1843push_error(vformat(R"(Annotation "%s" must be at the top of the script, before "extends" and "class_name".)", annotation->name));1844} else {1845push_error(vformat(R"(Annotation "%s" is not allowed in this level.)", annotation->name));1846}1847valid = false;1848}1849}18501851if (check(GDScriptTokenizer::Token::PARENTHESIS_OPEN)) {1852push_multiline(true);1853advance();1854// Arguments.1855push_completion_call(annotation);1856int argument_index = 0;1857do {1858make_completion_context(COMPLETION_ANNOTATION_ARGUMENTS, annotation, argument_index);1859set_last_completion_call_arg(argument_index);1860if (check(GDScriptTokenizer::Token::PARENTHESIS_CLOSE)) {1861// Allow for trailing comma.1862break;1863}18641865ExpressionNode *argument = parse_expression(false);18661867if (argument == nullptr) {1868push_error("Expected expression as the annotation argument.");1869valid = false;1870} else {1871annotation->arguments.push_back(argument);18721873if (argument->type == Node::LITERAL) {1874override_completion_context(argument, COMPLETION_ANNOTATION_ARGUMENTS, annotation, argument_index);1875}1876}18771878argument_index++;1879} while (match(GDScriptTokenizer::Token::COMMA));18801881pop_multiline();1882consume(GDScriptTokenizer::Token::PARENTHESIS_CLOSE, R"*(Expected ")" after annotation arguments.)*");1883pop_completion_call();1884}1885complete_extents(annotation);18861887match(GDScriptTokenizer::Token::NEWLINE); // Newline after annotation is optional.18881889if (valid) {1890valid = validate_annotation_arguments(annotation);1891}18921893return valid ? annotation : nullptr;1894}18951896void GDScriptParser::clear_unused_annotations() {1897for (const AnnotationNode *annotation : annotation_stack) {1898push_error(vformat(R"(Annotation "%s" does not precede a valid target, so it will have no effect.)", annotation->name), annotation);1899}19001901annotation_stack.clear();1902}19031904bool GDScriptParser::register_annotation(const MethodInfo &p_info, uint32_t p_target_kinds, AnnotationAction p_apply, const Vector<Variant> &p_default_arguments, bool p_is_vararg) {1905ERR_FAIL_COND_V_MSG(valid_annotations.has(p_info.name), false, vformat(R"(Annotation "%s" already registered.)", p_info.name));19061907AnnotationInfo new_annotation;1908new_annotation.info = p_info;1909new_annotation.info.default_arguments = p_default_arguments;1910if (p_is_vararg) {1911new_annotation.info.flags |= METHOD_FLAG_VARARG;1912}1913new_annotation.apply = p_apply;1914new_annotation.target_kind = p_target_kinds;19151916valid_annotations[p_info.name] = new_annotation;1917return true;1918}19191920GDScriptParser::SuiteNode *GDScriptParser::parse_suite(const String &p_context, SuiteNode *p_suite, bool p_for_lambda) {1921SuiteNode *suite = p_suite != nullptr ? p_suite : alloc_node<SuiteNode>();1922suite->parent_block = current_suite;1923suite->parent_function = current_function;1924current_suite = suite;19251926if (!p_for_lambda && suite->parent_block != nullptr && suite->parent_block->is_in_loop) {1927// Do not reset to false if true is set before calling parse_suite().1928suite->is_in_loop = true;1929}19301931bool multiline = false;19321933if (match(GDScriptTokenizer::Token::NEWLINE)) {1934multiline = true;1935}19361937if (multiline) {1938if (!consume(GDScriptTokenizer::Token::INDENT, vformat(R"(Expected indented block after %s.)", p_context))) {1939current_suite = suite->parent_block;1940complete_extents(suite);1941return suite;1942}1943}1944reset_extents(suite, current);19451946int error_count = 0;19471948do {1949if (is_at_end() || (!multiline && previous.type == GDScriptTokenizer::Token::SEMICOLON && check(GDScriptTokenizer::Token::NEWLINE))) {1950break;1951}1952Node *statement = parse_statement();1953if (statement == nullptr) {1954if (error_count++ > 100) {1955push_error("Too many statement errors.", suite);1956break;1957}1958continue;1959}1960suite->statements.push_back(statement);19611962// Register locals.1963switch (statement->type) {1964case Node::VARIABLE: {1965VariableNode *variable = static_cast<VariableNode *>(statement);1966const SuiteNode::Local &local = current_suite->get_local(variable->identifier->name);1967if (local.type != SuiteNode::Local::UNDEFINED) {1968push_error(vformat(R"(There is already a %s named "%s" declared in this scope.)", local.get_name(), variable->identifier->name), variable->identifier);1969}1970current_suite->add_local(variable, current_function);1971break;1972}1973case Node::CONSTANT: {1974ConstantNode *constant = static_cast<ConstantNode *>(statement);1975const SuiteNode::Local &local = current_suite->get_local(constant->identifier->name);1976if (local.type != SuiteNode::Local::UNDEFINED) {1977String name;1978if (local.type == SuiteNode::Local::CONSTANT) {1979name = "constant";1980} else {1981name = "variable";1982}1983push_error(vformat(R"(There is already a %s named "%s" declared in this scope.)", name, constant->identifier->name), constant->identifier);1984}1985current_suite->add_local(constant, current_function);1986break;1987}1988default:1989break;1990}19911992} while ((multiline || previous.type == GDScriptTokenizer::Token::SEMICOLON) && !check(GDScriptTokenizer::Token::DEDENT) && !lambda_ended && !is_at_end());19931994complete_extents(suite);19951996if (multiline) {1997if (!lambda_ended) {1998consume(GDScriptTokenizer::Token::DEDENT, vformat(R"(Missing unindent at the end of %s.)", p_context));19992000} else {2001match(GDScriptTokenizer::Token::DEDENT);2002}2003} else if (previous.type == GDScriptTokenizer::Token::SEMICOLON) {2004consume(GDScriptTokenizer::Token::NEWLINE, vformat(R"(Expected newline after ";" at the end of %s.)", p_context));2005}20062007if (p_for_lambda) {2008lambda_ended = true;2009}2010current_suite = suite->parent_block;2011return suite;2012}20132014GDScriptParser::Node *GDScriptParser::parse_statement() {2015Node *result = nullptr;2016#ifdef DEBUG_ENABLED2017bool unreachable = current_suite->has_return && !current_suite->has_unreachable_code;2018#endif20192020List<AnnotationNode *> annotations;2021if (current.type != GDScriptTokenizer::Token::ANNOTATION) {2022while (!annotation_stack.is_empty()) {2023AnnotationNode *last_annotation = annotation_stack.back()->get();2024if (last_annotation->applies_to(AnnotationInfo::STATEMENT)) {2025annotations.push_front(last_annotation);2026annotation_stack.pop_back();2027} else {2028push_error(vformat(R"(Annotation "%s" cannot be applied to a statement.)", last_annotation->name));2029clear_unused_annotations();2030}2031}2032}20332034switch (current.type) {2035case GDScriptTokenizer::Token::PASS:2036advance();2037result = alloc_node<PassNode>();2038complete_extents(result);2039end_statement(R"("pass")");2040break;2041case GDScriptTokenizer::Token::VAR:2042advance();2043result = parse_variable(false, false);2044break;2045case GDScriptTokenizer::Token::TK_CONST:2046advance();2047result = parse_constant(false);2048break;2049case GDScriptTokenizer::Token::IF:2050advance();2051result = parse_if();2052break;2053case GDScriptTokenizer::Token::FOR:2054advance();2055result = parse_for();2056break;2057case GDScriptTokenizer::Token::WHILE:2058advance();2059result = parse_while();2060break;2061case GDScriptTokenizer::Token::MATCH:2062advance();2063result = parse_match();2064break;2065case GDScriptTokenizer::Token::BREAK:2066advance();2067result = parse_break();2068break;2069case GDScriptTokenizer::Token::CONTINUE:2070advance();2071result = parse_continue();2072break;2073case GDScriptTokenizer::Token::RETURN: {2074advance();2075ReturnNode *n_return = alloc_node<ReturnNode>();2076if (!is_statement_end()) {2077if (current_function && (current_function->identifier->name == GDScriptLanguage::get_singleton()->strings._init || current_function->identifier->name == GDScriptLanguage::get_singleton()->strings._static_init)) {2078push_error(R"(Constructor cannot return a value.)");2079}2080n_return->return_value = parse_expression(false);2081} else if (in_lambda && !is_statement_end_token()) {2082// Try to parse it anyway as this might not be the statement end in a lambda.2083// If this fails the expression will be nullptr, but that's the same as no return, so it's fine.2084n_return->return_value = parse_expression(false);2085}2086complete_extents(n_return);2087result = n_return;20882089current_suite->has_return = true;20902091end_statement("return statement");2092break;2093}2094case GDScriptTokenizer::Token::BREAKPOINT:2095advance();2096result = alloc_node<BreakpointNode>();2097complete_extents(result);2098end_statement(R"("breakpoint")");2099break;2100case GDScriptTokenizer::Token::ASSERT:2101advance();2102result = parse_assert();2103break;2104case GDScriptTokenizer::Token::ANNOTATION: {2105advance();2106AnnotationNode *annotation = parse_annotation(AnnotationInfo::STATEMENT | AnnotationInfo::STANDALONE);2107if (annotation != nullptr) {2108if (annotation->applies_to(AnnotationInfo::STANDALONE)) {2109if (previous.type != GDScriptTokenizer::Token::NEWLINE) {2110push_error(R"(Expected newline after a standalone annotation.)");2111}2112if (annotation->name == SNAME("@warning_ignore_start") || annotation->name == SNAME("@warning_ignore_restore")) {2113// Some annotations need to be resolved and applied in the parser.2114annotation->apply(this, nullptr, nullptr);2115} else {2116push_error(R"(Unexpected standalone annotation.)");2117}2118} else {2119annotation_stack.push_back(annotation);2120}2121}2122break;2123}2124default: {2125// Expression statement.2126ExpressionNode *expression = parse_expression(true); // Allow assignment here.2127bool has_ended_lambda = false;2128if (expression == nullptr) {2129if (in_lambda) {2130// If it's not a valid expression beginning, it might be the continuation of the outer expression where this lambda is.2131lambda_ended = true;2132has_ended_lambda = true;2133} else {2134advance();2135push_error(vformat(R"(Expected statement, found "%s" instead.)", previous.get_name()));2136}2137} else {2138end_statement("expression");2139}2140lambda_ended = lambda_ended || has_ended_lambda;2141result = expression;21422143#ifdef DEBUG_ENABLED2144if (expression != nullptr) {2145switch (expression->type) {2146case Node::ASSIGNMENT:2147case Node::AWAIT:2148case Node::CALL:2149// Fine.2150break;2151case Node::PRELOAD:2152// `preload` is a function-like keyword.2153push_warning(expression, GDScriptWarning::RETURN_VALUE_DISCARDED, "preload");2154break;2155case Node::LAMBDA:2156// Standalone lambdas can't be used, so make this an error.2157push_error("Standalone lambdas cannot be accessed. Consider assigning it to a variable.", expression);2158break;2159case Node::LITERAL:2160// Allow strings as multiline comments.2161if (static_cast<GDScriptParser::LiteralNode *>(expression)->value.get_type() != Variant::STRING) {2162push_warning(expression, GDScriptWarning::STANDALONE_EXPRESSION);2163}2164break;2165case Node::TERNARY_OPERATOR:2166push_warning(expression, GDScriptWarning::STANDALONE_TERNARY);2167break;2168default:2169push_warning(expression, GDScriptWarning::STANDALONE_EXPRESSION);2170}2171}2172#endif2173break;2174}2175}21762177#ifdef TOOLS_ENABLED2178int doc_comment_line = 0;2179if (result != nullptr) {2180doc_comment_line = result->start_line - 1;2181}2182#endif // TOOLS_ENABLED21832184if (result != nullptr && !annotations.is_empty()) {2185for (AnnotationNode *&annotation : annotations) {2186result->annotations.push_back(annotation);2187#ifdef TOOLS_ENABLED2188if (annotation->start_line <= doc_comment_line) {2189doc_comment_line = annotation->start_line - 1;2190}2191#endif // TOOLS_ENABLED2192}2193}21942195#ifdef TOOLS_ENABLED2196if (result != nullptr) {2197MemberDocData doc_data;2198if (has_comment(result->start_line, true)) {2199// Inline doc comment.2200doc_data = parse_doc_comment(result->start_line, true);2201} else if (doc_comment_line >= current_function->min_local_doc_line && has_comment(doc_comment_line, true) && tokenizer->get_comments()[doc_comment_line].new_line) {2202// Normal doc comment.2203doc_data = parse_doc_comment(doc_comment_line);2204}22052206if (result->type == Node::CONSTANT) {2207static_cast<ConstantNode *>(result)->doc_data = doc_data;2208} else if (result->type == Node::VARIABLE) {2209static_cast<VariableNode *>(result)->doc_data = doc_data;2210}22112212current_function->min_local_doc_line = result->end_line + 1; // Prevent multiple locals from using the same doc comment.2213}2214#endif // TOOLS_ENABLED22152216#ifdef DEBUG_ENABLED2217if (unreachable && result != nullptr) {2218current_suite->has_unreachable_code = true;2219if (current_function) {2220push_warning(result, GDScriptWarning::UNREACHABLE_CODE, current_function->identifier ? current_function->identifier->name : "<anonymous lambda>");2221} else {2222// TODO: Properties setters and getters with unreachable code are not being warned2223}2224}2225#endif22262227if (panic_mode) {2228synchronize();2229}22302231return result;2232}22332234GDScriptParser::AssertNode *GDScriptParser::parse_assert() {2235// TODO: Add assert message.2236AssertNode *assert = alloc_node<AssertNode>();22372238push_multiline(true);2239consume(GDScriptTokenizer::Token::PARENTHESIS_OPEN, R"(Expected "(" after "assert".)");22402241assert->condition = parse_expression(false);2242if (assert->condition == nullptr) {2243push_error("Expected expression to assert.");2244pop_multiline();2245complete_extents(assert);2246return nullptr;2247}22482249if (match(GDScriptTokenizer::Token::COMMA) && !check(GDScriptTokenizer::Token::PARENTHESIS_CLOSE)) {2250assert->message = parse_expression(false);2251if (assert->message == nullptr) {2252push_error(R"(Expected error message for assert after ",".)");2253pop_multiline();2254complete_extents(assert);2255return nullptr;2256}2257match(GDScriptTokenizer::Token::COMMA);2258}22592260pop_multiline();2261consume(GDScriptTokenizer::Token::PARENTHESIS_CLOSE, R"*(Expected ")" after assert expression.)*");22622263complete_extents(assert);2264end_statement(R"("assert")");22652266return assert;2267}22682269GDScriptParser::BreakNode *GDScriptParser::parse_break() {2270if (!can_break) {2271push_error(R"(Cannot use "break" outside of a loop.)");2272}2273BreakNode *break_node = alloc_node<BreakNode>();2274complete_extents(break_node);2275end_statement(R"("break")");2276return break_node;2277}22782279GDScriptParser::ContinueNode *GDScriptParser::parse_continue() {2280if (!can_continue) {2281push_error(R"(Cannot use "continue" outside of a loop.)");2282}2283current_suite->has_continue = true;2284ContinueNode *cont = alloc_node<ContinueNode>();2285complete_extents(cont);2286end_statement(R"("continue")");2287return cont;2288}22892290GDScriptParser::ForNode *GDScriptParser::parse_for() {2291ForNode *n_for = alloc_node<ForNode>();22922293if (consume(GDScriptTokenizer::Token::IDENTIFIER, R"(Expected loop variable name after "for".)")) {2294n_for->variable = parse_identifier();2295}22962297if (match(GDScriptTokenizer::Token::COLON)) {2298n_for->datatype_specifier = parse_type();2299if (n_for->datatype_specifier == nullptr) {2300push_error(R"(Expected type specifier after ":".)");2301}2302}23032304if (n_for->datatype_specifier == nullptr) {2305consume(GDScriptTokenizer::Token::TK_IN, R"(Expected "in" or ":" after "for" variable name.)");2306} else {2307consume(GDScriptTokenizer::Token::TK_IN, R"(Expected "in" after "for" variable type specifier.)");2308}23092310n_for->list = parse_expression(false);23112312if (!n_for->list) {2313push_error(R"(Expected iterable after "in".)");2314}23152316consume(GDScriptTokenizer::Token::COLON, R"(Expected ":" after "for" condition.)");23172318// Save break/continue state.2319bool could_break = can_break;2320bool could_continue = can_continue;23212322// Allow break/continue.2323can_break = true;2324can_continue = true;23252326SuiteNode *suite = alloc_node<SuiteNode>();2327if (n_for->variable) {2328const SuiteNode::Local &local = current_suite->get_local(n_for->variable->name);2329if (local.type != SuiteNode::Local::UNDEFINED) {2330push_error(vformat(R"(There is already a %s named "%s" declared in this scope.)", local.get_name(), n_for->variable->name), n_for->variable);2331}2332suite->add_local(SuiteNode::Local(n_for->variable, current_function));2333}2334suite->is_in_loop = true;2335n_for->loop = parse_suite(R"("for" block)", suite);2336complete_extents(n_for);23372338// Reset break/continue state.2339can_break = could_break;2340can_continue = could_continue;23412342return n_for;2343}23442345GDScriptParser::IfNode *GDScriptParser::parse_if(const String &p_token) {2346IfNode *n_if = alloc_node<IfNode>();23472348n_if->condition = parse_expression(false);2349if (n_if->condition == nullptr) {2350push_error(vformat(R"(Expected conditional expression after "%s".)", p_token));2351}23522353consume(GDScriptTokenizer::Token::COLON, vformat(R"(Expected ":" after "%s" condition.)", p_token));23542355n_if->true_block = parse_suite(vformat(R"("%s" block)", p_token));2356n_if->true_block->parent_if = n_if;23572358if (n_if->true_block->has_continue) {2359current_suite->has_continue = true;2360}23612362if (match(GDScriptTokenizer::Token::ELIF)) {2363SuiteNode *else_block = alloc_node<SuiteNode>();2364else_block->parent_function = current_function;2365else_block->parent_block = current_suite;23662367SuiteNode *previous_suite = current_suite;2368current_suite = else_block;23692370IfNode *elif = parse_if("elif");2371else_block->statements.push_back(elif);2372complete_extents(else_block);2373n_if->false_block = else_block;23742375current_suite = previous_suite;2376} else if (match(GDScriptTokenizer::Token::ELSE)) {2377consume(GDScriptTokenizer::Token::COLON, R"(Expected ":" after "else".)");2378n_if->false_block = parse_suite(R"("else" block)");2379}2380complete_extents(n_if);23812382if (n_if->false_block != nullptr && n_if->false_block->has_return && n_if->true_block->has_return) {2383current_suite->has_return = true;2384}2385if (n_if->false_block != nullptr && n_if->false_block->has_continue) {2386current_suite->has_continue = true;2387}23882389return n_if;2390}23912392GDScriptParser::MatchNode *GDScriptParser::parse_match() {2393MatchNode *match_node = alloc_node<MatchNode>();23942395match_node->test = parse_expression(false);2396if (match_node->test == nullptr) {2397push_error(R"(Expected expression to test after "match".)");2398}23992400consume(GDScriptTokenizer::Token::COLON, R"(Expected ":" after "match" expression.)");2401consume(GDScriptTokenizer::Token::NEWLINE, R"(Expected a newline after "match" statement.)");24022403if (!consume(GDScriptTokenizer::Token::INDENT, R"(Expected an indented block after "match" statement.)")) {2404complete_extents(match_node);2405return match_node;2406}24072408bool all_have_return = true;2409bool have_wildcard = false;24102411List<AnnotationNode *> match_branch_annotation_stack;24122413while (!check(GDScriptTokenizer::Token::DEDENT) && !is_at_end()) {2414if (match(GDScriptTokenizer::Token::PASS)) {2415consume(GDScriptTokenizer::Token::NEWLINE, R"(Expected newline after "pass".)");2416continue;2417}24182419if (match(GDScriptTokenizer::Token::ANNOTATION)) {2420AnnotationNode *annotation = parse_annotation(AnnotationInfo::STATEMENT);2421if (annotation == nullptr) {2422continue;2423}2424if (annotation->name != SNAME("@warning_ignore")) {2425push_error(vformat(R"(Annotation "%s" is not allowed in this level.)", annotation->name), annotation);2426continue;2427}2428match_branch_annotation_stack.push_back(annotation);2429continue;2430}24312432MatchBranchNode *branch = parse_match_branch();2433if (branch == nullptr) {2434advance();2435continue;2436}24372438for (AnnotationNode *annotation : match_branch_annotation_stack) {2439branch->annotations.push_back(annotation);2440}2441match_branch_annotation_stack.clear();24422443#ifdef DEBUG_ENABLED2444if (have_wildcard && !branch->patterns.is_empty()) {2445push_warning(branch->patterns[0], GDScriptWarning::UNREACHABLE_PATTERN);2446}2447#endif24482449have_wildcard = have_wildcard || branch->has_wildcard;2450all_have_return = all_have_return && branch->block->has_return;2451match_node->branches.push_back(branch);2452}2453complete_extents(match_node);24542455consume(GDScriptTokenizer::Token::DEDENT, R"(Expected an indented block after "match" statement.)");24562457if (all_have_return && have_wildcard) {2458current_suite->has_return = true;2459}24602461for (const AnnotationNode *annotation : match_branch_annotation_stack) {2462push_error(vformat(R"(Annotation "%s" does not precede a valid target, so it will have no effect.)", annotation->name), annotation);2463}2464match_branch_annotation_stack.clear();24652466return match_node;2467}24682469GDScriptParser::MatchBranchNode *GDScriptParser::parse_match_branch() {2470MatchBranchNode *branch = alloc_node<MatchBranchNode>();2471reset_extents(branch, current);24722473bool has_bind = false;24742475do {2476PatternNode *pattern = parse_match_pattern();2477if (pattern == nullptr) {2478continue;2479}2480if (pattern->binds.size() > 0) {2481has_bind = true;2482}2483if (branch->patterns.size() > 0 && has_bind) {2484push_error(R"(Cannot use a variable bind with multiple patterns.)");2485}2486if (pattern->pattern_type == PatternNode::PT_REST) {2487push_error(R"(Rest pattern can only be used inside array and dictionary patterns.)");2488} else if (pattern->pattern_type == PatternNode::PT_BIND || pattern->pattern_type == PatternNode::PT_WILDCARD) {2489branch->has_wildcard = true;2490}2491branch->patterns.push_back(pattern);2492} while (match(GDScriptTokenizer::Token::COMMA));24932494if (branch->patterns.is_empty()) {2495push_error(R"(No pattern found for "match" branch.)");2496}24972498bool has_guard = false;2499if (match(GDScriptTokenizer::Token::WHEN)) {2500// Pattern guard.2501// Create block for guard because it also needs to access the bound variables from patterns, and we don't want to add them to the outer scope.2502branch->guard_body = alloc_node<SuiteNode>();2503if (branch->patterns.size() > 0) {2504for (const KeyValue<StringName, IdentifierNode *> &E : branch->patterns[0]->binds) {2505SuiteNode::Local local(E.value, current_function);2506local.type = SuiteNode::Local::PATTERN_BIND;2507branch->guard_body->add_local(local);2508}2509}25102511SuiteNode *parent_block = current_suite;2512branch->guard_body->parent_block = parent_block;2513current_suite = branch->guard_body;25142515ExpressionNode *guard = parse_expression(false);2516if (guard == nullptr) {2517push_error(R"(Expected expression for pattern guard after "when".)");2518} else {2519branch->guard_body->statements.append(guard);2520}2521current_suite = parent_block;2522complete_extents(branch->guard_body);25232524has_guard = true;2525branch->has_wildcard = false; // If it has a guard, the wildcard might still not match.2526}25272528if (!consume(GDScriptTokenizer::Token::COLON, vformat(R"(Expected ":"%s after "match" %s.)", has_guard ? "" : R"( or "when")", has_guard ? "pattern guard" : "patterns"))) {2529branch->block = alloc_recovery_suite();2530complete_extents(branch);2531// Consume the whole line and treat the next one as new match branch.2532while (current.type != GDScriptTokenizer::Token::NEWLINE && !is_at_end()) {2533advance();2534}2535if (!is_at_end()) {2536advance();2537}2538return branch;2539}25402541SuiteNode *suite = alloc_node<SuiteNode>();2542if (branch->patterns.size() > 0) {2543for (const KeyValue<StringName, IdentifierNode *> &E : branch->patterns[0]->binds) {2544SuiteNode::Local local(E.value, current_function);2545local.type = SuiteNode::Local::PATTERN_BIND;2546suite->add_local(local);2547}2548}25492550branch->block = parse_suite("match pattern block", suite);2551complete_extents(branch);25522553return branch;2554}25552556GDScriptParser::PatternNode *GDScriptParser::parse_match_pattern(PatternNode *p_root_pattern) {2557PatternNode *pattern = alloc_node<PatternNode>();2558reset_extents(pattern, current);25592560switch (current.type) {2561case GDScriptTokenizer::Token::VAR: {2562// Bind.2563advance();2564if (!consume(GDScriptTokenizer::Token::IDENTIFIER, R"(Expected bind name after "var".)")) {2565complete_extents(pattern);2566return nullptr;2567}2568pattern->pattern_type = PatternNode::PT_BIND;2569pattern->bind = parse_identifier();25702571PatternNode *root_pattern = p_root_pattern == nullptr ? pattern : p_root_pattern;25722573if (p_root_pattern != nullptr) {2574if (p_root_pattern->has_bind(pattern->bind->name)) {2575push_error(vformat(R"(Bind variable name "%s" was already used in this pattern.)", pattern->bind->name));2576complete_extents(pattern);2577return nullptr;2578}2579}25802581if (current_suite->has_local(pattern->bind->name)) {2582push_error(vformat(R"(There's already a %s named "%s" in this scope.)", current_suite->get_local(pattern->bind->name).get_name(), pattern->bind->name));2583complete_extents(pattern);2584return nullptr;2585}25862587root_pattern->binds[pattern->bind->name] = pattern->bind;25882589} break;2590case GDScriptTokenizer::Token::UNDERSCORE:2591// Wildcard.2592advance();2593pattern->pattern_type = PatternNode::PT_WILDCARD;2594break;2595case GDScriptTokenizer::Token::PERIOD_PERIOD:2596// Rest.2597advance();2598pattern->pattern_type = PatternNode::PT_REST;2599break;2600case GDScriptTokenizer::Token::BRACKET_OPEN: {2601// Array.2602push_multiline(true);2603advance();2604pattern->pattern_type = PatternNode::PT_ARRAY;2605do {2606if (is_at_end() || check(GDScriptTokenizer::Token::BRACKET_CLOSE)) {2607break;2608}2609PatternNode *sub_pattern = parse_match_pattern(p_root_pattern != nullptr ? p_root_pattern : pattern);2610if (sub_pattern == nullptr) {2611continue;2612}2613if (pattern->rest_used) {2614push_error(R"(The ".." pattern must be the last element in the pattern array.)");2615} else if (sub_pattern->pattern_type == PatternNode::PT_REST) {2616pattern->rest_used = true;2617}2618pattern->array.push_back(sub_pattern);2619} while (match(GDScriptTokenizer::Token::COMMA));2620consume(GDScriptTokenizer::Token::BRACKET_CLOSE, R"(Expected "]" to close the array pattern.)");2621pop_multiline();2622break;2623}2624case GDScriptTokenizer::Token::BRACE_OPEN: {2625// Dictionary.2626push_multiline(true);2627advance();2628pattern->pattern_type = PatternNode::PT_DICTIONARY;2629do {2630if (check(GDScriptTokenizer::Token::BRACE_CLOSE) || is_at_end()) {2631break;2632}2633if (match(GDScriptTokenizer::Token::PERIOD_PERIOD)) {2634// Rest.2635if (pattern->rest_used) {2636push_error(R"(The ".." pattern must be the last element in the pattern dictionary.)");2637} else {2638PatternNode *sub_pattern = alloc_node<PatternNode>();2639complete_extents(sub_pattern);2640sub_pattern->pattern_type = PatternNode::PT_REST;2641pattern->dictionary.push_back({ nullptr, sub_pattern });2642pattern->rest_used = true;2643}2644} else {2645ExpressionNode *key = parse_expression(false);2646if (key == nullptr) {2647push_error(R"(Expected expression as key for dictionary pattern.)");2648}2649if (match(GDScriptTokenizer::Token::COLON)) {2650// Value pattern.2651PatternNode *sub_pattern = parse_match_pattern(p_root_pattern != nullptr ? p_root_pattern : pattern);2652if (sub_pattern == nullptr) {2653continue;2654}2655if (pattern->rest_used) {2656push_error(R"(The ".." pattern must be the last element in the pattern dictionary.)");2657} else if (sub_pattern->pattern_type == PatternNode::PT_REST) {2658push_error(R"(The ".." pattern cannot be used as a value.)");2659} else {2660pattern->dictionary.push_back({ key, sub_pattern });2661}2662} else {2663// Key match only.2664pattern->dictionary.push_back({ key, nullptr });2665}2666}2667} while (match(GDScriptTokenizer::Token::COMMA));2668consume(GDScriptTokenizer::Token::BRACE_CLOSE, R"(Expected "}" to close the dictionary pattern.)");2669pop_multiline();2670break;2671}2672default: {2673// Expression.2674ExpressionNode *expression = parse_expression(false);2675if (expression == nullptr) {2676push_error(R"(Expected expression for match pattern.)");2677complete_extents(pattern);2678return nullptr;2679} else {2680if (expression->type == GDScriptParser::Node::LITERAL) {2681pattern->pattern_type = PatternNode::PT_LITERAL;2682} else {2683pattern->pattern_type = PatternNode::PT_EXPRESSION;2684}2685pattern->expression = expression;2686}2687break;2688}2689}2690complete_extents(pattern);26912692return pattern;2693}26942695bool GDScriptParser::PatternNode::has_bind(const StringName &p_name) {2696return binds.has(p_name);2697}26982699GDScriptParser::IdentifierNode *GDScriptParser::PatternNode::get_bind(const StringName &p_name) {2700return binds[p_name];2701}27022703GDScriptParser::WhileNode *GDScriptParser::parse_while() {2704WhileNode *n_while = alloc_node<WhileNode>();27052706n_while->condition = parse_expression(false);2707if (n_while->condition == nullptr) {2708push_error(R"(Expected conditional expression after "while".)");2709}27102711consume(GDScriptTokenizer::Token::COLON, R"(Expected ":" after "while" condition.)");27122713// Save break/continue state.2714bool could_break = can_break;2715bool could_continue = can_continue;27162717// Allow break/continue.2718can_break = true;2719can_continue = true;27202721SuiteNode *suite = alloc_node<SuiteNode>();2722suite->is_in_loop = true;2723n_while->loop = parse_suite(R"("while" block)", suite);2724complete_extents(n_while);27252726// Reset break/continue state.2727can_break = could_break;2728can_continue = could_continue;27292730return n_while;2731}27322733GDScriptParser::ExpressionNode *GDScriptParser::parse_precedence(Precedence p_precedence, bool p_can_assign, bool p_stop_on_assign) {2734// Switch multiline mode on for grouping tokens.2735// Do this early to avoid the tokenizer generating whitespace tokens.2736switch (current.type) {2737case GDScriptTokenizer::Token::PARENTHESIS_OPEN:2738case GDScriptTokenizer::Token::BRACE_OPEN:2739case GDScriptTokenizer::Token::BRACKET_OPEN:2740push_multiline(true);2741break;2742default:2743break; // Nothing to do.2744}27452746// Completion can appear whenever an expression is expected.2747make_completion_context(COMPLETION_IDENTIFIER, nullptr, -1, false);27482749GDScriptTokenizer::Token token = current;2750GDScriptTokenizer::Token::Type token_type = token.type;2751if (token.is_identifier()) {2752// Allow keywords that can be treated as identifiers.2753token_type = GDScriptTokenizer::Token::IDENTIFIER;2754}2755ParseFunction prefix_rule = get_rule(token_type)->prefix;27562757if (prefix_rule == nullptr) {2758// Expected expression. Let the caller give the proper error message.2759return nullptr;2760}27612762advance(); // Only consume the token if there's a valid rule.27632764// After a token was consumed, update the completion context regardless of a previously set context.27652766ExpressionNode *previous_operand = (this->*prefix_rule)(nullptr, p_can_assign);27672768#ifdef TOOLS_ENABLED2769// HACK: We can't create a context in parse_identifier since it is used in places were we don't want completion.2770if (previous_operand != nullptr && previous_operand->type == GDScriptParser::Node::IDENTIFIER && prefix_rule == static_cast<ParseFunction>(&GDScriptParser::parse_identifier)) {2771make_completion_context(COMPLETION_IDENTIFIER, previous_operand);2772}2773#endif27742775while (p_precedence <= get_rule(current.type)->precedence) {2776if (previous_operand == nullptr || (p_stop_on_assign && current.type == GDScriptTokenizer::Token::EQUAL) || lambda_ended) {2777return previous_operand;2778}2779// Also switch multiline mode on here for infix operators.2780switch (current.type) {2781// case GDScriptTokenizer::Token::BRACE_OPEN: // Not an infix operator.2782case GDScriptTokenizer::Token::PARENTHESIS_OPEN:2783case GDScriptTokenizer::Token::BRACKET_OPEN:2784push_multiline(true);2785break;2786default:2787break; // Nothing to do.2788}2789token = advance();2790ParseFunction infix_rule = get_rule(token.type)->infix;2791previous_operand = (this->*infix_rule)(previous_operand, p_can_assign);2792}27932794return previous_operand;2795}27962797GDScriptParser::ExpressionNode *GDScriptParser::parse_expression(bool p_can_assign, bool p_stop_on_assign) {2798return parse_precedence(PREC_ASSIGNMENT, p_can_assign, p_stop_on_assign);2799}28002801GDScriptParser::IdentifierNode *GDScriptParser::parse_identifier() {2802IdentifierNode *identifier = static_cast<IdentifierNode *>(parse_identifier(nullptr, false));2803#ifdef DEBUG_ENABLED2804// Check for spoofing here (if available in TextServer) since this isn't called inside expressions. This is only relevant for declarations.2805if (identifier && TS->has_feature(TextServer::FEATURE_UNICODE_SECURITY) && TS->spoof_check(identifier->name)) {2806push_warning(identifier, GDScriptWarning::CONFUSABLE_IDENTIFIER, identifier->name.operator String());2807}2808#endif2809return identifier;2810}28112812GDScriptParser::ExpressionNode *GDScriptParser::parse_identifier(ExpressionNode *p_previous_operand, bool p_can_assign) {2813if (!previous.is_identifier()) {2814ERR_FAIL_V_MSG(nullptr, "Parser bug: parsing identifier node without identifier token.");2815}2816IdentifierNode *identifier = alloc_node<IdentifierNode>();2817complete_extents(identifier);2818identifier->name = previous.get_identifier();2819if (identifier->name.operator String().is_empty()) {2820print_line("Empty identifier found.");2821}2822identifier->suite = current_suite;28232824if (current_suite != nullptr && current_suite->has_local(identifier->name)) {2825const SuiteNode::Local &declaration = current_suite->get_local(identifier->name);28262827identifier->source_function = declaration.source_function;2828switch (declaration.type) {2829case SuiteNode::Local::CONSTANT:2830identifier->source = IdentifierNode::LOCAL_CONSTANT;2831identifier->constant_source = declaration.constant;2832declaration.constant->usages++;2833break;2834case SuiteNode::Local::VARIABLE:2835identifier->source = IdentifierNode::LOCAL_VARIABLE;2836identifier->variable_source = declaration.variable;2837declaration.variable->usages++;2838break;2839case SuiteNode::Local::PARAMETER:2840identifier->source = IdentifierNode::FUNCTION_PARAMETER;2841identifier->parameter_source = declaration.parameter;2842declaration.parameter->usages++;2843break;2844case SuiteNode::Local::FOR_VARIABLE:2845identifier->source = IdentifierNode::LOCAL_ITERATOR;2846identifier->bind_source = declaration.bind;2847declaration.bind->usages++;2848break;2849case SuiteNode::Local::PATTERN_BIND:2850identifier->source = IdentifierNode::LOCAL_BIND;2851identifier->bind_source = declaration.bind;2852declaration.bind->usages++;2853break;2854case SuiteNode::Local::UNDEFINED:2855ERR_FAIL_V_MSG(nullptr, "Undefined local found.");2856}2857}28582859return identifier;2860}28612862GDScriptParser::LiteralNode *GDScriptParser::parse_literal() {2863return static_cast<LiteralNode *>(parse_literal(nullptr, false));2864}28652866GDScriptParser::ExpressionNode *GDScriptParser::parse_literal(ExpressionNode *p_previous_operand, bool p_can_assign) {2867if (previous.type != GDScriptTokenizer::Token::LITERAL) {2868push_error("Parser bug: parsing literal node without literal token.");2869ERR_FAIL_V_MSG(nullptr, "Parser bug: parsing literal node without literal token.");2870}28712872LiteralNode *literal = alloc_node<LiteralNode>();2873literal->value = previous.literal;2874reset_extents(literal, p_previous_operand);2875update_extents(literal);2876make_completion_context(COMPLETION_NONE, literal, -1);2877complete_extents(literal);2878return literal;2879}28802881GDScriptParser::ExpressionNode *GDScriptParser::parse_self(ExpressionNode *p_previous_operand, bool p_can_assign) {2882if (current_function && current_function->is_static) {2883push_error(R"(Cannot use "self" inside a static function.)");2884}2885SelfNode *self = alloc_node<SelfNode>();2886complete_extents(self);2887self->current_class = current_class;2888return self;2889}28902891GDScriptParser::ExpressionNode *GDScriptParser::parse_builtin_constant(ExpressionNode *p_previous_operand, bool p_can_assign) {2892GDScriptTokenizer::Token::Type op_type = previous.type;2893LiteralNode *constant = alloc_node<LiteralNode>();2894complete_extents(constant);28952896switch (op_type) {2897case GDScriptTokenizer::Token::CONST_PI:2898constant->value = Math::PI;2899break;2900case GDScriptTokenizer::Token::CONST_TAU:2901constant->value = Math::TAU;2902break;2903case GDScriptTokenizer::Token::CONST_INF:2904constant->value = Math::INF;2905break;2906case GDScriptTokenizer::Token::CONST_NAN:2907constant->value = Math::NaN;2908break;2909default:2910return nullptr; // Unreachable.2911}29122913return constant;2914}29152916GDScriptParser::ExpressionNode *GDScriptParser::parse_unary_operator(ExpressionNode *p_previous_operand, bool p_can_assign) {2917GDScriptTokenizer::Token::Type op_type = previous.type;2918UnaryOpNode *operation = alloc_node<UnaryOpNode>();29192920switch (op_type) {2921case GDScriptTokenizer::Token::MINUS:2922operation->operation = UnaryOpNode::OP_NEGATIVE;2923operation->variant_op = Variant::OP_NEGATE;2924operation->operand = parse_precedence(PREC_SIGN, false);2925if (operation->operand == nullptr) {2926push_error(R"(Expected expression after "-" operator.)");2927}2928break;2929case GDScriptTokenizer::Token::PLUS:2930operation->operation = UnaryOpNode::OP_POSITIVE;2931operation->variant_op = Variant::OP_POSITIVE;2932operation->operand = parse_precedence(PREC_SIGN, false);2933if (operation->operand == nullptr) {2934push_error(R"(Expected expression after "+" operator.)");2935}2936break;2937case GDScriptTokenizer::Token::TILDE:2938operation->operation = UnaryOpNode::OP_COMPLEMENT;2939operation->variant_op = Variant::OP_BIT_NEGATE;2940operation->operand = parse_precedence(PREC_BIT_NOT, false);2941if (operation->operand == nullptr) {2942push_error(R"(Expected expression after "~" operator.)");2943}2944break;2945case GDScriptTokenizer::Token::NOT:2946case GDScriptTokenizer::Token::BANG:2947operation->operation = UnaryOpNode::OP_LOGIC_NOT;2948operation->variant_op = Variant::OP_NOT;2949operation->operand = parse_precedence(PREC_LOGIC_NOT, false);2950if (operation->operand == nullptr) {2951push_error(vformat(R"(Expected expression after "%s" operator.)", op_type == GDScriptTokenizer::Token::NOT ? "not" : "!"));2952}2953break;2954default:2955complete_extents(operation);2956return nullptr; // Unreachable.2957}2958complete_extents(operation);29592960return operation;2961}29622963GDScriptParser::ExpressionNode *GDScriptParser::parse_binary_not_in_operator(ExpressionNode *p_previous_operand, bool p_can_assign) {2964// check that NOT is followed by IN by consuming it before calling parse_binary_operator which will only receive a plain IN2965UnaryOpNode *operation = alloc_node<UnaryOpNode>();2966reset_extents(operation, p_previous_operand);2967update_extents(operation);2968consume(GDScriptTokenizer::Token::TK_IN, R"(Expected "in" after "not" in content-test operator.)");2969ExpressionNode *in_operation = parse_binary_operator(p_previous_operand, p_can_assign);2970operation->operation = UnaryOpNode::OP_LOGIC_NOT;2971operation->variant_op = Variant::OP_NOT;2972operation->operand = in_operation;2973complete_extents(operation);2974return operation;2975}29762977GDScriptParser::ExpressionNode *GDScriptParser::parse_binary_operator(ExpressionNode *p_previous_operand, bool p_can_assign) {2978GDScriptTokenizer::Token op = previous;2979BinaryOpNode *operation = alloc_node<BinaryOpNode>();2980reset_extents(operation, p_previous_operand);2981update_extents(operation);29822983Precedence precedence = (Precedence)(get_rule(op.type)->precedence + 1);2984operation->left_operand = p_previous_operand;2985operation->right_operand = parse_precedence(precedence, false);2986complete_extents(operation);29872988if (operation->right_operand == nullptr) {2989push_error(vformat(R"(Expected expression after "%s" operator.)", op.get_name()));2990}29912992// TODO: Also for unary, ternary, and assignment.2993switch (op.type) {2994case GDScriptTokenizer::Token::PLUS:2995operation->operation = BinaryOpNode::OP_ADDITION;2996operation->variant_op = Variant::OP_ADD;2997break;2998case GDScriptTokenizer::Token::MINUS:2999operation->operation = BinaryOpNode::OP_SUBTRACTION;3000operation->variant_op = Variant::OP_SUBTRACT;3001break;3002case GDScriptTokenizer::Token::STAR:3003operation->operation = BinaryOpNode::OP_MULTIPLICATION;3004operation->variant_op = Variant::OP_MULTIPLY;3005break;3006case GDScriptTokenizer::Token::SLASH:3007operation->operation = BinaryOpNode::OP_DIVISION;3008operation->variant_op = Variant::OP_DIVIDE;3009break;3010case GDScriptTokenizer::Token::PERCENT:3011operation->operation = BinaryOpNode::OP_MODULO;3012operation->variant_op = Variant::OP_MODULE;3013break;3014case GDScriptTokenizer::Token::STAR_STAR:3015operation->operation = BinaryOpNode::OP_POWER;3016operation->variant_op = Variant::OP_POWER;3017break;3018case GDScriptTokenizer::Token::LESS_LESS:3019operation->operation = BinaryOpNode::OP_BIT_LEFT_SHIFT;3020operation->variant_op = Variant::OP_SHIFT_LEFT;3021break;3022case GDScriptTokenizer::Token::GREATER_GREATER:3023operation->operation = BinaryOpNode::OP_BIT_RIGHT_SHIFT;3024operation->variant_op = Variant::OP_SHIFT_RIGHT;3025break;3026case GDScriptTokenizer::Token::AMPERSAND:3027operation->operation = BinaryOpNode::OP_BIT_AND;3028operation->variant_op = Variant::OP_BIT_AND;3029break;3030case GDScriptTokenizer::Token::PIPE:3031operation->operation = BinaryOpNode::OP_BIT_OR;3032operation->variant_op = Variant::OP_BIT_OR;3033break;3034case GDScriptTokenizer::Token::CARET:3035operation->operation = BinaryOpNode::OP_BIT_XOR;3036operation->variant_op = Variant::OP_BIT_XOR;3037break;3038case GDScriptTokenizer::Token::AND:3039case GDScriptTokenizer::Token::AMPERSAND_AMPERSAND:3040operation->operation = BinaryOpNode::OP_LOGIC_AND;3041operation->variant_op = Variant::OP_AND;3042break;3043case GDScriptTokenizer::Token::OR:3044case GDScriptTokenizer::Token::PIPE_PIPE:3045operation->operation = BinaryOpNode::OP_LOGIC_OR;3046operation->variant_op = Variant::OP_OR;3047break;3048case GDScriptTokenizer::Token::TK_IN:3049operation->operation = BinaryOpNode::OP_CONTENT_TEST;3050operation->variant_op = Variant::OP_IN;3051break;3052case GDScriptTokenizer::Token::EQUAL_EQUAL:3053operation->operation = BinaryOpNode::OP_COMP_EQUAL;3054operation->variant_op = Variant::OP_EQUAL;3055break;3056case GDScriptTokenizer::Token::BANG_EQUAL:3057operation->operation = BinaryOpNode::OP_COMP_NOT_EQUAL;3058operation->variant_op = Variant::OP_NOT_EQUAL;3059break;3060case GDScriptTokenizer::Token::LESS:3061operation->operation = BinaryOpNode::OP_COMP_LESS;3062operation->variant_op = Variant::OP_LESS;3063break;3064case GDScriptTokenizer::Token::LESS_EQUAL:3065operation->operation = BinaryOpNode::OP_COMP_LESS_EQUAL;3066operation->variant_op = Variant::OP_LESS_EQUAL;3067break;3068case GDScriptTokenizer::Token::GREATER:3069operation->operation = BinaryOpNode::OP_COMP_GREATER;3070operation->variant_op = Variant::OP_GREATER;3071break;3072case GDScriptTokenizer::Token::GREATER_EQUAL:3073operation->operation = BinaryOpNode::OP_COMP_GREATER_EQUAL;3074operation->variant_op = Variant::OP_GREATER_EQUAL;3075break;3076default:3077return nullptr; // Unreachable.3078}30793080return operation;3081}30823083GDScriptParser::ExpressionNode *GDScriptParser::parse_ternary_operator(ExpressionNode *p_previous_operand, bool p_can_assign) {3084// Only one ternary operation exists, so no abstraction here.3085TernaryOpNode *operation = alloc_node<TernaryOpNode>();3086reset_extents(operation, p_previous_operand);3087update_extents(operation);30883089operation->true_expr = p_previous_operand;3090operation->condition = parse_precedence(PREC_TERNARY, false);30913092if (operation->condition == nullptr) {3093push_error(R"(Expected expression as ternary condition after "if".)");3094}30953096consume(GDScriptTokenizer::Token::ELSE, R"(Expected "else" after ternary operator condition.)");30973098operation->false_expr = parse_precedence(PREC_TERNARY, false);30993100if (operation->false_expr == nullptr) {3101push_error(R"(Expected expression after "else".)");3102}31033104complete_extents(operation);3105return operation;3106}31073108GDScriptParser::ExpressionNode *GDScriptParser::parse_assignment(ExpressionNode *p_previous_operand, bool p_can_assign) {3109if (!p_can_assign) {3110push_error("Assignment is not allowed inside an expression.");3111return parse_expression(false); // Return the following expression.3112}3113if (p_previous_operand == nullptr) {3114return parse_expression(false); // Return the following expression.3115}31163117switch (p_previous_operand->type) {3118case Node::IDENTIFIER: {3119#ifdef DEBUG_ENABLED3120// Get source to store assignment count.3121// Also remove one usage since assignment isn't usage.3122IdentifierNode *id = static_cast<IdentifierNode *>(p_previous_operand);3123switch (id->source) {3124case IdentifierNode::LOCAL_VARIABLE:3125id->variable_source->usages--;3126break;3127case IdentifierNode::LOCAL_CONSTANT:3128id->constant_source->usages--;3129break;3130case IdentifierNode::FUNCTION_PARAMETER:3131id->parameter_source->usages--;3132break;3133case IdentifierNode::LOCAL_ITERATOR:3134case IdentifierNode::LOCAL_BIND:3135id->bind_source->usages--;3136break;3137default:3138break;3139}3140#endif3141} break;3142case Node::SUBSCRIPT:3143// Okay.3144break;3145default:3146push_error(R"(Only identifier, attribute access, and subscription access can be used as assignment target.)");3147return parse_expression(false); // Return the following expression.3148}31493150AssignmentNode *assignment = alloc_node<AssignmentNode>();3151reset_extents(assignment, p_previous_operand);3152update_extents(assignment);31533154make_completion_context(COMPLETION_ASSIGN, assignment);3155switch (previous.type) {3156case GDScriptTokenizer::Token::EQUAL:3157assignment->operation = AssignmentNode::OP_NONE;3158assignment->variant_op = Variant::OP_MAX;3159break;3160case GDScriptTokenizer::Token::PLUS_EQUAL:3161assignment->operation = AssignmentNode::OP_ADDITION;3162assignment->variant_op = Variant::OP_ADD;3163break;3164case GDScriptTokenizer::Token::MINUS_EQUAL:3165assignment->operation = AssignmentNode::OP_SUBTRACTION;3166assignment->variant_op = Variant::OP_SUBTRACT;3167break;3168case GDScriptTokenizer::Token::STAR_EQUAL:3169assignment->operation = AssignmentNode::OP_MULTIPLICATION;3170assignment->variant_op = Variant::OP_MULTIPLY;3171break;3172case GDScriptTokenizer::Token::STAR_STAR_EQUAL:3173assignment->operation = AssignmentNode::OP_POWER;3174assignment->variant_op = Variant::OP_POWER;3175break;3176case GDScriptTokenizer::Token::SLASH_EQUAL:3177assignment->operation = AssignmentNode::OP_DIVISION;3178assignment->variant_op = Variant::OP_DIVIDE;3179break;3180case GDScriptTokenizer::Token::PERCENT_EQUAL:3181assignment->operation = AssignmentNode::OP_MODULO;3182assignment->variant_op = Variant::OP_MODULE;3183break;3184case GDScriptTokenizer::Token::LESS_LESS_EQUAL:3185assignment->operation = AssignmentNode::OP_BIT_SHIFT_LEFT;3186assignment->variant_op = Variant::OP_SHIFT_LEFT;3187break;3188case GDScriptTokenizer::Token::GREATER_GREATER_EQUAL:3189assignment->operation = AssignmentNode::OP_BIT_SHIFT_RIGHT;3190assignment->variant_op = Variant::OP_SHIFT_RIGHT;3191break;3192case GDScriptTokenizer::Token::AMPERSAND_EQUAL:3193assignment->operation = AssignmentNode::OP_BIT_AND;3194assignment->variant_op = Variant::OP_BIT_AND;3195break;3196case GDScriptTokenizer::Token::PIPE_EQUAL:3197assignment->operation = AssignmentNode::OP_BIT_OR;3198assignment->variant_op = Variant::OP_BIT_OR;3199break;3200case GDScriptTokenizer::Token::CARET_EQUAL:3201assignment->operation = AssignmentNode::OP_BIT_XOR;3202assignment->variant_op = Variant::OP_BIT_XOR;3203break;3204default:3205break; // Unreachable.3206}3207assignment->assignee = p_previous_operand;3208assignment->assigned_value = parse_expression(false);3209#ifdef TOOLS_ENABLED3210if (assignment->assigned_value != nullptr && assignment->assigned_value->type == GDScriptParser::Node::IDENTIFIER) {3211override_completion_context(assignment->assigned_value, COMPLETION_ASSIGN, assignment);3212}3213#endif3214if (assignment->assigned_value == nullptr) {3215push_error(R"(Expected an expression after "=".)");3216}3217complete_extents(assignment);32183219return assignment;3220}32213222GDScriptParser::ExpressionNode *GDScriptParser::parse_await(ExpressionNode *p_previous_operand, bool p_can_assign) {3223AwaitNode *await = alloc_node<AwaitNode>();3224ExpressionNode *element = parse_precedence(PREC_AWAIT, false);3225if (element == nullptr) {3226push_error(R"(Expected signal or coroutine after "await".)");3227}3228await->to_await = element;3229complete_extents(await);32303231if (current_function) { // Might be null in a getter or setter.3232current_function->is_coroutine = true;3233}32343235return await;3236}32373238GDScriptParser::ExpressionNode *GDScriptParser::parse_array(ExpressionNode *p_previous_operand, bool p_can_assign) {3239ArrayNode *array = alloc_node<ArrayNode>();32403241if (!check(GDScriptTokenizer::Token::BRACKET_CLOSE)) {3242do {3243if (check(GDScriptTokenizer::Token::BRACKET_CLOSE)) {3244// Allow for trailing comma.3245break;3246}32473248ExpressionNode *element = parse_expression(false);3249if (element == nullptr) {3250push_error(R"(Expected expression as array element.)");3251} else {3252array->elements.push_back(element);3253}3254} while (match(GDScriptTokenizer::Token::COMMA) && !is_at_end());3255}3256pop_multiline();3257consume(GDScriptTokenizer::Token::BRACKET_CLOSE, R"(Expected closing "]" after array elements.)");3258complete_extents(array);32593260return array;3261}32623263GDScriptParser::ExpressionNode *GDScriptParser::parse_dictionary(ExpressionNode *p_previous_operand, bool p_can_assign) {3264DictionaryNode *dictionary = alloc_node<DictionaryNode>();32653266bool decided_style = false;3267if (!check(GDScriptTokenizer::Token::BRACE_CLOSE)) {3268do {3269if (check(GDScriptTokenizer::Token::BRACE_CLOSE)) {3270// Allow for trailing comma.3271break;3272}32733274// Key.3275ExpressionNode *key = parse_expression(false, true); // Stop on "=" so we can check for Lua table style.32763277if (key == nullptr) {3278push_error(R"(Expected expression as dictionary key.)");3279}32803281if (!decided_style) {3282switch (current.type) {3283case GDScriptTokenizer::Token::COLON:3284dictionary->style = DictionaryNode::PYTHON_DICT;3285break;3286case GDScriptTokenizer::Token::EQUAL:3287dictionary->style = DictionaryNode::LUA_TABLE;3288break;3289default:3290push_error(R"(Expected ":" or "=" after dictionary key.)");3291break;3292}3293decided_style = true;3294}32953296switch (dictionary->style) {3297case DictionaryNode::LUA_TABLE:3298if (key != nullptr && key->type != Node::IDENTIFIER && key->type != Node::LITERAL) {3299push_error(R"(Expected identifier or string as Lua-style dictionary key (e.g "{ key = value }").)");3300}3301if (key != nullptr && key->type == Node::LITERAL && static_cast<LiteralNode *>(key)->value.get_type() != Variant::STRING) {3302push_error(R"(Expected identifier or string as Lua-style dictionary key (e.g "{ key = value }").)");3303}3304if (!match(GDScriptTokenizer::Token::EQUAL)) {3305if (match(GDScriptTokenizer::Token::COLON)) {3306push_error(R"(Expected "=" after dictionary key. Mixing dictionary styles is not allowed.)");3307advance(); // Consume wrong separator anyway.3308} else {3309push_error(R"(Expected "=" after dictionary key.)");3310}3311}3312if (key != nullptr) {3313key->is_constant = true;3314if (key->type == Node::IDENTIFIER) {3315key->reduced_value = static_cast<IdentifierNode *>(key)->name;3316} else if (key->type == Node::LITERAL) {3317key->reduced_value = StringName(static_cast<LiteralNode *>(key)->value.operator String());3318}3319}3320break;3321case DictionaryNode::PYTHON_DICT:3322if (!match(GDScriptTokenizer::Token::COLON)) {3323if (match(GDScriptTokenizer::Token::EQUAL)) {3324push_error(R"(Expected ":" after dictionary key. Mixing dictionary styles is not allowed.)");3325advance(); // Consume wrong separator anyway.3326} else {3327push_error(R"(Expected ":" after dictionary key.)");3328}3329}3330break;3331}33323333// Value.3334ExpressionNode *value = parse_expression(false);3335if (value == nullptr) {3336push_error(R"(Expected expression as dictionary value.)");3337}33383339if (key != nullptr && value != nullptr) {3340dictionary->elements.push_back({ key, value });3341}33423343// Do phrase level recovery by inserting an imaginary expression for missing keys or values.3344// This ensures the successfully parsed expression is part of the AST and can be analyzed.3345if (key != nullptr && value == nullptr) {3346LiteralNode *dummy = alloc_recovery_node<LiteralNode>();3347dummy->value = Variant();33483349dictionary->elements.push_back({ key, dummy });3350} else if (key == nullptr && value != nullptr) {3351LiteralNode *dummy = alloc_recovery_node<LiteralNode>();3352dummy->value = Variant();33533354dictionary->elements.push_back({ dummy, value });3355}33563357} while (match(GDScriptTokenizer::Token::COMMA) && !is_at_end());3358}3359pop_multiline();3360consume(GDScriptTokenizer::Token::BRACE_CLOSE, R"(Expected closing "}" after dictionary elements.)");3361complete_extents(dictionary);33623363return dictionary;3364}33653366GDScriptParser::ExpressionNode *GDScriptParser::parse_grouping(ExpressionNode *p_previous_operand, bool p_can_assign) {3367ExpressionNode *grouped = parse_expression(false);3368pop_multiline();3369if (grouped == nullptr) {3370push_error(R"(Expected grouping expression.)");3371} else {3372consume(GDScriptTokenizer::Token::PARENTHESIS_CLOSE, R"*(Expected closing ")" after grouping expression.)*");3373}3374return grouped;3375}33763377GDScriptParser::ExpressionNode *GDScriptParser::parse_attribute(ExpressionNode *p_previous_operand, bool p_can_assign) {3378SubscriptNode *attribute = alloc_node<SubscriptNode>();3379reset_extents(attribute, p_previous_operand);3380update_extents(attribute);33813382if (for_completion) {3383bool is_builtin = false;3384if (p_previous_operand && p_previous_operand->type == Node::IDENTIFIER) {3385const IdentifierNode *id = static_cast<const IdentifierNode *>(p_previous_operand);3386Variant::Type builtin_type = get_builtin_type(id->name);3387if (builtin_type < Variant::VARIANT_MAX) {3388make_completion_context(COMPLETION_BUILT_IN_TYPE_CONSTANT_OR_STATIC_METHOD, builtin_type);3389is_builtin = true;3390}3391}3392if (!is_builtin) {3393make_completion_context(COMPLETION_ATTRIBUTE, attribute, -1);3394}3395}33963397attribute->base = p_previous_operand;33983399if (current.is_node_name()) {3400current.type = GDScriptTokenizer::Token::IDENTIFIER;3401}3402if (!consume(GDScriptTokenizer::Token::IDENTIFIER, R"(Expected identifier after "." for attribute access.)")) {3403complete_extents(attribute);3404return attribute;3405}34063407attribute->is_attribute = true;3408attribute->attribute = parse_identifier();34093410complete_extents(attribute);3411return attribute;3412}34133414GDScriptParser::ExpressionNode *GDScriptParser::parse_subscript(ExpressionNode *p_previous_operand, bool p_can_assign) {3415SubscriptNode *subscript = alloc_node<SubscriptNode>();3416reset_extents(subscript, p_previous_operand);3417update_extents(subscript);34183419make_completion_context(COMPLETION_SUBSCRIPT, subscript);34203421subscript->base = p_previous_operand;3422subscript->index = parse_expression(false);34233424#ifdef TOOLS_ENABLED3425if (subscript->index != nullptr && subscript->index->type == Node::LITERAL) {3426override_completion_context(subscript->index, COMPLETION_SUBSCRIPT, subscript);3427}3428#endif34293430if (subscript->index == nullptr) {3431push_error(R"(Expected expression after "[".)");3432}34333434pop_multiline();3435consume(GDScriptTokenizer::Token::BRACKET_CLOSE, R"(Expected "]" after subscription index.)");3436complete_extents(subscript);34373438return subscript;3439}34403441GDScriptParser::ExpressionNode *GDScriptParser::parse_cast(ExpressionNode *p_previous_operand, bool p_can_assign) {3442CastNode *cast = alloc_node<CastNode>();3443reset_extents(cast, p_previous_operand);3444update_extents(cast);34453446cast->operand = p_previous_operand;3447cast->cast_type = parse_type();3448complete_extents(cast);34493450if (cast->cast_type == nullptr) {3451push_error(R"(Expected type specifier after "as".)");3452return p_previous_operand;3453}34543455return cast;3456}34573458GDScriptParser::ExpressionNode *GDScriptParser::parse_call(ExpressionNode *p_previous_operand, bool p_can_assign) {3459CallNode *call = alloc_node<CallNode>();3460reset_extents(call, p_previous_operand);34613462if (previous.type == GDScriptTokenizer::Token::SUPER) {3463// Super call.3464call->is_super = true;3465if (!check(GDScriptTokenizer::Token::PERIOD)) {3466make_completion_context(COMPLETION_SUPER, call);3467}3468if (check(GDScriptTokenizer::Token::PARENTHESIS_OPEN)) {3469push_multiline(true);3470advance();3471// Implicit call to the parent method of the same name.3472if (current_function == nullptr) {3473push_error(R"(Cannot use implicit "super" call outside of a function.)");3474pop_multiline();3475complete_extents(call);3476return nullptr;3477}3478if (current_function->identifier) {3479call->function_name = current_function->identifier->name;3480} else {3481call->function_name = SNAME("<anonymous>");3482}3483} else {3484consume(GDScriptTokenizer::Token::PERIOD, R"(Expected "." or "(" after "super".)");3485make_completion_context(COMPLETION_SUPER_METHOD, call);3486if (!consume(GDScriptTokenizer::Token::IDENTIFIER, R"(Expected function name after ".".)")) {3487complete_extents(call);3488return nullptr;3489}3490IdentifierNode *identifier = parse_identifier();3491call->callee = identifier;3492call->function_name = identifier->name;34933494if (check(GDScriptTokenizer::Token::PARENTHESIS_OPEN)) {3495push_multiline(true);3496advance();3497} else {3498push_error(R"(Expected "(" after function name.)");3499complete_extents(call);3500return nullptr;3501}3502}3503} else {3504call->callee = p_previous_operand;35053506if (call->callee == nullptr) {3507push_error(R"*(Cannot call on an expression. Use ".call()" if it's a Callable.)*");3508} else if (call->callee->type == Node::IDENTIFIER) {3509call->function_name = static_cast<IdentifierNode *>(call->callee)->name;3510make_completion_context(COMPLETION_METHOD, call->callee);3511} else if (call->callee->type == Node::SUBSCRIPT) {3512SubscriptNode *attribute = static_cast<SubscriptNode *>(call->callee);3513if (attribute->is_attribute) {3514if (attribute->attribute) {3515call->function_name = attribute->attribute->name;3516}3517make_completion_context(COMPLETION_ATTRIBUTE_METHOD, call->callee);3518} else {3519// TODO: The analyzer can see if this is actually a Callable and give better error message.3520push_error(R"*(Cannot call on an expression. Use ".call()" if it's a Callable.)*");3521}3522} else {3523push_error(R"*(Cannot call on an expression. Use ".call()" if it's a Callable.)*");3524}3525}35263527// Arguments.3528CompletionType ct = COMPLETION_CALL_ARGUMENTS;3529if (call->function_name == SNAME("load")) {3530ct = COMPLETION_RESOURCE_PATH;3531}3532push_completion_call(call);3533int argument_index = 0;3534do {3535make_completion_context(ct, call, argument_index);3536set_last_completion_call_arg(argument_index);3537if (check(GDScriptTokenizer::Token::PARENTHESIS_CLOSE)) {3538// Allow for trailing comma.3539break;3540}3541ExpressionNode *argument = parse_expression(false);3542if (argument == nullptr) {3543push_error(R"(Expected expression as the function argument.)");3544} else {3545call->arguments.push_back(argument);35463547if (argument->type == Node::LITERAL) {3548override_completion_context(argument, ct, call, argument_index);3549}3550}35513552ct = COMPLETION_CALL_ARGUMENTS;3553argument_index++;3554} while (match(GDScriptTokenizer::Token::COMMA));3555pop_completion_call();35563557pop_multiline();3558consume(GDScriptTokenizer::Token::PARENTHESIS_CLOSE, R"*(Expected closing ")" after call arguments.)*");3559complete_extents(call);35603561return call;3562}35633564GDScriptParser::ExpressionNode *GDScriptParser::parse_get_node(ExpressionNode *p_previous_operand, bool p_can_assign) {3565// We want code completion after a DOLLAR even if the current code is invalid.3566make_completion_context(COMPLETION_GET_NODE, nullptr, -1);35673568if (!current.is_node_name() && !check(GDScriptTokenizer::Token::LITERAL) && !check(GDScriptTokenizer::Token::SLASH) && !check(GDScriptTokenizer::Token::PERCENT)) {3569push_error(vformat(R"(Expected node path as string or identifier after "%s".)", previous.get_name()));3570return nullptr;3571}35723573if (check(GDScriptTokenizer::Token::LITERAL)) {3574if (current.literal.get_type() != Variant::STRING) {3575push_error(vformat(R"(Expected node path as string or identifier after "%s".)", previous.get_name()));3576return nullptr;3577}3578}35793580GetNodeNode *get_node = alloc_node<GetNodeNode>();35813582// Store the last item in the path so the parser knows what to expect.3583// Allow allows more specific error messages.3584enum PathState {3585PATH_STATE_START,3586PATH_STATE_SLASH,3587PATH_STATE_PERCENT,3588PATH_STATE_NODE_NAME,3589} path_state = PATH_STATE_START;35903591if (previous.type == GDScriptTokenizer::Token::DOLLAR) {3592// Detect initial slash, which will be handled in the loop if it matches.3593match(GDScriptTokenizer::Token::SLASH);3594} else {3595get_node->use_dollar = false;3596}35973598int context_argument = 0;35993600do {3601if (previous.type == GDScriptTokenizer::Token::PERCENT) {3602if (path_state != PATH_STATE_START && path_state != PATH_STATE_SLASH) {3603push_error(R"("%" is only valid in the beginning of a node name (either after "$" or after "/"))");3604complete_extents(get_node);3605return nullptr;3606}36073608get_node->full_path += "%";36093610path_state = PATH_STATE_PERCENT;3611} else if (previous.type == GDScriptTokenizer::Token::SLASH) {3612if (path_state != PATH_STATE_START && path_state != PATH_STATE_NODE_NAME) {3613push_error(R"("/" is only valid at the beginning of the path or after a node name.)");3614complete_extents(get_node);3615return nullptr;3616}36173618get_node->full_path += "/";36193620path_state = PATH_STATE_SLASH;3621}36223623make_completion_context(COMPLETION_GET_NODE, get_node, context_argument++);36243625if (match(GDScriptTokenizer::Token::LITERAL)) {3626if (previous.literal.get_type() != Variant::STRING) {3627String previous_token;3628switch (path_state) {3629case PATH_STATE_START:3630previous_token = "$";3631break;3632case PATH_STATE_PERCENT:3633previous_token = "%";3634break;3635case PATH_STATE_SLASH:3636previous_token = "/";3637break;3638default:3639break;3640}3641push_error(vformat(R"(Expected node path as string or identifier after "%s".)", previous_token));3642complete_extents(get_node);3643return nullptr;3644}36453646get_node->full_path += previous.literal.operator String();36473648path_state = PATH_STATE_NODE_NAME;3649} else if (current.is_node_name()) {3650advance();36513652String identifier = previous.get_identifier();3653#ifdef DEBUG_ENABLED3654// Check spoofing.3655if (TS->has_feature(TextServer::FEATURE_UNICODE_SECURITY) && TS->spoof_check(identifier)) {3656push_warning(get_node, GDScriptWarning::CONFUSABLE_IDENTIFIER, identifier);3657}3658#endif3659get_node->full_path += identifier;36603661path_state = PATH_STATE_NODE_NAME;3662} else if (!check(GDScriptTokenizer::Token::SLASH) && !check(GDScriptTokenizer::Token::PERCENT)) {3663push_error(vformat(R"(Unexpected "%s" in node path.)", current.get_name()));3664complete_extents(get_node);3665return nullptr;3666}3667} while (match(GDScriptTokenizer::Token::SLASH) || match(GDScriptTokenizer::Token::PERCENT));36683669complete_extents(get_node);3670return get_node;3671}36723673GDScriptParser::ExpressionNode *GDScriptParser::parse_preload(ExpressionNode *p_previous_operand, bool p_can_assign) {3674PreloadNode *preload = alloc_node<PreloadNode>();3675preload->resolved_path = "<missing path>";36763677push_multiline(true);3678consume(GDScriptTokenizer::Token::PARENTHESIS_OPEN, R"(Expected "(" after "preload".)");36793680make_completion_context(COMPLETION_RESOURCE_PATH, preload);3681push_completion_call(preload);36823683preload->path = parse_expression(false);36843685if (preload->path == nullptr) {3686push_error(R"(Expected resource path after "(".)");3687} else if (preload->path->type == Node::LITERAL) {3688override_completion_context(preload->path, COMPLETION_RESOURCE_PATH, preload);3689}36903691pop_completion_call();36923693// Allow trailing comma.3694match(GDScriptTokenizer::Token::COMMA);36953696pop_multiline();3697consume(GDScriptTokenizer::Token::PARENTHESIS_CLOSE, R"*(Expected ")" after preload path.)*");3698complete_extents(preload);36993700return preload;3701}37023703GDScriptParser::ExpressionNode *GDScriptParser::parse_lambda(ExpressionNode *p_previous_operand, bool p_can_assign) {3704LambdaNode *lambda = alloc_node<LambdaNode>();3705lambda->parent_function = current_function;3706lambda->parent_lambda = current_lambda;37073708FunctionNode *function = alloc_node<FunctionNode>();3709function->source_lambda = lambda;37103711function->is_static = current_function != nullptr ? current_function->is_static : false;37123713if (match(GDScriptTokenizer::Token::IDENTIFIER)) {3714function->identifier = parse_identifier();3715}37163717bool multiline_context = multiline_stack.back()->get();37183719push_completion_call(nullptr);37203721// Reset the multiline stack since we don't want the multiline mode one in the lambda body.3722push_multiline(false);3723if (multiline_context) {3724tokenizer->push_expression_indented_block();3725}37263727push_multiline(true); // For the parameters.3728if (function->identifier) {3729consume(GDScriptTokenizer::Token::PARENTHESIS_OPEN, R"(Expected opening "(" after lambda name.)");3730} else {3731consume(GDScriptTokenizer::Token::PARENTHESIS_OPEN, R"(Expected opening "(" after "func".)");3732}37333734FunctionNode *previous_function = current_function;3735current_function = function;37363737LambdaNode *previous_lambda = current_lambda;3738current_lambda = lambda;37393740SuiteNode *body = alloc_node<SuiteNode>();3741body->parent_function = current_function;3742body->parent_block = current_suite;37433744SuiteNode *previous_suite = current_suite;3745current_suite = body;37463747parse_function_signature(function, body, "lambda", -1);37483749current_suite = previous_suite;37503751bool previous_in_lambda = in_lambda;3752in_lambda = true;37533754// Save break/continue state.3755bool could_break = can_break;3756bool could_continue = can_continue;37573758// Disallow break/continue.3759can_break = false;3760can_continue = false;37613762function->body = parse_suite("lambda declaration", body, true);3763complete_extents(function);3764complete_extents(lambda);37653766pop_multiline();37673768pop_completion_call();37693770if (multiline_context) {3771// If we're in multiline mode, we want to skip the spurious DEDENT and NEWLINE tokens.3772while (check(GDScriptTokenizer::Token::DEDENT) || check(GDScriptTokenizer::Token::INDENT) || check(GDScriptTokenizer::Token::NEWLINE)) {3773current = tokenizer->scan(); // Not advance() since we don't want to change the previous token.3774}3775tokenizer->pop_expression_indented_block();3776}37773778current_function = previous_function;3779current_lambda = previous_lambda;3780in_lambda = previous_in_lambda;3781lambda->function = function;37823783// Reset break/continue state.3784can_break = could_break;3785can_continue = could_continue;37863787return lambda;3788}37893790GDScriptParser::ExpressionNode *GDScriptParser::parse_type_test(ExpressionNode *p_previous_operand, bool p_can_assign) {3791// x is not int3792// ^ ^^^ ExpressionNode, TypeNode3793// ^^^^^^^^^^^^ TypeTestNode3794// ^^^^^^^^^^^^ UnaryOpNode3795UnaryOpNode *not_node = nullptr;3796if (match(GDScriptTokenizer::Token::NOT)) {3797not_node = alloc_node<UnaryOpNode>();3798not_node->operation = UnaryOpNode::OP_LOGIC_NOT;3799not_node->variant_op = Variant::OP_NOT;3800reset_extents(not_node, p_previous_operand);3801update_extents(not_node);3802}38033804TypeTestNode *type_test = alloc_node<TypeTestNode>();3805reset_extents(type_test, p_previous_operand);3806update_extents(type_test);38073808type_test->operand = p_previous_operand;3809type_test->test_type = parse_type();3810complete_extents(type_test);38113812if (not_node != nullptr) {3813not_node->operand = type_test;3814complete_extents(not_node);3815}38163817if (type_test->test_type == nullptr) {3818if (not_node == nullptr) {3819push_error(R"(Expected type specifier after "is".)");3820} else {3821push_error(R"(Expected type specifier after "is not".)");3822}3823}38243825if (not_node != nullptr) {3826return not_node;3827}38283829return type_test;3830}38313832GDScriptParser::ExpressionNode *GDScriptParser::parse_yield(ExpressionNode *p_previous_operand, bool p_can_assign) {3833push_error(R"("yield" was removed in Godot 4. Use "await" instead.)");3834return nullptr;3835}38363837GDScriptParser::ExpressionNode *GDScriptParser::parse_invalid_token(ExpressionNode *p_previous_operand, bool p_can_assign) {3838// Just for better error messages.3839GDScriptTokenizer::Token::Type invalid = previous.type;38403841switch (invalid) {3842case GDScriptTokenizer::Token::QUESTION_MARK:3843push_error(R"(Unexpected "?" in source. If you want a ternary operator, use "truthy_value if true_condition else falsy_value".)");3844break;3845default:3846return nullptr; // Unreachable.3847}38483849// Return the previous expression.3850return p_previous_operand;3851}38523853GDScriptParser::TypeNode *GDScriptParser::parse_type(bool p_allow_void) {3854TypeNode *type = alloc_node<TypeNode>();3855make_completion_context(p_allow_void ? COMPLETION_TYPE_NAME_OR_VOID : COMPLETION_TYPE_NAME, type);3856if (!match(GDScriptTokenizer::Token::IDENTIFIER)) {3857if (match(GDScriptTokenizer::Token::TK_VOID)) {3858if (p_allow_void) {3859complete_extents(type);3860TypeNode *void_type = type;3861return void_type;3862} else {3863push_error(R"("void" is only allowed for a function return type.)");3864}3865}3866// Leave error message to the caller who knows the context.3867complete_extents(type);3868return nullptr;3869}38703871IdentifierNode *type_element = parse_identifier();38723873type->type_chain.push_back(type_element);38743875if (match(GDScriptTokenizer::Token::BRACKET_OPEN)) {3876// Typed collection (like Array[int], Dictionary[String, int]).3877bool first_pass = true;3878do {3879TypeNode *container_type = parse_type(false); // Don't allow void for element type.3880if (container_type == nullptr) {3881push_error(vformat(R"(Expected type for collection after "%s".)", first_pass ? "[" : ","));3882complete_extents(type);3883type = nullptr;3884break;3885} else if (container_type->container_types.size() > 0) {3886push_error("Nested typed collections are not supported.");3887} else {3888type->container_types.append(container_type);3889}3890first_pass = false;3891} while (match(GDScriptTokenizer::Token::COMMA));3892consume(GDScriptTokenizer::Token::BRACKET_CLOSE, R"(Expected closing "]" after collection type.)");3893if (type != nullptr) {3894complete_extents(type);3895}3896return type;3897}38983899int chain_index = 1;3900while (match(GDScriptTokenizer::Token::PERIOD)) {3901make_completion_context(COMPLETION_TYPE_ATTRIBUTE, type, chain_index++);3902if (consume(GDScriptTokenizer::Token::IDENTIFIER, R"(Expected inner type name after ".".)")) {3903type_element = parse_identifier();3904type->type_chain.push_back(type_element);3905}3906}39073908complete_extents(type);3909return type;3910}39113912#ifdef TOOLS_ENABLED3913enum DocLineState {3914DOC_LINE_NORMAL,3915DOC_LINE_IN_CODE,3916DOC_LINE_IN_CODEBLOCK,3917DOC_LINE_IN_KBD,3918};39193920static void _process_doc_line(const String &p_line, String &r_text, const String &p_space_prefix, DocLineState &r_state) {3921String line = p_line;3922if (r_state == DOC_LINE_NORMAL) {3923line = line.lstrip(" \t");3924} else {3925line = line.trim_prefix(p_space_prefix);3926}39273928String line_join;3929if (!r_text.is_empty()) {3930if (r_state == DOC_LINE_NORMAL) {3931if (r_text.ends_with("[/codeblock]")) {3932line_join = "\n";3933} else if (r_text.ends_with("[br]")) {3934// We want to replace `[br][br]` with `\n` (paragraph), so we move the trailing `[br]` here.3935r_text = r_text.left(-4); // `-len("[br]")`.3936line = "[br]" + line;3937} else if (!r_text.ends_with("\n")) {3938line_join = " ";3939}3940} else {3941line_join = "\n";3942}3943}39443945String result;3946int from = 0;3947int buffer_start = 0;3948const int len = line.length();3949bool process = true;3950while (process) {3951switch (r_state) {3952case DOC_LINE_NORMAL: {3953int lb_pos = line.find_char('[', from);3954if (lb_pos < 0) {3955process = false;3956break;3957}3958int rb_pos = line.find_char(']', lb_pos + 1);3959if (rb_pos < 0) {3960process = false;3961break;3962}39633964from = rb_pos + 1;39653966String tag = line.substr(lb_pos + 1, rb_pos - lb_pos - 1);3967if (tag == "br") {3968if (line.substr(from, 4) == "[br]") { // `len("[br]")`.3969// Replace `[br][br]` with `\n` (paragraph).3970result += line.substr(buffer_start, lb_pos - buffer_start) + '\n';3971from += 4; // `len("[br]")`.3972buffer_start = from;3973}3974} else if (tag == "code" || tag.begins_with("code ")) {3975r_state = DOC_LINE_IN_CODE;3976} else if (tag == "codeblock" || tag.begins_with("codeblock ")) {3977if (lb_pos == 0) {3978line_join = "\n";3979} else {3980result += line.substr(buffer_start, lb_pos - buffer_start) + '\n';3981}3982result += "[" + tag + "]";3983if (from < len) {3984result += '\n';3985}39863987r_state = DOC_LINE_IN_CODEBLOCK;3988buffer_start = from;3989} else if (tag == "kbd") {3990r_state = DOC_LINE_IN_KBD;3991}3992} break;3993case DOC_LINE_IN_CODE: {3994int pos = line.find("[/code]", from);3995if (pos < 0) {3996process = false;3997break;3998}39994000from = pos + 7; // `len("[/code]")`.40014002r_state = DOC_LINE_NORMAL;4003} break;4004case DOC_LINE_IN_CODEBLOCK: {4005int pos = line.find("[/codeblock]", from);4006if (pos < 0) {4007process = false;4008break;4009}40104011from = pos + 12; // `len("[/codeblock]")`.40124013if (pos == 0) {4014line_join = "\n";4015} else {4016result += line.substr(buffer_start, pos - buffer_start) + '\n';4017}4018result += "[/codeblock]";4019if (from < len) {4020result += '\n';4021}40224023r_state = DOC_LINE_NORMAL;4024buffer_start = from;4025} break;4026case DOC_LINE_IN_KBD: {4027int pos = line.find("[/kbd]", from);4028if (pos < 0) {4029process = false;4030break;4031}40324033from = pos + 6; // `len("[/kbd]")`.40344035r_state = DOC_LINE_NORMAL;4036} break;4037}4038}40394040result += line.substr(buffer_start);4041if (r_state == DOC_LINE_NORMAL) {4042result = result.rstrip(" \t");4043}40444045r_text += line_join + result;4046}40474048bool GDScriptParser::has_comment(int p_line, bool p_must_be_doc) {4049bool has_comment = tokenizer->get_comments().has(p_line);4050// If there are no comments or if we don't care whether the comment4051// is a docstring, we have our result.4052if (!p_must_be_doc || !has_comment) {4053return has_comment;4054}40554056return tokenizer->get_comments()[p_line].comment.begins_with("##");4057}40584059GDScriptParser::MemberDocData GDScriptParser::parse_doc_comment(int p_line, bool p_single_line) {4060ERR_FAIL_COND_V(!has_comment(p_line, true), MemberDocData());40614062const HashMap<int, GDScriptTokenizer::CommentData> &comments = tokenizer->get_comments();4063int line = p_line;40644065if (!p_single_line) {4066while (comments.has(line - 1) && comments[line - 1].new_line && comments[line - 1].comment.begins_with("##")) {4067line--;4068}4069}40704071max_script_doc_line = MIN(max_script_doc_line, line - 1);40724073String space_prefix;4074{4075int i = 2;4076for (; i < comments[line].comment.length(); i++) {4077if (comments[line].comment[i] != ' ') {4078break;4079}4080}4081space_prefix = String(" ").repeat(i - 2);4082}40834084DocLineState state = DOC_LINE_NORMAL;4085MemberDocData result;40864087while (line <= p_line) {4088String doc_line = comments[line].comment.trim_prefix("##");4089line++;40904091if (state == DOC_LINE_NORMAL) {4092String stripped_line = doc_line.strip_edges();4093if (stripped_line == "@deprecated" || stripped_line.begins_with("@deprecated:")) {4094result.is_deprecated = true;4095if (stripped_line.begins_with("@deprecated:")) {4096result.deprecated_message = stripped_line.trim_prefix("@deprecated:").strip_edges();4097}4098continue;4099} else if (stripped_line == "@experimental" || stripped_line.begins_with("@experimental:")) {4100result.is_experimental = true;4101if (stripped_line.begins_with("@experimental:")) {4102result.experimental_message = stripped_line.trim_prefix("@experimental:").strip_edges();4103}4104continue;4105}4106}41074108_process_doc_line(doc_line, result.description, space_prefix, state);4109}41104111return result;4112}41134114GDScriptParser::ClassDocData GDScriptParser::parse_class_doc_comment(int p_line, bool p_single_line) {4115ERR_FAIL_COND_V(!has_comment(p_line, true), ClassDocData());41164117const HashMap<int, GDScriptTokenizer::CommentData> &comments = tokenizer->get_comments();4118int line = p_line;41194120if (!p_single_line) {4121while (comments.has(line - 1) && comments[line - 1].new_line && comments[line - 1].comment.begins_with("##")) {4122line--;4123}4124}41254126max_script_doc_line = MIN(max_script_doc_line, line - 1);41274128String space_prefix;4129{4130int i = 2;4131for (; i < comments[line].comment.length(); i++) {4132if (comments[line].comment[i] != ' ') {4133break;4134}4135}4136space_prefix = String(" ").repeat(i - 2);4137}41384139DocLineState state = DOC_LINE_NORMAL;4140bool is_in_brief = true;4141ClassDocData result;41424143while (line <= p_line) {4144String doc_line = comments[line].comment.trim_prefix("##");4145line++;41464147if (state == DOC_LINE_NORMAL) {4148String stripped_line = doc_line.strip_edges();41494150// A blank line separates the description from the brief.4151if (is_in_brief && !result.brief.is_empty() && stripped_line.is_empty()) {4152is_in_brief = false;4153continue;4154}41554156if (stripped_line.begins_with("@tutorial")) {4157String title, link;41584159int begin_scan = String("@tutorial").length();4160if (begin_scan >= stripped_line.length()) {4161continue; // Invalid syntax.4162}41634164if (stripped_line[begin_scan] == ':') { // No title.4165// Syntax: ## @tutorial: https://godotengine.org/ // The title argument is optional.4166title = "";4167link = stripped_line.trim_prefix("@tutorial:").strip_edges();4168} else {4169/* Syntax:4170* @tutorial ( The Title Here ) : https://the.url/4171* ^ open ^ close ^ colon ^ url4172*/4173int open_bracket_pos = begin_scan, close_bracket_pos = 0;4174while (open_bracket_pos < stripped_line.length() && (stripped_line[open_bracket_pos] == ' ' || stripped_line[open_bracket_pos] == '\t')) {4175open_bracket_pos++;4176}4177if (open_bracket_pos == stripped_line.length() || stripped_line[open_bracket_pos++] != '(') {4178continue; // Invalid syntax.4179}4180close_bracket_pos = open_bracket_pos;4181while (close_bracket_pos < stripped_line.length() && stripped_line[close_bracket_pos] != ')') {4182close_bracket_pos++;4183}4184if (close_bracket_pos == stripped_line.length()) {4185continue; // Invalid syntax.4186}41874188int colon_pos = close_bracket_pos + 1;4189while (colon_pos < stripped_line.length() && (stripped_line[colon_pos] == ' ' || stripped_line[colon_pos] == '\t')) {4190colon_pos++;4191}4192if (colon_pos == stripped_line.length() || stripped_line[colon_pos++] != ':') {4193continue; // Invalid syntax.4194}41954196title = stripped_line.substr(open_bracket_pos, close_bracket_pos - open_bracket_pos).strip_edges();4197link = stripped_line.substr(colon_pos).strip_edges();4198}41994200result.tutorials.append(Pair<String, String>(title, link));4201continue;4202} else if (stripped_line == "@deprecated" || stripped_line.begins_with("@deprecated:")) {4203result.is_deprecated = true;4204if (stripped_line.begins_with("@deprecated:")) {4205result.deprecated_message = stripped_line.trim_prefix("@deprecated:").strip_edges();4206}4207continue;4208} else if (stripped_line == "@experimental" || stripped_line.begins_with("@experimental:")) {4209result.is_experimental = true;4210if (stripped_line.begins_with("@experimental:")) {4211result.experimental_message = stripped_line.trim_prefix("@experimental:").strip_edges();4212}4213continue;4214}4215}42164217if (is_in_brief) {4218_process_doc_line(doc_line, result.brief, space_prefix, state);4219} else {4220_process_doc_line(doc_line, result.description, space_prefix, state);4221}4222}42234224return result;4225}4226#endif // TOOLS_ENABLED42274228GDScriptParser::ParseRule *GDScriptParser::get_rule(GDScriptTokenizer::Token::Type p_token_type) {4229// Function table for expression parsing.4230// clang-format destroys the alignment here, so turn off for the table.4231/* clang-format off */4232static ParseRule rules[] = {4233// PREFIX INFIX PRECEDENCE (for infix)4234{ nullptr, nullptr, PREC_NONE }, // EMPTY,4235// Basic4236{ nullptr, nullptr, PREC_NONE }, // ANNOTATION,4237{ &GDScriptParser::parse_identifier, nullptr, PREC_NONE }, // IDENTIFIER,4238{ &GDScriptParser::parse_literal, nullptr, PREC_NONE }, // LITERAL,4239// Comparison4240{ nullptr, &GDScriptParser::parse_binary_operator, PREC_COMPARISON }, // LESS,4241{ nullptr, &GDScriptParser::parse_binary_operator, PREC_COMPARISON }, // LESS_EQUAL,4242{ nullptr, &GDScriptParser::parse_binary_operator, PREC_COMPARISON }, // GREATER,4243{ nullptr, &GDScriptParser::parse_binary_operator, PREC_COMPARISON }, // GREATER_EQUAL,4244{ nullptr, &GDScriptParser::parse_binary_operator, PREC_COMPARISON }, // EQUAL_EQUAL,4245{ nullptr, &GDScriptParser::parse_binary_operator, PREC_COMPARISON }, // BANG_EQUAL,4246// Logical4247{ nullptr, &GDScriptParser::parse_binary_operator, PREC_LOGIC_AND }, // AND,4248{ nullptr, &GDScriptParser::parse_binary_operator, PREC_LOGIC_OR }, // OR,4249{ &GDScriptParser::parse_unary_operator, &GDScriptParser::parse_binary_not_in_operator, PREC_CONTENT_TEST }, // NOT,4250{ nullptr, &GDScriptParser::parse_binary_operator, PREC_LOGIC_AND }, // AMPERSAND_AMPERSAND,4251{ nullptr, &GDScriptParser::parse_binary_operator, PREC_LOGIC_OR }, // PIPE_PIPE,4252{ &GDScriptParser::parse_unary_operator, nullptr, PREC_NONE }, // BANG,4253// Bitwise4254{ nullptr, &GDScriptParser::parse_binary_operator, PREC_BIT_AND }, // AMPERSAND,4255{ nullptr, &GDScriptParser::parse_binary_operator, PREC_BIT_OR }, // PIPE,4256{ &GDScriptParser::parse_unary_operator, nullptr, PREC_NONE }, // TILDE,4257{ nullptr, &GDScriptParser::parse_binary_operator, PREC_BIT_XOR }, // CARET,4258{ nullptr, &GDScriptParser::parse_binary_operator, PREC_BIT_SHIFT }, // LESS_LESS,4259{ nullptr, &GDScriptParser::parse_binary_operator, PREC_BIT_SHIFT }, // GREATER_GREATER,4260// Math4261{ &GDScriptParser::parse_unary_operator, &GDScriptParser::parse_binary_operator, PREC_ADDITION_SUBTRACTION }, // PLUS,4262{ &GDScriptParser::parse_unary_operator, &GDScriptParser::parse_binary_operator, PREC_ADDITION_SUBTRACTION }, // MINUS,4263{ nullptr, &GDScriptParser::parse_binary_operator, PREC_FACTOR }, // STAR,4264{ nullptr, &GDScriptParser::parse_binary_operator, PREC_POWER }, // STAR_STAR,4265{ nullptr, &GDScriptParser::parse_binary_operator, PREC_FACTOR }, // SLASH,4266{ &GDScriptParser::parse_get_node, &GDScriptParser::parse_binary_operator, PREC_FACTOR }, // PERCENT,4267// Assignment4268{ nullptr, &GDScriptParser::parse_assignment, PREC_ASSIGNMENT }, // EQUAL,4269{ nullptr, &GDScriptParser::parse_assignment, PREC_ASSIGNMENT }, // PLUS_EQUAL,4270{ nullptr, &GDScriptParser::parse_assignment, PREC_ASSIGNMENT }, // MINUS_EQUAL,4271{ nullptr, &GDScriptParser::parse_assignment, PREC_ASSIGNMENT }, // STAR_EQUAL,4272{ nullptr, &GDScriptParser::parse_assignment, PREC_ASSIGNMENT }, // STAR_STAR_EQUAL,4273{ nullptr, &GDScriptParser::parse_assignment, PREC_ASSIGNMENT }, // SLASH_EQUAL,4274{ nullptr, &GDScriptParser::parse_assignment, PREC_ASSIGNMENT }, // PERCENT_EQUAL,4275{ nullptr, &GDScriptParser::parse_assignment, PREC_ASSIGNMENT }, // LESS_LESS_EQUAL,4276{ nullptr, &GDScriptParser::parse_assignment, PREC_ASSIGNMENT }, // GREATER_GREATER_EQUAL,4277{ nullptr, &GDScriptParser::parse_assignment, PREC_ASSIGNMENT }, // AMPERSAND_EQUAL,4278{ nullptr, &GDScriptParser::parse_assignment, PREC_ASSIGNMENT }, // PIPE_EQUAL,4279{ nullptr, &GDScriptParser::parse_assignment, PREC_ASSIGNMENT }, // CARET_EQUAL,4280// Control flow4281{ nullptr, &GDScriptParser::parse_ternary_operator, PREC_TERNARY }, // IF,4282{ nullptr, nullptr, PREC_NONE }, // ELIF,4283{ nullptr, nullptr, PREC_NONE }, // ELSE,4284{ nullptr, nullptr, PREC_NONE }, // FOR,4285{ nullptr, nullptr, PREC_NONE }, // WHILE,4286{ nullptr, nullptr, PREC_NONE }, // BREAK,4287{ nullptr, nullptr, PREC_NONE }, // CONTINUE,4288{ nullptr, nullptr, PREC_NONE }, // PASS,4289{ nullptr, nullptr, PREC_NONE }, // RETURN,4290{ nullptr, nullptr, PREC_NONE }, // MATCH,4291{ nullptr, nullptr, PREC_NONE }, // WHEN,4292// Keywords4293{ nullptr, &GDScriptParser::parse_cast, PREC_CAST }, // AS,4294{ nullptr, nullptr, PREC_NONE }, // ASSERT,4295{ &GDScriptParser::parse_await, nullptr, PREC_NONE }, // AWAIT,4296{ nullptr, nullptr, PREC_NONE }, // BREAKPOINT,4297{ nullptr, nullptr, PREC_NONE }, // CLASS,4298{ nullptr, nullptr, PREC_NONE }, // CLASS_NAME,4299{ nullptr, nullptr, PREC_NONE }, // TK_CONST,4300{ nullptr, nullptr, PREC_NONE }, // ENUM,4301{ nullptr, nullptr, PREC_NONE }, // EXTENDS,4302{ &GDScriptParser::parse_lambda, nullptr, PREC_NONE }, // FUNC,4303{ nullptr, &GDScriptParser::parse_binary_operator, PREC_CONTENT_TEST }, // TK_IN,4304{ nullptr, &GDScriptParser::parse_type_test, PREC_TYPE_TEST }, // IS,4305{ nullptr, nullptr, PREC_NONE }, // NAMESPACE,4306{ &GDScriptParser::parse_preload, nullptr, PREC_NONE }, // PRELOAD,4307{ &GDScriptParser::parse_self, nullptr, PREC_NONE }, // SELF,4308{ nullptr, nullptr, PREC_NONE }, // SIGNAL,4309{ nullptr, nullptr, PREC_NONE }, // STATIC,4310{ &GDScriptParser::parse_call, nullptr, PREC_NONE }, // SUPER,4311{ nullptr, nullptr, PREC_NONE }, // TRAIT,4312{ nullptr, nullptr, PREC_NONE }, // VAR,4313{ nullptr, nullptr, PREC_NONE }, // TK_VOID,4314{ &GDScriptParser::parse_yield, nullptr, PREC_NONE }, // YIELD,4315// Punctuation4316{ &GDScriptParser::parse_array, &GDScriptParser::parse_subscript, PREC_SUBSCRIPT }, // BRACKET_OPEN,4317{ nullptr, nullptr, PREC_NONE }, // BRACKET_CLOSE,4318{ &GDScriptParser::parse_dictionary, nullptr, PREC_NONE }, // BRACE_OPEN,4319{ nullptr, nullptr, PREC_NONE }, // BRACE_CLOSE,4320{ &GDScriptParser::parse_grouping, &GDScriptParser::parse_call, PREC_CALL }, // PARENTHESIS_OPEN,4321{ nullptr, nullptr, PREC_NONE }, // PARENTHESIS_CLOSE,4322{ nullptr, nullptr, PREC_NONE }, // COMMA,4323{ nullptr, nullptr, PREC_NONE }, // SEMICOLON,4324{ nullptr, &GDScriptParser::parse_attribute, PREC_ATTRIBUTE }, // PERIOD,4325{ nullptr, nullptr, PREC_NONE }, // PERIOD_PERIOD,4326{ nullptr, nullptr, PREC_NONE }, // PERIOD_PERIOD_PERIOD,4327{ nullptr, nullptr, PREC_NONE }, // COLON,4328{ &GDScriptParser::parse_get_node, nullptr, PREC_NONE }, // DOLLAR,4329{ nullptr, nullptr, PREC_NONE }, // FORWARD_ARROW,4330{ nullptr, nullptr, PREC_NONE }, // UNDERSCORE,4331// Whitespace4332{ nullptr, nullptr, PREC_NONE }, // NEWLINE,4333{ nullptr, nullptr, PREC_NONE }, // INDENT,4334{ nullptr, nullptr, PREC_NONE }, // DEDENT,4335// Constants4336{ &GDScriptParser::parse_builtin_constant, nullptr, PREC_NONE }, // CONST_PI,4337{ &GDScriptParser::parse_builtin_constant, nullptr, PREC_NONE }, // CONST_TAU,4338{ &GDScriptParser::parse_builtin_constant, nullptr, PREC_NONE }, // CONST_INF,4339{ &GDScriptParser::parse_builtin_constant, nullptr, PREC_NONE }, // CONST_NAN,4340// Error message improvement4341{ nullptr, nullptr, PREC_NONE }, // VCS_CONFLICT_MARKER,4342{ nullptr, nullptr, PREC_NONE }, // BACKTICK,4343{ nullptr, &GDScriptParser::parse_invalid_token, PREC_CAST }, // QUESTION_MARK,4344// Special4345{ nullptr, nullptr, PREC_NONE }, // ERROR,4346{ nullptr, nullptr, PREC_NONE }, // TK_EOF,4347};4348/* clang-format on */4349// Avoid desync.4350static_assert(std_size(rules) == GDScriptTokenizer::Token::TK_MAX, "Amount of parse rules don't match the amount of token types.");43514352// Let's assume this is never invalid, since nothing generates a TK_MAX.4353return &rules[p_token_type];4354}43554356bool GDScriptParser::SuiteNode::has_local(const StringName &p_name) const {4357if (locals_indices.has(p_name)) {4358return true;4359}4360if (parent_block != nullptr) {4361return parent_block->has_local(p_name);4362}4363return false;4364}43654366const GDScriptParser::SuiteNode::Local &GDScriptParser::SuiteNode::get_local(const StringName &p_name) const {4367if (locals_indices.has(p_name)) {4368return locals[locals_indices[p_name]];4369}4370if (parent_block != nullptr) {4371return parent_block->get_local(p_name);4372}4373return empty;4374}43754376bool GDScriptParser::AnnotationNode::apply(GDScriptParser *p_this, Node *p_target, ClassNode *p_class) {4377if (is_applied) {4378return true;4379}4380is_applied = true;4381return (p_this->*(p_this->valid_annotations[name].apply))(this, p_target, p_class);4382}43834384bool GDScriptParser::AnnotationNode::applies_to(uint32_t p_target_kinds) const {4385return (info->target_kind & p_target_kinds) > 0;4386}43874388bool GDScriptParser::validate_annotation_arguments(AnnotationNode *p_annotation) {4389ERR_FAIL_COND_V_MSG(!valid_annotations.has(p_annotation->name), false, vformat(R"(Annotation "%s" not found to validate.)", p_annotation->name));43904391const MethodInfo &info = valid_annotations[p_annotation->name].info;43924393if (((info.flags & METHOD_FLAG_VARARG) == 0) && p_annotation->arguments.size() > info.arguments.size()) {4394push_error(vformat(R"(Annotation "%s" requires at most %d arguments, but %d were given.)", p_annotation->name, info.arguments.size(), p_annotation->arguments.size()));4395return false;4396}43974398if (p_annotation->arguments.size() < info.arguments.size() - info.default_arguments.size()) {4399push_error(vformat(R"(Annotation "%s" requires at least %d arguments, but %d were given.)", p_annotation->name, info.arguments.size() - info.default_arguments.size(), p_annotation->arguments.size()));4400return false;4401}44024403// Some annotations need to be resolved and applied in the parser.4404if (p_annotation->name == SNAME("@icon") || p_annotation->name == SNAME("@warning_ignore_start") || p_annotation->name == SNAME("@warning_ignore_restore")) {4405for (int i = 0; i < p_annotation->arguments.size(); i++) {4406ExpressionNode *argument = p_annotation->arguments[i];44074408if (argument->type != Node::LITERAL) {4409push_error(vformat(R"(Argument %d of annotation "%s" must be a string literal.)", i + 1, p_annotation->name), argument);4410return false;4411}44124413Variant value = static_cast<LiteralNode *>(argument)->value;44144415if (value.get_type() != Variant::STRING) {4416push_error(vformat(R"(Argument %d of annotation "%s" must be a string literal.)", i + 1, p_annotation->name), argument);4417return false;4418}44194420p_annotation->resolved_arguments.push_back(value);4421}4422}44234424// For other annotations, see `GDScriptAnalyzer::resolve_annotation()`.44254426return true;4427}44284429bool GDScriptParser::tool_annotation(AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class) {4430#ifdef DEBUG_ENABLED4431if (_is_tool) {4432push_error(R"("@tool" annotation can only be used once.)", p_annotation);4433return false;4434}4435#endif // DEBUG_ENABLED4436_is_tool = true;4437return true;4438}44394440bool GDScriptParser::icon_annotation(AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class) {4441ERR_FAIL_COND_V_MSG(p_target->type != Node::CLASS, false, R"("@icon" annotation can only be applied to classes.)");4442ERR_FAIL_COND_V(p_annotation->resolved_arguments.is_empty(), false);44434444ClassNode *class_node = static_cast<ClassNode *>(p_target);4445String path = p_annotation->resolved_arguments[0];44464447#ifdef DEBUG_ENABLED4448if (!class_node->icon_path.is_empty()) {4449push_error(R"("@icon" annotation can only be used once.)", p_annotation);4450return false;4451}4452if (path.is_empty()) {4453push_error(R"("@icon" annotation argument must contain the path to the icon.)", p_annotation->arguments[0]);4454return false;4455}4456#endif // DEBUG_ENABLED44574458class_node->icon_path = path;44594460if (path.is_empty() || path.is_absolute_path()) {4461class_node->simplified_icon_path = path.simplify_path();4462} else if (path.is_relative_path()) {4463class_node->simplified_icon_path = script_path.get_base_dir().path_join(path).simplify_path();4464} else {4465class_node->simplified_icon_path = path;4466}44674468return true;4469}44704471bool GDScriptParser::static_unload_annotation(AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class) {4472ERR_FAIL_COND_V_MSG(p_target->type != Node::CLASS, false, vformat(R"("%s" annotation can only be applied to classes.)", p_annotation->name));4473ClassNode *class_node = static_cast<ClassNode *>(p_target);4474if (class_node->annotated_static_unload) {4475push_error(vformat(R"("%s" annotation can only be used once per script.)", p_annotation->name), p_annotation);4476return false;4477}4478class_node->annotated_static_unload = true;4479return true;4480}44814482bool GDScriptParser::abstract_annotation(AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class) {4483// NOTE: Use `p_target`, **not** `p_class`, because when `p_target` is a class then `p_class` refers to the outer class.4484if (p_target->type == Node::CLASS) {4485ClassNode *class_node = static_cast<ClassNode *>(p_target);4486if (class_node->is_abstract) {4487push_error(R"("@abstract" annotation can only be used once per class.)", p_annotation);4488return false;4489}4490class_node->is_abstract = true;4491return true;4492}4493if (p_target->type == Node::FUNCTION) {4494FunctionNode *function_node = static_cast<FunctionNode *>(p_target);4495if (function_node->is_static) {4496push_error(R"("@abstract" annotation cannot be applied to static functions.)", p_annotation);4497return false;4498}4499if (function_node->is_abstract) {4500push_error(R"("@abstract" annotation can only be used once per function.)", p_annotation);4501return false;4502}4503function_node->is_abstract = true;4504return true;4505}4506ERR_FAIL_V_MSG(false, R"("@abstract" annotation can only be applied to classes and functions.)");4507}45084509bool GDScriptParser::onready_annotation(AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class) {4510ERR_FAIL_COND_V_MSG(p_target->type != Node::VARIABLE, false, R"("@onready" annotation can only be applied to class variables.)");45114512if (current_class && !ClassDB::is_parent_class(current_class->get_datatype().native_type, SNAME("Node"))) {4513push_error(R"("@onready" can only be used in classes that inherit "Node".)", p_annotation);4514return false;4515}45164517VariableNode *variable = static_cast<VariableNode *>(p_target);4518if (variable->is_static) {4519push_error(R"("@onready" annotation cannot be applied to a static variable.)", p_annotation);4520return false;4521}4522if (variable->onready) {4523push_error(R"("@onready" annotation can only be used once per variable.)", p_annotation);4524return false;4525}4526variable->onready = true;4527current_class->onready_used = true;4528return true;4529}45304531static String _get_annotation_error_string(const StringName &p_annotation_name, const Vector<Variant::Type> &p_expected_types, const GDScriptParser::DataType &p_provided_type) {4532Vector<String> types;4533for (int i = 0; i < p_expected_types.size(); i++) {4534const Variant::Type &type = p_expected_types[i];4535types.push_back(Variant::get_type_name(type));4536types.push_back("Array[" + Variant::get_type_name(type) + "]");4537switch (type) {4538case Variant::INT:4539types.push_back("PackedByteArray");4540types.push_back("PackedInt32Array");4541types.push_back("PackedInt64Array");4542break;4543case Variant::FLOAT:4544types.push_back("PackedFloat32Array");4545types.push_back("PackedFloat64Array");4546break;4547case Variant::STRING:4548types.push_back("PackedStringArray");4549break;4550case Variant::VECTOR2:4551types.push_back("PackedVector2Array");4552break;4553case Variant::VECTOR3:4554types.push_back("PackedVector3Array");4555break;4556case Variant::COLOR:4557types.push_back("PackedColorArray");4558break;4559case Variant::VECTOR4:4560types.push_back("PackedVector4Array");4561break;4562default:4563break;4564}4565}45664567String string;4568if (types.size() == 1) {4569string = types[0].quote();4570} else if (types.size() == 2) {4571string = types[0].quote() + " or " + types[1].quote();4572} else if (types.size() >= 3) {4573string = types[0].quote();4574for (int i = 1; i < types.size() - 1; i++) {4575string += ", " + types[i].quote();4576}4577string += ", or " + types[types.size() - 1].quote();4578}45794580return 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());4581}45824583static StringName _find_narrowest_native_or_global_class(const GDScriptParser::DataType &p_type) {4584switch (p_type.kind) {4585case GDScriptParser::DataType::NATIVE: {4586if (p_type.is_meta_type) {4587return Object::get_class_static(); // `GDScriptNativeClass` is not an exposed class.4588}4589return p_type.native_type;4590} break;4591case GDScriptParser::DataType::SCRIPT: {4592Ref<Script> script;4593if (p_type.script_type.is_valid()) {4594script = p_type.script_type;4595} else {4596script = ResourceLoader::load(p_type.script_path, SNAME("Script"));4597}45984599if (p_type.is_meta_type) {4600return script.is_valid() ? script->get_class_name() : Script::get_class_static();4601}4602if (script.is_null()) {4603return p_type.native_type;4604}4605if (script->get_global_name() != StringName()) {4606return script->get_global_name();4607}46084609Ref<Script> base_script = script->get_base_script();4610if (base_script.is_null()) {4611return script->get_instance_base_type();4612}46134614GDScriptParser::DataType base_type;4615base_type.kind = GDScriptParser::DataType::SCRIPT;4616base_type.builtin_type = Variant::OBJECT;4617base_type.native_type = base_script->get_instance_base_type();4618base_type.script_type = base_script;4619base_type.script_path = base_script->get_path();46204621return _find_narrowest_native_or_global_class(base_type);4622} break;4623case GDScriptParser::DataType::CLASS: {4624if (p_type.is_meta_type) {4625return GDScript::get_class_static();4626}4627if (p_type.class_type == nullptr) {4628return p_type.native_type;4629}4630if (p_type.class_type->get_global_name() != StringName()) {4631return p_type.class_type->get_global_name();4632}4633return _find_narrowest_native_or_global_class(p_type.class_type->base_type);4634} break;4635default: {4636ERR_FAIL_V(StringName());4637} break;4638}4639}46404641template <PropertyHint t_hint, Variant::Type t_type>4642bool GDScriptParser::export_annotations(AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class) {4643ERR_FAIL_COND_V_MSG(p_target->type != Node::VARIABLE, false, vformat(R"("%s" annotation can only be applied to variables.)", p_annotation->name));4644ERR_FAIL_NULL_V(p_class, false);46454646VariableNode *variable = static_cast<VariableNode *>(p_target);4647if (variable->is_static) {4648push_error(vformat(R"(Annotation "%s" cannot be applied to a static variable.)", p_annotation->name), p_annotation);4649return false;4650}4651if (variable->exported) {4652push_error(vformat(R"(Annotation "%s" cannot be used with another "@export" annotation.)", p_annotation->name), p_annotation);4653return false;4654}46554656variable->exported = true;46574658variable->export_info.type = t_type;4659variable->export_info.hint = t_hint;46604661String hint_string;4662for (int i = 0; i < p_annotation->resolved_arguments.size(); i++) {4663String arg_string = String(p_annotation->resolved_arguments[i]);46644665if (p_annotation->name != SNAME("@export_placeholder")) {4666if (arg_string.is_empty()) {4667push_error(vformat(R"(Argument %d of annotation "%s" is empty.)", i + 1, p_annotation->name), p_annotation->arguments[i]);4668return false;4669}4670if (arg_string.contains_char(',')) {4671push_error(vformat(R"(Argument %d of annotation "%s" contains a comma. Use separate arguments instead.)", i + 1, p_annotation->name), p_annotation->arguments[i]);4672return false;4673}4674}46754676// WARNING: Do not merge with the previous `if` because there `!=`, not `==`!4677if (p_annotation->name == SNAME("@export_flags")) {4678const int64_t max_flags = 32;4679Vector<String> t = arg_string.split(":", true, 1);4680if (t[0].is_empty()) {4681push_error(vformat(R"(Invalid argument %d of annotation "@export_flags": Expected flag name.)", i + 1), p_annotation->arguments[i]);4682return false;4683}4684if (t.size() == 2) {4685if (t[1].is_empty()) {4686push_error(vformat(R"(Invalid argument %d of annotation "@export_flags": Expected flag value.)", i + 1), p_annotation->arguments[i]);4687return false;4688}4689if (!t[1].is_valid_int()) {4690push_error(vformat(R"(Invalid argument %d of annotation "@export_flags": The flag value must be a valid integer.)", i + 1), p_annotation->arguments[i]);4691return false;4692}4693int64_t value = t[1].to_int();4694if (value < 1 || value >= (1LL << max_flags)) {4695push_error(vformat(R"(Invalid argument %d of annotation "@export_flags": The flag value must be at least 1 and at most 2 ** %d - 1.)", i + 1, max_flags), p_annotation->arguments[i]);4696return false;4697}4698} else if (i >= max_flags) {4699push_error(vformat(R"(Invalid argument %d of annotation "@export_flags": Starting from argument %d, the flag value must be specified explicitly.)", i + 1, max_flags + 1), p_annotation->arguments[i]);4700return false;4701}4702} else if (p_annotation->name == SNAME("@export_node_path")) {4703String native_class = arg_string;4704if (ScriptServer::is_global_class(arg_string)) {4705native_class = ScriptServer::get_global_class_native_base(arg_string);4706}4707if (!ClassDB::class_exists(native_class) || !ClassDB::is_class_exposed(native_class)) {4708push_error(vformat(R"(Invalid argument %d of annotation "@export_node_path": The class "%s" was not found in the global scope.)", i + 1, arg_string), p_annotation->arguments[i]);4709return false;4710} else if (!ClassDB::is_parent_class(native_class, SNAME("Node"))) {4711push_error(vformat(R"(Invalid argument %d of annotation "@export_node_path": The class "%s" does not inherit "Node".)", i + 1, arg_string), p_annotation->arguments[i]);4712return false;4713}4714}47154716if (i > 0) {4717hint_string += ",";4718}4719hint_string += arg_string;4720}4721variable->export_info.hint_string = hint_string;47224723// This is called after the analyzer is done finding the type, so this should be set here.4724DataType export_type = variable->get_datatype();47254726// Use initializer type if specified type is `Variant`.4727if (export_type.is_variant() && variable->initializer != nullptr && variable->initializer->datatype.is_set()) {4728export_type = variable->initializer->get_datatype();4729export_type.type_source = DataType::INFERRED;4730}47314732const Variant::Type original_export_type_builtin = export_type.builtin_type;47334734// Process array and packed array annotations on the element type.4735bool is_array = false;4736if (export_type.builtin_type == Variant::ARRAY && export_type.has_container_element_type(0)) {4737is_array = true;4738export_type = export_type.get_container_element_type(0);4739} else if (export_type.is_typed_container_type()) {4740is_array = true;4741export_type = export_type.get_typed_container_type();4742export_type.type_source = variable->datatype.type_source;4743}47444745bool is_dict = false;4746if (export_type.builtin_type == Variant::DICTIONARY && export_type.has_container_element_types()) {4747is_dict = true;4748DataType inner_type = export_type.get_container_element_type_or_variant(1);4749export_type = export_type.get_container_element_type_or_variant(0);4750export_type.set_container_element_type(0, inner_type); // Store earlier extracted value within key to separately parse after.4751}47524753bool use_default_variable_type_check = true;47544755if (p_annotation->name == SNAME("@export_range")) {4756if (export_type.builtin_type == Variant::INT) {4757variable->export_info.type = Variant::INT;4758}4759} else if (p_annotation->name == SNAME("@export_multiline")) {4760use_default_variable_type_check = false;47614762if (export_type.builtin_type != Variant::STRING && export_type.builtin_type != Variant::DICTIONARY) {4763Vector<Variant::Type> expected_types = { Variant::STRING, Variant::DICTIONARY };4764push_error(_get_annotation_error_string(p_annotation->name, expected_types, variable->get_datatype()), p_annotation);4765return false;4766}47674768if (export_type.builtin_type == Variant::DICTIONARY) {4769variable->export_info.type = Variant::DICTIONARY;4770}4771} else if (p_annotation->name == SNAME("@export")) {4772use_default_variable_type_check = false;47734774if (variable->datatype_specifier == nullptr && variable->initializer == nullptr) {4775push_error(R"(Cannot use simple "@export" annotation with variable without type or initializer, since type can't be inferred.)", p_annotation);4776return false;4777}47784779if (export_type.has_no_type()) {4780push_error(R"(Cannot use simple "@export" annotation because the type of the initialized value can't be inferred.)", p_annotation);4781return false;4782}47834784switch (export_type.kind) {4785case GDScriptParser::DataType::BUILTIN:4786variable->export_info.type = export_type.builtin_type;4787variable->export_info.hint = PROPERTY_HINT_NONE;4788variable->export_info.hint_string = String();4789break;4790case GDScriptParser::DataType::NATIVE:4791case GDScriptParser::DataType::SCRIPT:4792case GDScriptParser::DataType::CLASS: {4793const StringName class_name = _find_narrowest_native_or_global_class(export_type);4794if (ClassDB::is_parent_class(export_type.native_type, SNAME("Resource"))) {4795variable->export_info.type = Variant::OBJECT;4796variable->export_info.hint = PROPERTY_HINT_RESOURCE_TYPE;4797variable->export_info.hint_string = class_name;4798} else if (ClassDB::is_parent_class(export_type.native_type, SNAME("Node"))) {4799variable->export_info.type = Variant::OBJECT;4800variable->export_info.hint = PROPERTY_HINT_NODE_TYPE;4801variable->export_info.hint_string = class_name;4802} else {4803push_error(R"(Export type can only be built-in, a resource, a node, or an enum.)", p_annotation);4804return false;4805}4806} break;4807case GDScriptParser::DataType::ENUM: {4808if (export_type.is_meta_type) {4809variable->export_info.type = Variant::DICTIONARY;4810} else {4811variable->export_info.type = Variant::INT;4812variable->export_info.hint = PROPERTY_HINT_ENUM;48134814String enum_hint_string;4815bool first = true;4816for (const KeyValue<StringName, int64_t> &E : export_type.enum_values) {4817if (first) {4818first = false;4819} else {4820enum_hint_string += ",";4821}4822enum_hint_string += E.key.operator String().capitalize().xml_escape();4823enum_hint_string += ":";4824enum_hint_string += String::num_int64(E.value).xml_escape();4825}48264827variable->export_info.hint_string = enum_hint_string;4828variable->export_info.usage |= PROPERTY_USAGE_CLASS_IS_ENUM;4829variable->export_info.class_name = String(export_type.native_type).replace("::", ".");4830}4831} break;4832case GDScriptParser::DataType::VARIANT: {4833if (export_type.is_variant()) {4834variable->export_info.type = Variant::NIL;4835variable->export_info.usage |= PROPERTY_USAGE_NIL_IS_VARIANT;4836}4837} break;4838default:4839push_error(R"(Export type can only be built-in, a resource, a node, or an enum.)", p_annotation);4840return false;4841}48424843if (variable->export_info.hint == PROPERTY_HINT_NODE_TYPE && !ClassDB::is_parent_class(p_class->base_type.native_type, SNAME("Node"))) {4844push_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);4845return false;4846}48474848if (is_dict) {4849String key_prefix = itos(variable->export_info.type);4850if (variable->export_info.hint) {4851key_prefix += "/" + itos(variable->export_info.hint);4852}4853key_prefix += ":" + variable->export_info.hint_string;48544855// Now parse value.4856export_type = export_type.get_container_element_type(0);48574858if (export_type.is_variant() || export_type.has_no_type()) {4859export_type.kind = GDScriptParser::DataType::BUILTIN;4860}4861switch (export_type.kind) {4862case GDScriptParser::DataType::BUILTIN:4863variable->export_info.type = export_type.builtin_type;4864variable->export_info.hint = PROPERTY_HINT_NONE;4865variable->export_info.hint_string = String();4866break;4867case GDScriptParser::DataType::NATIVE:4868case GDScriptParser::DataType::SCRIPT:4869case GDScriptParser::DataType::CLASS: {4870const StringName class_name = _find_narrowest_native_or_global_class(export_type);4871if (ClassDB::is_parent_class(export_type.native_type, SNAME("Resource"))) {4872variable->export_info.type = Variant::OBJECT;4873variable->export_info.hint = PROPERTY_HINT_RESOURCE_TYPE;4874variable->export_info.hint_string = class_name;4875} else if (ClassDB::is_parent_class(export_type.native_type, SNAME("Node"))) {4876variable->export_info.type = Variant::OBJECT;4877variable->export_info.hint = PROPERTY_HINT_NODE_TYPE;4878variable->export_info.hint_string = class_name;4879} else {4880push_error(R"(Export type can only be built-in, a resource, a node, or an enum.)", p_annotation);4881return false;4882}4883} break;4884case GDScriptParser::DataType::ENUM: {4885if (export_type.is_meta_type) {4886variable->export_info.type = Variant::DICTIONARY;4887} else {4888variable->export_info.type = Variant::INT;4889variable->export_info.hint = PROPERTY_HINT_ENUM;48904891String enum_hint_string;4892bool first = true;4893for (const KeyValue<StringName, int64_t> &E : export_type.enum_values) {4894if (first) {4895first = false;4896} else {4897enum_hint_string += ",";4898}4899enum_hint_string += E.key.operator String().capitalize().xml_escape();4900enum_hint_string += ":";4901enum_hint_string += String::num_int64(E.value).xml_escape();4902}49034904variable->export_info.hint_string = enum_hint_string;4905variable->export_info.usage |= PROPERTY_USAGE_CLASS_IS_ENUM;4906variable->export_info.class_name = String(export_type.native_type).replace("::", ".");4907}4908} break;4909default:4910push_error(R"(Export type can only be built-in, a resource, a node, or an enum.)", p_annotation);4911return false;4912}49134914if (variable->export_info.hint == PROPERTY_HINT_NODE_TYPE && !ClassDB::is_parent_class(p_class->base_type.native_type, SNAME("Node"))) {4915push_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);4916return false;4917}49184919String value_prefix = itos(variable->export_info.type);4920if (variable->export_info.hint) {4921value_prefix += "/" + itos(variable->export_info.hint);4922}4923value_prefix += ":" + variable->export_info.hint_string;49244925variable->export_info.type = Variant::DICTIONARY;4926variable->export_info.hint = PROPERTY_HINT_TYPE_STRING;4927variable->export_info.hint_string = key_prefix + ";" + value_prefix;4928variable->export_info.usage = PROPERTY_USAGE_DEFAULT;4929variable->export_info.class_name = StringName();4930}4931} else if (p_annotation->name == SNAME("@export_enum")) {4932use_default_variable_type_check = false;49334934Variant::Type enum_type = Variant::INT;49354936if (export_type.kind == DataType::BUILTIN && export_type.builtin_type == Variant::STRING) {4937enum_type = Variant::STRING;4938}49394940variable->export_info.type = enum_type;49414942if (!export_type.is_variant() && (export_type.kind != DataType::BUILTIN || export_type.builtin_type != enum_type)) {4943Vector<Variant::Type> expected_types = { Variant::INT, Variant::STRING };4944push_error(_get_annotation_error_string(p_annotation->name, expected_types, variable->get_datatype()), p_annotation);4945return false;4946}4947}49484949if (use_default_variable_type_check) {4950// Validate variable type with export.4951if (!export_type.is_variant() && (export_type.kind != DataType::BUILTIN || export_type.builtin_type != t_type)) {4952// Allow float/int conversion.4953if ((t_type != Variant::FLOAT || export_type.builtin_type != Variant::INT) && (t_type != Variant::INT || export_type.builtin_type != Variant::FLOAT)) {4954Vector<Variant::Type> expected_types = { t_type };4955push_error(_get_annotation_error_string(p_annotation->name, expected_types, variable->get_datatype()), p_annotation);4956return false;4957}4958}4959}49604961if (is_array) {4962String hint_prefix = itos(variable->export_info.type);4963if (variable->export_info.hint) {4964hint_prefix += "/" + itos(variable->export_info.hint);4965}4966variable->export_info.type = original_export_type_builtin;4967variable->export_info.hint = PROPERTY_HINT_TYPE_STRING;4968variable->export_info.hint_string = hint_prefix + ":" + variable->export_info.hint_string;4969variable->export_info.usage = PROPERTY_USAGE_DEFAULT;4970variable->export_info.class_name = StringName();4971}49724973return true;4974}49754976// For `@export_storage` and `@export_custom`, there is no need to check the variable type, argument values,4977// or handle array exports in a special way, so they are implemented as separate methods.49784979bool GDScriptParser::export_storage_annotation(AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class) {4980ERR_FAIL_COND_V_MSG(p_target->type != Node::VARIABLE, false, vformat(R"("%s" annotation can only be applied to variables.)", p_annotation->name));49814982VariableNode *variable = static_cast<VariableNode *>(p_target);4983if (variable->is_static) {4984push_error(vformat(R"(Annotation "%s" cannot be applied to a static variable.)", p_annotation->name), p_annotation);4985return false;4986}4987if (variable->exported) {4988push_error(vformat(R"(Annotation "%s" cannot be used with another "@export" annotation.)", p_annotation->name), p_annotation);4989return false;4990}49914992variable->exported = true;49934994// Save the info because the compiler uses export info for overwriting member info.4995variable->export_info = variable->get_datatype().to_property_info(variable->identifier->name);4996variable->export_info.usage |= PROPERTY_USAGE_STORAGE;49974998return true;4999}50005001bool GDScriptParser::export_custom_annotation(AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class) {5002ERR_FAIL_COND_V_MSG(p_target->type != Node::VARIABLE, false, vformat(R"("%s" annotation can only be applied to variables.)", p_annotation->name));5003ERR_FAIL_COND_V_MSG(p_annotation->resolved_arguments.size() < 2, false, R"(Annotation "@export_custom" requires 2 arguments.)");50045005VariableNode *variable = static_cast<VariableNode *>(p_target);5006if (variable->is_static) {5007push_error(vformat(R"(Annotation "%s" cannot be applied to a static variable.)", p_annotation->name), p_annotation);5008return false;5009}5010if (variable->exported) {5011push_error(vformat(R"(Annotation "%s" cannot be used with another "@export" annotation.)", p_annotation->name), p_annotation);5012return false;5013}50145015variable->exported = true;50165017DataType export_type = variable->get_datatype();50185019variable->export_info.type = export_type.builtin_type;5020variable->export_info.hint = static_cast<PropertyHint>(p_annotation->resolved_arguments[0].operator int64_t());5021variable->export_info.hint_string = p_annotation->resolved_arguments[1];50225023if (p_annotation->resolved_arguments.size() >= 3) {5024variable->export_info.usage = p_annotation->resolved_arguments[2].operator int64_t();5025}5026return true;5027}50285029bool GDScriptParser::export_tool_button_annotation(AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class) {5030#ifdef TOOLS_ENABLED5031ERR_FAIL_COND_V_MSG(p_target->type != Node::VARIABLE, false, vformat(R"("%s" annotation can only be applied to variables.)", p_annotation->name));5032ERR_FAIL_COND_V(p_annotation->resolved_arguments.is_empty(), false);50335034if (!is_tool()) {5035push_error(R"(Tool buttons can only be used in tool scripts (add "@tool" to the top of the script).)", p_annotation);5036return false;5037}50385039VariableNode *variable = static_cast<VariableNode *>(p_target);50405041if (variable->is_static) {5042push_error(vformat(R"(Annotation "%s" cannot be applied to a static variable.)", p_annotation->name), p_annotation);5043return false;5044}5045if (variable->exported) {5046push_error(vformat(R"(Annotation "%s" cannot be used with another "@export" annotation.)", p_annotation->name), p_annotation);5047return false;5048}50495050const DataType variable_type = variable->get_datatype();5051if (!variable_type.is_variant() && variable_type.is_hard_type()) {5052if (variable_type.kind != DataType::BUILTIN || variable_type.builtin_type != Variant::CALLABLE) {5053push_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);5054return false;5055}5056}50575058variable->exported = true;50595060// Build the hint string (format: `<text>[,<icon>]`).5061String hint_string = p_annotation->resolved_arguments[0].operator String(); // Button text.5062if (p_annotation->resolved_arguments.size() > 1) {5063hint_string += "," + p_annotation->resolved_arguments[1].operator String(); // Button icon.5064}50655066variable->export_info.type = Variant::CALLABLE;5067variable->export_info.hint = PROPERTY_HINT_TOOL_BUTTON;5068variable->export_info.hint_string = hint_string;5069variable->export_info.usage = PROPERTY_USAGE_EDITOR;5070#endif // TOOLS_ENABLED50715072return true; // Only available in editor.5073}50745075template <PropertyUsageFlags t_usage>5076bool GDScriptParser::export_group_annotations(AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class) {5077ERR_FAIL_COND_V(p_annotation->resolved_arguments.is_empty(), false);50785079p_annotation->export_info.name = p_annotation->resolved_arguments[0];50805081switch (t_usage) {5082case PROPERTY_USAGE_CATEGORY: {5083p_annotation->export_info.usage = t_usage;5084} break;50855086case PROPERTY_USAGE_GROUP: {5087p_annotation->export_info.usage = t_usage;5088if (p_annotation->resolved_arguments.size() == 2) {5089p_annotation->export_info.hint_string = p_annotation->resolved_arguments[1];5090}5091} break;50925093case PROPERTY_USAGE_SUBGROUP: {5094p_annotation->export_info.usage = t_usage;5095if (p_annotation->resolved_arguments.size() == 2) {5096p_annotation->export_info.hint_string = p_annotation->resolved_arguments[1];5097}5098} break;5099}51005101return true;5102}51035104bool GDScriptParser::warning_ignore_annotation(AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class) {5105#ifdef DEBUG_ENABLED5106bool has_error = false;5107for (const Variant &warning_name : p_annotation->resolved_arguments) {5108GDScriptWarning::Code warning_code = GDScriptWarning::get_code_from_name(String(warning_name).to_upper());5109if (warning_code == GDScriptWarning::WARNING_MAX) {5110push_error(vformat(R"(Invalid warning name: "%s".)", warning_name), p_annotation);5111has_error = true;5112} else {5113int start_line = p_annotation->start_line;5114int end_line = p_target->end_line;51155116switch (p_target->type) {5117#define SIMPLE_CASE(m_type, m_class, m_property) \5118case m_type: { \5119m_class *node = static_cast<m_class *>(p_target); \5120if (node->m_property == nullptr) { \5121end_line = node->start_line; \5122} else { \5123end_line = node->m_property->end_line; \5124} \5125} break;51265127// Can contain properties (set/get).5128SIMPLE_CASE(Node::VARIABLE, VariableNode, initializer)51295130// Contain bodies.5131SIMPLE_CASE(Node::FOR, ForNode, list)5132SIMPLE_CASE(Node::IF, IfNode, condition)5133SIMPLE_CASE(Node::MATCH, MatchNode, test)5134SIMPLE_CASE(Node::WHILE, WhileNode, condition)5135#undef SIMPLE_CASE51365137case Node::CLASS: {5138end_line = p_target->start_line;5139for (const AnnotationNode *annotation : p_target->annotations) {5140start_line = MIN(start_line, annotation->start_line);5141end_line = MAX(end_line, annotation->end_line);5142}5143} break;51445145case Node::FUNCTION: {5146FunctionNode *function = static_cast<FunctionNode *>(p_target);5147end_line = function->start_line;5148for (int i = 0; i < function->parameters.size(); i++) {5149end_line = MAX(end_line, function->parameters[i]->end_line);5150if (function->parameters[i]->initializer != nullptr) {5151end_line = MAX(end_line, function->parameters[i]->initializer->end_line);5152}5153}5154} break;51555156case Node::MATCH_BRANCH: {5157MatchBranchNode *branch = static_cast<MatchBranchNode *>(p_target);5158end_line = branch->start_line;5159for (int i = 0; i < branch->patterns.size(); i++) {5160end_line = MAX(end_line, branch->patterns[i]->end_line);5161}5162} break;51635164default: {5165} break;5166}51675168end_line = MAX(start_line, end_line); // Prevent infinite loop.5169for (int line = start_line; line <= end_line; line++) {5170warning_ignored_lines[warning_code].insert(line);5171}5172}5173}5174return !has_error;5175#else // !DEBUG_ENABLED5176// Only available in debug builds.5177return true;5178#endif // DEBUG_ENABLED5179}51805181bool GDScriptParser::warning_ignore_region_annotations(AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class) {5182#ifdef DEBUG_ENABLED5183bool has_error = false;5184const bool is_start = p_annotation->name == SNAME("@warning_ignore_start");5185for (const Variant &warning_name : p_annotation->resolved_arguments) {5186GDScriptWarning::Code warning_code = GDScriptWarning::get_code_from_name(String(warning_name).to_upper());5187if (warning_code == GDScriptWarning::WARNING_MAX) {5188push_error(vformat(R"(Invalid warning name: "%s".)", warning_name), p_annotation);5189has_error = true;5190continue;5191}5192if (is_start) {5193if (warning_ignore_start_lines[warning_code] != INT_MAX) {5194push_error(vformat(R"(Warning "%s" is already being ignored by "@warning_ignore_start" at line %d.)", String(warning_name).to_upper(), warning_ignore_start_lines[warning_code]), p_annotation);5195has_error = true;5196continue;5197}5198warning_ignore_start_lines[warning_code] = p_annotation->start_line;5199} else {5200if (warning_ignore_start_lines[warning_code] == INT_MAX) {5201push_error(vformat(R"(Warning "%s" is not being ignored by "@warning_ignore_start".)", String(warning_name).to_upper()), p_annotation);5202has_error = true;5203continue;5204}5205const int start_line = warning_ignore_start_lines[warning_code];5206const int end_line = MAX(start_line, p_annotation->start_line); // Prevent infinite loop.5207for (int i = start_line; i <= end_line; i++) {5208warning_ignored_lines[warning_code].insert(i);5209}5210warning_ignore_start_lines[warning_code] = INT_MAX;5211}5212}5213return !has_error;5214#else // !DEBUG_ENABLED5215// Only available in debug builds.5216return true;5217#endif // DEBUG_ENABLED5218}52195220bool GDScriptParser::rpc_annotation(AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class) {5221ERR_FAIL_COND_V_MSG(p_target->type != Node::FUNCTION, false, vformat(R"("%s" annotation can only be applied to functions.)", p_annotation->name));52225223FunctionNode *function = static_cast<FunctionNode *>(p_target);5224if (function->rpc_config.get_type() != Variant::NIL) {5225push_error(R"(RPC annotations can only be used once per function.)", p_annotation);5226return false;5227}52285229// Default values should match the annotation registration defaults and `SceneRPCInterface::_parse_rpc_config()`.5230Dictionary rpc_config;5231rpc_config["rpc_mode"] = MultiplayerAPI::RPC_MODE_AUTHORITY;5232if (!p_annotation->resolved_arguments.is_empty()) {5233unsigned char locality_args = 0;5234unsigned char permission_args = 0;5235unsigned char transfer_mode_args = 0;52365237for (int i = 0; i < p_annotation->resolved_arguments.size(); i++) {5238if (i == 3) {5239rpc_config["channel"] = p_annotation->resolved_arguments[i].operator int();5240continue;5241}52425243String arg = p_annotation->resolved_arguments[i].operator String();5244if (arg == "call_local") {5245locality_args++;5246rpc_config["call_local"] = true;5247} else if (arg == "call_remote") {5248locality_args++;5249rpc_config["call_local"] = false;5250} else if (arg == "any_peer") {5251permission_args++;5252rpc_config["rpc_mode"] = MultiplayerAPI::RPC_MODE_ANY_PEER;5253} else if (arg == "authority") {5254permission_args++;5255rpc_config["rpc_mode"] = MultiplayerAPI::RPC_MODE_AUTHORITY;5256} else if (arg == "reliable") {5257transfer_mode_args++;5258rpc_config["transfer_mode"] = MultiplayerPeer::TRANSFER_MODE_RELIABLE;5259} else if (arg == "unreliable") {5260transfer_mode_args++;5261rpc_config["transfer_mode"] = MultiplayerPeer::TRANSFER_MODE_UNRELIABLE;5262} else if (arg == "unreliable_ordered") {5263transfer_mode_args++;5264rpc_config["transfer_mode"] = MultiplayerPeer::TRANSFER_MODE_UNRELIABLE_ORDERED;5265} else {5266push_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);5267}5268}52695270if (locality_args > 1) {5271push_error(R"(Invalid RPC config. The locality ("call_local"/"call_remote") must be specified no more than once.)", p_annotation);5272} else if (permission_args > 1) {5273push_error(R"(Invalid RPC config. The permission ("any_peer"/"authority") must be specified no more than once.)", p_annotation);5274} else if (transfer_mode_args > 1) {5275push_error(R"(Invalid RPC config. The transfer mode ("reliable"/"unreliable"/"unreliable_ordered") must be specified no more than once.)", p_annotation);5276}5277}5278function->rpc_config = rpc_config;5279return true;5280}52815282GDScriptParser::DataType GDScriptParser::SuiteNode::Local::get_datatype() const {5283switch (type) {5284case CONSTANT:5285return constant->get_datatype();5286case VARIABLE:5287return variable->get_datatype();5288case PARAMETER:5289return parameter->get_datatype();5290case FOR_VARIABLE:5291case PATTERN_BIND:5292return bind->get_datatype();5293case UNDEFINED:5294return DataType();5295}5296return DataType();5297}52985299String GDScriptParser::SuiteNode::Local::get_name() const {5300switch (type) {5301case SuiteNode::Local::PARAMETER:5302return "parameter";5303case SuiteNode::Local::CONSTANT:5304return "constant";5305case SuiteNode::Local::VARIABLE:5306return "variable";5307case SuiteNode::Local::FOR_VARIABLE:5308return "for loop iterator";5309case SuiteNode::Local::PATTERN_BIND:5310return "pattern bind";5311case SuiteNode::Local::UNDEFINED:5312return "<undefined>";5313default:5314return String();5315}5316}53175318String GDScriptParser::DataType::to_string() const {5319switch (kind) {5320case VARIANT:5321return "Variant";5322case BUILTIN:5323if (builtin_type == Variant::NIL) {5324return "null";5325}5326if (builtin_type == Variant::ARRAY && has_container_element_type(0)) {5327return vformat("Array[%s]", get_container_element_type(0).to_string());5328}5329if (builtin_type == Variant::DICTIONARY && has_container_element_types()) {5330return vformat("Dictionary[%s, %s]", get_container_element_type_or_variant(0).to_string(), get_container_element_type_or_variant(1).to_string());5331}5332return Variant::get_type_name(builtin_type);5333case NATIVE:5334if (is_meta_type) {5335return GDScriptNativeClass::get_class_static();5336}5337return native_type.operator String();5338case CLASS:5339if (class_type->identifier != nullptr) {5340return class_type->identifier->name.operator String();5341}5342return class_type->fqcn;5343case SCRIPT: {5344if (is_meta_type) {5345return script_type.is_valid() ? script_type->get_class_name().operator String() : "";5346}5347String name = script_type.is_valid() ? script_type->get_name() : "";5348if (!name.is_empty()) {5349return name;5350}5351name = script_path;5352if (!name.is_empty()) {5353return name;5354}5355return native_type.operator String();5356}5357case ENUM: {5358// native_type contains either the native class defining the enum5359// or the fully qualified class name of the script defining the enum5360return String(native_type).get_file(); // Remove path, keep filename5361}5362case RESOLVING:5363case UNRESOLVED:5364return "<unresolved type>";5365}53665367ERR_FAIL_V_MSG("<unresolved type>", "Kind set outside the enum range.");5368}53695370PropertyInfo GDScriptParser::DataType::to_property_info(const String &p_name) const {5371PropertyInfo result;5372result.name = p_name;5373result.usage = PROPERTY_USAGE_NONE;53745375if (!is_hard_type()) {5376result.usage |= PROPERTY_USAGE_NIL_IS_VARIANT;5377return result;5378}53795380switch (kind) {5381case BUILTIN:5382result.type = builtin_type;5383if (builtin_type == Variant::ARRAY && has_container_element_type(0)) {5384const DataType elem_type = get_container_element_type(0);5385switch (elem_type.kind) {5386case BUILTIN:5387result.hint = PROPERTY_HINT_ARRAY_TYPE;5388result.hint_string = Variant::get_type_name(elem_type.builtin_type);5389break;5390case NATIVE:5391result.hint = PROPERTY_HINT_ARRAY_TYPE;5392result.hint_string = elem_type.native_type;5393break;5394case SCRIPT:5395result.hint = PROPERTY_HINT_ARRAY_TYPE;5396if (elem_type.script_type.is_valid() && elem_type.script_type->get_global_name() != StringName()) {5397result.hint_string = elem_type.script_type->get_global_name();5398} else {5399result.hint_string = elem_type.native_type;5400}5401break;5402case CLASS:5403result.hint = PROPERTY_HINT_ARRAY_TYPE;5404if (elem_type.class_type != nullptr && elem_type.class_type->get_global_name() != StringName()) {5405result.hint_string = elem_type.class_type->get_global_name();5406} else {5407result.hint_string = elem_type.native_type;5408}5409break;5410case ENUM:5411result.hint = PROPERTY_HINT_ARRAY_TYPE;5412result.hint_string = String(elem_type.native_type).replace("::", ".");5413break;5414case VARIANT:5415case RESOLVING:5416case UNRESOLVED:5417break;5418}5419} else if (builtin_type == Variant::DICTIONARY && has_container_element_types()) {5420const DataType key_type = get_container_element_type_or_variant(0);5421const DataType value_type = get_container_element_type_or_variant(1);5422if ((key_type.kind == VARIANT && value_type.kind == VARIANT) || key_type.kind == RESOLVING ||5423key_type.kind == UNRESOLVED || value_type.kind == RESOLVING || value_type.kind == UNRESOLVED) {5424break;5425}5426String key_hint, value_hint;5427switch (key_type.kind) {5428case BUILTIN:5429key_hint = Variant::get_type_name(key_type.builtin_type);5430break;5431case NATIVE:5432key_hint = key_type.native_type;5433break;5434case SCRIPT:5435if (key_type.script_type.is_valid() && key_type.script_type->get_global_name() != StringName()) {5436key_hint = key_type.script_type->get_global_name();5437} else {5438key_hint = key_type.native_type;5439}5440break;5441case CLASS:5442if (key_type.class_type != nullptr && key_type.class_type->get_global_name() != StringName()) {5443key_hint = key_type.class_type->get_global_name();5444} else {5445key_hint = key_type.native_type;5446}5447break;5448case ENUM:5449key_hint = String(key_type.native_type).replace("::", ".");5450break;5451default:5452key_hint = "Variant";5453break;5454}5455switch (value_type.kind) {5456case BUILTIN:5457value_hint = Variant::get_type_name(value_type.builtin_type);5458break;5459case NATIVE:5460value_hint = value_type.native_type;5461break;5462case SCRIPT:5463if (value_type.script_type.is_valid() && value_type.script_type->get_global_name() != StringName()) {5464value_hint = value_type.script_type->get_global_name();5465} else {5466value_hint = value_type.native_type;5467}5468break;5469case CLASS:5470if (value_type.class_type != nullptr && value_type.class_type->get_global_name() != StringName()) {5471value_hint = value_type.class_type->get_global_name();5472} else {5473value_hint = value_type.native_type;5474}5475break;5476case ENUM:5477value_hint = String(value_type.native_type).replace("::", ".");5478break;5479default:5480value_hint = "Variant";5481break;5482}5483result.hint = PROPERTY_HINT_DICTIONARY_TYPE;5484result.hint_string = key_hint + ";" + value_hint;5485}5486break;5487case NATIVE:5488result.type = Variant::OBJECT;5489if (is_meta_type) {5490result.class_name = GDScriptNativeClass::get_class_static();5491} else {5492result.class_name = native_type;5493}5494break;5495case SCRIPT:5496result.type = Variant::OBJECT;5497if (is_meta_type) {5498result.class_name = script_type.is_valid() ? script_type->get_class_name() : Script::get_class_static();5499} else if (script_type.is_valid() && script_type->get_global_name() != StringName()) {5500result.class_name = script_type->get_global_name();5501} else {5502result.class_name = native_type;5503}5504break;5505case CLASS:5506result.type = Variant::OBJECT;5507if (is_meta_type) {5508result.class_name = GDScript::get_class_static();5509} else if (class_type != nullptr && class_type->get_global_name() != StringName()) {5510result.class_name = class_type->get_global_name();5511} else {5512result.class_name = native_type;5513}5514break;5515case ENUM:5516if (is_meta_type) {5517result.type = Variant::DICTIONARY;5518} else {5519result.type = Variant::INT;5520result.usage |= PROPERTY_USAGE_CLASS_IS_ENUM;5521result.class_name = String(native_type).replace("::", ".");5522}5523break;5524case VARIANT:5525case RESOLVING:5526case UNRESOLVED:5527result.usage |= PROPERTY_USAGE_NIL_IS_VARIANT;5528break;5529}55305531return result;5532}55335534static Variant::Type _variant_type_to_typed_array_element_type(Variant::Type p_type) {5535switch (p_type) {5536case Variant::PACKED_BYTE_ARRAY:5537case Variant::PACKED_INT32_ARRAY:5538case Variant::PACKED_INT64_ARRAY:5539return Variant::INT;5540case Variant::PACKED_FLOAT32_ARRAY:5541case Variant::PACKED_FLOAT64_ARRAY:5542return Variant::FLOAT;5543case Variant::PACKED_STRING_ARRAY:5544return Variant::STRING;5545case Variant::PACKED_VECTOR2_ARRAY:5546return Variant::VECTOR2;5547case Variant::PACKED_VECTOR3_ARRAY:5548return Variant::VECTOR3;5549case Variant::PACKED_COLOR_ARRAY:5550return Variant::COLOR;5551case Variant::PACKED_VECTOR4_ARRAY:5552return Variant::VECTOR4;5553default:5554return Variant::NIL;5555}5556}55575558bool GDScriptParser::DataType::is_typed_container_type() const {5559return kind == GDScriptParser::DataType::BUILTIN && _variant_type_to_typed_array_element_type(builtin_type) != Variant::NIL;5560}55615562GDScriptParser::DataType GDScriptParser::DataType::get_typed_container_type() const {5563GDScriptParser::DataType type;5564type.kind = GDScriptParser::DataType::BUILTIN;5565type.builtin_type = _variant_type_to_typed_array_element_type(builtin_type);5566return type;5567}55685569bool GDScriptParser::DataType::can_reference(const GDScriptParser::DataType &p_other) const {5570if (p_other.is_meta_type) {5571return false;5572} else if (builtin_type != p_other.builtin_type) {5573return false;5574} else if (builtin_type != Variant::OBJECT) {5575return true;5576}55775578if (native_type == StringName()) {5579return true;5580} else if (p_other.native_type == StringName()) {5581return false;5582} else if (native_type != p_other.native_type && !ClassDB::is_parent_class(p_other.native_type, native_type)) {5583return false;5584}55855586Ref<Script> script = script_type;5587if (kind == GDScriptParser::DataType::CLASS && script.is_null()) {5588Error err = OK;5589Ref<GDScript> scr = GDScriptCache::get_shallow_script(script_path, err);5590ERR_FAIL_COND_V_MSG(err, false, vformat(R"(Error while getting cache for script "%s".)", script_path));5591script.reference_ptr(scr->find_class(class_type->fqcn));5592}55935594Ref<Script> script_other = p_other.script_type;5595if (p_other.kind == GDScriptParser::DataType::CLASS && script_other.is_null()) {5596Error err = OK;5597Ref<GDScript> scr = GDScriptCache::get_shallow_script(p_other.script_path, err);5598ERR_FAIL_COND_V_MSG(err, false, vformat(R"(Error while getting cache for script "%s".)", p_other.script_path));5599script_other.reference_ptr(scr->find_class(p_other.class_type->fqcn));5600}56015602if (script.is_null()) {5603return true;5604} else if (script_other.is_null()) {5605return false;5606} else if (script != script_other && !script_other->inherits_script(script)) {5607return false;5608}56095610return true;5611}56125613void GDScriptParser::complete_extents(Node *p_node) {5614while (!nodes_in_progress.is_empty() && nodes_in_progress.back()->get() != p_node) {5615ERR_PRINT("Parser bug: Mismatch in extents tracking stack.");5616nodes_in_progress.pop_back();5617}5618if (nodes_in_progress.is_empty()) {5619ERR_PRINT("Parser bug: Extents tracking stack is empty.");5620} else {5621nodes_in_progress.pop_back();5622}5623}56245625void GDScriptParser::update_extents(Node *p_node) {5626p_node->end_line = previous.end_line;5627p_node->end_column = previous.end_column;5628}56295630void GDScriptParser::reset_extents(Node *p_node, GDScriptTokenizer::Token p_token) {5631p_node->start_line = p_token.start_line;5632p_node->end_line = p_token.end_line;5633p_node->start_column = p_token.start_column;5634p_node->end_column = p_token.end_column;5635}56365637void GDScriptParser::reset_extents(Node *p_node, Node *p_from) {5638if (p_from == nullptr) {5639return;5640}5641p_node->start_line = p_from->start_line;5642p_node->end_line = p_from->end_line;5643p_node->start_column = p_from->start_column;5644p_node->end_column = p_from->end_column;5645}56465647/*---------- PRETTY PRINT FOR DEBUG ----------*/56485649#ifdef DEBUG_ENABLED56505651void GDScriptParser::TreePrinter::increase_indent() {5652indent_level++;5653indent = "";5654for (int i = 0; i < indent_level * 4; i++) {5655if (i % 4 == 0) {5656indent += "|";5657} else {5658indent += " ";5659}5660}5661}56625663void GDScriptParser::TreePrinter::decrease_indent() {5664indent_level--;5665indent = "";5666for (int i = 0; i < indent_level * 4; i++) {5667if (i % 4 == 0) {5668indent += "|";5669} else {5670indent += " ";5671}5672}5673}56745675void GDScriptParser::TreePrinter::push_line(const String &p_line) {5676if (!p_line.is_empty()) {5677push_text(p_line);5678}5679printed += "\n";5680pending_indent = true;5681}56825683void GDScriptParser::TreePrinter::push_text(const String &p_text) {5684if (pending_indent) {5685printed += indent;5686pending_indent = false;5687}5688printed += p_text;5689}56905691void GDScriptParser::TreePrinter::print_annotation(const AnnotationNode *p_annotation) {5692push_text(p_annotation->name);5693push_text(" (");5694for (int i = 0; i < p_annotation->arguments.size(); i++) {5695if (i > 0) {5696push_text(" , ");5697}5698print_expression(p_annotation->arguments[i]);5699}5700push_line(")");5701}57025703void GDScriptParser::TreePrinter::print_array(ArrayNode *p_array) {5704push_text("[ ");5705for (int i = 0; i < p_array->elements.size(); i++) {5706if (i > 0) {5707push_text(" , ");5708}5709print_expression(p_array->elements[i]);5710}5711push_text(" ]");5712}57135714void GDScriptParser::TreePrinter::print_assert(AssertNode *p_assert) {5715push_text("Assert ( ");5716print_expression(p_assert->condition);5717push_line(" )");5718}57195720void GDScriptParser::TreePrinter::print_assignment(AssignmentNode *p_assignment) {5721switch (p_assignment->assignee->type) {5722case Node::IDENTIFIER:5723print_identifier(static_cast<IdentifierNode *>(p_assignment->assignee));5724break;5725case Node::SUBSCRIPT:5726print_subscript(static_cast<SubscriptNode *>(p_assignment->assignee));5727break;5728default:5729break; // Unreachable.5730}57315732push_text(" ");5733switch (p_assignment->operation) {5734case AssignmentNode::OP_ADDITION:5735push_text("+");5736break;5737case AssignmentNode::OP_SUBTRACTION:5738push_text("-");5739break;5740case AssignmentNode::OP_MULTIPLICATION:5741push_text("*");5742break;5743case AssignmentNode::OP_DIVISION:5744push_text("/");5745break;5746case AssignmentNode::OP_MODULO:5747push_text("%");5748break;5749case AssignmentNode::OP_POWER:5750push_text("**");5751break;5752case AssignmentNode::OP_BIT_SHIFT_LEFT:5753push_text("<<");5754break;5755case AssignmentNode::OP_BIT_SHIFT_RIGHT:5756push_text(">>");5757break;5758case AssignmentNode::OP_BIT_AND:5759push_text("&");5760break;5761case AssignmentNode::OP_BIT_OR:5762push_text("|");5763break;5764case AssignmentNode::OP_BIT_XOR:5765push_text("^");5766break;5767case AssignmentNode::OP_NONE:5768break;5769}5770push_text("= ");5771print_expression(p_assignment->assigned_value);5772push_line();5773}57745775void GDScriptParser::TreePrinter::print_await(AwaitNode *p_await) {5776push_text("Await ");5777print_expression(p_await->to_await);5778}57795780void GDScriptParser::TreePrinter::print_binary_op(BinaryOpNode *p_binary_op) {5781// Surround in parenthesis for disambiguation.5782push_text("(");5783print_expression(p_binary_op->left_operand);5784switch (p_binary_op->operation) {5785case BinaryOpNode::OP_ADDITION:5786push_text(" + ");5787break;5788case BinaryOpNode::OP_SUBTRACTION:5789push_text(" - ");5790break;5791case BinaryOpNode::OP_MULTIPLICATION:5792push_text(" * ");5793break;5794case BinaryOpNode::OP_DIVISION:5795push_text(" / ");5796break;5797case BinaryOpNode::OP_MODULO:5798push_text(" % ");5799break;5800case BinaryOpNode::OP_POWER:5801push_text(" ** ");5802break;5803case BinaryOpNode::OP_BIT_LEFT_SHIFT:5804push_text(" << ");5805break;5806case BinaryOpNode::OP_BIT_RIGHT_SHIFT:5807push_text(" >> ");5808break;5809case BinaryOpNode::OP_BIT_AND:5810push_text(" & ");5811break;5812case BinaryOpNode::OP_BIT_OR:5813push_text(" | ");5814break;5815case BinaryOpNode::OP_BIT_XOR:5816push_text(" ^ ");5817break;5818case BinaryOpNode::OP_LOGIC_AND:5819push_text(" AND ");5820break;5821case BinaryOpNode::OP_LOGIC_OR:5822push_text(" OR ");5823break;5824case BinaryOpNode::OP_CONTENT_TEST:5825push_text(" IN ");5826break;5827case BinaryOpNode::OP_COMP_EQUAL:5828push_text(" == ");5829break;5830case BinaryOpNode::OP_COMP_NOT_EQUAL:5831push_text(" != ");5832break;5833case BinaryOpNode::OP_COMP_LESS:5834push_text(" < ");5835break;5836case BinaryOpNode::OP_COMP_LESS_EQUAL:5837push_text(" <= ");5838break;5839case BinaryOpNode::OP_COMP_GREATER:5840push_text(" > ");5841break;5842case BinaryOpNode::OP_COMP_GREATER_EQUAL:5843push_text(" >= ");5844break;5845}5846print_expression(p_binary_op->right_operand);5847// Surround in parenthesis for disambiguation.5848push_text(")");5849}58505851void GDScriptParser::TreePrinter::print_call(CallNode *p_call) {5852if (p_call->is_super) {5853push_text("super");5854if (p_call->callee != nullptr) {5855push_text(".");5856print_expression(p_call->callee);5857}5858} else {5859print_expression(p_call->callee);5860}5861push_text("( ");5862for (int i = 0; i < p_call->arguments.size(); i++) {5863if (i > 0) {5864push_text(" , ");5865}5866print_expression(p_call->arguments[i]);5867}5868push_text(" )");5869}58705871void GDScriptParser::TreePrinter::print_cast(CastNode *p_cast) {5872print_expression(p_cast->operand);5873push_text(" AS ");5874print_type(p_cast->cast_type);5875}58765877void GDScriptParser::TreePrinter::print_class(ClassNode *p_class) {5878for (const AnnotationNode *E : p_class->annotations) {5879print_annotation(E);5880}5881push_text("Class ");5882if (p_class->identifier == nullptr) {5883push_text("<unnamed>");5884} else {5885print_identifier(p_class->identifier);5886}58875888if (p_class->extends_used) {5889bool first = true;5890push_text(" Extends ");5891if (!p_class->extends_path.is_empty()) {5892push_text(vformat(R"("%s")", p_class->extends_path));5893first = false;5894}5895for (int i = 0; i < p_class->extends.size(); i++) {5896if (!first) {5897push_text(".");5898} else {5899first = false;5900}5901push_text(p_class->extends[i]->name);5902}5903}59045905push_line(" :");59065907increase_indent();59085909for (int i = 0; i < p_class->members.size(); i++) {5910const ClassNode::Member &m = p_class->members[i];59115912switch (m.type) {5913case ClassNode::Member::CLASS:5914print_class(m.m_class);5915break;5916case ClassNode::Member::VARIABLE:5917print_variable(m.variable);5918break;5919case ClassNode::Member::CONSTANT:5920print_constant(m.constant);5921break;5922case ClassNode::Member::SIGNAL:5923print_signal(m.signal);5924break;5925case ClassNode::Member::FUNCTION:5926print_function(m.function);5927break;5928case ClassNode::Member::ENUM:5929print_enum(m.m_enum);5930break;5931case ClassNode::Member::ENUM_VALUE:5932break; // Nothing. Will be printed by enum.5933case ClassNode::Member::GROUP:5934break; // Nothing. Groups are only used by inspector.5935case ClassNode::Member::UNDEFINED:5936push_line("<unknown member>");5937break;5938}5939}59405941decrease_indent();5942}59435944void GDScriptParser::TreePrinter::print_constant(ConstantNode *p_constant) {5945push_text("Constant ");5946print_identifier(p_constant->identifier);59475948increase_indent();59495950push_line();5951push_text("= ");5952if (p_constant->initializer == nullptr) {5953push_text("<missing value>");5954} else {5955print_expression(p_constant->initializer);5956}5957decrease_indent();5958push_line();5959}59605961void GDScriptParser::TreePrinter::print_dictionary(DictionaryNode *p_dictionary) {5962push_line("{");5963increase_indent();5964for (int i = 0; i < p_dictionary->elements.size(); i++) {5965print_expression(p_dictionary->elements[i].key);5966if (p_dictionary->style == DictionaryNode::PYTHON_DICT) {5967push_text(" : ");5968} else {5969push_text(" = ");5970}5971print_expression(p_dictionary->elements[i].value);5972push_line(" ,");5973}5974decrease_indent();5975push_text("}");5976}59775978void GDScriptParser::TreePrinter::print_expression(ExpressionNode *p_expression) {5979if (p_expression == nullptr) {5980push_text("<invalid expression>");5981return;5982}5983switch (p_expression->type) {5984case Node::ARRAY:5985print_array(static_cast<ArrayNode *>(p_expression));5986break;5987case Node::ASSIGNMENT:5988print_assignment(static_cast<AssignmentNode *>(p_expression));5989break;5990case Node::AWAIT:5991print_await(static_cast<AwaitNode *>(p_expression));5992break;5993case Node::BINARY_OPERATOR:5994print_binary_op(static_cast<BinaryOpNode *>(p_expression));5995break;5996case Node::CALL:5997print_call(static_cast<CallNode *>(p_expression));5998break;5999case Node::CAST:6000print_cast(static_cast<CastNode *>(p_expression));6001break;6002case Node::DICTIONARY:6003print_dictionary(static_cast<DictionaryNode *>(p_expression));6004break;6005case Node::GET_NODE:6006print_get_node(static_cast<GetNodeNode *>(p_expression));6007break;6008case Node::IDENTIFIER:6009print_identifier(static_cast<IdentifierNode *>(p_expression));6010break;6011case Node::LAMBDA:6012print_lambda(static_cast<LambdaNode *>(p_expression));6013break;6014case Node::LITERAL:6015print_literal(static_cast<LiteralNode *>(p_expression));6016break;6017case Node::PRELOAD:6018print_preload(static_cast<PreloadNode *>(p_expression));6019break;6020case Node::SELF:6021print_self(static_cast<SelfNode *>(p_expression));6022break;6023case Node::SUBSCRIPT:6024print_subscript(static_cast<SubscriptNode *>(p_expression));6025break;6026case Node::TERNARY_OPERATOR:6027print_ternary_op(static_cast<TernaryOpNode *>(p_expression));6028break;6029case Node::TYPE_TEST:6030print_type_test(static_cast<TypeTestNode *>(p_expression));6031break;6032case Node::UNARY_OPERATOR:6033print_unary_op(static_cast<UnaryOpNode *>(p_expression));6034break;6035default:6036push_text(vformat("<unknown expression %d>", p_expression->type));6037break;6038}6039}60406041void GDScriptParser::TreePrinter::print_enum(EnumNode *p_enum) {6042push_text("Enum ");6043if (p_enum->identifier != nullptr) {6044print_identifier(p_enum->identifier);6045} else {6046push_text("<unnamed>");6047}60486049push_line(" {");6050increase_indent();6051for (int i = 0; i < p_enum->values.size(); i++) {6052const EnumNode::Value &item = p_enum->values[i];6053print_identifier(item.identifier);6054push_text(" = ");6055push_text(itos(item.value));6056push_line(" ,");6057}6058decrease_indent();6059push_line("}");6060}60616062void GDScriptParser::TreePrinter::print_for(ForNode *p_for) {6063push_text("For ");6064print_identifier(p_for->variable);6065push_text(" IN ");6066print_expression(p_for->list);6067push_line(" :");60686069increase_indent();60706071print_suite(p_for->loop);60726073decrease_indent();6074}60756076void GDScriptParser::TreePrinter::print_function(FunctionNode *p_function, const String &p_context) {6077for (const AnnotationNode *E : p_function->annotations) {6078print_annotation(E);6079}6080if (p_function->is_static) {6081push_text("Static ");6082}6083push_text(p_context);6084push_text(" ");6085if (p_function->identifier) {6086print_identifier(p_function->identifier);6087} else {6088push_text("<anonymous>");6089}6090push_text("( ");6091for (int i = 0; i < p_function->parameters.size(); i++) {6092if (i > 0) {6093push_text(" , ");6094}6095print_parameter(p_function->parameters[i]);6096}6097push_line(" ) :");6098increase_indent();6099print_suite(p_function->body);6100decrease_indent();6101}61026103void GDScriptParser::TreePrinter::print_get_node(GetNodeNode *p_get_node) {6104if (p_get_node->use_dollar) {6105push_text("$");6106}6107push_text(p_get_node->full_path);6108}61096110void GDScriptParser::TreePrinter::print_identifier(IdentifierNode *p_identifier) {6111if (p_identifier != nullptr) {6112push_text(p_identifier->name);6113} else {6114push_text("<invalid identifier>");6115}6116}61176118void GDScriptParser::TreePrinter::print_if(IfNode *p_if, bool p_is_elif) {6119if (p_is_elif) {6120push_text("Elif ");6121} else {6122push_text("If ");6123}6124print_expression(p_if->condition);6125push_line(" :");61266127increase_indent();6128print_suite(p_if->true_block);6129decrease_indent();61306131// FIXME: Properly detect "elif" blocks.6132if (p_if->false_block != nullptr) {6133push_line("Else :");6134increase_indent();6135print_suite(p_if->false_block);6136decrease_indent();6137}6138}61396140void GDScriptParser::TreePrinter::print_lambda(LambdaNode *p_lambda) {6141print_function(p_lambda->function, "Lambda");6142push_text("| captures [ ");6143for (int i = 0; i < p_lambda->captures.size(); i++) {6144if (i > 0) {6145push_text(" , ");6146}6147push_text(p_lambda->captures[i]->name.operator String());6148}6149push_line(" ]");6150}61516152void GDScriptParser::TreePrinter::print_literal(LiteralNode *p_literal) {6153// Prefix for string types.6154switch (p_literal->value.get_type()) {6155case Variant::NODE_PATH:6156push_text("^\"");6157break;6158case Variant::STRING:6159push_text("\"");6160break;6161case Variant::STRING_NAME:6162push_text("&\"");6163break;6164default:6165break;6166}6167push_text(p_literal->value);6168// Suffix for string types.6169switch (p_literal->value.get_type()) {6170case Variant::NODE_PATH:6171case Variant::STRING:6172case Variant::STRING_NAME:6173push_text("\"");6174break;6175default:6176break;6177}6178}61796180void GDScriptParser::TreePrinter::print_match(MatchNode *p_match) {6181push_text("Match ");6182print_expression(p_match->test);6183push_line(" :");61846185increase_indent();6186for (int i = 0; i < p_match->branches.size(); i++) {6187print_match_branch(p_match->branches[i]);6188}6189decrease_indent();6190}61916192void GDScriptParser::TreePrinter::print_match_branch(MatchBranchNode *p_match_branch) {6193for (int i = 0; i < p_match_branch->patterns.size(); i++) {6194if (i > 0) {6195push_text(" , ");6196}6197print_match_pattern(p_match_branch->patterns[i]);6198}61996200push_line(" :");62016202increase_indent();6203print_suite(p_match_branch->block);6204decrease_indent();6205}62066207void GDScriptParser::TreePrinter::print_match_pattern(PatternNode *p_match_pattern) {6208switch (p_match_pattern->pattern_type) {6209case PatternNode::PT_LITERAL:6210print_literal(p_match_pattern->literal);6211break;6212case PatternNode::PT_WILDCARD:6213push_text("_");6214break;6215case PatternNode::PT_REST:6216push_text("..");6217break;6218case PatternNode::PT_BIND:6219push_text("Var ");6220print_identifier(p_match_pattern->bind);6221break;6222case PatternNode::PT_EXPRESSION:6223print_expression(p_match_pattern->expression);6224break;6225case PatternNode::PT_ARRAY:6226push_text("[ ");6227for (int i = 0; i < p_match_pattern->array.size(); i++) {6228if (i > 0) {6229push_text(" , ");6230}6231print_match_pattern(p_match_pattern->array[i]);6232}6233push_text(" ]");6234break;6235case PatternNode::PT_DICTIONARY:6236push_text("{ ");6237for (int i = 0; i < p_match_pattern->dictionary.size(); i++) {6238if (i > 0) {6239push_text(" , ");6240}6241if (p_match_pattern->dictionary[i].key != nullptr) {6242// Key can be null for rest pattern.6243print_expression(p_match_pattern->dictionary[i].key);6244push_text(" : ");6245}6246print_match_pattern(p_match_pattern->dictionary[i].value_pattern);6247}6248push_text(" }");6249break;6250}6251}62526253void GDScriptParser::TreePrinter::print_parameter(ParameterNode *p_parameter) {6254print_identifier(p_parameter->identifier);6255if (p_parameter->datatype_specifier != nullptr) {6256push_text(" : ");6257print_type(p_parameter->datatype_specifier);6258}6259if (p_parameter->initializer != nullptr) {6260push_text(" = ");6261print_expression(p_parameter->initializer);6262}6263}62646265void GDScriptParser::TreePrinter::print_preload(PreloadNode *p_preload) {6266push_text(R"(Preload ( ")");6267push_text(p_preload->resolved_path);6268push_text(R"(" )");6269}62706271void GDScriptParser::TreePrinter::print_return(ReturnNode *p_return) {6272push_text("Return");6273if (p_return->return_value != nullptr) {6274push_text(" ");6275print_expression(p_return->return_value);6276}6277push_line();6278}62796280void GDScriptParser::TreePrinter::print_self(SelfNode *p_self) {6281push_text("Self(");6282if (p_self->current_class->identifier != nullptr) {6283print_identifier(p_self->current_class->identifier);6284} else {6285push_text("<main class>");6286}6287push_text(")");6288}62896290void GDScriptParser::TreePrinter::print_signal(SignalNode *p_signal) {6291push_text("Signal ");6292print_identifier(p_signal->identifier);6293push_text("( ");6294for (int i = 0; i < p_signal->parameters.size(); i++) {6295print_parameter(p_signal->parameters[i]);6296}6297push_line(" )");6298}62996300void GDScriptParser::TreePrinter::print_subscript(SubscriptNode *p_subscript) {6301print_expression(p_subscript->base);6302if (p_subscript->is_attribute) {6303push_text(".");6304print_identifier(p_subscript->attribute);6305} else {6306push_text("[ ");6307print_expression(p_subscript->index);6308push_text(" ]");6309}6310}63116312void GDScriptParser::TreePrinter::print_statement(Node *p_statement) {6313switch (p_statement->type) {6314case Node::ASSERT:6315print_assert(static_cast<AssertNode *>(p_statement));6316break;6317case Node::VARIABLE:6318print_variable(static_cast<VariableNode *>(p_statement));6319break;6320case Node::CONSTANT:6321print_constant(static_cast<ConstantNode *>(p_statement));6322break;6323case Node::IF:6324print_if(static_cast<IfNode *>(p_statement));6325break;6326case Node::FOR:6327print_for(static_cast<ForNode *>(p_statement));6328break;6329case Node::WHILE:6330print_while(static_cast<WhileNode *>(p_statement));6331break;6332case Node::MATCH:6333print_match(static_cast<MatchNode *>(p_statement));6334break;6335case Node::RETURN:6336print_return(static_cast<ReturnNode *>(p_statement));6337break;6338case Node::BREAK:6339push_line("Break");6340break;6341case Node::CONTINUE:6342push_line("Continue");6343break;6344case Node::PASS:6345push_line("Pass");6346break;6347case Node::BREAKPOINT:6348push_line("Breakpoint");6349break;6350case Node::ASSIGNMENT:6351print_assignment(static_cast<AssignmentNode *>(p_statement));6352break;6353default:6354if (p_statement->is_expression()) {6355print_expression(static_cast<ExpressionNode *>(p_statement));6356push_line();6357} else {6358push_line(vformat("<unknown statement %d>", p_statement->type));6359}6360break;6361}6362}63636364void GDScriptParser::TreePrinter::print_suite(SuiteNode *p_suite) {6365for (int i = 0; i < p_suite->statements.size(); i++) {6366print_statement(p_suite->statements[i]);6367}6368}63696370void GDScriptParser::TreePrinter::print_ternary_op(TernaryOpNode *p_ternary_op) {6371// Surround in parenthesis for disambiguation.6372push_text("(");6373print_expression(p_ternary_op->true_expr);6374push_text(") IF (");6375print_expression(p_ternary_op->condition);6376push_text(") ELSE (");6377print_expression(p_ternary_op->false_expr);6378push_text(")");6379}63806381void GDScriptParser::TreePrinter::print_type(TypeNode *p_type) {6382if (p_type->type_chain.is_empty()) {6383push_text("Void");6384} else {6385for (int i = 0; i < p_type->type_chain.size(); i++) {6386if (i > 0) {6387push_text(".");6388}6389print_identifier(p_type->type_chain[i]);6390}6391}6392}63936394void GDScriptParser::TreePrinter::print_type_test(TypeTestNode *p_test) {6395print_expression(p_test->operand);6396push_text(" IS ");6397print_type(p_test->test_type);6398}63996400void GDScriptParser::TreePrinter::print_unary_op(UnaryOpNode *p_unary_op) {6401// Surround in parenthesis for disambiguation.6402push_text("(");6403switch (p_unary_op->operation) {6404case UnaryOpNode::OP_POSITIVE:6405push_text("+");6406break;6407case UnaryOpNode::OP_NEGATIVE:6408push_text("-");6409break;6410case UnaryOpNode::OP_LOGIC_NOT:6411push_text("NOT");6412break;6413case UnaryOpNode::OP_COMPLEMENT:6414push_text("~");6415break;6416}6417print_expression(p_unary_op->operand);6418// Surround in parenthesis for disambiguation.6419push_text(")");6420}64216422void GDScriptParser::TreePrinter::print_variable(VariableNode *p_variable) {6423for (const AnnotationNode *E : p_variable->annotations) {6424print_annotation(E);6425}64266427if (p_variable->is_static) {6428push_text("Static ");6429}6430push_text("Variable ");6431print_identifier(p_variable->identifier);64326433push_text(" : ");6434if (p_variable->datatype_specifier != nullptr) {6435print_type(p_variable->datatype_specifier);6436} else if (p_variable->infer_datatype) {6437push_text("<inferred type>");6438} else {6439push_text("Variant");6440}64416442increase_indent();64436444push_line();6445push_text("= ");6446if (p_variable->initializer == nullptr) {6447push_text("<default value>");6448} else {6449print_expression(p_variable->initializer);6450}6451push_line();64526453if (p_variable->property != VariableNode::PROP_NONE) {6454if (p_variable->getter != nullptr) {6455push_text("Get");6456if (p_variable->property == VariableNode::PROP_INLINE) {6457push_line(":");6458increase_indent();6459print_suite(p_variable->getter->body);6460decrease_indent();6461} else {6462push_line(" =");6463increase_indent();6464print_identifier(p_variable->getter_pointer);6465push_line();6466decrease_indent();6467}6468}6469if (p_variable->setter != nullptr) {6470push_text("Set (");6471if (p_variable->property == VariableNode::PROP_INLINE) {6472if (p_variable->setter_parameter != nullptr) {6473print_identifier(p_variable->setter_parameter);6474} else {6475push_text("<missing>");6476}6477push_line("):");6478increase_indent();6479print_suite(p_variable->setter->body);6480decrease_indent();6481} else {6482push_line(" =");6483increase_indent();6484print_identifier(p_variable->setter_pointer);6485push_line();6486decrease_indent();6487}6488}6489}64906491decrease_indent();6492push_line();6493}64946495void GDScriptParser::TreePrinter::print_while(WhileNode *p_while) {6496push_text("While ");6497print_expression(p_while->condition);6498push_line(" :");64996500increase_indent();6501print_suite(p_while->loop);6502decrease_indent();6503}65046505void GDScriptParser::TreePrinter::print_tree(const GDScriptParser &p_parser) {6506ClassNode *class_tree = p_parser.get_tree();6507ERR_FAIL_NULL_MSG(class_tree, "Parse the code before printing the parse tree.");65086509if (p_parser.is_tool()) {6510push_line("@tool");6511}6512if (!class_tree->icon_path.is_empty()) {6513push_text(R"(@icon (")");6514push_text(class_tree->icon_path);6515push_line("\")");6516}6517print_class(class_tree);65186519print_line(String(printed));6520}65216522#endif // DEBUG_ENABLED652365246525