diff options
Diffstat (limited to 'modules/gdscript')
29 files changed, 390 insertions, 165 deletions
diff --git a/modules/gdscript/editor/gdscript_highlighter.cpp b/modules/gdscript/editor/gdscript_highlighter.cpp index 8b9bd2659d..ab441d194a 100644 --- a/modules/gdscript/editor/gdscript_highlighter.cpp +++ b/modules/gdscript/editor/gdscript_highlighter.cpp @@ -471,9 +471,9 @@ void GDScriptSyntaxHighlighter::_update_cache() { } /* Autoloads. */ - Map<StringName, ProjectSettings::AutoloadInfo> autoloads = ProjectSettings::get_singleton()->get_autoload_list(); - for (Map<StringName, ProjectSettings::AutoloadInfo>::Element *E = autoloads.front(); E; E = E->next()) { - const ProjectSettings::AutoloadInfo &info = E->value(); + OrderedHashMap<StringName, ProjectSettings::AutoloadInfo> autoloads = ProjectSettings::get_singleton()->get_autoload_list(); + for (OrderedHashMap<StringName, ProjectSettings::AutoloadInfo>::Element E = autoloads.front(); E; E = E.next()) { + const ProjectSettings::AutoloadInfo &info = E.value(); if (info.is_singleton) { keywords[info.name] = usertype_color; } diff --git a/modules/gdscript/gdscript.cpp b/modules/gdscript/gdscript.cpp index 4589cf1a36..bc8801b8b9 100644 --- a/modules/gdscript/gdscript.cpp +++ b/modules/gdscript/gdscript.cpp @@ -87,6 +87,19 @@ Object *GDScriptNativeClass::instantiate() { return ClassDB::instantiate(name); } +GDScriptFunction *GDScript::_super_constructor(GDScript *p_script) { + if (p_script->initializer) { + return p_script->initializer; + } else { + GDScript *base = p_script->_base; + if (base != nullptr) { + return _super_constructor(base); + } else { + return nullptr; + } + } +} + void GDScript::_super_implicit_constructor(GDScript *p_script, GDScriptInstance *p_instance, Callable::CallError &r_error) { GDScript *base = p_script->_base; if (base != nullptr) { @@ -135,6 +148,8 @@ GDScriptInstance *GDScript::_create_instance(const Variant **p_args, int p_argco if (p_argcount < 0) { return instance; } + + initializer = _super_constructor(this); if (initializer != nullptr) { initializer->call(instance, p_args, p_argcount, r_error); if (r_error.error != Callable::CallError::CALL_OK) { @@ -625,9 +640,9 @@ bool GDScript::_update_exports(bool *r_err, bool p_recursive_call, PlaceHolderSc String path = ""; if (String(c->extends_path) != "" && String(c->extends_path) != get_path()) { path = c->extends_path; - if (path.is_rel_path()) { + if (path.is_relative_path()) { String base = get_path(); - if (base == "" || base.is_rel_path()) { + if (base == "" || base.is_relative_path()) { ERR_PRINT(("Could not resolve relative path for parent class: " + path).utf8().get_data()); } else { path = base.get_base_dir().plus_file(path); @@ -900,7 +915,7 @@ void GDScript::get_members(Set<StringName> *p_members) { } } -const Vector<MultiplayerAPI::RPCConfig> GDScript::get_rpc_methods() const { +const Vector<Multiplayer::RPCConfig> GDScript::get_rpc_methods() const { return rpc_functions; } @@ -1164,8 +1179,8 @@ void GDScript::_init_rpc_methods_properties() { while (cscript) { // RPC Methods for (Map<StringName, GDScriptFunction *>::Element *E = cscript->member_functions.front(); E; E = E->next()) { - MultiplayerAPI::RPCConfig config = E->get()->get_rpc_config(); - if (config.rpc_mode != MultiplayerAPI::RPC_MODE_DISABLED) { + Multiplayer::RPCConfig config = E->get()->get_rpc_config(); + if (config.rpc_mode != Multiplayer::RPC_MODE_DISABLED) { config.name = E->get()->get_name(); if (rpc_functions.find(config) == -1) { rpc_functions.push_back(config); @@ -1185,7 +1200,7 @@ void GDScript::_init_rpc_methods_properties() { } // Sort so we are 100% that they are always the same. - rpc_functions.sort_custom<MultiplayerAPI::SortRPCConfig>(); + rpc_functions.sort_custom<Multiplayer::SortRPCConfig>(); } GDScript::~GDScript() { @@ -1541,7 +1556,7 @@ ScriptLanguage *GDScriptInstance::get_language() { return GDScriptLanguage::get_singleton(); } -const Vector<MultiplayerAPI::RPCConfig> GDScriptInstance::get_rpc_methods() const { +const Vector<Multiplayer::RPCConfig> GDScriptInstance::get_rpc_methods() const { return script->get_rpc_methods(); } @@ -2059,7 +2074,7 @@ String GDScriptLanguage::get_global_class_name(const String &p_path, String *r_b if (r_icon_path) { if (c->icon_path.is_empty() || c->icon_path.is_absolute_path()) { *r_icon_path = c->icon_path; - } else if (c->icon_path.is_rel_path()) { + } else if (c->icon_path.is_relative_path()) { *r_icon_path = p_path.get_base_dir().plus_file(c->icon_path).simplify_path(); } } @@ -2087,7 +2102,7 @@ String GDScriptLanguage::get_global_class_name(const String &p_path, String *r_b break; } String subpath = subclass->extends_path; - if (subpath.is_rel_path()) { + if (subpath.is_relative_path()) { subpath = path.get_base_dir().plus_file(subpath).simplify_path(); } diff --git a/modules/gdscript/gdscript.h b/modules/gdscript/gdscript.h index 24809ad5fd..791f8a1431 100644 --- a/modules/gdscript/gdscript.h +++ b/modules/gdscript/gdscript.h @@ -85,7 +85,7 @@ class GDScript : public Script { Map<StringName, MemberInfo> member_indices; //members are just indices to the instantiated script. Map<StringName, Ref<GDScript>> subclasses; Map<StringName, Vector<StringName>> _signals; - Vector<MultiplayerAPI::RPCConfig> rpc_functions; + Vector<Multiplayer::RPCConfig> rpc_functions; #ifdef TOOLS_ENABLED @@ -130,6 +130,7 @@ class GDScript : public Script { SelfList<GDScriptFunctionState>::List pending_func_states; + GDScriptFunction *_super_constructor(GDScript *p_script); void _super_implicit_constructor(GDScript *p_script, GDScriptInstance *p_instance, Callable::CallError &r_error); GDScriptInstance *_create_instance(const Variant **p_args, int p_argcount, Object *p_owner, bool p_is_ref_counted, Callable::CallError &r_error); @@ -245,7 +246,7 @@ public: virtual void get_constants(Map<StringName, Variant> *p_constants) override; virtual void get_members(Set<StringName> *p_members) override; - virtual const Vector<MultiplayerAPI::RPCConfig> get_rpc_methods() const override; + virtual const Vector<Multiplayer::RPCConfig> get_rpc_methods() const override; #ifdef TOOLS_ENABLED virtual bool is_placeholder_fallback_enabled() const override { return placeholder_fallback_enabled; } @@ -298,7 +299,7 @@ public: void reload_members(); - virtual const Vector<MultiplayerAPI::RPCConfig> get_rpc_methods() const; + virtual const Vector<Multiplayer::RPCConfig> get_rpc_methods() const; GDScriptInstance(); ~GDScriptInstance(); diff --git a/modules/gdscript/gdscript_analyzer.cpp b/modules/gdscript/gdscript_analyzer.cpp index 6b7403d854..fc0bef3ba2 100644 --- a/modules/gdscript/gdscript_analyzer.cpp +++ b/modules/gdscript/gdscript_analyzer.cpp @@ -132,6 +132,76 @@ static GDScriptParser::DataType make_builtin_meta_type(Variant::Type p_type) { return type; } +bool GDScriptAnalyzer::has_member_name_conflict_in_script_class(const StringName &p_member_name, const GDScriptParser::ClassNode *p_class) { + if (p_class->members_indices.has(p_member_name)) { + int index = p_class->members_indices[p_member_name]; + const GDScriptParser::ClassNode::Member *member = &p_class->members[index]; + + if (member->type == GDScriptParser::ClassNode::Member::VARIABLE || + member->type == GDScriptParser::ClassNode::Member::CONSTANT || + member->type == GDScriptParser::ClassNode::Member::ENUM || + member->type == GDScriptParser::ClassNode::Member::ENUM_VALUE || + member->type == GDScriptParser::ClassNode::Member::CLASS || + member->type == GDScriptParser::ClassNode::Member::SIGNAL) { + return true; + } + } + + return false; +} + +bool GDScriptAnalyzer::has_member_name_conflict_in_native_type(const StringName &p_member_name, const StringName &p_native_type_string) { + if (ClassDB::has_signal(p_native_type_string, p_member_name)) { + return true; + } + if (ClassDB::has_property(p_native_type_string, p_member_name)) { + return true; + } + if (ClassDB::has_integer_constant(p_native_type_string, p_member_name)) { + return true; + } + + return false; +} + +Error GDScriptAnalyzer::check_native_member_name_conflict(const StringName &p_member_name, const GDScriptParser::Node *p_member_node, const StringName &p_native_type_string) { + if (has_member_name_conflict_in_native_type(p_member_name, p_native_type_string)) { + push_error(vformat(R"(Member "%s" redefined (original in native class '%s'))", p_member_name, p_native_type_string), p_member_node); + return ERR_PARSE_ERROR; + } + + if (class_exists(p_member_name)) { + push_error(vformat(R"(The class "%s" shadows a native class.)", p_member_name), p_member_node); + return ERR_PARSE_ERROR; + } + + return OK; +} + +Error GDScriptAnalyzer::check_class_member_name_conflict(const GDScriptParser::ClassNode *p_class_node, const StringName &p_member_name, const GDScriptParser::Node *p_member_node) { + const GDScriptParser::DataType *current_data_type = &p_class_node->base_type; + while (current_data_type && current_data_type->kind == GDScriptParser::DataType::Kind::CLASS) { + GDScriptParser::ClassNode *current_class_node = current_data_type->class_type; + if (has_member_name_conflict_in_script_class(p_member_name, current_class_node)) { + push_error(vformat(R"(The member "%s" already exists in a parent class.)", p_member_name), + p_member_node); + return ERR_PARSE_ERROR; + } + current_data_type = ¤t_class_node->base_type; + } + + if (current_data_type && current_data_type->kind == GDScriptParser::DataType::Kind::NATIVE) { + if (current_data_type->native_type != StringName("")) { + return check_native_member_name_conflict( + p_member_name, + p_member_node, + current_data_type->native_type); + } + } + + return OK; +} + Error GDScriptAnalyzer::resolve_inheritance(GDScriptParser::ClassNode *p_class, bool p_recursive) { if (p_class->base_type.is_set()) { // Already resolved @@ -525,6 +595,8 @@ void GDScriptAnalyzer::resolve_class_interface(GDScriptParser::ClassNode *p_clas switch (member.type) { case GDScriptParser::ClassNode::Member::VARIABLE: { + check_class_member_name_conflict(p_class, member.variable->identifier->name, member.variable); + GDScriptParser::DataType datatype; datatype.kind = GDScriptParser::DataType::VARIANT; datatype.type_source = GDScriptParser::DataType::UNDETECTED; @@ -598,6 +670,8 @@ void GDScriptAnalyzer::resolve_class_interface(GDScriptParser::ClassNode *p_clas } } break; case GDScriptParser::ClassNode::Member::CONSTANT: { + check_class_member_name_conflict(p_class, member.constant->identifier->name, member.constant); + reduce_expression(member.constant->initializer); GDScriptParser::DataType specified_type; @@ -647,6 +721,8 @@ void GDScriptAnalyzer::resolve_class_interface(GDScriptParser::ClassNode *p_clas } } break; case GDScriptParser::ClassNode::Member::SIGNAL: { + check_class_member_name_conflict(p_class, member.signal->identifier->name, member.signal); + for (int j = 0; j < member.signal->parameters.size(); j++) { GDScriptParser::DataType signal_type = resolve_datatype(member.signal->parameters[j]->datatype_specifier); signal_type.is_meta_type = false; @@ -666,6 +742,8 @@ void GDScriptAnalyzer::resolve_class_interface(GDScriptParser::ClassNode *p_clas } } break; case GDScriptParser::ClassNode::Member::ENUM: { + check_class_member_name_conflict(p_class, member.m_enum->identifier->name, member.m_enum); + GDScriptParser::DataType enum_type; enum_type.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT; enum_type.kind = GDScriptParser::DataType::ENUM; @@ -717,6 +795,8 @@ void GDScriptAnalyzer::resolve_class_interface(GDScriptParser::ClassNode *p_clas break; case GDScriptParser::ClassNode::Member::ENUM_VALUE: { if (member.enum_value.custom_value) { + check_class_member_name_conflict(p_class, member.enum_value.identifier->name, member.enum_value.custom_value); + current_enum = member.enum_value.parent_enum; reduce_expression(member.enum_value.custom_value); current_enum = nullptr; @@ -730,6 +810,8 @@ void GDScriptAnalyzer::resolve_class_interface(GDScriptParser::ClassNode *p_clas member.enum_value.resolved = true; } } else { + check_class_member_name_conflict(p_class, member.enum_value.identifier->name, member.enum_value.parent_enum); + if (member.enum_value.index > 0) { member.enum_value.value = member.enum_value.parent_enum->values[member.enum_value.index - 1].value + 1; } else { @@ -742,7 +824,8 @@ void GDScriptAnalyzer::resolve_class_interface(GDScriptParser::ClassNode *p_clas p_class->members.write[i].enum_value = member.enum_value; } break; case GDScriptParser::ClassNode::Member::CLASS: - break; // Done later. + check_class_member_name_conflict(p_class, member.m_class->identifier->name, member.m_class); + break; case GDScriptParser::ClassNode::Member::UNDEFINED: ERR_PRINT("Trying to resolve undefined member."); break; @@ -2411,6 +2494,7 @@ void GDScriptAnalyzer::reduce_identifier_from_base(GDScriptParser::IdentifierNod case GDScriptParser::ClassNode::Member::VARIABLE: p_identifier->source = GDScriptParser::IdentifierNode::MEMBER_VARIABLE; p_identifier->variable_source = member.variable; + member.variable->usages += 1; break; case GDScriptParser::ClassNode::Member::FUNCTION: resolve_function_signature(member.function); @@ -2727,7 +2811,7 @@ void GDScriptAnalyzer::reduce_preload(GDScriptParser::PreloadNode *p_preload) { } else { p_preload->resolved_path = p_preload->path->reduced_value; // TODO: Save this as script dependency. - if (p_preload->resolved_path.is_rel_path()) { + if (p_preload->resolved_path.is_relative_path()) { p_preload->resolved_path = parser->script_path.get_base_dir().plus_file(p_preload->resolved_path); } p_preload->resolved_path = p_preload->resolved_path.simplify_path(); @@ -2753,6 +2837,9 @@ void GDScriptAnalyzer::reduce_self(GDScriptParser::SelfNode *p_self) { } void GDScriptAnalyzer::reduce_subscript(GDScriptParser::SubscriptNode *p_subscript) { + if (p_subscript->base == nullptr) { + return; + } if (p_subscript->base->type == GDScriptParser::Node::IDENTIFIER) { reduce_identifier(static_cast<GDScriptParser::IdentifierNode *>(p_subscript->base), true); } else { diff --git a/modules/gdscript/gdscript_analyzer.h b/modules/gdscript/gdscript_analyzer.h index 32bf049fa1..2e17e15452 100644 --- a/modules/gdscript/gdscript_analyzer.h +++ b/modules/gdscript/gdscript_analyzer.h @@ -44,6 +44,12 @@ class GDScriptAnalyzer { const GDScriptParser::EnumNode *current_enum = nullptr; List<const GDScriptParser::LambdaNode *> lambda_stack; + // Tests for detecting invalid overloading of script members + static _FORCE_INLINE_ bool has_member_name_conflict_in_script_class(const StringName &p_name, const GDScriptParser::ClassNode *p_current_class_node); + static _FORCE_INLINE_ bool has_member_name_conflict_in_native_type(const StringName &p_name, const StringName &p_native_type_string); + Error check_native_member_name_conflict(const StringName &p_member_name, const GDScriptParser::Node *p_member_node, const StringName &p_native_type_string); + Error check_class_member_name_conflict(const GDScriptParser::ClassNode *p_class_node, const StringName &p_member_name, const GDScriptParser::Node *p_member_node); + Error resolve_inheritance(GDScriptParser::ClassNode *p_class, bool p_recursive = true); GDScriptParser::DataType resolve_datatype(GDScriptParser::TypeNode *p_type); diff --git a/modules/gdscript/gdscript_byte_codegen.cpp b/modules/gdscript/gdscript_byte_codegen.cpp index 5958326315..bed67b55f0 100644 --- a/modules/gdscript/gdscript_byte_codegen.cpp +++ b/modules/gdscript/gdscript_byte_codegen.cpp @@ -155,7 +155,7 @@ void GDScriptByteCodeGenerator::end_parameters() { function->default_arguments.reverse(); } -void GDScriptByteCodeGenerator::write_start(GDScript *p_script, const StringName &p_function_name, bool p_static, MultiplayerAPI::RPCConfig p_rpc_config, const GDScriptDataType &p_return_type) { +void GDScriptByteCodeGenerator::write_start(GDScript *p_script, const StringName &p_function_name, bool p_static, Multiplayer::RPCConfig p_rpc_config, const GDScriptDataType &p_return_type) { function = memnew(GDScriptFunction); debug_stack = EngineDebugger::is_active(); diff --git a/modules/gdscript/gdscript_byte_codegen.h b/modules/gdscript/gdscript_byte_codegen.h index 3d6fb291ad..ce1a043b28 100644 --- a/modules/gdscript/gdscript_byte_codegen.h +++ b/modules/gdscript/gdscript_byte_codegen.h @@ -419,7 +419,7 @@ public: virtual void start_block() override; virtual void end_block() override; - virtual void write_start(GDScript *p_script, const StringName &p_function_name, bool p_static, MultiplayerAPI::RPCConfig p_rpc_config, const GDScriptDataType &p_return_type) override; + virtual void write_start(GDScript *p_script, const StringName &p_function_name, bool p_static, Multiplayer::RPCConfig p_rpc_config, const GDScriptDataType &p_return_type) override; virtual GDScriptFunction *write_end() override; #ifdef DEBUG_ENABLED diff --git a/modules/gdscript/gdscript_codegen.h b/modules/gdscript/gdscript_codegen.h index ecc86c37f3..7713d13bc8 100644 --- a/modules/gdscript/gdscript_codegen.h +++ b/modules/gdscript/gdscript_codegen.h @@ -31,7 +31,7 @@ #ifndef GDSCRIPT_CODEGEN #define GDSCRIPT_CODEGEN -#include "core/io/multiplayer_api.h" +#include "core/multiplayer/multiplayer.h" #include "core/string/string_name.h" #include "core/variant/variant.h" #include "gdscript_function.h" @@ -80,7 +80,7 @@ public: virtual void start_block() = 0; virtual void end_block() = 0; - virtual void write_start(GDScript *p_script, const StringName &p_function_name, bool p_static, MultiplayerAPI::RPCConfig p_rpc_config, const GDScriptDataType &p_return_type) = 0; + virtual void write_start(GDScript *p_script, const StringName &p_function_name, bool p_static, Multiplayer::RPCConfig p_rpc_config, const GDScriptDataType &p_return_type) = 0; virtual GDScriptFunction *write_end() = 0; #ifdef DEBUG_ENABLED diff --git a/modules/gdscript/gdscript_compiler.cpp b/modules/gdscript/gdscript_compiler.cpp index ddf4f281b4..736f6eae79 100644 --- a/modules/gdscript/gdscript_compiler.cpp +++ b/modules/gdscript/gdscript_compiler.cpp @@ -1857,7 +1857,7 @@ GDScriptFunction *GDScriptCompiler::_parse_function(Error &r_error, GDScript *p_ StringName func_name; bool is_static = false; - MultiplayerAPI::RPCConfig rpc_config; + Multiplayer::RPCConfig rpc_config; GDScriptDataType return_type; return_type.has_type = true; return_type.kind = GDScriptDataType::BUILTIN; @@ -2086,7 +2086,7 @@ Error GDScriptCompiler::_parse_setter_getter(GDScript *p_script, const GDScriptP return_type = _gdtype_from_datatype(p_variable->get_datatype(), p_script); } - codegen.generator->write_start(p_script, func_name, false, MultiplayerAPI::RPCConfig(), return_type); + codegen.generator->write_start(p_script, func_name, false, Multiplayer::RPCConfig(), return_type); if (p_is_setter) { uint32_t par_addr = codegen.generator->add_parameter(p_variable->setter_parameter->name, false, _gdtype_from_datatype(p_variable->get_datatype())); @@ -2186,6 +2186,13 @@ Error GDScriptCompiler::_parse_class_level(GDScript *p_script, const GDScriptPar p_script->tool = parser->is_tool(); p_script->name = p_class->identifier ? p_class->identifier->name : ""; + if (p_script->name != "") { + if (ClassDB::class_exists(p_script->name) && ClassDB::is_class_exposed(p_script->name)) { + _set_error("The class '" + p_script->name + "' shadows a native class", p_class); + return ERR_ALREADY_EXISTS; + } + } + Ref<GDScriptNativeClass> native; GDScriptDataType base_type = _gdtype_from_datatype(p_class->base_type); @@ -2337,28 +2344,6 @@ Error GDScriptCompiler::_parse_class_level(GDScript *p_script, const GDScriptPar const GDScriptParser::SignalNode *signal = member.signal; StringName name = signal->identifier->name; - GDScript *c = p_script; - - while (c) { - if (c->_signals.has(name)) { - _set_error("Signal '" + name + "' redefined (in current or parent class)", p_class); - return ERR_ALREADY_EXISTS; - } - - if (c->base.is_valid()) { - c = c->base.ptr(); - } else { - c = nullptr; - } - } - - if (native.is_valid()) { - if (ClassDB::has_signal(native->get_name(), name)) { - _set_error("Signal '" + name + "' redefined (original in native class '" + String(native->get_name()) + "')", p_class); - return ERR_ALREADY_EXISTS; - } - } - Vector<StringName> parameters_names; parameters_names.resize(signal->parameters.size()); for (int j = 0; j < signal->parameters.size(); j++) { diff --git a/modules/gdscript/gdscript_editor.cpp b/modules/gdscript/gdscript_editor.cpp index 372a726d71..f809a4dab8 100644 --- a/modules/gdscript/gdscript_editor.cpp +++ b/modules/gdscript/gdscript_editor.cpp @@ -731,9 +731,9 @@ static void _list_available_types(bool p_inherit_only, GDScriptParser::Completio } // Autoload singletons - Map<StringName, ProjectSettings::AutoloadInfo> autoloads = ProjectSettings::get_singleton()->get_autoload_list(); - for (const Map<StringName, ProjectSettings::AutoloadInfo>::Element *E = autoloads.front(); E != nullptr; E = E->next()) { - const ProjectSettings::AutoloadInfo &info = E->get(); + OrderedHashMap<StringName, ProjectSettings::AutoloadInfo> autoloads = ProjectSettings::get_singleton()->get_autoload_list(); + for (OrderedHashMap<StringName, ProjectSettings::AutoloadInfo>::Element E = autoloads.front(); E; E = E.next()) { + const ProjectSettings::AutoloadInfo &info = E.get(); if (!info.is_singleton || info.path.get_extension().to_lower() != "gd") { continue; } @@ -1086,12 +1086,12 @@ static void _find_identifiers(GDScriptParser::CompletionContext &p_context, bool kwa++; } - Map<StringName, ProjectSettings::AutoloadInfo> autoloads = ProjectSettings::get_singleton()->get_autoload_list(); - for (const Map<StringName, ProjectSettings::AutoloadInfo>::Element *E = autoloads.front(); E != nullptr; E = E->next()) { - if (!E->value().is_singleton) { + OrderedHashMap<StringName, ProjectSettings::AutoloadInfo> autoloads = ProjectSettings::get_singleton()->get_autoload_list(); + for (OrderedHashMap<StringName, ProjectSettings::AutoloadInfo>::Element E = autoloads.front(); E; E = E.next()) { + if (!E.value().is_singleton) { continue; } - ScriptCodeCompletionOption option(E->key(), ScriptCodeCompletionOption::KIND_CONSTANT); + ScriptCodeCompletionOption option(E.key(), ScriptCodeCompletionOption::KIND_CONSTANT); r_result.insert(option.display, option); } @@ -1359,12 +1359,12 @@ static bool _guess_expression_type(GDScriptParser::CompletionContext &p_context, r_type = _type_from_variant(GDScriptLanguage::get_singleton()->get_named_globals_map()[which]); found = true; } else { - Map<StringName, ProjectSettings::AutoloadInfo> autoloads = ProjectSettings::get_singleton()->get_autoload_list(); + OrderedHashMap<StringName, ProjectSettings::AutoloadInfo> autoloads = ProjectSettings::get_singleton()->get_autoload_list(); - for (Map<StringName, ProjectSettings::AutoloadInfo>::Element *E = autoloads.front(); E; E = E->next()) { - String name = E->key(); + for (OrderedHashMap<StringName, ProjectSettings::AutoloadInfo>::Element E = autoloads.front(); E; E = E.next()) { + String name = E.key(); if (name == which) { - String script = E->value().path; + String script = E.value().path; if (!script.begins_with("res://")) { script = "res://" + script; @@ -2660,10 +2660,10 @@ static void _find_call_arguments(GDScriptParser::CompletionContext &p_context, c } // Get autoloads. - Map<StringName, ProjectSettings::AutoloadInfo> autoloads = ProjectSettings::get_singleton()->get_autoload_list(); + OrderedHashMap<StringName, ProjectSettings::AutoloadInfo> autoloads = ProjectSettings::get_singleton()->get_autoload_list(); - for (Map<StringName, ProjectSettings::AutoloadInfo>::Element *E = autoloads.front(); E; E = E->next()) { - String path = "/root/" + E->key(); + for (OrderedHashMap<StringName, ProjectSettings::AutoloadInfo>::Element E = autoloads.front(); E; E = E.next()) { + String path = "/root/" + E.key(); ScriptCodeCompletionOption option(path.quote(quote_style), ScriptCodeCompletionOption::KIND_NODE_PATH); options.insert(option.display, option); } @@ -3078,6 +3078,15 @@ static Error _lookup_symbol_from_base(const GDScriptParser::DataType &p_base, co r_result.class_member = p_symbol; return OK; } + } else { + List<StringName> utility_functions; + Variant::get_utility_function_list(&utility_functions); + if (utility_functions.find(p_symbol) != nullptr) { + r_result.type = ScriptLanguage::LookupResult::RESULT_CLASS_TBD_GLOBALSCOPE; + r_result.class_name = "@GlobalScope"; + r_result.class_member = p_symbol; + return OK; + } } } } break; diff --git a/modules/gdscript/gdscript_function.h b/modules/gdscript/gdscript_function.h index 87d8c03494..b21cb47910 100644 --- a/modules/gdscript/gdscript_function.h +++ b/modules/gdscript/gdscript_function.h @@ -468,7 +468,7 @@ private: int _initial_line = 0; bool _static = false; - MultiplayerAPI::RPCConfig rpc_config; + Multiplayer::RPCConfig rpc_config; GDScript *_script = nullptr; @@ -588,7 +588,7 @@ public: void disassemble(const Vector<String> &p_code_lines) const; #endif - _FORCE_INLINE_ MultiplayerAPI::RPCConfig get_rpc_config() const { return rpc_config; } + _FORCE_INLINE_ Multiplayer::RPCConfig get_rpc_config() const { return rpc_config; } GDScriptFunction(); ~GDScriptFunction(); }; diff --git a/modules/gdscript/gdscript_parser.cpp b/modules/gdscript/gdscript_parser.cpp index af872e49e2..6ae3e36017 100644 --- a/modules/gdscript/gdscript_parser.cpp +++ b/modules/gdscript/gdscript_parser.cpp @@ -133,7 +133,7 @@ GDScriptParser::GDScriptParser() { register_annotation(MethodInfo("@export_flags_3d_physics"), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_LAYERS_3D_PHYSICS, Variant::INT>); register_annotation(MethodInfo("@export_flags_3d_navigation"), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_LAYERS_3D_NAVIGATION, Variant::INT>); // Networking. - register_annotation(MethodInfo("@rpc", { Variant::STRING, "mode" }, { Variant::STRING, "sync" }, { Variant::STRING, "transfer_mode" }, { Variant::INT, "transfer_channel" }), AnnotationInfo::FUNCTION, &GDScriptParser::network_annotations<MultiplayerAPI::RPC_MODE_PUPPET>, 4, true); + register_annotation(MethodInfo("@rpc", { Variant::STRING, "mode" }, { Variant::STRING, "sync" }, { Variant::STRING, "transfer_mode" }, { Variant::INT, "transfer_channel" }), AnnotationInfo::FUNCTION, &GDScriptParser::network_annotations<Multiplayer::RPC_MODE_AUTHORITY>, 4, true); // TODO: Warning annotations. } @@ -1682,6 +1682,7 @@ GDScriptParser::MatchNode *GDScriptParser::parse_match() { while (!check(GDScriptTokenizer::Token::DEDENT) && !is_at_end()) { MatchBranchNode *branch = parse_match_branch(); if (branch == nullptr) { + advance(); continue; } @@ -1745,7 +1746,9 @@ GDScriptParser::MatchBranchNode *GDScriptParser::parse_match_branch() { push_error(R"(No pattern found for "match" branch.)"); } - consume(GDScriptTokenizer::Token::COLON, R"(Expected ":" after "match" patterns.)"); + if (!consume(GDScriptTokenizer::Token::COLON, R"(Expected ":" after "match" patterns.)")) { + return nullptr; + } // Save continue state. bool could_continue = can_continue; @@ -1778,15 +1781,6 @@ GDScriptParser::PatternNode *GDScriptParser::parse_match_pattern(PatternNode *p_ PatternNode *pattern = alloc_node<PatternNode>(); switch (current.type) { - case GDScriptTokenizer::Token::LITERAL: - advance(); - pattern->pattern_type = PatternNode::PT_LITERAL; - pattern->literal = parse_literal(); - if (pattern->literal == nullptr) { - // Error happened. - return nullptr; - } - break; case GDScriptTokenizer::Token::VAR: { // Bind. advance(); @@ -1849,44 +1843,44 @@ GDScriptParser::PatternNode *GDScriptParser::parse_match_pattern(PatternNode *p_ // Dictionary. advance(); pattern->pattern_type = PatternNode::PT_DICTIONARY; - - if (!check(GDScriptTokenizer::Token::BRACE_CLOSE) && !is_at_end()) { - do { - if (match(GDScriptTokenizer::Token::PERIOD_PERIOD)) { - // Rest. + do { + if (check(GDScriptTokenizer::Token::BRACE_CLOSE) || is_at_end()) { + break; + } + if (match(GDScriptTokenizer::Token::PERIOD_PERIOD)) { + // Rest. + if (pattern->rest_used) { + push_error(R"(The ".." pattern must be the last element in the pattern dictionary.)"); + } else { + PatternNode *sub_pattern = alloc_node<PatternNode>(); + sub_pattern->pattern_type = PatternNode::PT_REST; + pattern->dictionary.push_back({ nullptr, sub_pattern }); + pattern->rest_used = true; + } + } else { + ExpressionNode *key = parse_expression(false); + if (key == nullptr) { + push_error(R"(Expected expression as key for dictionary pattern.)"); + } + if (match(GDScriptTokenizer::Token::COLON)) { + // Value pattern. + PatternNode *sub_pattern = parse_match_pattern(p_root_pattern != nullptr ? p_root_pattern : pattern); + if (sub_pattern == nullptr) { + continue; + } if (pattern->rest_used) { push_error(R"(The ".." pattern must be the last element in the pattern dictionary.)"); + } else if (sub_pattern->pattern_type == PatternNode::PT_REST) { + push_error(R"(The ".." pattern cannot be used as a value.)"); } else { - PatternNode *sub_pattern = alloc_node<PatternNode>(); - sub_pattern->pattern_type = PatternNode::PT_REST; - pattern->dictionary.push_back({ nullptr, sub_pattern }); - pattern->rest_used = true; + pattern->dictionary.push_back({ key, sub_pattern }); } } else { - ExpressionNode *key = parse_expression(false); - if (key == nullptr) { - push_error(R"(Expected expression as key for dictionary pattern.)"); - } - if (match(GDScriptTokenizer::Token::COLON)) { - // Value pattern. - PatternNode *sub_pattern = parse_match_pattern(p_root_pattern != nullptr ? p_root_pattern : pattern); - if (sub_pattern == nullptr) { - continue; - } - if (pattern->rest_used) { - push_error(R"(The ".." pattern must be the last element in the pattern dictionary.)"); - } else if (sub_pattern->pattern_type == PatternNode::PT_REST) { - push_error(R"(The ".." pattern cannot be used as a value.)"); - } else { - pattern->dictionary.push_back({ key, sub_pattern }); - } - } else { - // Key match only. - pattern->dictionary.push_back({ key, nullptr }); - } + // Key match only. + pattern->dictionary.push_back({ key, nullptr }); } - } while (match(GDScriptTokenizer::Token::COMMA)); - } + } + } while (match(GDScriptTokenizer::Token::COMMA)); consume(GDScriptTokenizer::Token::BRACE_CLOSE, R"(Expected "}" to close the dictionary pattern.)"); break; } @@ -1895,8 +1889,13 @@ GDScriptParser::PatternNode *GDScriptParser::parse_match_pattern(PatternNode *p_ ExpressionNode *expression = parse_expression(false); if (expression == nullptr) { push_error(R"(Expected expression for match pattern.)"); + return nullptr; } else { - pattern->pattern_type = PatternNode::PT_EXPRESSION; + if (expression->type == GDScriptParser::Node::LITERAL) { + pattern->pattern_type = PatternNode::PT_LITERAL; + } else { + pattern->pattern_type = PatternNode::PT_EXPRESSION; + } pattern->expression = expression; } break; @@ -2365,10 +2364,17 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_assignment(ExpressionNode } assignment->assignee = p_previous_operand; assignment->assigned_value = parse_expression(false); + if (assignment->assigned_value == nullptr) { + push_error(R"(Expected an expression after "=".)"); + } #ifdef DEBUG_ENABLED - if (has_operator && source_variable != nullptr && source_variable->assignments == 0) { - push_warning(assignment, GDScriptWarning::UNASSIGNED_VARIABLE_OP_ASSIGN, source_variable->identifier->name); + if (source_variable != nullptr) { + if (has_operator && source_variable->assignments == 0) { + push_warning(assignment, GDScriptWarning::UNASSIGNED_VARIABLE_OP_ASSIGN, source_variable->identifier->name); + } + + source_variable->assignments += 1; } #endif @@ -2377,9 +2383,15 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_assignment(ExpressionNode GDScriptParser::ExpressionNode *GDScriptParser::parse_await(ExpressionNode *p_previous_operand, bool p_can_assign) { AwaitNode *await = alloc_node<AwaitNode>(); - await->to_await = parse_precedence(PREC_AWAIT, false); + ExpressionNode *element = parse_precedence(PREC_AWAIT, false); + if (element == nullptr) { + push_error(R"(Expected signal or coroutine after "await".)"); + } + await->to_await = element; - current_function->is_coroutine = true; + if (current_function) { // Might be null in a getter or setter. + current_function->is_coroutine = true; + } return await; } @@ -2456,8 +2468,10 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_dictionary(ExpressionNode push_error(R"(Expected "=" after dictionary key.)"); } } - key->is_constant = true; - key->reduced_value = static_cast<IdentifierNode *>(key)->name; + if (key != nullptr) { + key->is_constant = true; + key->reduced_value = static_cast<IdentifierNode *>(key)->name; + } break; case DictionaryNode::PYTHON_DICT: if (!match(GDScriptTokenizer::Token::COLON)) { @@ -3386,55 +3400,47 @@ bool GDScriptParser::warning_annotations(const AnnotationNode *p_annotation, Nod ERR_FAIL_V_MSG(false, "Not implemented."); } -template <MultiplayerAPI::RPCMode t_mode> +template <Multiplayer::RPCMode t_mode> bool GDScriptParser::network_annotations(const AnnotationNode *p_annotation, Node *p_node) { ERR_FAIL_COND_V_MSG(p_node->type != Node::VARIABLE && p_node->type != Node::FUNCTION, false, vformat(R"("%s" annotation can only be applied to variables and functions.)", p_annotation->name)); - MultiplayerAPI::RPCConfig rpc_config; + Multiplayer::RPCConfig rpc_config; rpc_config.rpc_mode = t_mode; - for (int i = 0; i < p_annotation->resolved_arguments.size(); i++) { - if (i == 0) { + if (p_annotation->resolved_arguments.size()) { + int last = p_annotation->resolved_arguments.size() - 1; + if (p_annotation->resolved_arguments[last].get_type() == Variant::INT) { + rpc_config.channel = p_annotation->resolved_arguments[last].operator int(); + last -= 1; + } + if (last > 3) { + push_error(R"(Invalid RPC arguments. At most 4 arguments are allowed, where only the last argument can be an integer to specify the channel.')", p_annotation); + return false; + } + for (int i = last; i >= 0; i--) { String mode = p_annotation->resolved_arguments[i].operator String(); if (mode == "any") { - rpc_config.rpc_mode = MultiplayerAPI::RPC_MODE_REMOTE; - } else if (mode == "master") { - rpc_config.rpc_mode = MultiplayerAPI::RPC_MODE_MASTER; - } else if (mode == "puppet") { - rpc_config.rpc_mode = MultiplayerAPI::RPC_MODE_PUPPET; - } else { - push_error(R"(Invalid RPC mode. Must be one of: 'any', 'master', or 'puppet')", p_annotation); - return false; - } - } else if (i == 1) { - String sync = p_annotation->resolved_arguments[i].operator String(); - if (sync == "sync") { + rpc_config.rpc_mode = Multiplayer::RPC_MODE_ANY; + } else if (mode == "auth") { + rpc_config.rpc_mode = Multiplayer::RPC_MODE_AUTHORITY; + } else if (mode == "sync") { rpc_config.sync = true; - } else if (sync == "nosync") { + } else if (mode == "nosync") { rpc_config.sync = false; - } else { - push_error(R"(Invalid RPC sync mode. Must be one of: 'sync' or 'nosync')", p_annotation); - return false; - } - } else if (i == 2) { - String mode = p_annotation->resolved_arguments[i].operator String(); - if (mode == "reliable") { - rpc_config.transfer_mode = MultiplayerPeer::TRANSFER_MODE_RELIABLE; + } else if (mode == "reliable") { + rpc_config.transfer_mode = Multiplayer::TRANSFER_MODE_RELIABLE; } else if (mode == "unreliable") { - rpc_config.transfer_mode = MultiplayerPeer::TRANSFER_MODE_UNRELIABLE; + rpc_config.transfer_mode = Multiplayer::TRANSFER_MODE_UNRELIABLE; } else if (mode == "ordered") { - rpc_config.transfer_mode = MultiplayerPeer::TRANSFER_MODE_UNRELIABLE_ORDERED; + rpc_config.transfer_mode = Multiplayer::TRANSFER_MODE_ORDERED; } else { - push_error(R"(Invalid RPC transfer mode. Must be one of: 'reliable', 'unreliable', 'ordered')", p_annotation); - return false; + push_error(R"(Invalid RPC argument. Must be one of: 'sync'/'nosync' (local calls), 'any'/'auth' (permission), 'reliable'/'unreliable'/'ordered' (transfer mode).)", p_annotation); } - } else if (i == 3) { - rpc_config.channel = p_annotation->resolved_arguments[i].operator int(); } } switch (p_node->type) { case Node::FUNCTION: { FunctionNode *function = static_cast<FunctionNode *>(p_node); - if (function->rpc_config.rpc_mode != MultiplayerAPI::RPC_MODE_DISABLED) { + if (function->rpc_config.rpc_mode != Multiplayer::RPC_MODE_DISABLED) { push_error(R"(RPC annotations can only be used once per function.)", p_annotation); return false; } diff --git a/modules/gdscript/gdscript_parser.h b/modules/gdscript/gdscript_parser.h index 0bce8d7ddd..4902f0d4a6 100644 --- a/modules/gdscript/gdscript_parser.h +++ b/modules/gdscript/gdscript_parser.h @@ -31,8 +31,8 @@ #ifndef GDSCRIPT_PARSER_H #define GDSCRIPT_PARSER_H -#include "core/io/multiplayer_api.h" #include "core/io/resource.h" +#include "core/multiplayer/multiplayer.h" #include "core/object/ref_counted.h" #include "core/object/script_language.h" #include "core/string/string_name.h" @@ -729,7 +729,7 @@ public: SuiteNode *body = nullptr; bool is_static = false; bool is_coroutine = false; - MultiplayerAPI::RPCConfig rpc_config; + Multiplayer::RPCConfig rpc_config; MethodInfo info; LambdaNode *source_lambda = nullptr; #ifdef TOOLS_ENABLED @@ -1340,7 +1340,7 @@ private: template <PropertyHint t_hint, Variant::Type t_type> bool export_annotations(const AnnotationNode *p_annotation, Node *p_target); bool warning_annotations(const AnnotationNode *p_annotation, Node *p_target); - template <MultiplayerAPI::RPCMode t_mode> + template <Multiplayer::RPCMode t_mode> bool network_annotations(const AnnotationNode *p_annotation, Node *p_target); // Statements. Node *parse_statement(); diff --git a/modules/gdscript/tests/README.md b/modules/gdscript/tests/README.md new file mode 100644 index 0000000000..6e54085962 --- /dev/null +++ b/modules/gdscript/tests/README.md @@ -0,0 +1,8 @@ +# GDScript integration tests + +The `scripts/` folder contains integration tests in the form of GDScript files +and output files. + +See the +[Integration tests for GDScript documentation](https://docs.godotengine.org/en/latest/development/cpp/unit_testing.html#integration-tests-for-gdscript) +for information about creating and running GDScript integration tests. diff --git a/modules/gdscript/tests/gdscript_test_runner.cpp b/modules/gdscript/tests/gdscript_test_runner.cpp index 03a48bf071..6225e5d1eb 100644 --- a/modules/gdscript/tests/gdscript_test_runner.cpp +++ b/modules/gdscript/tests/gdscript_test_runner.cpp @@ -48,11 +48,11 @@ namespace GDScriptTests { void init_autoloads() { - Map<StringName, ProjectSettings::AutoloadInfo> autoloads = ProjectSettings::get_singleton()->get_autoload_list(); + OrderedHashMap<StringName, ProjectSettings::AutoloadInfo> autoloads = ProjectSettings::get_singleton()->get_autoload_list(); // First pass, add the constants so they exist before any script is loaded. - for (Map<StringName, ProjectSettings::AutoloadInfo>::Element *E = autoloads.front(); E; E = E->next()) { - const ProjectSettings::AutoloadInfo &info = E->get(); + for (OrderedHashMap<StringName, ProjectSettings::AutoloadInfo>::Element E = autoloads.front(); E; E = E.next()) { + const ProjectSettings::AutoloadInfo &info = E.get(); if (info.is_singleton) { for (int i = 0; i < ScriptServer::get_language_count(); i++) { @@ -62,8 +62,8 @@ void init_autoloads() { } // Second pass, load into global constants. - for (Map<StringName, ProjectSettings::AutoloadInfo>::Element *E = autoloads.front(); E; E = E->next()) { - const ProjectSettings::AutoloadInfo &info = E->get(); + for (OrderedHashMap<StringName, ProjectSettings::AutoloadInfo>::Element E = autoloads.front(); E; E = E.next()) { + const ProjectSettings::AutoloadInfo &info = E.get(); if (!info.is_singleton) { // Skip non-singletons since we don't have a scene tree here anyway. diff --git a/modules/gdscript/tests/scripts/analyzer/features/call_self_get_name.out b/modules/gdscript/tests/scripts/analyzer/features/call_self_get_name.out index dc4348d9c3..2c8cc9c03f 100644 --- a/modules/gdscript/tests/scripts/analyzer/features/call_self_get_name.out +++ b/modules/gdscript/tests/scripts/analyzer/features/call_self_get_name.out @@ -1,3 +1,3 @@ GDTEST_OK Name is equal -True +true diff --git a/modules/gdscript/tests/scripts/analyzer/features/call_static_builtin_function.out b/modules/gdscript/tests/scripts/analyzer/features/call_static_builtin_function.out index 75b002aa62..ad6beeb646 100644 --- a/modules/gdscript/tests/scripts/analyzer/features/call_static_builtin_function.out +++ b/modules/gdscript/tests/scripts/analyzer/features/call_static_builtin_function.out @@ -1,3 +1,3 @@ GDTEST_OK -True +true OK diff --git a/modules/gdscript/tests/scripts/parser/errors/assignment_empty_assignee.gd b/modules/gdscript/tests/scripts/parser/errors/assignment_empty_assignee.gd new file mode 100644 index 0000000000..17c65ad60a --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/errors/assignment_empty_assignee.gd @@ -0,0 +1,3 @@ +func test(): + var a = 0 + a = diff --git a/modules/gdscript/tests/scripts/parser/errors/assignment_empty_assignee.out b/modules/gdscript/tests/scripts/parser/errors/assignment_empty_assignee.out new file mode 100644 index 0000000000..1369a7a0dc --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/errors/assignment_empty_assignee.out @@ -0,0 +1,2 @@ +GDTEST_PARSER_ERROR +Expected an expression after "=". diff --git a/modules/gdscript/tests/scripts/parser/errors/double_dictionary_comma.gd b/modules/gdscript/tests/scripts/parser/errors/double_dictionary_comma.gd new file mode 100644 index 0000000000..92dfb2366d --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/errors/double_dictionary_comma.gd @@ -0,0 +1,2 @@ +func test(): + var dictionary = { hello = "world",, } diff --git a/modules/gdscript/tests/scripts/parser/errors/double_dictionary_comma.out b/modules/gdscript/tests/scripts/parser/errors/double_dictionary_comma.out new file mode 100644 index 0000000000..d1dcd1cb4b --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/errors/double_dictionary_comma.out @@ -0,0 +1,2 @@ +GDTEST_PARSER_ERROR +Expected expression as dictionary key. diff --git a/modules/gdscript/tests/scripts/parser/features/advanced_expression_matching.gd b/modules/gdscript/tests/scripts/parser/features/advanced_expression_matching.gd new file mode 100644 index 0000000000..43b513045b --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/features/advanced_expression_matching.gd @@ -0,0 +1,34 @@ +func foo(x): + match x: + 1 + 1: + print("1+1") + [1,2,[1,{1:2,2:var z,..}]]: + print("[1,2,[1,{1:2,2:var z,..}]]") + print(z) + 1 if true else 2: + print("1 if true else 2") + 1 < 2: + print("1 < 2") + 1 or 2 and 1: + print("1 or 2 and 1") + 6 | 1: + print("1 | 1") + 1 >> 1: + print("1 >> 1") + 1, 2 or 3, 4: + print("1, 2 or 3, 4") + _: + print("wildcard") + +func test(): + foo(6 | 1) + foo(1 >> 1) + foo(2) + foo(1) + foo(1+1) + foo(1 < 2) + foo([2, 1]) + foo(4) + foo([1, 2, [1, {1 : 2, 2:3}]]) + foo([1, 2, [1, {1 : 2, 2:[1,3,5, "123"], 4:2}]]) + foo([1, 2, [1, {1 : 2}]]) diff --git a/modules/gdscript/tests/scripts/parser/features/advanced_expression_matching.out b/modules/gdscript/tests/scripts/parser/features/advanced_expression_matching.out new file mode 100644 index 0000000000..67c7e28046 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/features/advanced_expression_matching.out @@ -0,0 +1,14 @@ +GDTEST_OK +1 | 1 +1 >> 1 +1+1 +1 if true else 2 +1+1 +1 < 2 +wildcard +1, 2 or 3, 4 +[1,2,[1,{1:2,2:var z,..}]] +3 +[1,2,[1,{1:2,2:var z,..}]] +[1, 3, 5, 123] +wildcard diff --git a/modules/gdscript/tests/scripts/parser/features/basic_expression_matching.gd b/modules/gdscript/tests/scripts/parser/features/basic_expression_matching.gd new file mode 100644 index 0000000000..2b46f1e88a --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/features/basic_expression_matching.gd @@ -0,0 +1,27 @@ +func foo(x): + match x: + 1: + print("1") + 2: + print("2") + [1, 2]: + print("[1, 2]") + 3 or 4: + print("3 or 4") + 4: + print("4") + {1 : 2, 2 : 3}: + print("{1 : 2, 2 : 3}") + _: + print("wildcard") + +func test(): + foo(0) + foo(1) + foo(2) + foo([1, 2]) + foo(3) + foo(4) + foo([4,4]) + foo({1 : 2, 2 : 3}) + foo({1 : 2, 4 : 3}) diff --git a/modules/gdscript/tests/scripts/parser/features/basic_expression_matching.out b/modules/gdscript/tests/scripts/parser/features/basic_expression_matching.out new file mode 100644 index 0000000000..46ee4b04da --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/features/basic_expression_matching.out @@ -0,0 +1,10 @@ +GDTEST_OK +wildcard +1 +2 +[1, 2] +wildcard +4 +wildcard +{1 : 2, 2 : 3} +wildcard diff --git a/modules/gdscript/tests/scripts/parser/features/str_preserves_case.gd b/modules/gdscript/tests/scripts/parser/features/str_preserves_case.gd new file mode 100644 index 0000000000..9e48a7f0da --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/features/str_preserves_case.gd @@ -0,0 +1,7 @@ +func test(): + var null_var = null + var true_var: bool = true + var false_var: bool = false + print(str(null_var)) + print(str(true_var)) + print(str(false_var)) diff --git a/modules/gdscript/tests/scripts/parser/features/str_preserves_case.out b/modules/gdscript/tests/scripts/parser/features/str_preserves_case.out new file mode 100644 index 0000000000..abba38e87c --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/features/str_preserves_case.out @@ -0,0 +1,4 @@ +GDTEST_OK +null +true +false diff --git a/modules/gdscript/tests/scripts/parser/features/variable_declaration.gd b/modules/gdscript/tests/scripts/parser/features/variable_declaration.gd index 3b48f10ca7..38567d35c6 100644 --- a/modules/gdscript/tests/scripts/parser/features/variable_declaration.gd +++ b/modules/gdscript/tests/scripts/parser/features/variable_declaration.gd @@ -1,12 +1,19 @@ -var a # No init. -var b = 42 # Init. +var m1 # No init. +var m2 = 22 # Init. +var m3: String # No init, typed. +var m4: String = "44" # Init, typed. func test(): - var c # No init, local. - var d = 23 # Init, local. + var loc5 # No init, local. + var loc6 = 66 # Init, local. + var loc7: String # No init, typed. + var loc8: String = "88" # Init, typed. - a = 1 - c = 2 + m1 = 11 + m3 = "33" - prints(a, b, c, d) + loc5 = 55 + loc7 = "77" + + prints(m1, m2, m3, m4, loc5, loc6, loc7, loc8) print("OK") diff --git a/modules/gdscript/tests/scripts/parser/features/variable_declaration.out b/modules/gdscript/tests/scripts/parser/features/variable_declaration.out index 2e0a63c024..7817dd3169 100644 --- a/modules/gdscript/tests/scripts/parser/features/variable_declaration.out +++ b/modules/gdscript/tests/scripts/parser/features/variable_declaration.out @@ -1,7 +1,3 @@ GDTEST_OK ->> WARNING ->> Line: 5 ->> UNASSIGNED_VARIABLE ->> The variable 'c' was used but never assigned a value. -1 42 2 23 +11 22 33 44 55 66 77 88 OK |