diff options
Diffstat (limited to 'modules/gdscript')
95 files changed, 1801 insertions, 535 deletions
diff --git a/modules/gdscript/doc_classes/@GDScript.xml b/modules/gdscript/doc_classes/@GDScript.xml index 5bed1b9da3..923b2fe30d 100644 --- a/modules/gdscript/doc_classes/@GDScript.xml +++ b/modules/gdscript/doc_classes/@GDScript.xml @@ -227,18 +227,6 @@ [/codeblock] </description> </method> - <method name="str" qualifiers="vararg"> - <return type="String" /> - <description> - Converts one or more arguments to a [String] in the best way possible. - [codeblock] - var a = [10, 20, 30] - var b = str(a); - len(a) # Returns 3 - len(b) # Returns 12 - [/codeblock] - </description> - </method> <method name="type_exists"> <return type="bool" /> <param index="0" name="type" type="StringName" /> @@ -316,12 +304,21 @@ <return type="void" /> <param index="0" name="names" type="String" /> <description> - Export a [String] or integer property as an enumerated list of options. If the property is an integer field, then the index of the value is stored, in the same order the values are provided. You can add specific identifiers for allowed values using a colon. + Export an [int] or [String] property as an enumerated list of options. If the property is an [int], then the index of the value is stored, in the same order the values are provided. You can add explicit values using a colon. If the property is a [String], then the value is stored. See also [constant PROPERTY_HINT_ENUM]. [codeblock] - @export_enum("Rebecca", "Mary", "Leah") var character_name: String @export_enum("Warrior", "Magician", "Thief") var character_class: int @export_enum("Slow:30", "Average:60", "Very Fast:200") var character_speed: int + @export_enum("Rebecca", "Mary", "Leah") var character_name: String + [/codeblock] + If you want to set an initial value, you must specify it explicitly: + [codeblock] + @export_enum("Rebecca", "Mary", "Leah") var character_name: String = "Rebecca" + [/codeblock] + If you want to use named GDScript enums, then use [annotation @export] instead: + [codeblock] + enum CharacterName {REBECCA, MARY, LEAH} + @export var character_name: CharacterName [/codeblock] </description> </annotation> @@ -360,6 +357,20 @@ [codeblock] @export_flags("Fire", "Water", "Earth", "Wind") var spell_elements = 0 [/codeblock] + You can add explicit values using a colon: + [codeblock] + @export_flags("Self:4", "Allies:8", "Foes:16") var spell_targets = 0 + [/codeblock] + You can also combine several flags: + [codeblock] + @export_flags("Self:4", "Allies:8", "Self and Allies:12", "Foes:16") + var spell_targets = 0 + [/codeblock] + [b]Note:[/b] A flag value must be at least [code]1[/code] and at most [code]2 ** 32 - 1[/code]. + [b]Note:[/b] Unlike [annotation @export_enum], the previous explicit value is not taken into account. In the following example, A is 16, B is 2, C is 4. + [codeblock] + @export_flags("A:16", "B", "C") var x + [/codeblock] </description> </annotation> <annotation name="@export_flags_2d_navigation"> @@ -507,7 +518,7 @@ <param index="2" name="step" type="float" default="1.0" /> <param index="3" name="extra_hints" type="String" default="""" /> <description> - Export a numeric property as a range value. The range must be defined by [param min] and [param max], as well as an optional [param step] and a variety of extra hints. The [param step] defaults to [code]1[/code] for integer properties. For floating-point numbers this value depends on your [code]EditorSettings.interface/inspector/default_float_step[/code] setting. + Export an [int] or [float] property as a range value. The range must be defined by [param min] and [param max], as well as an optional [param step] and a variety of extra hints. The [param step] defaults to [code]1[/code] for integer properties. For floating-point numbers this value depends on your [code]EditorSettings.interface/inspector/default_float_step[/code] setting. If hints [code]"or_greater"[/code] and [code]"or_less"[/code] are provided, the editor widget will not cap the value at range boundaries. The [code]"exp"[/code] hint will make the edited values on range to change exponentially. The [code]"hide_slider"[/code] hint will hide the slider element of the editor widget. Hints also allow to indicate the units for the edited value. Using [code]"radians"[/code] you can specify that the actual value is in radians, but should be displayed in degrees in the Inspector dock. [code]"degrees"[/code] allows to add a degree sign as a unit suffix. Finally, a custom suffix can be provided using [code]"suffix:unit"[/code], where "unit" can be any string. See also [constant PROPERTY_HINT_RANGE]. @@ -554,6 +565,7 @@ [/codeblock] [b]Note:[/b] Only the script can have a custom icon. Inner classes are not supported. [b]Note:[/b] As annotations describe their subject, the [code]@icon[/code] annotation must be placed before the class definition and inheritance. + [b]Note:[/b] Unlike other annotations, the argument of the [code]@icon[/code] annotation must be a string literal (constant expressions are not supported). </description> </annotation> <annotation name="@onready"> @@ -567,14 +579,22 @@ </annotation> <annotation name="@rpc" qualifiers="vararg"> <return type="void" /> - <param index="0" name="mode" type="String" default="""" /> - <param index="1" name="sync" type="String" default="""" /> - <param index="2" name="transfer_mode" type="String" default="""" /> + <param index="0" name="mode" type="String" default=""authority"" /> + <param index="1" name="sync" type="String" default=""call_remote"" /> + <param index="2" name="transfer_mode" type="String" default=""unreliable"" /> <param index="3" name="transfer_channel" type="int" default="0" /> <description> Mark the following method for remote procedure calls. See [url=$DOCS_URL/tutorials/networking/high_level_multiplayer.html]High-level multiplayer[/url]. + The order of [code]mode[/code], [code]sync[/code] and [code]transfer_mode[/code] does not matter and all arguments can be omitted, but [code]transfer_channel[/code] always has to be the last argument. The accepted values for [code]mode[/code] are [code]"any_peer"[/code] or [code]"authority"[/code], for [code]sync[/code] are [code]"call_remote"[/code] or [code]"call_local"[/code] and for [code]transfer_mode[/code] are [code]"unreliable"[/code], [code]"unreliable_ordered"[/code] or [code]"reliable"[/code]. [codeblock] - @rpc() + @rpc + func fn(): pass + + @rpc("any_peer", "unreliable_ordered") + func fn_update_pos(): pass + + @rpc("authority", "call_remote", "unreliable", 0) # Equivalent to @rpc + func fn_default(): pass [/codeblock] </description> </annotation> diff --git a/modules/gdscript/gdscript.cpp b/modules/gdscript/gdscript.cpp index d9b8a540c0..a876229276 100644 --- a/modules/gdscript/gdscript.cpp +++ b/modules/gdscript/gdscript.cpp @@ -706,11 +706,7 @@ bool GDScript::_update_exports(bool *r_err, bool p_recursive_call, PlaceHolderSc } members_cache.push_back(member.variable->export_info); - Variant default_value; - if (member.variable->initializer && member.variable->initializer->is_constant) { - default_value = member.variable->initializer->reduced_value; - GDScriptCompiler::convert_to_initializer_type(default_value, member.variable); - } + Variant default_value = analyzer.make_variable_default_value(member.variable); member_default_values_cache[member.variable->identifier->name] = default_value; } break; case GDScriptParser::ClassNode::Member::SIGNAL: { @@ -1500,7 +1496,12 @@ GDScript::~GDScript() { // Order matters since clearing the stack may already cause // the GDScriptFunctionState to be destroyed and thus removed from the list. pending_func_states.remove(E); - E->self()->_clear_stack(); + GDScriptFunctionState *state = E->self(); + ObjectID state_id = state->get_instance_id(); + state->_clear_connections(); + if (ObjectDB::get_instance(state_id)) { + state->_clear_stack(); + } } } @@ -1525,41 +1526,24 @@ bool GDScriptInstance::set(const StringName &p_name, const Variant &p_value) { HashMap<StringName, GDScript::MemberInfo>::Iterator E = script->member_indices.find(p_name); if (E) { const GDScript::MemberInfo *member = &E->value; - if (member->setter) { - const Variant *val = &p_value; + Variant value = p_value; + if (member->data_type.has_type && !member->data_type.is_type(value)) { + const Variant *args = &p_value; Callable::CallError err; - callp(member->setter, &val, 1, err); - if (err.error == Callable::CallError::CALL_OK) { - return true; //function exists, call was successful - } else { + Variant::construct(member->data_type.builtin_type, value, &args, 1, err); + if (err.error != Callable::CallError::CALL_OK || !member->data_type.is_type(value)) { return false; } + } + if (member->setter) { + const Variant *args = &value; + Callable::CallError err; + callp(member->setter, &args, 1, err); + return err.error == Callable::CallError::CALL_OK; } else { - if (member->data_type.has_type) { - if (member->data_type.builtin_type == Variant::ARRAY && member->data_type.has_container_element_type()) { - // Typed array. - if (p_value.get_type() == Variant::ARRAY) { - return VariantInternal::get_array(&members.write[member->index])->typed_assign(p_value); - } else { - return false; - } - } else if (!member->data_type.is_type(p_value)) { - // Try conversion - Callable::CallError ce; - const Variant *value = &p_value; - Variant converted; - Variant::construct(member->data_type.builtin_type, converted, &value, 1, ce); - if (ce.error == Callable::CallError::CALL_OK) { - members.write[member->index] = converted; - return true; - } else { - return false; - } - } - } - members.write[member->index] = p_value; + members.write[member->index] = value; + return true; } - return true; } } @@ -1941,7 +1925,12 @@ GDScriptInstance::~GDScriptInstance() { // Order matters since clearing the stack may already cause // the GDSCriptFunctionState to be destroyed and thus removed from the list. pending_func_states.remove(E); - E->self()->_clear_stack(); + GDScriptFunctionState *state = E->self(); + ObjectID state_id = state->get_instance_id(); + state->_clear_connections(); + if (ObjectDB::get_instance(state_id)) { + state->_clear_stack(); + } } if (script.is_valid() && owner) { @@ -2458,21 +2447,25 @@ String GDScriptLanguage::get_global_class_name(const String &p_path, String *r_b GDScriptParser parser; err = parser.parse(source, p_path, false); - if (err) { - return String(); - } - - GDScriptAnalyzer analyzer(&parser); - err = analyzer.resolve_inheritance(); - if (err) { - return String(); - } const GDScriptParser::ClassNode *c = parser.get_tree(); - - if (r_base_type) { - *r_base_type = c->get_datatype().native_type; - } + if (!c) { + return String(); // No class parsed. + } + + /* **WARNING** + * + * This function is written with the goal to be *extremely* error tolerant, as such + * it should meet the following requirements: + * + * - It must not rely on the analyzer (in fact, the analyzer must not be used here), + * because at the time global classes are parsed, the dependencies may not be present + * yet, hence the function will fail (which is unintended). + * - It must not fail even if the parsing fails, because even if the file is broken, + * it should attempt its best to retrieve the inheritance metadata. + * + * Before changing this function, please ask the current maintainer of EditorFileSystem. + */ if (r_icon_path) { if (c->icon_path.is_empty() || c->icon_path.is_absolute_path()) { @@ -2481,7 +2474,73 @@ String GDScriptLanguage::get_global_class_name(const String &p_path, String *r_b *r_icon_path = p_path.get_base_dir().path_join(c->icon_path).simplify_path(); } } + if (r_base_type) { + const GDScriptParser::ClassNode *subclass = c; + String path = p_path; + GDScriptParser subparser; + while (subclass) { + if (subclass->extends_used) { + if (!subclass->extends_path.is_empty()) { + if (subclass->extends.size() == 0) { + get_global_class_name(subclass->extends_path, r_base_type); + subclass = nullptr; + break; + } else { + Vector<StringName> extend_classes = subclass->extends; + + Ref<FileAccess> subfile = FileAccess::open(subclass->extends_path, FileAccess::READ); + if (subfile.is_null()) { + break; + } + String subsource = subfile->get_as_utf8_string(); + + if (subsource.is_empty()) { + break; + } + String subpath = subclass->extends_path; + if (subpath.is_relative_path()) { + subpath = path.get_base_dir().path_join(subpath).simplify_path(); + } + if (OK != subparser.parse(subsource, subpath, false)) { + break; + } + path = subpath; + subclass = subparser.get_tree(); + + while (extend_classes.size() > 0) { + bool found = false; + for (int i = 0; i < subclass->members.size(); i++) { + if (subclass->members[i].type != GDScriptParser::ClassNode::Member::CLASS) { + continue; + } + + const GDScriptParser::ClassNode *inner_class = subclass->members[i].m_class; + if (inner_class->identifier->name == extend_classes[0]) { + extend_classes.remove_at(0); + found = true; + subclass = inner_class; + break; + } + } + if (!found) { + subclass = nullptr; + break; + } + } + } + } else if (subclass->extends.size() == 1) { + *r_base_type = subclass->extends[0]; + subclass = nullptr; + } else { + break; + } + } else { + *r_base_type = "RefCounted"; + subclass = nullptr; + } + } + } return c->identifier != nullptr ? String(c->identifier->name) : String(); } diff --git a/modules/gdscript/gdscript_analyzer.cpp b/modules/gdscript/gdscript_analyzer.cpp index 7ea0603d57..602d07d9a7 100644 --- a/modules/gdscript/gdscript_analyzer.cpp +++ b/modules/gdscript/gdscript_analyzer.cpp @@ -138,13 +138,25 @@ static GDScriptParser::DataType make_enum_type(const StringName &p_enum_name, co } static GDScriptParser::DataType make_native_enum_type(const StringName &p_enum_name, const StringName &p_native_class, const bool p_meta = true) { - GDScriptParser::DataType type = make_enum_type(p_enum_name, p_native_class, p_meta); + // Find out which base class declared the enum, so the name is always the same even when coming from other contexts. + StringName native_base = p_native_class; + while (true && native_base != StringName()) { + if (ClassDB::has_enum(native_base, p_enum_name, true)) { + break; + } + native_base = ClassDB::get_parent_class_nocheck(native_base); + } + + GDScriptParser::DataType type = make_enum_type(p_enum_name, native_base, p_meta); + if (p_meta) { + type.builtin_type = Variant::NIL; // Native enum types are not Dictionaries + } List<StringName> enum_values; - ClassDB::get_enum_constants(p_native_class, p_enum_name, &enum_values); + ClassDB::get_enum_constants(native_base, p_enum_name, &enum_values, true); for (const StringName &E : enum_values) { - type.enum_values[E] = ClassDB::get_integer_constant(p_native_class, E); + type.enum_values[E] = ClassDB::get_integer_constant(native_base, E); } return type; @@ -580,6 +592,7 @@ GDScriptParser::DataType GDScriptAnalyzer::resolve_datatype(GDScriptParser::Type if (result.builtin_type == Variant::ARRAY) { GDScriptParser::DataType container_type = type_from_metatype(resolve_datatype(p_type->container_type)); if (container_type.kind != GDScriptParser::DataType::VARIANT) { + container_type.is_constant = false; result.set_container_element_type(container_type); } } @@ -781,6 +794,22 @@ void GDScriptAnalyzer::resolve_class_member(GDScriptParser::ClassNode *p_class, resolving_datatype.kind = GDScriptParser::DataType::RESOLVING; { +#ifdef DEBUG_ENABLED + HashSet<GDScriptWarning::Code> previously_ignored_warnings = parser->ignored_warnings; + GDScriptParser::Node *member_node = member.get_source_node(); + if (member_node && member_node->type != GDScriptParser::Node::ANNOTATION) { + // Apply @warning_ignore annotations before resolving member. + for (GDScriptParser::AnnotationNode *&E : member_node->annotations) { + if (E->name == SNAME("@warning_ignore")) { + resolve_annotation(E); + E->apply(parser, member.variable); + } + } + for (GDScriptWarning::Code ignored_warning : member_node->ignored_warnings) { + parser->ignored_warnings.insert(ignored_warning); + } + } +#endif switch (member.type) { case GDScriptParser::ClassNode::Member::VARIABLE: { check_class_member_name_conflict(p_class, member.variable->identifier->name, member.variable); @@ -789,9 +818,48 @@ void GDScriptAnalyzer::resolve_class_member(GDScriptParser::ClassNode *p_class, // Apply annotations. for (GDScriptParser::AnnotationNode *&E : member.variable->annotations) { - resolve_annotation(E); - E->apply(parser, member.variable); + if (E->name != SNAME("@warning_ignore")) { + resolve_annotation(E); + E->apply(parser, member.variable); + } + } +#ifdef DEBUG_ENABLED + if (member.variable->exported && member.variable->onready) { + parser->push_warning(member.variable, GDScriptWarning::ONREADY_WITH_EXPORT); + } + if (member.variable->initializer) { + // Check if it is call to get_node() on self (using shorthand $ or not), so we can check if @onready is needed. + // This could be improved by traversing the expression fully and checking the presence of get_node at any level. + if (!member.variable->onready && member.variable->initializer && (member.variable->initializer->type == GDScriptParser::Node::GET_NODE || member.variable->initializer->type == GDScriptParser::Node::CALL || member.variable->initializer->type == GDScriptParser::Node::CAST)) { + GDScriptParser::Node *expr = member.variable->initializer; + if (expr->type == GDScriptParser::Node::CAST) { + expr = static_cast<GDScriptParser::CastNode *>(expr)->operand; + } + bool is_get_node = expr->type == GDScriptParser::Node::GET_NODE; + bool is_using_shorthand = is_get_node; + if (!is_get_node && expr->type == GDScriptParser::Node::CALL) { + is_using_shorthand = false; + GDScriptParser::CallNode *call = static_cast<GDScriptParser::CallNode *>(expr); + if (call->function_name == SNAME("get_node")) { + switch (call->get_callee_type()) { + case GDScriptParser::Node::IDENTIFIER: { + is_get_node = true; + } break; + case GDScriptParser::Node::SUBSCRIPT: { + GDScriptParser::SubscriptNode *subscript = static_cast<GDScriptParser::SubscriptNode *>(call->callee); + is_get_node = subscript->is_attribute && subscript->base->type == GDScriptParser::Node::SELF; + } break; + default: + break; + } + } + } + if (is_get_node) { + parser->push_warning(member.variable, GDScriptWarning::GET_NODE_DEFAULT_WITHOUT_ONREADY, is_using_shorthand ? "$" : "get_node()"); + } + } } +#endif } break; case GDScriptParser::ClassNode::Member::CONSTANT: { check_class_member_name_conflict(p_class, member.constant->identifier->name, member.constant); @@ -877,6 +945,10 @@ void GDScriptAnalyzer::resolve_class_member(GDScriptParser::ClassNode *p_class, } } break; case GDScriptParser::ClassNode::Member::FUNCTION: + for (GDScriptParser::AnnotationNode *&E : member.function->annotations) { + resolve_annotation(E); + E->apply(parser, member.function); + } resolve_function_signature(member.function, p_source); break; case GDScriptParser::ClassNode::Member::ENUM_VALUE: { @@ -930,6 +1002,9 @@ void GDScriptAnalyzer::resolve_class_member(GDScriptParser::ClassNode *p_class, ERR_PRINT("Trying to resolve undefined member."); break; } +#ifdef DEBUG_ENABLED + parser->ignored_warnings = previously_ignored_warnings; +#endif } parser->current_class = previous_class; @@ -1049,7 +1124,7 @@ void GDScriptAnalyzer::resolve_class_body(GDScriptParser::ClassNode *p_class, co resolve_class_body(base_class, p_class); } - // Do functions and properties now. + // Do functions, properties, and groups now. for (int i = 0; i < p_class->members.size(); i++) { GDScriptParser::ClassNode::Member member = p_class->members[i]; if (member.type == GDScriptParser::ClassNode::Member::FUNCTION) { @@ -1058,19 +1133,7 @@ void GDScriptAnalyzer::resolve_class_body(GDScriptParser::ClassNode *p_class, co resolve_annotation(E); E->apply(parser, member.function); } - -#ifdef DEBUG_ENABLED - HashSet<uint32_t> previously_ignored = parser->ignored_warning_codes; - for (uint32_t ignored_warning : member.function->ignored_warnings) { - parser->ignored_warning_codes.insert(ignored_warning); - } -#endif // DEBUG_ENABLED - resolve_function_body(member.function); - -#ifdef DEBUG_ENABLED - parser->ignored_warning_codes = previously_ignored; -#endif // DEBUG_ENABLED } else if (member.type == GDScriptParser::ClassNode::Member::VARIABLE && member.variable->property != GDScriptParser::VariableNode::PROP_NONE) { if (member.variable->property == GDScriptParser::VariableNode::PROP_INLINE) { if (member.variable->getter != nullptr) { @@ -1089,6 +1152,10 @@ void GDScriptAnalyzer::resolve_class_body(GDScriptParser::ClassNode *p_class, co resolve_function_body(member.variable->setter); } } + } else if (member.type == GDScriptParser::ClassNode::Member::GROUP) { + // Apply annotation (`@export_{category,group,subgroup}`). + resolve_annotation(member.annotation); + member.annotation->apply(parser, nullptr); } } @@ -1097,9 +1164,9 @@ void GDScriptAnalyzer::resolve_class_body(GDScriptParser::ClassNode *p_class, co GDScriptParser::ClassNode::Member member = p_class->members[i]; if (member.type == GDScriptParser::ClassNode::Member::VARIABLE) { #ifdef DEBUG_ENABLED - HashSet<uint32_t> previously_ignored = parser->ignored_warning_codes; - for (uint32_t ignored_warning : member.function->ignored_warnings) { - parser->ignored_warning_codes.insert(ignored_warning); + HashSet<GDScriptWarning::Code> previously_ignored_warnings = parser->ignored_warnings; + for (GDScriptWarning::Code ignored_warning : member.variable->ignored_warnings) { + parser->ignored_warnings.insert(ignored_warning); } if (member.variable->usages == 0 && String(member.variable->identifier->name).begins_with("_")) { parser->push_warning(member.variable->identifier, GDScriptWarning::UNUSED_PRIVATE_CLASS_VARIABLE, member.variable->identifier->name); @@ -1174,7 +1241,7 @@ void GDScriptAnalyzer::resolve_class_body(GDScriptParser::ClassNode *p_class, co } } #ifdef DEBUG_ENABLED - parser->ignored_warning_codes = previously_ignored; + parser->ignored_warnings = previously_ignored_warnings; #endif // DEBUG_ENABLED } } @@ -1284,6 +1351,11 @@ void GDScriptAnalyzer::resolve_node(GDScriptParser::Node *p_node, bool p_is_root void GDScriptAnalyzer::resolve_annotation(GDScriptParser::AnnotationNode *p_annotation) { ERR_FAIL_COND_MSG(!parser->valid_annotations.has(p_annotation->name), vformat(R"(Annotation "%s" not found to validate.)", p_annotation->name)); + if (p_annotation->is_resolved) { + return; + } + p_annotation->is_resolved = true; + const MethodInfo &annotation_info = parser->valid_annotations[p_annotation->name].info; const List<PropertyInfo>::Element *E = annotation_info.arguments.front(); @@ -1350,6 +1422,13 @@ void GDScriptAnalyzer::resolve_function_signature(GDScriptParser::FunctionNode * } p_function->resolved_signature = true; +#ifdef DEBUG_ENABLED + HashSet<GDScriptWarning::Code> previously_ignored_warnings = parser->ignored_warnings; + for (GDScriptWarning::Code ignored_warning : p_function->ignored_warnings) { + parser->ignored_warnings.insert(ignored_warning); + } +#endif + GDScriptParser::FunctionNode *previous_function = parser->current_function; parser->current_function = p_function; @@ -1416,7 +1495,8 @@ void GDScriptAnalyzer::resolve_function_signature(GDScriptParser::FunctionNode * int default_par_count = 0; bool is_static = false; bool is_vararg = false; - if (!p_is_lambda && get_function_signature(p_function, false, base_type, function_name, parent_return_type, parameters_types, default_par_count, is_static, is_vararg)) { + StringName native_base; + if (!p_is_lambda && get_function_signature(p_function, false, base_type, function_name, parent_return_type, parameters_types, default_par_count, is_static, is_vararg, &native_base)) { bool valid = p_function->is_static == is_static; valid = valid && parent_return_type == p_function->get_datatype(); @@ -1442,8 +1522,8 @@ void GDScriptAnalyzer::resolve_function_signature(GDScriptParser::FunctionNode * parameter = "Variant"; } parent_signature += parameter; - if (j == parameters_types.size() - default_par_count) { - parent_signature += " = default"; + if (j >= parameters_types.size() - default_par_count) { + parent_signature += " = <default>"; } j++; @@ -1459,6 +1539,11 @@ void GDScriptAnalyzer::resolve_function_signature(GDScriptParser::FunctionNode * push_error(vformat(R"(The function signature doesn't match the parent. Parent signature is "%s".)", parent_signature), p_function); } +#ifdef DEBUG_ENABLED + if (native_base != StringName()) { + parser->push_warning(p_function, GDScriptWarning::NATIVE_METHOD_OVERRIDE, function_name, native_base); + } +#endif } #endif // TOOLS_ENABLED } @@ -1467,6 +1552,9 @@ void GDScriptAnalyzer::resolve_function_signature(GDScriptParser::FunctionNode * p_function->set_datatype(prev_datatype); } +#ifdef DEBUG_ENABLED + parser->ignored_warnings = previously_ignored_warnings; +#endif parser->current_function = previous_function; } @@ -1476,16 +1564,20 @@ void GDScriptAnalyzer::resolve_function_body(GDScriptParser::FunctionNode *p_fun } p_function->resolved_body = true; +#ifdef DEBUG_ENABLED + HashSet<GDScriptWarning::Code> previously_ignored_warnings = parser->ignored_warnings; + for (GDScriptWarning::Code ignored_warning : p_function->ignored_warnings) { + parser->ignored_warnings.insert(ignored_warning); + } +#endif + GDScriptParser::FunctionNode *previous_function = parser->current_function; parser->current_function = p_function; resolve_suite(p_function->body); - GDScriptParser::DataType return_type = p_function->body->get_datatype(); - - if (!p_function->get_datatype().is_hard_type() && return_type.is_set()) { + if (!p_function->get_datatype().is_hard_type() && p_function->body->get_datatype().is_set()) { // Use the suite inferred type if return isn't explicitly set. - return_type.type_source = GDScriptParser::DataType::INFERRED; p_function->set_datatype(p_function->body->get_datatype()); } else if (p_function->get_datatype().is_hard_type() && (p_function->get_datatype().kind != GDScriptParser::DataType::BUILTIN || p_function->get_datatype().builtin_type != Variant::NIL)) { if (!p_function->body->has_return && (p_is_lambda || p_function->identifier->name != GDScriptLanguage::get_singleton()->strings._init)) { @@ -1493,6 +1585,9 @@ void GDScriptAnalyzer::resolve_function_body(GDScriptParser::FunctionNode *p_fun } } +#ifdef DEBUG_ENABLED + parser->ignored_warnings = previously_ignored_warnings; +#endif parser->current_function = previous_function; } @@ -1533,16 +1628,16 @@ void GDScriptAnalyzer::resolve_suite(GDScriptParser::SuiteNode *p_suite) { } #ifdef DEBUG_ENABLED - HashSet<uint32_t> previously_ignored = parser->ignored_warning_codes; - for (uint32_t ignored_warning : stmt->ignored_warnings) { - parser->ignored_warning_codes.insert(ignored_warning); + HashSet<GDScriptWarning::Code> previously_ignored_warnings = parser->ignored_warnings; + for (GDScriptWarning::Code ignored_warning : stmt->ignored_warnings) { + parser->ignored_warnings.insert(ignored_warning); } #endif // DEBUG_ENABLED resolve_node(stmt); #ifdef DEBUG_ENABLED - parser->ignored_warning_codes = previously_ignored; + parser->ignored_warnings = previously_ignored_warnings; #endif // DEBUG_ENABLED decide_suite_type(p_suite, stmt); @@ -1567,18 +1662,18 @@ void GDScriptAnalyzer::resolve_assignable(GDScriptParser::AssignableNode *p_assi if (p_assignable->initializer->type == GDScriptParser::Node::ARRAY) { GDScriptParser::ArrayNode *array = static_cast<GDScriptParser::ArrayNode *>(p_assignable->initializer); - if ((p_assignable->infer_datatype && array->elements.size() > 0) || (has_specified_type && specified_type.has_container_element_type())) { - update_array_literal_element_type(specified_type, array); + if (has_specified_type && specified_type.has_container_element_type()) { + update_array_literal_element_type(array, specified_type.get_container_element_type()); } } - if (is_constant) { - if (p_assignable->initializer->type == GDScriptParser::Node::ARRAY) { - const_fold_array(static_cast<GDScriptParser::ArrayNode *>(p_assignable->initializer), true); - } else if (p_assignable->initializer->type == GDScriptParser::Node::DICTIONARY) { - const_fold_dictionary(static_cast<GDScriptParser::DictionaryNode *>(p_assignable->initializer), true); - } - if (!p_assignable->initializer->is_constant) { + if (is_constant && !p_assignable->initializer->is_constant) { + bool is_initializer_value_reduced = false; + Variant initializer_value = make_expression_reduced_value(p_assignable->initializer, is_initializer_value_reduced); + if (is_initializer_value_reduced) { + p_assignable->initializer->is_constant = true; + p_assignable->initializer->reduced_value = initializer_value; + } else { push_error(vformat(R"(Assigned value for %s "%s" isn't a constant expression.)", p_kind, p_assignable->identifier->name), p_assignable->initializer); } } @@ -1594,6 +1689,11 @@ void GDScriptAnalyzer::resolve_assignable(GDScriptParser::AssignableNode *p_assi } else if (initializer_type.kind == GDScriptParser::DataType::BUILTIN && initializer_type.builtin_type == Variant::NIL && !is_constant) { push_error(vformat(R"(Cannot infer the type of "%s" %s because the value is "null".)", p_assignable->identifier->name, p_kind), p_assignable->initializer); } +#ifdef DEBUG_ENABLED + if (initializer_type.is_hard_type() && initializer_type.is_variant()) { + parser->push_warning(p_assignable, GDScriptWarning::INFERENCE_ON_VARIANT, p_kind); + } +#endif } else { if (!initializer_type.is_set()) { push_error(vformat(R"(Could not resolve type for %s "%s".)", p_kind, p_assignable->identifier->name), p_assignable->initializer); @@ -1626,6 +1726,8 @@ void GDScriptAnalyzer::resolve_assignable(GDScriptParser::AssignableNode *p_assi } else { push_error(vformat(R"(Cannot assign a value of type %s to %s "%s" with specified type %s.)", initializer_type.to_string(), p_kind, p_assignable->identifier->name, specified_type.to_string()), p_assignable->initializer); } + } else if (specified_type.has_container_element_type() && !initializer_type.has_container_element_type()) { + mark_node_unsafe(p_assignable->initializer); #ifdef DEBUG_ENABLED } else if (specified_type.builtin_type == Variant::INT && initializer_type.builtin_type == Variant::FLOAT) { parser->push_warning(p_assignable->initializer, GDScriptWarning::NARROWING_CONVERSION); @@ -1965,20 +2067,40 @@ void GDScriptAnalyzer::resolve_return(GDScriptParser::ReturnNode *p_return) { } if (p_return->return_value != nullptr) { - reduce_expression(p_return->return_value); - if (p_return->return_value->type == GDScriptParser::Node::ARRAY) { - // Check if assigned value is an array literal, so we can make it a typed array too if appropriate. - if (has_expected_type && expected_type.has_container_element_type() && p_return->return_value->type == GDScriptParser::Node::ARRAY) { - update_array_literal_element_type(expected_type, static_cast<GDScriptParser::ArrayNode *>(p_return->return_value)); + bool is_void_function = has_expected_type && expected_type.is_hard_type() && expected_type.kind == GDScriptParser::DataType::BUILTIN && expected_type.builtin_type == Variant::NIL; + bool is_call = p_return->return_value->type == GDScriptParser::Node::CALL; + if (is_void_function && is_call) { + // Pretend the call is a root expression to allow those that are "void". + reduce_call(static_cast<GDScriptParser::CallNode *>(p_return->return_value), false, true); + } else { + reduce_expression(p_return->return_value); + } + if (is_void_function) { + p_return->void_return = true; + const GDScriptParser::DataType &return_type = p_return->return_value->datatype; + if (is_call && !return_type.is_hard_type()) { + String function_name = parser->current_function->identifier ? parser->current_function->identifier->name.operator String() : String("<anonymous function>"); + String called_function_name = static_cast<GDScriptParser::CallNode *>(p_return->return_value)->function_name.operator String(); +#ifdef DEBUG_ENABLED + parser->push_warning(p_return, GDScriptWarning::UNSAFE_VOID_RETURN, function_name, called_function_name); +#endif + mark_node_unsafe(p_return); + } else if (!is_call) { + push_error("A void function cannot return a value.", p_return); } + result.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT; + result.kind = GDScriptParser::DataType::BUILTIN; + result.builtin_type = Variant::NIL; + result.is_constant = true; + } else { + if (p_return->return_value->type == GDScriptParser::Node::ARRAY && has_expected_type && expected_type.has_container_element_type()) { + update_array_literal_element_type(static_cast<GDScriptParser::ArrayNode *>(p_return->return_value), expected_type.get_container_element_type()); + } + if (has_expected_type && expected_type.is_hard_type() && p_return->return_value->is_constant) { + update_const_expression_builtin_type(p_return->return_value, expected_type, "return"); + } + result = p_return->return_value->get_datatype(); } - if (has_expected_type && expected_type.is_hard_type() && expected_type.kind == GDScriptParser::DataType::BUILTIN && expected_type.builtin_type == Variant::NIL) { - push_error("A void function cannot return a value.", p_return); - } - if (has_expected_type && expected_type.is_hard_type() && p_return->return_value->is_constant) { - update_const_expression_builtin_type(p_return->return_value, expected_type, "return"); - } - result = p_return->return_value->get_datatype(); } else { // Return type is null by default. result.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT; @@ -2179,49 +2301,26 @@ void GDScriptAnalyzer::update_const_expression_builtin_type(GDScriptParser::Expr // When an array literal is stored (or passed as function argument) to a typed context, we then assume the array is typed. // This function determines which type is that (if any). -void GDScriptAnalyzer::update_array_literal_element_type(const GDScriptParser::DataType &p_base_type, GDScriptParser::ArrayNode *p_array_literal) { - GDScriptParser::DataType array_type = p_array_literal->get_datatype(); - if (p_array_literal->elements.size() == 0) { - // Empty array literal, just make the same type as the storage. - array_type.set_container_element_type(p_base_type.get_container_element_type()); - } else { - // Check if elements match. - bool all_same_type = true; - bool all_have_type = true; - - GDScriptParser::DataType element_type; - for (int i = 0; i < p_array_literal->elements.size(); i++) { - if (i == 0) { - element_type = p_array_literal->elements[0]->get_datatype(); - } else { - GDScriptParser::DataType this_element_type = p_array_literal->elements[i]->get_datatype(); - if (this_element_type.has_no_type()) { - all_same_type = false; - all_have_type = false; - break; - } else if (element_type != this_element_type) { - if (!is_type_compatible(element_type, this_element_type, false)) { - if (is_type_compatible(this_element_type, element_type, false)) { - // This element is a super-type to the previous type, so we use the super-type. - element_type = this_element_type; - } else { - // It's incompatible. - all_same_type = false; - break; - } - } - } - } +void GDScriptAnalyzer::update_array_literal_element_type(GDScriptParser::ArrayNode *p_array, const GDScriptParser::DataType &p_element_type) { + for (int i = 0; i < p_array->elements.size(); i++) { + GDScriptParser::ExpressionNode *element_node = p_array->elements[i]; + if (element_node->is_constant) { + update_const_expression_builtin_type(element_node, p_element_type, "include"); } - if (all_same_type) { - element_type.is_constant = false; - array_type.set_container_element_type(element_type); - } else if (all_have_type) { - push_error(vformat(R"(Variant array is not compatible with an array of type "%s".)", p_base_type.get_container_element_type().to_string()), p_array_literal); + const GDScriptParser::DataType &element_type = element_node->get_datatype(); + if (element_type.has_no_type() || element_type.is_variant() || !element_type.is_hard_type()) { + mark_node_unsafe(element_node); + continue; + } + if (!is_type_compatible(p_element_type, element_type, true, p_array)) { + push_error(vformat(R"(Cannot have an element of type "%s" in an array of type "Array[%s]".)", element_type.to_string(), p_element_type.to_string()), element_node); + return; } } - // Update the type on the value itself. - p_array_literal->set_datatype(array_type); + + GDScriptParser::DataType array_type = p_array->get_datatype(); + array_type.set_container_element_type(p_element_type); + p_array->set_datatype(array_type); } void GDScriptAnalyzer::reduce_assignment(GDScriptParser::AssignmentNode *p_assignment) { @@ -2236,11 +2335,33 @@ void GDScriptAnalyzer::reduce_assignment(GDScriptParser::AssignmentNode *p_assig if (assignee_type.is_constant || (p_assignment->assignee->type == GDScriptParser::Node::SUBSCRIPT && static_cast<GDScriptParser::SubscriptNode *>(p_assignment->assignee)->base->is_constant)) { push_error("Cannot assign a new value to a constant.", p_assignment->assignee); + return; + } else if (assignee_type.is_read_only) { + push_error("Cannot assign a new value to a read-only property.", p_assignment->assignee); + return; + } else if (p_assignment->assignee->type == GDScriptParser::Node::SUBSCRIPT) { + GDScriptParser::SubscriptNode *sub = static_cast<GDScriptParser::SubscriptNode *>(p_assignment->assignee); + while (sub) { + const GDScriptParser::DataType &base_type = sub->base->datatype; + if (base_type.is_hard_type() && base_type.is_read_only) { + if (base_type.kind == GDScriptParser::DataType::BUILTIN && !Variant::is_type_shared(base_type.builtin_type)) { + push_error("Cannot assign a new value to a read-only property.", p_assignment->assignee); + return; + } + } else { + break; + } + if (sub->base->type == GDScriptParser::Node::SUBSCRIPT) { + sub = static_cast<GDScriptParser::SubscriptNode *>(sub->base); + } else { + sub = nullptr; + } + } } // Check if assigned value is an array literal, so we can make it a typed array too if appropriate. - if (assignee_type.has_container_element_type() && p_assignment->assigned_value->type == GDScriptParser::Node::ARRAY) { - update_array_literal_element_type(assignee_type, static_cast<GDScriptParser::ArrayNode *>(p_assignment->assigned_value)); + if (p_assignment->assigned_value->type == GDScriptParser::Node::ARRAY && assignee_type.has_container_element_type()) { + update_array_literal_element_type(static_cast<GDScriptParser::ArrayNode *>(p_assignment->assigned_value), assignee_type.get_container_element_type()); } if (p_assignment->operation == GDScriptParser::AssignmentNode::OP_NONE && assignee_type.is_hard_type() && p_assignment->assigned_value->is_constant) { @@ -2318,6 +2439,9 @@ void GDScriptAnalyzer::reduce_assignment(GDScriptParser::AssignmentNode *p_assig // weak non-variant assignee and incompatible result downgrades_assignee = true; } + } else if (assignee_type.has_container_element_type() && !op_type.has_container_element_type()) { + // typed array assignee and untyped array result + mark_node_unsafe(p_assignment); } } } @@ -2344,30 +2468,27 @@ void GDScriptAnalyzer::reduce_await(GDScriptParser::AwaitNode *p_await) { return; } - GDScriptParser::DataType awaiting_type; - if (p_await->to_await->type == GDScriptParser::Node::CALL) { reduce_call(static_cast<GDScriptParser::CallNode *>(p_await->to_await), true); - awaiting_type = p_await->to_await->get_datatype(); } else { reduce_expression(p_await->to_await); } - if (p_await->to_await->is_constant) { + GDScriptParser::DataType await_type = p_await->to_await->get_datatype(); + // We cannot infer the type of the result of waiting for a signal. + if (await_type.is_hard_type() && await_type.kind == GDScriptParser::DataType::BUILTIN && await_type.builtin_type == Variant::SIGNAL) { + await_type.kind = GDScriptParser::DataType::VARIANT; + await_type.type_source = GDScriptParser::DataType::UNDETECTED; + } else if (p_await->to_await->is_constant) { p_await->is_constant = p_await->to_await->is_constant; p_await->reduced_value = p_await->to_await->reduced_value; - - awaiting_type = p_await->to_await->get_datatype(); - } else { - awaiting_type.kind = GDScriptParser::DataType::VARIANT; - awaiting_type.type_source = GDScriptParser::DataType::UNDETECTED; } - - p_await->set_datatype(awaiting_type); + await_type.is_coroutine = false; + p_await->set_datatype(await_type); #ifdef DEBUG_ENABLED - awaiting_type = p_await->to_await->get_datatype(); - if (!(awaiting_type.has_no_type() || awaiting_type.is_coroutine || awaiting_type.builtin_type == Variant::SIGNAL)) { + GDScriptParser::DataType to_await_type = p_await->to_await->get_datatype(); + if (!(to_await_type.has_no_type() || to_await_type.is_coroutine || to_await_type.builtin_type == Variant::SIGNAL)) { parser->push_warning(p_await, GDScriptWarning::REDUNDANT_AWAIT); } #endif @@ -2480,6 +2601,62 @@ void GDScriptAnalyzer::reduce_binary_op(GDScriptParser::BinaryOpNode *p_binary_o p_binary_op->set_datatype(result); } +#ifdef TOOLS_ENABLED +#ifndef DISABLE_DEPRECATED +const char *GDScriptAnalyzer::get_rename_from_map(const char *map[][2], String key) { + for (int index = 0; map[index][0]; index++) { + if (map[index][0] == key) { + return map[index][1]; + } + } + return nullptr; +} + +// Checks if an identifier/function name has been renamed in Godot 4, uses ProjectConverter3To4 for rename map. +// Returns the new name if found, nullptr otherwise. +const char *GDScriptAnalyzer::check_for_renamed_identifier(String identifier, GDScriptParser::Node::Type type) { + switch (type) { + case GDScriptParser::Node::IDENTIFIER: { + // Check properties + const char *result = get_rename_from_map(ProjectConverter3To4::gdscript_properties_renames, identifier); + if (result) { + return result; + } + // Check enum values + result = get_rename_from_map(ProjectConverter3To4::enum_renames, identifier); + if (result) { + return result; + } + // Check color constants + result = get_rename_from_map(ProjectConverter3To4::color_renames, identifier); + if (result) { + return result; + } + // Check type names + result = get_rename_from_map(ProjectConverter3To4::class_renames, identifier); + if (result) { + return result; + } + return get_rename_from_map(ProjectConverter3To4::builtin_types_renames, identifier); + } + case GDScriptParser::Node::CALL: { + const char *result = get_rename_from_map(ProjectConverter3To4::gdscript_function_renames, identifier); + if (result) { + return result; + } + // Built-in Types are mistaken for function calls when the built-in type is not found. + // Check built-in types if function rename not found + return get_rename_from_map(ProjectConverter3To4::builtin_types_renames, identifier); + } + // Signal references don't get parsed through the GDScriptAnalyzer. No support for signal rename hints. + default: + // No rename found, return null + return nullptr; + } +} +#endif // DISABLE_DEPRECATED +#endif // TOOLS_ENABLED + void GDScriptAnalyzer::reduce_call(GDScriptParser::CallNode *p_call, bool p_is_await, bool p_is_root) { bool all_is_constant = true; HashMap<int, GDScriptParser::ArrayNode *> arrays; // For array literal to potentially type when passing. @@ -2818,7 +2995,7 @@ void GDScriptAnalyzer::reduce_call(GDScriptParser::CallNode *p_call, bool p_is_a for (const KeyValue<int, GDScriptParser::ArrayNode *> &E : arrays) { int index = E.key; if (index < par_types.size() && par_types[index].has_container_element_type()) { - update_array_literal_element_type(par_types[index], E.value); + update_array_literal_element_type(E.value, par_types[index].get_container_element_type()); } } validate_call_arg(par_types, default_arg_count, is_vararg, p_call); @@ -2868,7 +3045,11 @@ void GDScriptAnalyzer::reduce_call(GDScriptParser::CallNode *p_call, bool p_is_a // Enums do not have functions other than the built-in dictionary ones. if (base_type.kind == GDScriptParser::DataType::ENUM && base_type.is_meta_type) { - push_error(vformat(R"*(Enums only have Dictionary built-in methods. Function "%s()" does not exist for enum "%s".)*", p_call->function_name, base_type.enum_type), p_call->callee); + if (base_type.builtin_type == Variant::DICTIONARY) { + push_error(vformat(R"*(Enums only have Dictionary built-in methods. Function "%s()" does not exist for enum "%s".)*", p_call->function_name, base_type.enum_type), p_call->callee); + } else { + push_error(vformat(R"*(The native enum "%s" does not behave like Dictionary and does not have methods of its own.)*", base_type.enum_type), p_call->callee); + } } else if (!p_call->is_super && callee_type != GDScriptParser::Node::NONE) { // Check if the name exists as something else. GDScriptParser::IdentifierNode *callee_id; if (callee_type == GDScriptParser::Node::IDENTIFIER) { @@ -2897,7 +3078,22 @@ void GDScriptAnalyzer::reduce_call(GDScriptParser::CallNode *p_call, bool p_is_a } if (!found && (is_self || (base_type.is_hard_type() && base_type.kind == GDScriptParser::DataType::BUILTIN))) { String base_name = is_self && !p_call->is_super ? "self" : base_type.to_string(); +#ifdef TOOLS_ENABLED +#ifndef DISABLE_DEPRECATED + String rename_hint = String(); + if (GLOBAL_GET(GDScriptWarning::get_settings_path_from_code(GDScriptWarning::Code::RENAMED_IN_GD4_HINT)).booleanize()) { + const char *renamed_function_name = check_for_renamed_identifier(p_call->function_name, p_call->type); + if (renamed_function_name) { + rename_hint = " " + vformat(R"(Did you mean to use "%s"?)", String(renamed_function_name) + "()"); + } + } + push_error(vformat(R"*(Function "%s()" not found in base %s.%s)*", p_call->function_name, base_name, rename_hint), p_call->is_super ? p_call : p_call->callee); +#else // !DISABLE_DEPRECATED + push_error(vformat(R"*(Function "%s()" not found in base %s.)*", p_call->function_name, base_name), p_call->is_super ? p_call : p_call->callee); +#endif // DISABLE_DEPRECATED +#else push_error(vformat(R"*(Function "%s()" not found in base %s.)*", p_call->function_name, base_name), p_call->is_super ? p_call : p_call->callee); +#endif } else if (!found && (!p_call->is_super && base_type.is_hard_type() && base_type.kind == GDScriptParser::DataType::NATIVE && base_type.is_meta_type)) { push_error(vformat(R"*(Static function "%s()" not found in base "%s".)*", p_call->function_name, base_type.native_type), p_call); } @@ -2929,6 +3125,10 @@ void GDScriptAnalyzer::reduce_cast(GDScriptParser::CastNode *p_cast) { } } + if (p_cast->operand->type == GDScriptParser::Node::ARRAY && cast_type.has_container_element_type()) { + update_array_literal_element_type(static_cast<GDScriptParser::ArrayNode *>(p_cast->operand), cast_type.get_container_element_type()); + } + if (!cast_type.is_variant()) { GDScriptParser::DataType op_type = p_cast->operand->get_datatype(); if (op_type.is_variant() || !op_type.is_hard_type()) { @@ -3034,10 +3234,12 @@ void GDScriptAnalyzer::reduce_identifier_from_base_set_class(GDScriptParser::Ide p_identifier->set_datatype(p_identifier_datatype); Error err = OK; - GDScript *scr = GDScriptCache::get_shallow_script(p_identifier_datatype.script_path, err).ptr(); - ERR_FAIL_COND_MSG(err != OK, vformat(R"(Error while getting cache for script "%s".)", p_identifier_datatype.script_path)); - scr = scr->find_class(p_identifier_datatype.class_type->fqcn); - p_identifier->reduced_value = scr; + Ref<GDScript> scr = GDScriptCache::get_shallow_script(p_identifier_datatype.script_path, err); + if (err) { + push_error(vformat(R"(Error while getting cache for script "%s".)", p_identifier_datatype.script_path), p_identifier); + return; + } + p_identifier->reduced_value = scr->find_class(p_identifier_datatype.class_type->fqcn); p_identifier->is_constant = true; } @@ -3081,7 +3283,22 @@ void GDScriptAnalyzer::reduce_identifier_from_base(GDScriptParser::IdentifierNod p_identifier->reduced_value = result; p_identifier->set_datatype(type_from_variant(result, p_identifier)); } else if (base.is_hard_type()) { - push_error(vformat(R"(Cannot find constant "%s" on type "%s".)", name, base.to_string()), p_identifier); +#ifdef TOOLS_ENABLED +#ifndef DISABLE_DEPRECATED + String rename_hint = String(); + if (GLOBAL_GET(GDScriptWarning::get_settings_path_from_code(GDScriptWarning::Code::RENAMED_IN_GD4_HINT)).booleanize()) { + const char *renamed_identifier_name = check_for_renamed_identifier(name, p_identifier->type); + if (renamed_identifier_name) { + rename_hint = " " + vformat(R"(Did you mean to use "%s"?)", renamed_identifier_name); + } + } + push_error(vformat(R"(Cannot find constant "%s" on base "%s".%s)", name, base.to_string(), rename_hint), p_identifier); +#else // !DISABLE_DEPRECATED + push_error(vformat(R"(Cannot find constant "%s" on base "%s".)", name, base.to_string()), p_identifier); +#endif // DISABLE_DEPRECATED +#else + push_error(vformat(R"(Cannot find constant "%s" on base "%s".)", name, base.to_string()), p_identifier); +#endif } } else { switch (base.builtin_type) { @@ -3110,7 +3327,22 @@ void GDScriptAnalyzer::reduce_identifier_from_base(GDScriptParser::IdentifierNod } } if (base.is_hard_type()) { +#ifdef TOOLS_ENABLED +#ifndef DISABLE_DEPRECATED + String rename_hint = String(); + if (GLOBAL_GET(GDScriptWarning::get_settings_path_from_code(GDScriptWarning::Code::RENAMED_IN_GD4_HINT)).booleanize()) { + const char *renamed_identifier_name = check_for_renamed_identifier(name, p_identifier->type); + if (renamed_identifier_name) { + rename_hint = " " + vformat(R"(Did you mean to use "%s"?)", renamed_identifier_name); + } + } + push_error(vformat(R"(Cannot find property "%s" on base "%s".%s)", name, base.to_string(), rename_hint), p_identifier); +#else // !DISABLE_DEPRECATED push_error(vformat(R"(Cannot find property "%s" on base "%s".)", name, base.to_string()), p_identifier); +#endif // DISABLE_DEPRECATED +#else + push_error(vformat(R"(Cannot find property "%s" on base "%s".)", name, base.to_string()), p_identifier); +#endif } } } @@ -3215,7 +3447,8 @@ void GDScriptAnalyzer::reduce_identifier_from_base(GDScriptParser::IdentifierNod StringName getter_name = ClassDB::get_property_getter(native, name); MethodBind *getter = ClassDB::get_method(native, getter_name); if (getter != nullptr) { - p_identifier->set_datatype(type_from_property(getter->get_return_info())); + bool has_setter = ClassDB::get_property_setter(native, name) != StringName(); + p_identifier->set_datatype(type_from_property(getter->get_return_info(), false, !has_setter)); p_identifier->source = GDScriptParser::IdentifierNode::INHERITED_VARIABLE; } return; @@ -3449,7 +3682,22 @@ void GDScriptAnalyzer::reduce_identifier(GDScriptParser::IdentifierNode *p_ident if (GDScriptUtilityFunctions::function_exists(name)) { push_error(vformat(R"(Built-in function "%s" cannot be used as an identifier.)", name), p_identifier); } else { +#ifdef TOOLS_ENABLED +#ifndef DISABLE_DEPRECATED + String rename_hint = String(); + if (GLOBAL_GET(GDScriptWarning::get_settings_path_from_code(GDScriptWarning::Code::RENAMED_IN_GD4_HINT)).booleanize()) { + const char *renamed_identifier_name = check_for_renamed_identifier(name, p_identifier->type); + if (renamed_identifier_name) { + rename_hint = " " + vformat(R"(Did you mean to use "%s"?)", renamed_identifier_name); + } + } + push_error(vformat(R"(Identifier "%s" not declared in the current scope.%s)", name, rename_hint), p_identifier); +#else // !DISABLE_DEPRECATED push_error(vformat(R"(Identifier "%s" not declared in the current scope.)", name), p_identifier); +#endif // DISABLE_DEPRECATED +#else + push_error(vformat(R"(Identifier "%s" not declared in the current scope.)", name), p_identifier); +#endif } GDScriptParser::DataType dummy; dummy.kind = GDScriptParser::DataType::VARIANT; @@ -3581,12 +3829,6 @@ void GDScriptAnalyzer::reduce_subscript(GDScriptParser::SubscriptNode *p_subscri reduce_identifier(static_cast<GDScriptParser::IdentifierNode *>(p_subscript->base), true); } else { reduce_expression(p_subscript->base); - - if (p_subscript->base->type == GDScriptParser::Node::ARRAY) { - const_fold_array(static_cast<GDScriptParser::ArrayNode *>(p_subscript->base), false); - } else if (p_subscript->base->type == GDScriptParser::Node::DICTIONARY) { - const_fold_dictionary(static_cast<GDScriptParser::DictionaryNode *>(p_subscript->base), false); - } } GDScriptParser::DataType result_type; @@ -3865,7 +4107,6 @@ void GDScriptAnalyzer::reduce_ternary_op(GDScriptParser::TernaryOpNode *p_ternar if (!is_type_compatible(true_type, false_type)) { result = false_type; if (!is_type_compatible(false_type, true_type)) { - result.type_source = GDScriptParser::DataType::UNDETECTED; result.kind = GDScriptParser::DataType::VARIANT; #ifdef DEBUG_ENABLED parser->push_warning(p_ternary_op, GDScriptWarning::INCOMPATIBLE_TERNARY); @@ -3873,6 +4114,7 @@ void GDScriptAnalyzer::reduce_ternary_op(GDScriptParser::TernaryOpNode *p_ternar } } } + result.type_source = true_type.is_hard_type() && false_type.is_hard_type() ? GDScriptParser::DataType::ANNOTATED_INFERRED : GDScriptParser::DataType::INFERRED; p_ternary_op->set_datatype(result); } @@ -3911,58 +4153,154 @@ void GDScriptAnalyzer::reduce_unary_op(GDScriptParser::UnaryOpNode *p_unary_op) p_unary_op->set_datatype(result); } -void GDScriptAnalyzer::const_fold_array(GDScriptParser::ArrayNode *p_array, bool p_is_const) { - for (int i = 0; i < p_array->elements.size(); i++) { - GDScriptParser::ExpressionNode *element = p_array->elements[i]; +Variant GDScriptAnalyzer::make_expression_reduced_value(GDScriptParser::ExpressionNode *p_expression, bool &is_reduced) { + Variant value; - if (element->type == GDScriptParser::Node::ARRAY) { - const_fold_array(static_cast<GDScriptParser::ArrayNode *>(element), p_is_const); - } else if (element->type == GDScriptParser::Node::DICTIONARY) { - const_fold_dictionary(static_cast<GDScriptParser::DictionaryNode *>(element), p_is_const); - } + if (p_expression == nullptr) { + return value; + } - if (!element->is_constant) { - return; - } + if (p_expression->is_constant) { + is_reduced = true; + value = p_expression->reduced_value; + } else if (p_expression->type == GDScriptParser::Node::ARRAY) { + value = make_array_reduced_value(static_cast<GDScriptParser::ArrayNode *>(p_expression), is_reduced); + } else if (p_expression->type == GDScriptParser::Node::DICTIONARY) { + value = make_dictionary_reduced_value(static_cast<GDScriptParser::DictionaryNode *>(p_expression), is_reduced); + } else if (p_expression->type == GDScriptParser::Node::SUBSCRIPT) { + value = make_subscript_reduced_value(static_cast<GDScriptParser::SubscriptNode *>(p_expression), is_reduced); } - Array array; + return value; +} + +Variant GDScriptAnalyzer::make_array_reduced_value(GDScriptParser::ArrayNode *p_array, bool &is_reduced) { + Array array = p_array->get_datatype().has_container_element_type() ? make_array_from_element_datatype(p_array->get_datatype().get_container_element_type()) : Array(); + array.resize(p_array->elements.size()); for (int i = 0; i < p_array->elements.size(); i++) { - array[i] = p_array->elements[i]->reduced_value; - } - if (p_is_const) { - array.make_read_only(); + GDScriptParser::ExpressionNode *element = p_array->elements[i]; + + bool is_element_value_reduced = false; + Variant element_value = make_expression_reduced_value(element, is_element_value_reduced); + if (!is_element_value_reduced) { + return Variant(); + } + + array[i] = element_value; } - p_array->is_constant = true; - p_array->reduced_value = array; + + array.make_read_only(); + + is_reduced = true; + return array; } -void GDScriptAnalyzer::const_fold_dictionary(GDScriptParser::DictionaryNode *p_dictionary, bool p_is_const) { +Variant GDScriptAnalyzer::make_dictionary_reduced_value(GDScriptParser::DictionaryNode *p_dictionary, bool &is_reduced) { + Dictionary dictionary; + for (int i = 0; i < p_dictionary->elements.size(); i++) { const GDScriptParser::DictionaryNode::Pair &element = p_dictionary->elements[i]; - if (element.value->type == GDScriptParser::Node::ARRAY) { - const_fold_array(static_cast<GDScriptParser::ArrayNode *>(element.value), p_is_const); - } else if (element.value->type == GDScriptParser::Node::DICTIONARY) { - const_fold_dictionary(static_cast<GDScriptParser::DictionaryNode *>(element.value), p_is_const); + bool is_element_key_reduced = false; + Variant element_key = make_expression_reduced_value(element.key, is_element_key_reduced); + if (!is_element_key_reduced) { + return Variant(); } - if (!element.key->is_constant || !element.value->is_constant) { - return; + bool is_element_value_reduced = false; + Variant element_value = make_expression_reduced_value(element.value, is_element_value_reduced); + if (!is_element_value_reduced) { + return Variant(); } + + dictionary[element_key] = element_value; } - Dictionary dict; - for (int i = 0; i < p_dictionary->elements.size(); i++) { - const GDScriptParser::DictionaryNode::Pair &element = p_dictionary->elements[i]; - dict[element.key->reduced_value] = element.value->reduced_value; + dictionary.make_read_only(); + + is_reduced = true; + return dictionary; +} + +Variant GDScriptAnalyzer::make_subscript_reduced_value(GDScriptParser::SubscriptNode *p_subscript, bool &is_reduced) { + if (p_subscript->base == nullptr || p_subscript->index == nullptr) { + return Variant(); } - if (p_is_const) { - dict.make_read_only(); + + bool is_base_value_reduced = false; + Variant base_value = make_expression_reduced_value(p_subscript->base, is_base_value_reduced); + if (!is_base_value_reduced) { + return Variant(); } - p_dictionary->is_constant = true; - p_dictionary->reduced_value = dict; + + if (p_subscript->is_attribute) { + bool is_valid = false; + Variant value = base_value.get_named(p_subscript->attribute->name, is_valid); + if (is_valid) { + is_reduced = true; + return value; + } else { + return Variant(); + } + } else { + bool is_index_value_reduced = false; + Variant index_value = make_expression_reduced_value(p_subscript->index, is_index_value_reduced); + if (!is_index_value_reduced) { + return Variant(); + } + + bool is_valid = false; + Variant value = base_value.get(index_value, &is_valid); + if (is_valid) { + is_reduced = true; + return value; + } else { + return Variant(); + } + } +} + +Array GDScriptAnalyzer::make_array_from_element_datatype(const GDScriptParser::DataType &p_element_datatype, const GDScriptParser::Node *p_source_node) { + Array array; + + Ref<Script> script_type = p_element_datatype.script_type; + if (p_element_datatype.kind == GDScriptParser::DataType::CLASS && script_type.is_null()) { + Error err = OK; + Ref<GDScript> scr = GDScriptCache::get_shallow_script(p_element_datatype.script_path, err); + if (err) { + push_error(vformat(R"(Error while getting cache for script "%s".)", p_element_datatype.script_path), p_source_node); + return array; + } + script_type.reference_ptr(scr->find_class(p_element_datatype.class_type->fqcn)); + } + + array.set_typed(p_element_datatype.builtin_type, p_element_datatype.native_type, script_type); + + return array; +} + +Variant GDScriptAnalyzer::make_variable_default_value(GDScriptParser::VariableNode *p_variable) { + Variant result = Variant(); + + if (p_variable->initializer) { + bool is_initializer_value_reduced = false; + Variant initializer_value = make_expression_reduced_value(p_variable->initializer, is_initializer_value_reduced); + if (is_initializer_value_reduced) { + result = initializer_value; + } + } else { + GDScriptParser::DataType datatype = p_variable->get_datatype(); + if (datatype.is_hard_type() && datatype.kind == GDScriptParser::DataType::BUILTIN && datatype.builtin_type != Variant::OBJECT) { + if (datatype.builtin_type == Variant::ARRAY && datatype.has_container_element_type()) { + result = make_array_from_element_datatype(datatype.get_container_element_type()); + } else { + VariantInternal::initialize(&result, datatype.builtin_type); + } + } + } + + return result; } GDScriptParser::DataType GDScriptAnalyzer::type_from_variant(const Variant &p_value, const GDScriptParser::Node *p_source) { @@ -4049,8 +4387,9 @@ GDScriptParser::DataType GDScriptAnalyzer::type_from_metatype(const GDScriptPars return result; } -GDScriptParser::DataType GDScriptAnalyzer::type_from_property(const PropertyInfo &p_property, bool p_is_arg) const { +GDScriptParser::DataType GDScriptAnalyzer::type_from_property(const PropertyInfo &p_property, bool p_is_arg, bool p_is_readonly) const { GDScriptParser::DataType result; + result.is_read_only = p_is_readonly; result.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT; if (p_property.type == Variant::NIL && (p_is_arg || (p_property.usage & PROPERTY_USAGE_NIL_IS_VARIANT))) { // Variant @@ -4091,15 +4430,27 @@ GDScriptParser::DataType GDScriptAnalyzer::type_from_property(const PropertyInfo } elem_type.is_constant = false; result.set_container_element_type(elem_type); + } else if (p_property.type == Variant::INT) { + // Check if it's enum. + if (p_property.class_name != StringName()) { + Vector<String> names = String(p_property.class_name).split("."); + if (names.size() == 2) { + result = make_native_enum_type(names[1], names[0], false); + result.is_constant = false; + } + } } } return result; } -bool GDScriptAnalyzer::get_function_signature(GDScriptParser::Node *p_source, bool p_is_constructor, GDScriptParser::DataType p_base_type, const StringName &p_function, GDScriptParser::DataType &r_return_type, List<GDScriptParser::DataType> &r_par_types, int &r_default_arg_count, bool &r_static, bool &r_vararg) { +bool GDScriptAnalyzer::get_function_signature(GDScriptParser::Node *p_source, bool p_is_constructor, GDScriptParser::DataType p_base_type, const StringName &p_function, GDScriptParser::DataType &r_return_type, List<GDScriptParser::DataType> &r_par_types, int &r_default_arg_count, bool &r_static, bool &r_vararg, StringName *r_native_class) { r_static = false; r_vararg = false; r_default_arg_count = 0; + if (r_native_class) { + *r_native_class = StringName(); + } StringName function_name = p_function; bool was_enum = false; @@ -4234,6 +4585,12 @@ bool GDScriptAnalyzer::get_function_signature(GDScriptParser::Node *p_source, bo if (valid && Engine::get_singleton()->has_singleton(base_native)) { r_static = true; } +#ifdef DEBUG_ENABLED + MethodBind *native_method = ClassDB::get_method(base_native, function_name); + if (native_method && r_native_class) { + *r_native_class = native_method->get_instance_class(); + } +#endif return valid; } @@ -4433,14 +4790,8 @@ bool GDScriptAnalyzer::is_type_compatible(const GDScriptParser::DataType &p_targ } if (valid && p_target.builtin_type == Variant::ARRAY && p_source.builtin_type == Variant::ARRAY) { // Check the element type. - if (p_target.has_container_element_type()) { - if (!p_source.has_container_element_type()) { - // TODO: Maybe this is valid but unsafe? - // Variant array can't be appended to typed array. - valid = false; - } else { - valid = is_type_compatible(p_target.get_container_element_type(), p_source.get_container_element_type(), p_allow_implicit_conversion); - } + if (p_target.has_container_element_type() && p_source.has_container_element_type()) { + valid = p_target.get_container_element_type() == p_source.get_container_element_type(); } } return valid; @@ -4542,7 +4893,7 @@ bool GDScriptAnalyzer::is_type_compatible(const GDScriptParser::DataType &p_targ return ClassDB::is_parent_class(src_native, GDScript::get_class_static()); } while (src_class != nullptr) { - if (src_class->fqcn == p_target.class_type->fqcn) { + if (src_class == p_target.class_type || src_class->fqcn == p_target.class_type->fqcn) { return true; } src_class = src_class->base_type.class_type; @@ -4642,11 +4993,6 @@ Ref<GDScriptParserRef> GDScriptAnalyzer::get_parser_for(const String &p_path) { } Error GDScriptAnalyzer::resolve_inheritance() { - // Apply annotations. - for (GDScriptParser::AnnotationNode *&E : parser->head->annotations) { - resolve_annotation(E); - E->apply(parser, parser->head); - } return resolve_class_inheritance(parser->head, true); } @@ -4680,6 +5026,12 @@ Error GDScriptAnalyzer::analyze() { return err; } + // Apply annotations. + for (GDScriptParser::AnnotationNode *&E : parser->head->annotations) { + resolve_annotation(E); + E->apply(parser, parser->head); + } + resolve_interface(); resolve_body(); if (!parser->errors.is_empty()) { diff --git a/modules/gdscript/gdscript_analyzer.h b/modules/gdscript/gdscript_analyzer.h index cd2c4c6569..a4c84db6b9 100644 --- a/modules/gdscript/gdscript_analyzer.h +++ b/modules/gdscript/gdscript_analyzer.h @@ -37,6 +37,10 @@ #include "gdscript_cache.h" #include "gdscript_parser.h" +#ifdef TOOLS_ENABLED +#include "editor/project_converter_3_to_4.h" +#endif + class GDScriptAnalyzer { GDScriptParser *parser = nullptr; HashMap<String, Ref<GDScriptParserRef>> depended_parsers; @@ -102,22 +106,25 @@ class GDScriptAnalyzer { void reduce_ternary_op(GDScriptParser::TernaryOpNode *p_ternary_op, bool p_is_root = false); void reduce_unary_op(GDScriptParser::UnaryOpNode *p_unary_op); - void const_fold_array(GDScriptParser::ArrayNode *p_array, bool p_is_const); - void const_fold_dictionary(GDScriptParser::DictionaryNode *p_dictionary, bool p_is_const); + Variant make_expression_reduced_value(GDScriptParser::ExpressionNode *p_expression, bool &is_reduced); + Variant make_array_reduced_value(GDScriptParser::ArrayNode *p_array, bool &is_reduced); + Variant make_dictionary_reduced_value(GDScriptParser::DictionaryNode *p_dictionary, bool &is_reduced); + Variant make_subscript_reduced_value(GDScriptParser::SubscriptNode *p_subscript, bool &is_reduced); // Helpers. + Array make_array_from_element_datatype(const GDScriptParser::DataType &p_element_datatype, const GDScriptParser::Node *p_source_node = nullptr); GDScriptParser::DataType type_from_variant(const Variant &p_value, const GDScriptParser::Node *p_source); static GDScriptParser::DataType type_from_metatype(const GDScriptParser::DataType &p_meta_type); - GDScriptParser::DataType type_from_property(const PropertyInfo &p_property, bool p_is_arg = false) const; + GDScriptParser::DataType type_from_property(const PropertyInfo &p_property, bool p_is_arg = false, bool p_is_readonly = false) const; GDScriptParser::DataType make_global_class_meta_type(const StringName &p_class_name, const GDScriptParser::Node *p_source); - bool get_function_signature(GDScriptParser::Node *p_source, bool p_is_constructor, GDScriptParser::DataType base_type, const StringName &p_function, GDScriptParser::DataType &r_return_type, List<GDScriptParser::DataType> &r_par_types, int &r_default_arg_count, bool &r_static, bool &r_vararg); + bool get_function_signature(GDScriptParser::Node *p_source, bool p_is_constructor, GDScriptParser::DataType base_type, const StringName &p_function, GDScriptParser::DataType &r_return_type, List<GDScriptParser::DataType> &r_par_types, int &r_default_arg_count, bool &r_static, bool &r_vararg, StringName *r_native_class = nullptr); bool function_signature_from_info(const MethodInfo &p_info, GDScriptParser::DataType &r_return_type, List<GDScriptParser::DataType> &r_par_types, int &r_default_arg_count, bool &r_static, bool &r_vararg); void validate_call_arg(const List<GDScriptParser::DataType> &p_par_types, int p_default_args_count, bool p_is_vararg, const GDScriptParser::CallNode *p_call); void validate_call_arg(const MethodInfo &p_method, const GDScriptParser::CallNode *p_call); GDScriptParser::DataType get_operation_type(Variant::Operator p_operation, const GDScriptParser::DataType &p_a, const GDScriptParser::DataType &p_b, bool &r_valid, const GDScriptParser::Node *p_source); GDScriptParser::DataType get_operation_type(Variant::Operator p_operation, const GDScriptParser::DataType &p_a, bool &r_valid, const GDScriptParser::Node *p_source); void update_const_expression_builtin_type(GDScriptParser::ExpressionNode *p_expression, const GDScriptParser::DataType &p_type, const char *p_usage, bool p_is_cast = false); - void update_array_literal_element_type(const GDScriptParser::DataType &p_base_type, GDScriptParser::ArrayNode *p_array_literal); + void update_array_literal_element_type(GDScriptParser::ArrayNode *p_array, const GDScriptParser::DataType &p_element_type); bool is_type_compatible(const GDScriptParser::DataType &p_target, const GDScriptParser::DataType &p_source, bool p_allow_implicit_conversion = false, const GDScriptParser::Node *p_source_node = nullptr); void push_error(const String &p_message, const GDScriptParser::Node *p_origin = nullptr); void mark_node_unsafe(const GDScriptParser::Node *p_node); @@ -125,11 +132,18 @@ class GDScriptAnalyzer { void mark_lambda_use_self(); bool class_exists(const StringName &p_class) const; Ref<GDScriptParserRef> get_parser_for(const String &p_path); - static void reduce_identifier_from_base_set_class(GDScriptParser::IdentifierNode *p_identifier, GDScriptParser::DataType p_identifier_datatype); + void reduce_identifier_from_base_set_class(GDScriptParser::IdentifierNode *p_identifier, GDScriptParser::DataType p_identifier_datatype); #ifdef DEBUG_ENABLED bool is_shadowing(GDScriptParser::IdentifierNode *p_local, const String &p_context); #endif +#ifdef TOOLS_ENABLED +#ifndef DISABLE_DEPRECATED + const char *get_rename_from_map(const char *map[][2], String key); + const char *check_for_renamed_identifier(String identifier, GDScriptParser::Node::Type type); +#endif // DISABLE_DEPRECATED +#endif // TOOLS_ENABLED + public: Error resolve_inheritance(); Error resolve_interface(); @@ -137,6 +151,8 @@ public: Error resolve_dependencies(); Error analyze(); + Variant make_variable_default_value(GDScriptParser::VariableNode *p_variable); + GDScriptAnalyzer(GDScriptParser *p_parser); }; diff --git a/modules/gdscript/gdscript_byte_codegen.cpp b/modules/gdscript/gdscript_byte_codegen.cpp index e19dda090e..ec7a2b0f1c 100644 --- a/modules/gdscript/gdscript_byte_codegen.cpp +++ b/modules/gdscript/gdscript_byte_codegen.cpp @@ -826,9 +826,13 @@ void GDScriptByteCodeGenerator::write_assign_with_conversion(const Address &p_ta switch (p_target.type.kind) { case GDScriptDataType::BUILTIN: { if (p_target.type.builtin_type == Variant::ARRAY && p_target.type.has_container_element_type()) { + const GDScriptDataType &element_type = p_target.type.get_container_element_type(); append_opcode(GDScriptFunction::OPCODE_ASSIGN_TYPED_ARRAY); append(p_target); append(p_source); + append(get_constant_pos(element_type.script_type) | (GDScriptFunction::ADDR_TYPE_CONSTANT << GDScriptFunction::ADDR_BITS)); + append(element_type.builtin_type); + append(element_type.native_type); } else { append_opcode(GDScriptFunction::OPCODE_ASSIGN_TYPED_BUILTIN); append(p_target); @@ -868,9 +872,13 @@ void GDScriptByteCodeGenerator::write_assign_with_conversion(const Address &p_ta void GDScriptByteCodeGenerator::write_assign(const Address &p_target, const Address &p_source) { if (p_target.type.kind == GDScriptDataType::BUILTIN && p_target.type.builtin_type == Variant::ARRAY && p_target.type.has_container_element_type()) { + const GDScriptDataType &element_type = p_target.type.get_container_element_type(); append_opcode(GDScriptFunction::OPCODE_ASSIGN_TYPED_ARRAY); append(p_target); append(p_source); + append(get_constant_pos(element_type.script_type) | (GDScriptFunction::ADDR_TYPE_CONSTANT << GDScriptFunction::ADDR_BITS)); + append(element_type.builtin_type); + append(element_type.native_type); } else if (p_target.type.kind == GDScriptDataType::BUILTIN && p_source.type.kind == GDScriptDataType::BUILTIN && p_target.type.builtin_type != p_source.type.builtin_type) { // Need conversion. append_opcode(GDScriptFunction::OPCODE_ASSIGN_TYPED_BUILTIN); @@ -1326,14 +1334,7 @@ void GDScriptByteCodeGenerator::write_construct_typed_array(const Address &p_tar append(p_arguments[i]); } append(get_call_target(p_target)); - if (p_element_type.script_type) { - Variant script_type = Ref<Script>(p_element_type.script_type); - int addr = get_constant_pos(script_type); - addr |= GDScriptFunction::ADDR_TYPE_CONSTANT << GDScriptFunction::ADDR_BITS; - append(addr); - } else { - append(Address()); // null. - } + append(get_constant_pos(p_element_type.script_type) | (GDScriptFunction::ADDR_TYPE_CONSTANT << GDScriptFunction::ADDR_BITS)); append(p_arguments.size()); append(p_element_type.builtin_type); append(p_element_type.native_type); @@ -1608,14 +1609,10 @@ void GDScriptByteCodeGenerator::write_return(const Address &p_return_value) { if (function->return_type.kind == GDScriptDataType::BUILTIN && function->return_type.builtin_type == Variant::ARRAY && function->return_type.has_container_element_type()) { // Typed array. const GDScriptDataType &element_type = function->return_type.get_container_element_type(); - - Variant script = element_type.script_type; - int script_idx = get_constant_pos(script) | (GDScriptFunction::ADDR_TYPE_CONSTANT << GDScriptFunction::ADDR_BITS); - append_opcode(GDScriptFunction::OPCODE_RETURN_TYPED_ARRAY); append(p_return_value); - append(script_idx); - append(element_type.kind == GDScriptDataType::BUILTIN ? element_type.builtin_type : Variant::OBJECT); + append(get_constant_pos(element_type.script_type) | (GDScriptFunction::ADDR_TYPE_CONSTANT << GDScriptFunction::ADDR_BITS)); + append(element_type.builtin_type); append(element_type.native_type); } else if (function->return_type.kind == GDScriptDataType::BUILTIN && p_return_value.type.kind == GDScriptDataType::BUILTIN && function->return_type.builtin_type != p_return_value.type.builtin_type) { // Add conversion. @@ -1636,15 +1633,10 @@ void GDScriptByteCodeGenerator::write_return(const Address &p_return_value) { case GDScriptDataType::BUILTIN: { if (function->return_type.builtin_type == Variant::ARRAY && function->return_type.has_container_element_type()) { const GDScriptDataType &element_type = function->return_type.get_container_element_type(); - - Variant script = function->return_type.script_type; - int script_idx = get_constant_pos(script); - script_idx |= (GDScriptFunction::ADDR_TYPE_CONSTANT << GDScriptFunction::ADDR_BITS); - append_opcode(GDScriptFunction::OPCODE_RETURN_TYPED_ARRAY); append(p_return_value); - append(script_idx); - append(element_type.kind == GDScriptDataType::BUILTIN ? element_type.builtin_type : Variant::OBJECT); + append(get_constant_pos(element_type.script_type) | (GDScriptFunction::ADDR_TYPE_CONSTANT << GDScriptFunction::ADDR_BITS)); + append(element_type.builtin_type); append(element_type.native_type); } else { append_opcode(GDScriptFunction::OPCODE_RETURN_TYPED_BUILTIN); diff --git a/modules/gdscript/gdscript_compiler.cpp b/modules/gdscript/gdscript_compiler.cpp index c78dd1528f..46cd4b0d55 100644 --- a/modules/gdscript/gdscript_compiler.cpp +++ b/modules/gdscript/gdscript_compiler.cpp @@ -196,7 +196,11 @@ static bool _is_exact_type(const PropertyInfo &p_par_type, const GDScriptDataTyp } } -static bool _have_exact_arguments(const MethodBind *p_method, const Vector<GDScriptCodeGenerator::Address> &p_arguments) { +static bool _can_use_ptrcall(const MethodBind *p_method, const Vector<GDScriptCodeGenerator::Address> &p_arguments) { + if (p_method->is_vararg()) { + // ptrcall won't work with vararg methods. + return false; + } if (p_method->get_argument_count() != p_arguments.size()) { // ptrcall won't work with default arguments. return false; @@ -563,7 +567,7 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &code self.mode = GDScriptCodeGenerator::Address::SELF; MethodBind *method = ClassDB::get_method(codegen.script->native->get_name(), call->function_name); - if (_have_exact_arguments(method, arguments)) { + if (_can_use_ptrcall(method, arguments)) { // Exact arguments, use ptrcall. gen->write_call_ptrcall(result, self, method, arguments); } else { @@ -613,7 +617,7 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &code } if (ClassDB::class_exists(class_name) && ClassDB::has_method(class_name, call->function_name)) { MethodBind *method = ClassDB::get_method(class_name, call->function_name); - if (_have_exact_arguments(method, arguments)) { + if (_can_use_ptrcall(method, arguments)) { // Exact arguments, use ptrcall. gen->write_call_ptrcall(result, base, method, arguments); } else { @@ -1855,7 +1859,12 @@ Error GDScriptCompiler::_parse_block(CodeGen &codegen, const GDScriptParser::Sui } } - gen->write_return(return_value); + if (return_n->void_return) { + // Always return "null", even if the expression is a call to a void function. + gen->write_return(codegen.add_constant(Variant())); + } else { + gen->write_return(return_value); + } if (return_value.mode == GDScriptCodeGenerator::Address::TEMPORARY) { codegen.generator->pop_temporary(); } @@ -1900,14 +1909,6 @@ Error GDScriptCompiler::_parse_block(CodeGen &codegen, const GDScriptParser::Sui bool initialized = false; if (lv->initializer != nullptr) { - // For typed arrays we need to make sure this is already initialized correctly so typed assignment work. - if (local_type.has_type && local_type.builtin_type == Variant::ARRAY) { - if (local_type.has_container_element_type()) { - codegen.generator->write_construct_typed_array(local, local_type.get_container_element_type(), Vector<GDScriptCodeGenerator::Address>()); - } else { - codegen.generator->write_construct_array(local, Vector<GDScriptCodeGenerator::Address>()); - } - } GDScriptCodeGenerator::Address src_address = _parse_expression(codegen, err, lv->initializer); if (err) { return err; @@ -2048,14 +2049,6 @@ GDScriptFunction *GDScriptCompiler::_parse_function(Error &r_error, GDScript *p_ // Emit proper line change. codegen.generator->write_newline(field->initializer->start_line); - // For typed arrays we need to make sure this is already initialized correctly so typed assignment work. - if (field_type.has_type && field_type.builtin_type == Variant::ARRAY) { - if (field_type.has_container_element_type()) { - codegen.generator->write_construct_typed_array(dst_address, field_type.get_container_element_type(), Vector<GDScriptCodeGenerator::Address>()); - } else { - codegen.generator->write_construct_array(dst_address, Vector<GDScriptCodeGenerator::Address>()); - } - } GDScriptCodeGenerator::Address src_address = _parse_expression(codegen, r_error, field->initializer, false, true); if (r_error) { memdelete(codegen.generator); @@ -2096,17 +2089,6 @@ GDScriptFunction *GDScriptCompiler::_parse_function(Error &r_error, GDScript *p_ return nullptr; } GDScriptCodeGenerator::Address dst_addr = codegen.parameters[parameter->identifier->name]; - - // For typed arrays we need to make sure this is already initialized correctly so typed assignment work. - GDScriptDataType par_type = dst_addr.type; - if (par_type.has_type && par_type.builtin_type == Variant::ARRAY) { - if (par_type.has_container_element_type()) { - codegen.generator->write_construct_typed_array(dst_addr, par_type.get_container_element_type(), Vector<GDScriptCodeGenerator::Address>()); - } else { - codegen.generator->write_construct_array(dst_addr, Vector<GDScriptCodeGenerator::Address>()); - } - } - codegen.generator->write_assign_default_parameter(dst_addr, src_addr, parameter->use_conversion_assign); if (src_addr.mode == GDScriptCodeGenerator::Address::TEMPORARY) { codegen.generator->pop_temporary(); diff --git a/modules/gdscript/gdscript_disassembler.cpp b/modules/gdscript/gdscript_disassembler.cpp index b5c8a6f478..d4f4358ac1 100644 --- a/modules/gdscript/gdscript_disassembler.cpp +++ b/modules/gdscript/gdscript_disassembler.cpp @@ -317,7 +317,7 @@ void GDScriptFunction::disassemble(const Vector<String> &p_code_lines) const { text += " = "; text += DADDR(2); - incr += 3; + incr += 6; } break; case OPCODE_ASSIGN_TYPED_NATIVE: { text += "assign typed native ("; @@ -434,7 +434,7 @@ void GDScriptFunction::disassemble(const Vector<String> &p_code_lines) const { int instr_var_args = _code_ptr[++ip]; int argc = _code_ptr[ip + 1 + instr_var_args]; - Ref<Script> script_type = get_constant(_code_ptr[ip + argc + 2]); + Ref<Script> script_type = get_constant(_code_ptr[ip + argc + 2] & GDScriptFunction::ADDR_MASK); Variant::Type builtin_type = (Variant::Type)_code_ptr[ip + argc + 4]; StringName native_type = get_global_name(_code_ptr[ip + argc + 5]); @@ -463,7 +463,7 @@ void GDScriptFunction::disassemble(const Vector<String> &p_code_lines) const { text += "]"; - incr += 3 + argc; + incr += 6 + argc; } break; case OPCODE_CONSTRUCT_DICTIONARY: { int instr_var_args = _code_ptr[++ip]; diff --git a/modules/gdscript/gdscript_editor.cpp b/modules/gdscript/gdscript_editor.cpp index f88ac581ca..3543c0a79f 100644 --- a/modules/gdscript/gdscript_editor.cpp +++ b/modules/gdscript/gdscript_editor.cpp @@ -800,6 +800,15 @@ static void _find_annotation_arguments(const GDScriptParser::AnnotationNode *p_a warning.insert_text = warning.display.quote(p_quote_style); r_result.insert(warning.display, warning); } + } else if (p_annotation->name == SNAME("@rpc")) { + if (p_argument == 0 || p_argument == 1 || p_argument == 2) { + static const char *options[7] = { "call_local", "call_remote", "any_peer", "authority", "reliable", "unreliable", "unreliable_ordered" }; + for (int i = 0; i < 7; i++) { + ScriptLanguage::CodeCompletionOption option(options[i], ScriptLanguage::CODE_COMPLETION_KIND_PLAIN_TEXT); + option.insert_text = option.display.quote(p_quote_style); + r_result.insert(option.display, option); + } + } } } @@ -968,7 +977,7 @@ static void _find_identifiers_in_class(const GDScriptParser::ClassNode *p_class, } break; case GDScriptParser::ClassNode::Member::SIGNAL: - if (p_only_functions || outer) { + if (p_only_functions || outer || p_static) { continue; } option = ScriptLanguage::CodeCompletionOption(member.signal->identifier->name, ScriptLanguage::CODE_COMPLETION_KIND_SIGNAL, location); @@ -1024,6 +1033,14 @@ static void _find_identifiers_in_base(const GDScriptCompletionIdentifier &p_base ScriptLanguage::CodeCompletionOption option(E.name, ScriptLanguage::CODE_COMPLETION_KIND_MEMBER, location); r_result.insert(option.display, option); } + + List<MethodInfo> signals; + scr->get_script_signal_list(&signals); + for (const MethodInfo &E : signals) { + int location = p_recursion_depth + _get_signal_location(scr->get_class_name(), E.name); + ScriptLanguage::CodeCompletionOption option(E.name, ScriptLanguage::CODE_COMPLETION_KIND_SIGNAL, location); + r_result.insert(option.display, option); + } } HashMap<StringName, Variant> constants; scr->get_constants(&constants); @@ -1032,14 +1049,6 @@ static void _find_identifiers_in_base(const GDScriptCompletionIdentifier &p_base ScriptLanguage::CodeCompletionOption option(E.key.operator String(), ScriptLanguage::CODE_COMPLETION_KIND_CONSTANT, location); r_result.insert(option.display, option); } - - List<MethodInfo> signals; - scr->get_script_signal_list(&signals); - for (const MethodInfo &E : signals) { - int location = p_recursion_depth + _get_signal_location(scr->get_class_name(), E.name); - ScriptLanguage::CodeCompletionOption option(E.name, ScriptLanguage::CODE_COMPLETION_KIND_SIGNAL, location); - r_result.insert(option.display, option); - } } List<MethodInfo> methods; @@ -1084,14 +1093,6 @@ static void _find_identifiers_in_base(const GDScriptCompletionIdentifier &p_base r_result.insert(option.display, option); } - List<MethodInfo> signals; - ClassDB::get_signal_list(type, &signals); - for (const MethodInfo &E : signals) { - int location = p_recursion_depth + _get_signal_location(type, StringName(E.name)); - ScriptLanguage::CodeCompletionOption option(E.name, ScriptLanguage::CODE_COMPLETION_KIND_SIGNAL, location); - r_result.insert(option.display, option); - } - if (!base_type.is_meta_type || Engine::get_singleton()->has_singleton(type)) { List<PropertyInfo> pinfo; ClassDB::get_property_list(type, &pinfo); @@ -1106,6 +1107,14 @@ static void _find_identifiers_in_base(const GDScriptCompletionIdentifier &p_base ScriptLanguage::CodeCompletionOption option(E.name, ScriptLanguage::CODE_COMPLETION_KIND_MEMBER, location); r_result.insert(option.display, option); } + + List<MethodInfo> signals; + ClassDB::get_signal_list(type, &signals); + for (const MethodInfo &E : signals) { + int location = p_recursion_depth + _get_signal_location(type, StringName(E.name)); + ScriptLanguage::CodeCompletionOption option(E.name, ScriptLanguage::CODE_COMPLETION_KIND_SIGNAL, location); + r_result.insert(option.display, option); + } } } @@ -1953,17 +1962,19 @@ static bool _guess_identifier_type(GDScriptParser::CompletionContext &p_context, case GDScriptParser::DataType::CLASS: if (base_type.class_type->has_function(p_context.current_function->identifier->name)) { GDScriptParser::FunctionNode *parent_function = base_type.class_type->get_member(p_context.current_function->identifier->name).function; - const GDScriptParser::ParameterNode *parameter = parent_function->parameters[parent_function->parameters_indices[p_identifier]]; - if ((!id_type.is_set() || id_type.is_variant()) && parameter->get_datatype().is_hard_type()) { - id_type = parameter->get_datatype(); - } - if (parameter->initializer) { - GDScriptParser::CompletionContext c = p_context; - c.current_function = parent_function; - c.current_class = base_type.class_type; - c.base = nullptr; - if (_guess_expression_type(c, parameter->initializer, r_type)) { - return true; + if (parent_function->parameters_indices.has(p_identifier)) { + const GDScriptParser::ParameterNode *parameter = parent_function->parameters[parent_function->parameters_indices[p_identifier]]; + if ((!id_type.is_set() || id_type.is_variant()) && parameter->get_datatype().is_hard_type()) { + id_type = parameter->get_datatype(); + } + if (parameter->initializer) { + GDScriptParser::CompletionContext c = p_context; + c.current_function = parent_function; + c.current_class = base_type.class_type; + c.base = nullptr; + if (_guess_expression_type(c, parameter->initializer, r_type)) { + return true; + } } } } @@ -2020,6 +2031,7 @@ static bool _guess_identifier_type(GDScriptParser::CompletionContext &p_context, r_type.type.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT; r_type.type.script_path = script; r_type.type.class_type = parser->get_parser()->get_tree(); + r_type.type.is_meta_type = true; r_type.type.is_constant = false; r_type.type.kind = GDScriptParser::DataType::CLASS; r_type.value = Variant(); @@ -2131,6 +2143,7 @@ static bool _guess_identifier_type_from_base(GDScriptParser::CompletionContext & r_type.type.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT; r_type.type.kind = GDScriptParser::DataType::CLASS; r_type.type.class_type = member.m_class; + r_type.type.is_meta_type = true; return true; case GDScriptParser::ClassNode::Member::GROUP: return false; // No-op, but silences warnings. @@ -3478,6 +3491,14 @@ static Error _lookup_symbol_from_base(const GDScriptParser::DataType &p_base, co break; } + if (context.current_class) { + if (context.type != GDScriptParser::COMPLETION_SUPER_METHOD) { + base.type = context.current_class->get_datatype(); + } else { + base.type = context.current_class->base_type; + } + } + if (_lookup_symbol_from_base(base.type, p_symbol, is_function, r_result) == OK) { return OK; } diff --git a/modules/gdscript/gdscript_function.cpp b/modules/gdscript/gdscript_function.cpp index 71831a3a97..a6b4dc7981 100644 --- a/modules/gdscript/gdscript_function.cpp +++ b/modules/gdscript/gdscript_function.cpp @@ -296,6 +296,15 @@ void GDScriptFunctionState::_clear_stack() { } } +void GDScriptFunctionState::_clear_connections() { + List<Object::Connection> conns; + get_signals_connected_to_this(&conns); + + for (Object::Connection &c : conns) { + c.signal.disconnect(c.callable); + } +} + void GDScriptFunctionState::_bind_methods() { ClassDB::bind_method(D_METHOD("resume", "arg"), &GDScriptFunctionState::resume, DEFVAL(Variant())); ClassDB::bind_method(D_METHOD("is_valid", "extended_check"), &GDScriptFunctionState::is_valid, DEFVAL(false)); diff --git a/modules/gdscript/gdscript_function.h b/modules/gdscript/gdscript_function.h index 37416a734d..f45c1f9577 100644 --- a/modules/gdscript/gdscript_function.h +++ b/modules/gdscript/gdscript_function.h @@ -628,6 +628,7 @@ public: Variant resume(const Variant &p_arg = Variant()); void _clear_stack(); + void _clear_connections(); GDScriptFunctionState(); ~GDScriptFunctionState(); diff --git a/modules/gdscript/gdscript_parser.cpp b/modules/gdscript/gdscript_parser.cpp index 74d7f94d7c..0393a8c241 100644 --- a/modules/gdscript/gdscript_parser.cpp +++ b/modules/gdscript/gdscript_parser.cpp @@ -51,46 +51,8 @@ static HashMap<StringName, Variant::Type> builtin_types; Variant::Type GDScriptParser::get_builtin_type(const StringName &p_type) { if (builtin_types.is_empty()) { - builtin_types["bool"] = Variant::BOOL; - builtin_types["int"] = Variant::INT; - builtin_types["float"] = Variant::FLOAT; - builtin_types["String"] = Variant::STRING; - builtin_types["Vector2"] = Variant::VECTOR2; - builtin_types["Vector2i"] = Variant::VECTOR2I; - builtin_types["Rect2"] = Variant::RECT2; - builtin_types["Rect2i"] = Variant::RECT2I; - builtin_types["Transform2D"] = Variant::TRANSFORM2D; - builtin_types["Vector3"] = Variant::VECTOR3; - builtin_types["Vector3i"] = Variant::VECTOR3I; - builtin_types["Vector4"] = Variant::VECTOR4; - builtin_types["Vector4i"] = Variant::VECTOR4I; - builtin_types["AABB"] = Variant::AABB; - builtin_types["Plane"] = Variant::PLANE; - builtin_types["Quaternion"] = Variant::QUATERNION; - builtin_types["Basis"] = Variant::BASIS; - builtin_types["Transform3D"] = Variant::TRANSFORM3D; - builtin_types["Projection"] = Variant::PROJECTION; - builtin_types["Color"] = Variant::COLOR; - builtin_types["RID"] = Variant::RID; - builtin_types["Object"] = Variant::OBJECT; - builtin_types["StringName"] = Variant::STRING_NAME; - builtin_types["NodePath"] = Variant::NODE_PATH; - builtin_types["Dictionary"] = Variant::DICTIONARY; - builtin_types["Callable"] = Variant::CALLABLE; - builtin_types["Signal"] = Variant::SIGNAL; - builtin_types["Array"] = Variant::ARRAY; - builtin_types["PackedByteArray"] = Variant::PACKED_BYTE_ARRAY; - builtin_types["PackedInt32Array"] = Variant::PACKED_INT32_ARRAY; - builtin_types["PackedInt64Array"] = Variant::PACKED_INT64_ARRAY; - builtin_types["PackedFloat32Array"] = Variant::PACKED_FLOAT32_ARRAY; - builtin_types["PackedFloat64Array"] = Variant::PACKED_FLOAT64_ARRAY; - builtin_types["PackedStringArray"] = Variant::PACKED_STRING_ARRAY; - builtin_types["PackedVector2Array"] = Variant::PACKED_VECTOR2_ARRAY; - builtin_types["PackedVector3Array"] = Variant::PACKED_VECTOR3_ARRAY; - builtin_types["PackedColorArray"] = Variant::PACKED_COLOR_ARRAY; - // NIL is not here, hence the -1. - if (builtin_types.size() != Variant::VARIANT_MAX - 1) { - ERR_PRINT("Outdated parser: amount of built-in types don't match the amount of types in Variant."); + for (int i = 1; i < Variant::VARIANT_MAX; i++) { + builtin_types[Variant::get_type_name((Variant::Type)i)] = (Variant::Type)i; } } @@ -122,7 +84,7 @@ GDScriptParser::GDScriptParser() { register_annotation(MethodInfo("@onready"), AnnotationInfo::VARIABLE, &GDScriptParser::onready_annotation); // Export annotations. register_annotation(MethodInfo("@export"), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_NONE, Variant::NIL>); - register_annotation(MethodInfo("@export_enum", PropertyInfo(Variant::STRING, "names")), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_ENUM, Variant::INT>, varray(), true); + register_annotation(MethodInfo("@export_enum", PropertyInfo(Variant::STRING, "names")), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_ENUM, Variant::NIL>, varray(), true); register_annotation(MethodInfo("@export_file", PropertyInfo(Variant::STRING, "filter")), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_FILE, Variant::STRING>, varray(""), true); register_annotation(MethodInfo("@export_dir"), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_DIR, Variant::STRING>); register_annotation(MethodInfo("@export_global_file", PropertyInfo(Variant::STRING, "filter")), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_GLOBAL_FILE, Variant::STRING>, varray(""), true); @@ -147,7 +109,7 @@ GDScriptParser::GDScriptParser() { // Warning annotations. register_annotation(MethodInfo("@warning_ignore", PropertyInfo(Variant::STRING, "warning")), AnnotationInfo::CLASS | AnnotationInfo::VARIABLE | AnnotationInfo::SIGNAL | AnnotationInfo::CONSTANT | AnnotationInfo::FUNCTION | AnnotationInfo::STATEMENT, &GDScriptParser::warning_annotations, varray(), true); // Networking. - register_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("", "", "", 0), true); + register_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", "unreliable", 0), true); #ifdef DEBUG_ENABLED is_ignoring_warnings = !(bool)GLOBAL_GET("debug/gdscript/warnings/enable"); @@ -196,14 +158,10 @@ void GDScriptParser::push_warning(const Node *p_source, GDScriptWarning::Code p_ return; } - if (ignored_warning_codes.has(p_code)) { + if (ignored_warnings.has(p_code)) { return; } - String warn_name = GDScriptWarning::get_name_from_code((GDScriptWarning::Code)p_code).to_lower(); - if (ignored_warnings.has(warn_name)) { - return; - } int warn_level = (int)GLOBAL_GET(GDScriptWarning::get_settings_path_from_code(p_code)); if (!warn_level) { return; @@ -218,7 +176,7 @@ void GDScriptParser::push_warning(const Node *p_source, GDScriptWarning::Code p_ warning.rightmost_column = p_source->rightmost_column; if (warn_level == GDScriptWarning::WarnLevel::ERROR || bool(GLOBAL_GET("debug/gdscript/warnings/treat_warnings_as_errors"))) { - push_error(warning.get_message(), p_source); + push_error(warning.get_message() + String(" (Warning treated as error.)"), p_source); return; } @@ -529,7 +487,12 @@ void GDScriptParser::parse_program() { AnnotationNode *annotation = parse_annotation(AnnotationInfo::SCRIPT | AnnotationInfo::STANDALONE | AnnotationInfo::CLASS_LEVEL); if (annotation != nullptr) { if (annotation->applies_to(AnnotationInfo::SCRIPT)) { - head->annotations.push_back(annotation); + // `@icon` needs to be applied in the parser. See GH-72444. + if (annotation->name == SNAME("@icon")) { + annotation->apply(this, head); + } else { + head->annotations.push_back(annotation); + } } else { annotation_stack.push_back(annotation); // This annotation must appear after script-level annotations @@ -840,14 +803,19 @@ void GDScriptParser::parse_class_body(bool p_is_multiline) { case GDScriptTokenizer::Token::ANNOTATION: { advance(); - // Check for class-level annotations. + // Check for standalone and class-level annotations. AnnotationNode *annotation = parse_annotation(AnnotationInfo::STANDALONE | AnnotationInfo::CLASS_LEVEL); if (annotation != nullptr) { if (annotation->applies_to(AnnotationInfo::STANDALONE)) { if (previous.type != GDScriptTokenizer::Token::NEWLINE) { push_error(R"(Expected newline after a standalone annotation.)"); } - head->annotations.push_back(annotation); + if (annotation->name == SNAME("@export_category") || annotation->name == SNAME("@export_group") || annotation->name == SNAME("@export_subgroup")) { + current_class->add_member_group(annotation); + } else { + // For potential non-group standalone annotations. + push_error(R"(Unexpected standalone annotation in class body.)"); + } } else { annotation_stack.push_back(annotation); } @@ -977,14 +945,14 @@ GDScriptParser::VariableNode *GDScriptParser::parse_property(VariableNode *p_var // Run with a loop because order doesn't matter. for (int i = 0; i < 2; i++) { - if (function->name == "set") { + if (function->name == SNAME("set")) { if (setter_used) { push_error(R"(Properties can only have one setter.)"); } else { parse_property_setter(property); setter_used = true; } - } else if (function->name == "get") { + } else if (function->name == SNAME("get")) { if (getter_used) { push_error(R"(Properties can only have one getter.)"); } else { @@ -1469,7 +1437,7 @@ GDScriptParser::AnnotationNode *GDScriptParser::parse_annotation(uint32_t p_vali match(GDScriptTokenizer::Token::NEWLINE); // Newline after annotation is optional. if (valid) { - valid = validate_annotation_argument_count(annotation); + valid = validate_annotation_arguments(annotation); } return valid ? annotation : nullptr; @@ -1865,10 +1833,18 @@ GDScriptParser::IfNode *GDScriptParser::parse_if(const String &p_token) { if (match(GDScriptTokenizer::Token::ELIF)) { SuiteNode *else_block = alloc_node<SuiteNode>(); + else_block->parent_function = current_function; + else_block->parent_block = current_suite; + + SuiteNode *previous_suite = current_suite; + current_suite = else_block; + IfNode *elif = parse_if("elif"); else_block->statements.push_back(elif); complete_extents(else_block); n_if->false_block = else_block; + + current_suite = previous_suite; } else if (match(GDScriptTokenizer::Token::ELSE)) { consume(GDScriptTokenizer::Token::COLON, R"(Expected ":" after "else".)"); n_if->false_block = parse_suite(R"("else" block)"); @@ -1901,10 +1877,8 @@ GDScriptParser::MatchNode *GDScriptParser::parse_match() { return match; } -#ifdef DEBUG_ENABLED bool all_have_return = true; bool have_wildcard = false; -#endif while (!check(GDScriptTokenizer::Token::DEDENT) && !is_at_end()) { MatchBranchNode *branch = parse_match_branch(); @@ -1917,21 +1891,19 @@ GDScriptParser::MatchNode *GDScriptParser::parse_match() { if (have_wildcard && !branch->patterns.is_empty()) { push_warning(branch->patterns[0], GDScriptWarning::UNREACHABLE_PATTERN); } +#endif have_wildcard = have_wildcard || branch->has_wildcard; all_have_return = all_have_return && branch->block->has_return; -#endif match->branches.push_back(branch); } complete_extents(match); consume(GDScriptTokenizer::Token::DEDENT, R"(Expected an indented block after "match" statement.)"); -#ifdef DEBUG_ENABLED if (all_have_return && have_wildcard) { current_suite->has_return = true; } -#endif return match; } @@ -2173,7 +2145,12 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_precedence(Precedence p_pr make_completion_context(COMPLETION_IDENTIFIER, nullptr); GDScriptTokenizer::Token token = current; - ParseFunction prefix_rule = get_rule(token.type)->prefix; + GDScriptTokenizer::Token::Type token_type = token.type; + if (token.is_identifier()) { + // Allow keywords that can be treated as identifiers. + token_type = GDScriptTokenizer::Token::IDENTIFIER; + } + ParseFunction prefix_rule = get_rule(token_type)->prefix; if (prefix_rule == nullptr) { // Expected expression. Let the caller give the proper error message. @@ -2920,7 +2897,7 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_call(ExpressionNode *p_pre // Arguments. CompletionType ct = COMPLETION_CALL_ARGUMENTS; - if (call->function_name == "load") { + if (call->function_name == SNAME("load")) { ct = COMPLETION_RESOURCE_PATH; } push_completion_call(call); @@ -3038,7 +3015,14 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_get_node(ExpressionNode *p path_state = PATH_STATE_NODE_NAME; } else if (current.is_node_name()) { advance(); - get_node->full_path += previous.get_identifier(); + String identifier = previous.get_identifier(); +#ifdef DEBUG_ENABLED + // Check spoofing. + if (TS->has_feature(TextServer::FEATURE_UNICODE_SECURITY) && TS->spoof_check(identifier)) { + push_warning(get_node, GDScriptWarning::CONFUSABLE_IDENTIFIER, identifier); + } +#endif + get_node->full_path += identifier; path_state = PATH_STATE_NODE_NAME; } else if (!check(GDScriptTokenizer::Token::SLASH) && !check(GDScriptTokenizer::Token::PERCENT)) { @@ -3580,7 +3564,11 @@ const GDScriptParser::SuiteNode::Local &GDScriptParser::SuiteNode::get_local(con return empty; } -bool GDScriptParser::AnnotationNode::apply(GDScriptParser *p_this, Node *p_target) const { +bool GDScriptParser::AnnotationNode::apply(GDScriptParser *p_this, Node *p_target) { + if (is_applied) { + return true; + } + is_applied = true; return (p_this->*(p_this->valid_annotations[name].apply))(this, p_target); } @@ -3588,7 +3576,7 @@ bool GDScriptParser::AnnotationNode::applies_to(uint32_t p_target_kinds) const { return (info->target_kind & p_target_kinds) > 0; } -bool GDScriptParser::validate_annotation_argument_count(AnnotationNode *p_annotation) { +bool GDScriptParser::validate_annotation_arguments(AnnotationNode *p_annotation) { ERR_FAIL_COND_V_MSG(!valid_annotations.has(p_annotation->name), false, vformat(R"(Annotation "%s" not found to validate.)", p_annotation->name)); const MethodInfo &info = valid_annotations[p_annotation->name].info; @@ -3603,6 +3591,27 @@ bool GDScriptParser::validate_annotation_argument_count(AnnotationNode *p_annota return false; } + // `@icon`'s argument needs to be resolved in the parser. See GH-72444. + if (p_annotation->name == SNAME("@icon")) { + ExpressionNode *argument = p_annotation->arguments[0]; + + if (argument->type != Node::LITERAL) { + push_error(R"(Argument 1 of annotation "@icon" must be a string literal.)", argument); + return false; + } + + Variant value = static_cast<LiteralNode *>(argument)->value; + + if (value.get_type() != Variant::STRING) { + push_error(R"(Argument 1 of annotation "@icon" must be a string literal.)", argument); + return false; + } + + p_annotation->resolved_arguments.push_back(value); + } + + // For other annotations, see `GDScriptAnalyzer::resolve_annotation()`. + return true; } @@ -3613,6 +3622,7 @@ bool GDScriptParser::tool_annotation(const AnnotationNode *p_annotation, Node *p bool GDScriptParser::icon_annotation(const AnnotationNode *p_annotation, Node *p_node) { ERR_FAIL_COND_V_MSG(p_node->type != Node::CLASS, false, R"("@icon" annotation can only be applied to classes.)"); + ERR_FAIL_COND_V(p_annotation->resolved_arguments.is_empty(), false); ClassNode *p_class = static_cast<ClassNode *>(p_node); p_class->icon_path = p_annotation->resolved_arguments[0]; return true; @@ -3621,6 +3631,10 @@ bool GDScriptParser::icon_annotation(const AnnotationNode *p_annotation, Node *p bool GDScriptParser::onready_annotation(const AnnotationNode *p_annotation, Node *p_node) { ERR_FAIL_COND_V_MSG(p_node->type != Node::VARIABLE, false, R"("@onready" annotation can only be applied to class variables.)"); + if (current_class && !ClassDB::is_parent_class(current_class->get_datatype().native_type, SNAME("Node"))) { + push_error(R"("@onready" can only be used in classes that inherit "Node".)", p_annotation); + } + VariableNode *variable = static_cast<VariableNode *>(p_node); if (variable->onready) { push_error(R"("@onready" annotation can only be used once per variable.)"); @@ -3635,15 +3649,6 @@ template <PropertyHint t_hint, Variant::Type t_type> bool GDScriptParser::export_annotations(const AnnotationNode *p_annotation, Node *p_node) { ERR_FAIL_COND_V_MSG(p_node->type != Node::VARIABLE, false, vformat(R"("%s" annotation can only be applied to variables.)", p_annotation->name)); - { - const int max_flags = 32; - - if (t_hint == PropertyHint::PROPERTY_HINT_FLAGS && p_annotation->resolved_arguments.size() > max_flags) { - push_error(vformat(R"(The argument count limit for "@export_flags" is exceeded (%d/%d).)", p_annotation->resolved_arguments.size(), max_flags), p_annotation); - return false; - } - } - VariableNode *variable = static_cast<VariableNode *>(p_node); if (variable->exported) { push_error(vformat(R"(Annotation "%s" cannot be used with another "@export" annotation.)", p_annotation->name), p_annotation); @@ -3657,10 +3662,50 @@ bool GDScriptParser::export_annotations(const AnnotationNode *p_annotation, Node String hint_string; for (int i = 0; i < p_annotation->resolved_arguments.size(); i++) { + String arg_string = String(p_annotation->resolved_arguments[i]); + + if (p_annotation->name != SNAME("@export_placeholder")) { + if (arg_string.is_empty()) { + push_error(vformat(R"(Argument %d of annotation "%s" is empty.)", i + 1, p_annotation->name), p_annotation->arguments[i]); + return false; + } + if (arg_string.contains(",")) { + push_error(vformat(R"(Argument %d of annotation "%s" contains a comma. Use separate arguments instead.)", i + 1, p_annotation->name), p_annotation->arguments[i]); + return false; + } + } + + if (p_annotation->name == SNAME("@export_flags")) { + const int64_t max_flags = 32; + Vector<String> t = arg_string.split(":", true, 1); + if (t[0].is_empty()) { + push_error(vformat(R"(Invalid argument %d of annotation "@export_flags": Expected flag name.)", i + 1), p_annotation->arguments[i]); + return false; + } + if (t.size() == 2) { + if (t[1].is_empty()) { + push_error(vformat(R"(Invalid argument %d of annotation "@export_flags": Expected flag value.)", i + 1), p_annotation->arguments[i]); + return false; + } + if (!t[1].is_valid_int()) { + push_error(vformat(R"(Invalid argument %d of annotation "@export_flags": The flag value must be a valid integer.)", i + 1), p_annotation->arguments[i]); + return false; + } + int64_t value = t[1].to_int(); + if (value < 1 || value >= (1LL << max_flags)) { + push_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]); + return false; + } + } else if (i >= max_flags) { + push_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]); + return false; + } + } + if (i > 0) { hint_string += ","; } - hint_string += String(p_annotation->resolved_arguments[i]); + hint_string += arg_string; } variable->export_info.hint_string = hint_string; @@ -3691,6 +3736,13 @@ bool GDScriptParser::export_annotations(const AnnotationNode *p_annotation, Node variable->export_info.type = Variant::DICTIONARY; return true; + } else if (export_type.builtin_type == Variant::PACKED_STRING_ARRAY) { + String hint_prefix = itos(Variant::STRING) + "/" + itos(variable->export_info.hint); + variable->export_info.hint = PROPERTY_HINT_TYPE_STRING; + variable->export_info.hint_string = hint_prefix + ":" + variable->export_info.hint_string; + variable->export_info.type = Variant::PACKED_STRING_ARRAY; + + return true; } } @@ -3801,6 +3853,24 @@ bool GDScriptParser::export_annotations(const AnnotationNode *p_annotation, Node variable->export_info.hint_string = hint_prefix + ":" + variable->export_info.hint_string; variable->export_info.type = Variant::ARRAY; } + } else if (p_annotation->name == SNAME("@export_enum")) { + Variant::Type enum_type = Variant::INT; + + if (export_type.kind == DataType::BUILTIN && export_type.builtin_type == Variant::STRING) { + enum_type = Variant::STRING; + } else if (export_type.is_variant() && variable->initializer != nullptr) { + DataType initializer_type = variable->initializer->get_datatype(); + if (initializer_type.kind == DataType::BUILTIN && initializer_type.builtin_type == Variant::STRING) { + enum_type = Variant::STRING; + } + } + + variable->export_info.type = enum_type; + + if (!export_type.is_variant() && (export_type.kind != DataType::BUILTIN || export_type.builtin_type != enum_type)) { + push_error(vformat(R"("@export_enum" annotation requires a variable of type "int" or "String" but type "%s" was given instead.)", export_type.to_string()), variable); + return false; + } } else { // Validate variable type with export. if (!export_type.is_variant() && (export_type.kind != DataType::BUILTIN || export_type.builtin_type != t_type)) { @@ -3819,6 +3889,10 @@ template <PropertyUsageFlags t_usage> bool GDScriptParser::export_group_annotations(const AnnotationNode *p_annotation, Node *p_node) { AnnotationNode *annotation = const_cast<AnnotationNode *>(p_annotation); + if (annotation->resolved_arguments.is_empty()) { + return false; + } + annotation->export_info.name = annotation->resolved_arguments[0]; switch (t_usage) { @@ -3841,7 +3915,6 @@ bool GDScriptParser::export_group_annotations(const AnnotationNode *p_annotation } break; } - current_class->add_member_group(annotation); return true; } @@ -3877,7 +3950,7 @@ bool GDScriptParser::rpc_annotation(const AnnotationNode *p_annotation, Node *p_ Dictionary rpc_config; rpc_config["rpc_mode"] = MultiplayerAPI::RPC_MODE_AUTHORITY; - if (p_annotation->resolved_arguments.size()) { + if (!p_annotation->resolved_arguments.is_empty()) { 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(); @@ -3887,26 +3960,46 @@ bool GDScriptParser::rpc_annotation(const AnnotationNode *p_annotation, Node *p_ 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; } + + unsigned char locality_args = 0; + unsigned char permission_args = 0; + unsigned char transfer_mode_args = 0; + for (int i = last; i >= 0; i--) { - String mode = p_annotation->resolved_arguments[i].operator String(); - if (mode == "any_peer") { - rpc_config["rpc_mode"] = MultiplayerAPI::RPC_MODE_ANY_PEER; - } else if (mode == "authority") { - rpc_config["rpc_mode"] = MultiplayerAPI::RPC_MODE_AUTHORITY; - } else if (mode == "call_local") { + String arg = p_annotation->resolved_arguments[i].operator String(); + if (arg == "call_local") { + locality_args++; rpc_config["call_local"] = true; - } else if (mode == "call_remote") { + } else if (arg == "call_remote") { + locality_args++; rpc_config["call_local"] = false; - } else if (mode == "reliable") { + } else if (arg == "any_peer") { + permission_args++; + rpc_config["rpc_mode"] = MultiplayerAPI::RPC_MODE_ANY_PEER; + } else if (arg == "authority") { + permission_args++; + rpc_config["rpc_mode"] = MultiplayerAPI::RPC_MODE_AUTHORITY; + } else if (arg == "reliable") { + transfer_mode_args++; rpc_config["transfer_mode"] = MultiplayerPeer::TRANSFER_MODE_RELIABLE; - } else if (mode == "unreliable") { + } else if (arg == "unreliable") { + transfer_mode_args++; rpc_config["transfer_mode"] = MultiplayerPeer::TRANSFER_MODE_UNRELIABLE; - } else if (mode == "unreliable_ordered") { + } else if (arg == "unreliable_ordered") { + transfer_mode_args++; rpc_config["transfer_mode"] = MultiplayerPeer::TRANSFER_MODE_UNRELIABLE_ORDERED; } else { - push_error(R"(Invalid RPC argument. Must be one of: 'call_local'/'call_remote' (local calls), 'any_peer'/'authority' (permission), 'reliable'/'unreliable'/'unreliable_ordered' (transfer mode).)", p_annotation); + push_error(R"(Invalid RPC argument. Must be one of: "call_local"/"call_remote" (local calls), "any_peer"/"authority" (permission), "reliable"/"unreliable"/"unreliable_ordered" (transfer mode).)", p_annotation); } } + + if (locality_args > 1) { + push_error(R"(Invalid RPC config. The locality ("call_local"/"call_remote") must be specified no more than once.)", p_annotation); + } else if (permission_args > 1) { + push_error(R"(Invalid RPC config. The permission ("any_peer"/"authority") must be specified no more than once.)", p_annotation); + } else if (transfer_mode_args > 1) { + push_error(R"(Invalid RPC config. The transfer mode ("reliable"/"unreliable"/"unreliable_ordered") must be specified no more than once.)", p_annotation); + } } function->rpc_config = rpc_config; return true; diff --git a/modules/gdscript/gdscript_parser.h b/modules/gdscript/gdscript_parser.h index 74e12d0b5e..0ba0d5b6da 100644 --- a/modules/gdscript/gdscript_parser.h +++ b/modules/gdscript/gdscript_parser.h @@ -122,6 +122,7 @@ public: TypeSource type_source = UNDETECTED; bool is_constant = false; + bool is_read_only = false; bool is_meta_type = false; bool is_coroutine = false; // For function calls. @@ -190,7 +191,7 @@ public: case SCRIPT: return script_type == p_other.script_type; case CLASS: - return class_type == p_other.class_type; + return class_type == p_other.class_type || class_type->fqcn == p_other.class_type->fqcn; case RESOLVING: case UNRESOLVED: break; @@ -206,6 +207,7 @@ public: void operator=(const DataType &p_other) { kind = p_other.kind; type_source = p_other.type_source; + is_read_only = p_other.is_read_only; is_constant = p_other.is_constant; is_meta_type = p_other.is_meta_type; is_coroutine = p_other.is_coroutine; @@ -297,7 +299,9 @@ public: int leftmost_column = 0, rightmost_column = 0; Node *next = nullptr; List<AnnotationNode *> annotations; - Vector<uint32_t> ignored_warnings; +#ifdef DEBUG_ENABLED + Vector<GDScriptWarning::Code> ignored_warnings; +#endif DataType datatype; @@ -329,8 +333,10 @@ public: AnnotationInfo *info = nullptr; PropertyInfo export_info; + bool is_resolved = false; + bool is_applied = false; - bool apply(GDScriptParser *p_this, Node *p_target) const; + bool apply(GDScriptParser *p_this, Node *p_target); bool applies_to(uint32_t p_target_kinds) const; AnnotationNode() { @@ -970,6 +976,7 @@ public: struct ReturnNode : public Node { ExpressionNode *return_value = nullptr; + bool void_return = false; ReturnNode() { type = RETURN; @@ -1262,8 +1269,7 @@ private: #ifdef DEBUG_ENABLED bool is_ignoring_warnings = false; List<GDScriptWarning> warnings; - HashSet<String> ignored_warnings; - HashSet<uint32_t> ignored_warning_codes; + HashSet<GDScriptWarning::Code> ignored_warnings; HashSet<int> unsafe_lines; #endif @@ -1401,7 +1407,7 @@ private: // Annotations AnnotationNode *parse_annotation(uint32_t p_valid_targets); bool register_annotation(const MethodInfo &p_info, uint32_t p_target_kinds, AnnotationAction p_apply, const Vector<Variant> &p_default_arguments = Vector<Variant>(), bool p_is_vararg = false); - bool validate_annotation_argument_count(AnnotationNode *p_annotation); + bool validate_annotation_arguments(AnnotationNode *p_annotation); void clear_unused_annotations(); bool tool_annotation(const AnnotationNode *p_annotation, Node *p_target); bool icon_annotation(const AnnotationNode *p_annotation, Node *p_target); diff --git a/modules/gdscript/gdscript_tokenizer.cpp b/modules/gdscript/gdscript_tokenizer.cpp index d7f1114fd3..d586380c41 100644 --- a/modules/gdscript/gdscript_tokenizer.cpp +++ b/modules/gdscript/gdscript_tokenizer.cpp @@ -168,7 +168,11 @@ bool GDScriptTokenizer::Token::is_identifier() const { switch (type) { case IDENTIFIER: case MATCH: // Used in String.match(). - case CONST_INF: // Used in Vector{2,3,4}.INF + // Allow constants to be treated as regular identifiers. + case CONST_PI: + case CONST_INF: + case CONST_NAN: + case CONST_TAU: return true; default: return false; @@ -188,6 +192,10 @@ bool GDScriptTokenizer::Token::is_node_name() const { case CLASS_NAME: case CLASS: case CONST: + case CONST_PI: + case CONST_INF: + case CONST_NAN: + case CONST_TAU: case CONTINUE: case ELIF: case ELSE: @@ -530,9 +538,12 @@ void GDScriptTokenizer::make_keyword_list() { #endif // DEBUG_ENABLED GDScriptTokenizer::Token GDScriptTokenizer::potential_identifier() { + bool only_ascii = _peek(-1) < 128; + // Consume all identifier characters. while (is_unicode_identifier_continue(_peek())) { - _advance(); + char32_t c = _advance(); + only_ascii = only_ascii && c < 128; } int len = _current - _start; @@ -587,7 +598,7 @@ GDScriptTokenizer::Token GDScriptTokenizer::potential_identifier() { #ifdef DEBUG_ENABLED // Additional checks for identifiers but only in debug and if it's available in TextServer. - if (TS->has_feature(TextServer::FEATURE_UNICODE_SECURITY)) { + if (!only_ascii && TS->has_feature(TextServer::FEATURE_UNICODE_SECURITY)) { int64_t confusable = TS->is_confusable(name, keyword_list); if (confusable >= 0) { push_error(vformat(R"(Identifier "%s" is visually similar to the GDScript keyword "%s" and thus not allowed.)", name, keyword_list[confusable])); diff --git a/modules/gdscript/gdscript_utility_functions.cpp b/modules/gdscript/gdscript_utility_functions.cpp index 10d83dcfe5..758b61bb31 100644 --- a/modules/gdscript/gdscript_utility_functions.cpp +++ b/modules/gdscript/gdscript_utility_functions.cpp @@ -112,28 +112,6 @@ struct GDScriptUtilityFunctionsDefinitions { *r_ret = String(result); } - static inline void str(Variant *r_ret, const Variant **p_args, int p_arg_count, Callable::CallError &r_error) { - if (p_arg_count < 1) { - r_error.error = Callable::CallError::CALL_ERROR_TOO_FEW_ARGUMENTS; - r_error.argument = 1; - r_error.expected = 1; - *r_ret = Variant(); - return; - } - - String str; - for (int i = 0; i < p_arg_count; i++) { - String os = p_args[i]->operator String(); - - if (i == 0) { - str = os; - } else { - str += os; - } - } - *r_ret = str; - } - static inline void range(Variant *r_ret, const Variant **p_args, int p_arg_count, Callable::CallError &r_error) { switch (p_arg_count) { case 0: { @@ -651,7 +629,6 @@ void GDScriptUtilityFunctions::register_functions() { REGISTER_VARIANT_FUNC(convert, true, VARARG("what"), ARG("type", Variant::INT)); REGISTER_FUNC(type_exists, true, Variant::BOOL, ARG("type", Variant::STRING_NAME)); REGISTER_FUNC(_char, true, Variant::STRING, ARG("char", Variant::INT)); - REGISTER_VARARG_FUNC(str, true, Variant::STRING); REGISTER_VARARG_FUNC(range, false, Variant::ARRAY); REGISTER_CLASS_FUNC(load, false, "Resource", ARG("path", Variant::STRING)); REGISTER_FUNC(inst_to_dict, false, Variant::DICTIONARY, ARG("instance", Variant::OBJECT)); diff --git a/modules/gdscript/gdscript_vm.cpp b/modules/gdscript/gdscript_vm.cpp index 4ea4438b5e..6c26e226a5 100644 --- a/modules/gdscript/gdscript_vm.cpp +++ b/modules/gdscript/gdscript_vm.cpp @@ -47,6 +47,16 @@ static String _get_script_name(const Ref<Script> p_script) { } } +static String _get_element_type(Variant::Type builtin_type, const StringName &native_type, const Ref<Script> &script_type) { + if (script_type.is_valid() && script_type->is_valid()) { + return _get_script_name(script_type); + } else if (native_type != StringName()) { + return native_type.operator String(); + } else { + return Variant::get_type_name(builtin_type); + } +} + static String _get_var_type(const Variant *p_var) { String basestr; @@ -75,15 +85,8 @@ static String _get_var_type(const Variant *p_var) { basestr = "Array"; const Array *p_array = VariantInternal::get_array(p_var); Variant::Type builtin_type = (Variant::Type)p_array->get_typed_builtin(); - StringName native_type = p_array->get_typed_class_name(); - Ref<Script> script_type = p_array->get_typed_script(); - - if (script_type.is_valid() && script_type->is_valid()) { - basestr += "[" + _get_script_name(script_type) + "]"; - } else if (native_type != StringName()) { - basestr += "[" + native_type.operator String() + "]"; - } else if (builtin_type != Variant::NIL) { - basestr += "[" + Variant::get_type_name(builtin_type) + "]"; + if (builtin_type != Variant::NIL) { + basestr += "[" + _get_element_type(builtin_type, p_array->get_typed_class_name(), p_array->get_typed_script()) + "]"; } } else { basestr = Variant::get_type_name(p_var->get_type()); @@ -101,10 +104,7 @@ Variant GDScriptFunction::_get_default_variant_for_data_type(const GDScriptDataT // Typed array. if (p_data_type.has_container_element_type()) { const GDScriptDataType &element_type = p_data_type.get_container_element_type(); - array.set_typed( - element_type.kind == GDScriptDataType::BUILTIN ? element_type.builtin_type : Variant::OBJECT, - element_type.native_type, - element_type.script_type); + array.set_typed(element_type.builtin_type, element_type.native_type, element_type.script_type); } return array; @@ -131,6 +131,8 @@ String GDScriptFunction::_get_call_error(const Callable::CallError &p_err, const #ifdef DEBUG_ENABLED if (p_err.expected == Variant::OBJECT && argptrs[errorarg]->get_type() == p_err.expected) { err_text = "Invalid type in " + p_where + ". The Object-derived class of argument " + itos(errorarg + 1) + " (" + _get_var_type(argptrs[errorarg]) + ") is not a subclass of the expected argument class."; + } else if (p_err.expected == Variant::ARRAY && argptrs[errorarg]->get_type() == p_err.expected) { + err_text = "Invalid type in " + p_where + ". The array of argument " + itos(errorarg + 1) + " (" + _get_var_type(argptrs[errorarg]) + ") does not have the same element type as the expected typed array argument."; } else #endif // DEBUG_ENABLED { @@ -518,7 +520,7 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a if (!argument_types[i].is_type(*p_args[i], true)) { r_err.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT; r_err.argument = i; - r_err.expected = argument_types[i].kind == GDScriptDataType::BUILTIN ? argument_types[i].builtin_type : Variant::OBJECT; + r_err.expected = argument_types[i].builtin_type; return _get_default_variant_for_data_type(return_type); } if (argument_types[i].kind == GDScriptDataType::BUILTIN) { @@ -809,13 +811,22 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a #ifdef DEBUG_ENABLED if (!valid) { + Object *obj = dst->get_validated_object(); String v = index->operator String(); - if (!v.is_empty()) { - v = "'" + v + "'"; + bool read_only_property = false; + if (obj) { + read_only_property = ClassDB::has_property(obj->get_class_name(), v) && (ClassDB::get_property_setter(obj->get_class_name(), v) == StringName()); + } + if (read_only_property) { + err_text = vformat(R"(Cannot set value into property "%s" (on base "%s") because it is read-only.)", v, _get_var_type(dst)); } else { - v = "of type '" + _get_var_type(index) + "'"; + if (!v.is_empty()) { + v = "'" + v + "'"; + } else { + v = "of type '" + _get_var_type(index) + "'"; + } + err_text = "Invalid set index " + v + " (on base: '" + _get_var_type(dst) + "') with value of type '" + _get_var_type(value) + "'"; } - err_text = "Invalid set index " + v + " (on base: '" + _get_var_type(dst) + "') with value of type '" + _get_var_type(value) + "'"; OPCODE_BREAK; } #endif @@ -1001,8 +1012,16 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a #ifdef DEBUG_ENABLED if (!valid) { - String err_type; - err_text = "Invalid set index '" + String(*index) + "' (on base: '" + _get_var_type(dst) + "') with value of type '" + _get_var_type(value) + "'."; + Object *obj = dst->get_validated_object(); + bool read_only_property = false; + if (obj) { + read_only_property = ClassDB::has_property(obj->get_class_name(), *index) && (ClassDB::get_property_setter(obj->get_class_name(), *index) == StringName()); + } + if (read_only_property) { + err_text = vformat(R"(Cannot set value into property "%s" (on base "%s") because it is read-only.)", String(*index), _get_var_type(dst)); + } else { + err_text = "Invalid set index '" + String(*index) + "' (on base: '" + _get_var_type(dst) + "') with value of type '" + _get_var_type(value) + "'."; + } OPCODE_BREAK; } #endif @@ -1174,27 +1193,37 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a DISPATCH_OPCODE; OPCODE(OPCODE_ASSIGN_TYPED_ARRAY) { - CHECK_SPACE(3); + CHECK_SPACE(6); GET_VARIANT_PTR(dst, 0); GET_VARIANT_PTR(src, 1); - Array *dst_arr = VariantInternal::get_array(dst); + GET_VARIANT_PTR(script_type, 2); + Variant::Type builtin_type = (Variant::Type)_code_ptr[ip + 4]; + int native_type_idx = _code_ptr[ip + 5]; + GD_ERR_BREAK(native_type_idx < 0 || native_type_idx >= _global_names_count); + const StringName native_type = _global_names_ptr[native_type_idx]; if (src->get_type() != Variant::ARRAY) { #ifdef DEBUG_ENABLED - err_text = "Trying to assign value of type '" + Variant::get_type_name(src->get_type()) + - "' to a variable of type '" + +"'."; -#endif + err_text = vformat(R"(Trying to assign a value of type "%s" to a variable of type "Array[%s]".)", + _get_var_type(src), _get_element_type(builtin_type, native_type, *script_type)); +#endif // DEBUG_ENABLED OPCODE_BREAK; } - if (!dst_arr->typed_assign(*src)) { + + Array *array = VariantInternal::get_array(src); + + if (array->get_typed_builtin() != ((uint32_t)builtin_type) || array->get_typed_class_name() != native_type || array->get_typed_script() != *script_type || array->get_typed_class_name() != native_type) { #ifdef DEBUG_ENABLED - err_text = "Trying to assign a typed array with an array of different type.'"; -#endif + err_text = vformat(R"(Trying to assign an array of type "%s" to a variable of type "Array[%s]".)", + _get_var_type(src), _get_element_type(builtin_type, native_type, *script_type)); +#endif // DEBUG_ENABLED OPCODE_BREAK; } - ip += 3; + *dst = *src; + + ip += 6; } DISPATCH_OPCODE; @@ -1469,9 +1498,7 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a const StringName native_type = _global_names_ptr[native_type_idx]; Array array; - array.set_typed(builtin_type, native_type, *script_type); array.resize(argc); - for (int i = 0; i < argc; i++) { array[i] = *(instruction_args[i]); } @@ -1479,7 +1506,7 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a GET_INSTRUCTION_ARG(dst, argc); *dst = Variant(); // Clear potential previous typed array. - *dst = array; + *dst = Array(array, builtin_type, native_type, *script_type); ip += 4; } @@ -2486,30 +2513,25 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a if (r->get_type() != Variant::ARRAY) { #ifdef DEBUG_ENABLED - err_text = vformat(R"(Trying to return value of type "%s" from a function which the return type is "Array[%s]".)", - Variant::get_type_name(r->get_type()), Variant::get_type_name(builtin_type)); -#endif + err_text = vformat(R"(Trying to return a value of type "%s" where expected return type is "Array[%s]".)", + _get_var_type(r), _get_element_type(builtin_type, native_type, *script_type)); +#endif // DEBUG_ENABLED OPCODE_BREAK; } - Array array; - array.set_typed(builtin_type, native_type, *script_type); + Array *array = VariantInternal::get_array(r); + if (array->get_typed_builtin() != ((uint32_t)builtin_type) || array->get_typed_class_name() != native_type || array->get_typed_script() != *script_type || array->get_typed_class_name() != native_type) { #ifdef DEBUG_ENABLED - bool valid = array.typed_assign(*VariantInternal::get_array(r)); -#else - array.typed_assign(*VariantInternal::get_array(r)); + err_text = vformat(R"(Trying to return an array of type "%s" where expected return type is "Array[%s]".)", + _get_var_type(r), _get_element_type(builtin_type, native_type, *script_type)); #endif // DEBUG_ENABLED - - // Assign the return value anyway since we want it to be the valid type. - retvalue = array; - -#ifdef DEBUG_ENABLED - if (!valid) { - err_text = "Trying to return a typed array with an array of different type.'"; OPCODE_BREAK; } + retvalue = *array; + +#ifdef DEBUG_ENABLED exit_ok = true; #endif // DEBUG_ENABLED OPCODE_BREAK; @@ -3405,7 +3427,10 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a String message_str; if (_code_ptr[ip + 2] != 0) { GET_VARIANT_PTR(message, 1); - message_str = *message; + Variant message_var = *message; + if (message->get_type() != Variant::NIL) { + message_str = message_var; + } } if (message_str.is_empty()) { err_text = "Assertion failed."; diff --git a/modules/gdscript/gdscript_warning.cpp b/modules/gdscript/gdscript_warning.cpp index 024fed8517..ef59a07f1a 100644 --- a/modules/gdscript/gdscript_warning.cpp +++ b/modules/gdscript/gdscript_warning.cpp @@ -125,6 +125,10 @@ String GDScriptWarning::get_message() const { CHECK_SYMBOLS(4); return "The argument '" + symbols[0] + "' of the function '" + symbols[1] + "' requires a the subtype '" + symbols[2] + "' but the supertype '" + symbols[3] + "' was provided"; } break; + case UNSAFE_VOID_RETURN: { + CHECK_SYMBOLS(2); + return "The method '" + symbols[0] + "()' returns 'void' but it's trying to return a call to '" + symbols[1] + "()' that can't be ensured to also be 'void'."; + } break; case DEPRECATED_KEYWORD: { CHECK_SYMBOLS(2); return "The '" + symbols[0] + "' keyword is deprecated and will be removed in a future release, please replace its uses by '" + symbols[1] + "'."; @@ -163,6 +167,24 @@ String GDScriptWarning::get_message() const { CHECK_SYMBOLS(1); return vformat(R"(The identifier "%s" has misleading characters and might be confused with something else.)", symbols[0]); } + case RENAMED_IN_GD4_HINT: { + break; // Renamed identifier hint is taken care of by the GDScriptAnalyzer. No message needed here. + } + case INFERENCE_ON_VARIANT: { + CHECK_SYMBOLS(1); + return vformat("The %s type is being inferred from a Variant value, so it will be typed as Variant.", symbols[0]); + } + case NATIVE_METHOD_OVERRIDE: { + CHECK_SYMBOLS(2); + return vformat(R"(The method "%s" overrides a method from native class "%s". This won't be called by the engine and may not work as expected.)", symbols[0], symbols[1]); + } + case GET_NODE_DEFAULT_WITHOUT_ONREADY: { + CHECK_SYMBOLS(1); + return vformat(R"*(The default value is using "%s" which won't return nodes in the scene tree before "_ready()" is called. Use the "@onready" annotation to solve this.)*", symbols[0]); + } + case ONREADY_WITH_EXPORT: { + return R"(The "@onready" annotation will make the default value to be set after the "@export" takes effect and will override it.)"; + } case WARNING_MAX: break; // Can't happen, but silences warning } @@ -172,18 +194,15 @@ String GDScriptWarning::get_message() const { } int GDScriptWarning::get_default_value(Code p_code) { - if (get_name_from_code(p_code).to_lower().begins_with("unsafe_")) { - return WarnLevel::IGNORE; - } - // Too spammy by default on common cases (connect, Tween, etc.). - if (p_code == RETURN_VALUE_DISCARDED) { - return WarnLevel::IGNORE; - } - return WarnLevel::WARN; + ERR_FAIL_INDEX_V_MSG(p_code, WARNING_MAX, WarnLevel::IGNORE, "Getting default value of invalid warning code."); + return default_warning_levels[p_code]; } PropertyInfo GDScriptWarning::get_property_info(Code p_code) { // Making this a separate function in case a warning needs different PropertyInfo in the future. + if (p_code == Code::RENAMED_IN_GD4_HINT) { + return PropertyInfo(Variant::BOOL, get_settings_path_from_code(p_code)); + } return PropertyInfo(Variant::INT, get_settings_path_from_code(p_code), PROPERTY_HINT_ENUM, "Ignore,Warn,Error"); } @@ -218,6 +237,7 @@ String GDScriptWarning::get_name_from_code(Code p_code) { "UNSAFE_METHOD_ACCESS", "UNSAFE_CAST", "UNSAFE_CALL_ARGUMENT", + "UNSAFE_VOID_RETURN", "DEPRECATED_KEYWORD", "STANDALONE_TERNARY", "ASSERT_ALWAYS_TRUE", @@ -229,6 +249,11 @@ String GDScriptWarning::get_name_from_code(Code p_code) { "INT_AS_ENUM_WITHOUT_MATCH", "STATIC_CALLED_ON_INSTANCE", "CONFUSABLE_IDENTIFIER", + "RENAMED_IN_GODOT_4_HINT", + "INFERENCE_ON_VARIANT", + "NATIVE_METHOD_OVERRIDE", + "GET_NODE_DEFAULT_WITHOUT_ONREADY", + "ONREADY_WITH_EXPORT", }; static_assert((sizeof(names) / sizeof(*names)) == WARNING_MAX, "Amount of warning types don't match the amount of warning names."); diff --git a/modules/gdscript/gdscript_warning.h b/modules/gdscript/gdscript_warning.h index 7492972c1a..f0123c518c 100644 --- a/modules/gdscript/gdscript_warning.h +++ b/modules/gdscript/gdscript_warning.h @@ -69,6 +69,7 @@ public: UNSAFE_METHOD_ACCESS, // Function not found in the detected type (but can be in subtypes). UNSAFE_CAST, // Cast used in an unknown type. UNSAFE_CALL_ARGUMENT, // Function call argument is of a supertype of the require argument. + UNSAFE_VOID_RETURN, // Function returns void but returned a call to a function that can't be type checked. DEPRECATED_KEYWORD, // The keyword is deprecated and should be replaced. STANDALONE_TERNARY, // Return value of ternary expression is discarded. ASSERT_ALWAYS_TRUE, // Expression for assert argument is always true. @@ -80,9 +81,59 @@ public: INT_AS_ENUM_WITHOUT_MATCH, // An integer value was used as an enum value without matching enum member. STATIC_CALLED_ON_INSTANCE, // A static method was called on an instance of a class instead of on the class itself. CONFUSABLE_IDENTIFIER, // The identifier contains misleading characters that can be confused. E.g. "usеr" (has Cyrillic "е" instead of Latin "e"). + RENAMED_IN_GD4_HINT, // A variable or function that could not be found has been renamed in Godot 4 + INFERENCE_ON_VARIANT, // The declaration uses type inference but the value is typed as Variant. + NATIVE_METHOD_OVERRIDE, // The script method overrides a native one, this may not work as intended. + GET_NODE_DEFAULT_WITHOUT_ONREADY, // A class variable uses `get_node()` (or the `$` notation) as its default value, but does not use the @onready annotation. + ONREADY_WITH_EXPORT, // The `@onready` annotation will set the value after `@export` which is likely not intended. WARNING_MAX, }; + constexpr static WarnLevel default_warning_levels[] = { + WARN, // UNASSIGNED_VARIABLE + WARN, // UNASSIGNED_VARIABLE_OP_ASSIGN + WARN, // UNUSED_VARIABLE + WARN, // UNUSED_LOCAL_CONSTANT + WARN, // SHADOWED_VARIABLE + WARN, // SHADOWED_VARIABLE_BASE_CLASS + WARN, // UNUSED_PRIVATE_CLASS_VARIABLE + WARN, // UNUSED_PARAMETER + WARN, // UNREACHABLE_CODE + WARN, // UNREACHABLE_PATTERN + WARN, // STANDALONE_EXPRESSION + WARN, // NARROWING_CONVERSION + WARN, // INCOMPATIBLE_TERNARY + WARN, // UNUSED_SIGNAL + IGNORE, // RETURN_VALUE_DISCARDED // Too spammy by default on common cases (connect, Tween, etc.). + WARN, // PROPERTY_USED_AS_FUNCTION + WARN, // CONSTANT_USED_AS_FUNCTION + WARN, // FUNCTION_USED_AS_PROPERTY + WARN, // INTEGER_DIVISION + IGNORE, // UNSAFE_PROPERTY_ACCESS // Too common in untyped scenarios. + IGNORE, // UNSAFE_METHOD_ACCESS // Too common in untyped scenarios. + IGNORE, // UNSAFE_CAST // Too common in untyped scenarios. + IGNORE, // UNSAFE_CALL_ARGUMENT // Too common in untyped scenarios. + WARN, // UNSAFE_VOID_RETURN + WARN, // DEPRECATED_KEYWORD + WARN, // STANDALONE_TERNARY + WARN, // ASSERT_ALWAYS_TRUE + WARN, // ASSERT_ALWAYS_FALSE + WARN, // REDUNDANT_AWAIT + WARN, // EMPTY_FILE + WARN, // SHADOWED_GLOBAL_IDENTIFIER + WARN, // INT_AS_ENUM_WITHOUT_CAST + WARN, // INT_AS_ENUM_WITHOUT_MATCH + WARN, // STATIC_CALLED_ON_INSTANCE + WARN, // CONFUSABLE_IDENTIFIER + WARN, // RENAMED_IN_GD4_HINT + ERROR, // INFERENCE_ON_VARIANT // Most likely done by accident, usually inference is trying for a particular type. + ERROR, // NATIVE_METHOD_OVERRIDE // May not work as expected. + ERROR, // GET_NODE_DEFAULT_WITHOUT_ONREADY // May not work as expected. + ERROR, // ONREADY_WITH_EXPORT // May not work as expected. + }; + + static_assert((sizeof(default_warning_levels) / sizeof(default_warning_levels[0])) == WARNING_MAX, "Amount of default levels does not match the amount of warnings."); + Code code = WARNING_MAX; int start_line = -1, end_line = -1; int leftmost_column = -1, rightmost_column = -1; diff --git a/modules/gdscript/language_server/gdscript_text_document.cpp b/modules/gdscript/language_server/gdscript_text_document.cpp index b9e6921034..35fbdca949 100644 --- a/modules/gdscript/language_server/gdscript_text_document.cpp +++ b/modules/gdscript/language_server/gdscript_text_document.cpp @@ -108,6 +108,7 @@ void GDScriptTextDocument::didSave(const Variant &p_param) { scr->reload(true); } scr->update_exports(); + ScriptEditor::get_singleton()->reload_scripts(true); ScriptEditor::get_singleton()->update_docs_from_script(scr); } } diff --git a/modules/gdscript/tests/gdscript_test_runner.cpp b/modules/gdscript/tests/gdscript_test_runner.cpp index d2c8b5c317..57405aa1ce 100644 --- a/modules/gdscript/tests/gdscript_test_runner.cpp +++ b/modules/gdscript/tests/gdscript_test_runner.cpp @@ -132,9 +132,10 @@ void finish_language() { StringName GDScriptTestRunner::test_function_name; -GDScriptTestRunner::GDScriptTestRunner(const String &p_source_dir, bool p_init_language) { +GDScriptTestRunner::GDScriptTestRunner(const String &p_source_dir, bool p_init_language, bool p_print_filenames) { test_function_name = StaticCString::create("test"); do_init_languages = p_init_language; + print_filenames = p_print_filenames; source_dir = p_source_dir; if (!source_dir.ends_with("/")) { @@ -145,11 +146,11 @@ GDScriptTestRunner::GDScriptTestRunner(const String &p_source_dir, bool p_init_l init_language(p_source_dir); } #ifdef DEBUG_ENABLED - // Enable all warnings for GDScript, so we can test them. + // Set all warning levels to "Warn" in order to test them properly, even the ones that default to error. ProjectSettings::get_singleton()->set_setting("debug/gdscript/warnings/enable", true); for (int i = 0; i < (int)GDScriptWarning::WARNING_MAX; i++) { - String warning = GDScriptWarning::get_name_from_code((GDScriptWarning::Code)i).to_lower(); - ProjectSettings::get_singleton()->set_setting("debug/gdscript/warnings/" + warning, true); + String warning_setting = GDScriptWarning::get_settings_path_from_code((GDScriptWarning::Code)i); + ProjectSettings::get_singleton()->set_setting(warning_setting, (int)GDScriptWarning::WARN); } #endif @@ -194,6 +195,9 @@ int GDScriptTestRunner::run_tests() { int failed = 0; for (int i = 0; i < tests.size(); i++) { GDScriptTest test = tests[i]; + if (print_filenames) { + print_line(test.get_source_relative_filepath()); + } GDScriptTest::TestResult result = test.run_test(); String expected = FileAccess::get_file_as_string(test.get_output_file()); @@ -225,8 +229,13 @@ bool GDScriptTestRunner::generate_outputs() { } for (int i = 0; i < tests.size(); i++) { - OS::get_singleton()->print("."); GDScriptTest test = tests[i]; + if (print_filenames) { + print_line(test.get_source_relative_filepath()); + } else { + OS::get_singleton()->print("."); + } + bool result = test.generate_output(); if (!result) { @@ -337,15 +346,10 @@ GDScriptTest::GDScriptTest(const String &p_source_path, const String &p_output_p void GDScriptTestRunner::handle_cmdline() { List<String> cmdline_args = OS::get_singleton()->get_cmdline_args(); - // TODO: this could likely be ported to use test commands: - // https://github.com/godotengine/godot/pull/41355 - // Currently requires to startup the whole engine, which is slow. - String test_cmd = "--gdscript-test"; - String gen_cmd = "--gdscript-generate-tests"; for (List<String>::Element *E = cmdline_args.front(); E; E = E->next()) { String &cmd = E->get(); - if (cmd == test_cmd || cmd == gen_cmd) { + if (cmd == "--gdscript-generate-tests") { if (E->next() == nullptr) { ERR_PRINT("Needed a path for the test files."); exit(-1); @@ -353,14 +357,10 @@ void GDScriptTestRunner::handle_cmdline() { const String &path = E->next()->get(); - GDScriptTestRunner runner(path, false); - int failed = 0; - if (cmd == test_cmd) { - failed = runner.run_tests(); - } else { - bool completed = runner.generate_outputs(); - failed = completed ? 0 : -1; - } + GDScriptTestRunner runner(path, false, cmdline_args.find("--print-filenames") != nullptr); + + bool completed = runner.generate_outputs(); + int failed = completed ? 0 : -1; exit(failed); } } diff --git a/modules/gdscript/tests/gdscript_test_runner.h b/modules/gdscript/tests/gdscript_test_runner.h index b097f1b485..60b48c6a57 100644 --- a/modules/gdscript/tests/gdscript_test_runner.h +++ b/modules/gdscript/tests/gdscript_test_runner.h @@ -92,6 +92,7 @@ public: bool generate_output(); const String &get_source_file() const { return source_file; } + const String get_source_relative_filepath() const { return source_file.trim_prefix(base_dir); } const String &get_output_file() const { return output_file; } GDScriptTest(const String &p_source_path, const String &p_output_path, const String &p_base_dir); @@ -105,6 +106,7 @@ class GDScriptTestRunner { bool is_generating = false; bool do_init_languages = false; + bool print_filenames; // Whether filenames should be printed when generated/running tests bool make_tests(); bool make_tests_for_dir(const String &p_dir); @@ -117,7 +119,7 @@ public: int run_tests(); bool generate_outputs(); - GDScriptTestRunner(const String &p_source_dir, bool p_init_language); + GDScriptTestRunner(const String &p_source_dir, bool p_init_language, bool p_print_filenames = false); ~GDScriptTestRunner(); }; diff --git a/modules/gdscript/tests/gdscript_test_runner_suite.h b/modules/gdscript/tests/gdscript_test_runner_suite.h index aed0ac2baf..e27b6218f1 100644 --- a/modules/gdscript/tests/gdscript_test_runner_suite.h +++ b/modules/gdscript/tests/gdscript_test_runner_suite.h @@ -41,7 +41,8 @@ TEST_SUITE("[Modules][GDScript]") { // Allow the tests to fail, but do not ignore errors during development. // Update the scripts and expected output as needed. TEST_CASE("Script compilation and runtime") { - GDScriptTestRunner runner("modules/gdscript/tests/scripts", true); + bool print_filenames = OS::get_singleton()->get_cmdline_args().find("--print-filenames") != nullptr; + GDScriptTestRunner runner("modules/gdscript/tests/scripts", true, print_filenames); int fail_count = runner.run_tests(); INFO("Make sure `*.out` files have expected results."); REQUIRE_MESSAGE(fail_count == 0, "All GDScript tests should pass."); diff --git a/modules/gdscript/tests/scripts/analyzer/errors/assign_to_read_only_property.gd b/modules/gdscript/tests/scripts/analyzer/errors/assign_to_read_only_property.gd new file mode 100644 index 0000000000..2b1c4c9594 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/assign_to_read_only_property.gd @@ -0,0 +1,4 @@ +func test(): + var tree := SceneTree.new() + tree.root = Window.new() + tree.free() diff --git a/modules/gdscript/tests/scripts/analyzer/errors/assign_to_read_only_property.out b/modules/gdscript/tests/scripts/analyzer/errors/assign_to_read_only_property.out new file mode 100644 index 0000000000..b236d70ec8 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/assign_to_read_only_property.out @@ -0,0 +1,2 @@ +GDTEST_ANALYZER_ERROR +Cannot assign a new value to a read-only property. diff --git a/modules/gdscript/tests/scripts/analyzer/errors/assign_to_read_only_property_indirectly.gd b/modules/gdscript/tests/scripts/analyzer/errors/assign_to_read_only_property_indirectly.gd new file mode 100644 index 0000000000..c97ee0ea69 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/assign_to_read_only_property_indirectly.gd @@ -0,0 +1,4 @@ +func test(): + var state := PhysicsDirectBodyState3DExtension.new() + state.center_of_mass.x += 1.0 + state.free() diff --git a/modules/gdscript/tests/scripts/analyzer/errors/assign_to_read_only_property_indirectly.out b/modules/gdscript/tests/scripts/analyzer/errors/assign_to_read_only_property_indirectly.out new file mode 100644 index 0000000000..b236d70ec8 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/assign_to_read_only_property_indirectly.out @@ -0,0 +1,2 @@ +GDTEST_ANALYZER_ERROR +Cannot assign a new value to a read-only property. diff --git a/modules/gdscript/tests/scripts/analyzer/errors/await_signal_no_infer.gd b/modules/gdscript/tests/scripts/analyzer/errors/await_signal_no_infer.gd new file mode 100644 index 0000000000..c787d9e50e --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/await_signal_no_infer.gd @@ -0,0 +1,4 @@ +signal my_signal() + +func test(): + var _a := await my_signal diff --git a/modules/gdscript/tests/scripts/analyzer/errors/await_signal_no_infer.out b/modules/gdscript/tests/scripts/analyzer/errors/await_signal_no_infer.out new file mode 100644 index 0000000000..8f8744ad7e --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/await_signal_no_infer.out @@ -0,0 +1,2 @@ +GDTEST_ANALYZER_ERROR +Cannot infer the type of "_a" variable because the value doesn't have a set type. diff --git a/modules/gdscript/tests/scripts/analyzer/errors/function_dont_match_parent_signature_parameter_default_values.out b/modules/gdscript/tests/scripts/analyzer/errors/function_dont_match_parent_signature_parameter_default_values.out index c70a1df10d..508e46742f 100644 --- a/modules/gdscript/tests/scripts/analyzer/errors/function_dont_match_parent_signature_parameter_default_values.out +++ b/modules/gdscript/tests/scripts/analyzer/errors/function_dont_match_parent_signature_parameter_default_values.out @@ -1,2 +1,2 @@ GDTEST_ANALYZER_ERROR -The function signature doesn't match the parent. Parent signature is "my_function(int = default) -> int". +The function signature doesn't match the parent. Parent signature is "my_function(int = <default>) -> int". diff --git a/modules/gdscript/tests/scripts/analyzer/errors/get_node_shorthand_within_non_node.gd b/modules/gdscript/tests/scripts/analyzer/errors/get_node_shorthand_within_non_node.gd new file mode 100644 index 0000000000..91f5071fa9 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/get_node_shorthand_within_non_node.gd @@ -0,0 +1,5 @@ +extends RefCounted + +func test(): + var nope := $Node + print("Cannot use $ without a Node base") diff --git a/modules/gdscript/tests/scripts/analyzer/errors/get_node_shorthand_within_non_node.out b/modules/gdscript/tests/scripts/analyzer/errors/get_node_shorthand_within_non_node.out new file mode 100644 index 0000000000..33365908bf --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/get_node_shorthand_within_non_node.out @@ -0,0 +1,2 @@ +GDTEST_ANALYZER_ERROR +Cannot use shorthand "get_node()" notation ("$") on a class that isn't a node. diff --git a/modules/gdscript/tests/scripts/analyzer/errors/invalid_array_index.out b/modules/gdscript/tests/scripts/analyzer/errors/invalid_array_index.out index 6f7f0783f0..015ad756f8 100644 --- a/modules/gdscript/tests/scripts/analyzer/errors/invalid_array_index.out +++ b/modules/gdscript/tests/scripts/analyzer/errors/invalid_array_index.out @@ -1,2 +1,2 @@ GDTEST_ANALYZER_ERROR -Cannot get index "true" from "[0, 1]". +Invalid index type "bool" for a base of type "Array". diff --git a/modules/gdscript/tests/scripts/analyzer/errors/onready_within_non_node.gd b/modules/gdscript/tests/scripts/analyzer/errors/onready_within_non_node.gd new file mode 100644 index 0000000000..e781315266 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/onready_within_non_node.gd @@ -0,0 +1,6 @@ +extends RefCounted + +@onready var nope := 0 + +func test(): + print("Cannot use @onready without a Node base") diff --git a/modules/gdscript/tests/scripts/analyzer/errors/onready_within_non_node.out b/modules/gdscript/tests/scripts/analyzer/errors/onready_within_non_node.out new file mode 100644 index 0000000000..8088d28329 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/onready_within_non_node.out @@ -0,0 +1,2 @@ +GDTEST_ANALYZER_ERROR +"@onready" can only be used in classes that inherit "Node". diff --git a/modules/gdscript/tests/scripts/analyzer/errors/onready_within_non_node_inner_class.gd b/modules/gdscript/tests/scripts/analyzer/errors/onready_within_non_node_inner_class.gd new file mode 100644 index 0000000000..1639bbbd52 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/onready_within_non_node_inner_class.gd @@ -0,0 +1,7 @@ +extends Node + +class Inner extends RefCounted: + @onready var nope = 0 + +func test(): + print("Cannot use @onready without a Node base") diff --git a/modules/gdscript/tests/scripts/analyzer/errors/onready_within_non_node_inner_class.out b/modules/gdscript/tests/scripts/analyzer/errors/onready_within_non_node_inner_class.out new file mode 100644 index 0000000000..8088d28329 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/onready_within_non_node_inner_class.out @@ -0,0 +1,2 @@ +GDTEST_ANALYZER_ERROR +"@onready" can only be used in classes that inherit "Node". diff --git a/modules/gdscript/tests/scripts/analyzer/errors/ternary_weak_infer.gd b/modules/gdscript/tests/scripts/analyzer/errors/ternary_weak_infer.gd new file mode 100644 index 0000000000..fac0e8756c --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/ternary_weak_infer.gd @@ -0,0 +1,6 @@ +func test(): + var left_hard_int := 1 + var right_weak_int = 2 + var result_hm_int := left_hard_int if true else right_weak_int + + print('not ok') diff --git a/modules/gdscript/tests/scripts/analyzer/errors/ternary_weak_infer.out b/modules/gdscript/tests/scripts/analyzer/errors/ternary_weak_infer.out new file mode 100644 index 0000000000..71d1e2f8ae --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/ternary_weak_infer.out @@ -0,0 +1,2 @@ +GDTEST_ANALYZER_ERROR +Cannot infer the type of "result_hm_int" variable because the value doesn't have a set type. diff --git a/modules/gdscript/tests/scripts/analyzer/errors/typed_array_assign_differently_typed.gd b/modules/gdscript/tests/scripts/analyzer/errors/typed_array_assign_differently_typed.gd new file mode 100644 index 0000000000..ce50cccb3c --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/typed_array_assign_differently_typed.gd @@ -0,0 +1,4 @@ +func test(): + var differently: Array[float] = [1.0] + var typed: Array[int] = differently + print('not ok') diff --git a/modules/gdscript/tests/scripts/analyzer/errors/typed_array_assign_differently_typed.out b/modules/gdscript/tests/scripts/analyzer/errors/typed_array_assign_differently_typed.out new file mode 100644 index 0000000000..c6d39781ee --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/typed_array_assign_differently_typed.out @@ -0,0 +1,2 @@ +GDTEST_ANALYZER_ERROR +Cannot assign a value of type Array[float] to variable "typed" with specified type Array[int]. diff --git a/modules/gdscript/tests/scripts/analyzer/typed_array_assignment.gd b/modules/gdscript/tests/scripts/analyzer/errors/typed_array_assignment.gd index 9f86d0531c..9f86d0531c 100644 --- a/modules/gdscript/tests/scripts/analyzer/typed_array_assignment.gd +++ b/modules/gdscript/tests/scripts/analyzer/errors/typed_array_assignment.gd diff --git a/modules/gdscript/tests/scripts/analyzer/errors/typed_array_assignment.out b/modules/gdscript/tests/scripts/analyzer/errors/typed_array_assignment.out new file mode 100644 index 0000000000..8530783673 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/typed_array_assignment.out @@ -0,0 +1,2 @@ +GDTEST_ANALYZER_ERROR +Cannot include a value of type "String" as "int". diff --git a/modules/gdscript/tests/scripts/analyzer/errors/typed_array_init_with_unconvertable_in_literal.gd b/modules/gdscript/tests/scripts/analyzer/errors/typed_array_init_with_unconvertable_in_literal.gd new file mode 100644 index 0000000000..25cde1d40b --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/typed_array_init_with_unconvertable_in_literal.gd @@ -0,0 +1,4 @@ +func test(): + var unconvertable := 1 + var typed: Array[Object] = [unconvertable] + print('not ok') diff --git a/modules/gdscript/tests/scripts/analyzer/errors/typed_array_init_with_unconvertable_in_literal.out b/modules/gdscript/tests/scripts/analyzer/errors/typed_array_init_with_unconvertable_in_literal.out new file mode 100644 index 0000000000..dfe3443761 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/typed_array_init_with_unconvertable_in_literal.out @@ -0,0 +1,2 @@ +GDTEST_ANALYZER_ERROR +Cannot have an element of type "int" in an array of type "Array[Object]". diff --git a/modules/gdscript/tests/scripts/analyzer/errors/typed_array_pass_differently_to_typed.gd b/modules/gdscript/tests/scripts/analyzer/errors/typed_array_pass_differently_to_typed.gd new file mode 100644 index 0000000000..1a90bd121e --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/typed_array_pass_differently_to_typed.gd @@ -0,0 +1,7 @@ +func expect_typed(typed: Array[int]): + print(typed.size()) + +func test(): + var differently: Array[float] = [1.0] + expect_typed(differently) + print('not ok') diff --git a/modules/gdscript/tests/scripts/analyzer/errors/typed_array_pass_differently_to_typed.out b/modules/gdscript/tests/scripts/analyzer/errors/typed_array_pass_differently_to_typed.out new file mode 100644 index 0000000000..297e1283e8 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/typed_array_pass_differently_to_typed.out @@ -0,0 +1,2 @@ +GDTEST_ANALYZER_ERROR +Invalid argument for "expect_typed()" function: argument 1 should be "Array[int]" but is "Array[float]". diff --git a/modules/gdscript/tests/scripts/analyzer/features/allow_get_node_with_onready.gd b/modules/gdscript/tests/scripts/analyzer/features/allow_get_node_with_onready.gd new file mode 100644 index 0000000000..a9004a346b --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/allow_get_node_with_onready.gd @@ -0,0 +1,18 @@ +extends Node + +@onready var shorthand = $Node +@onready var call = get_node(^"Node") +@onready var shorthand_with_cast = $Node as Node +@onready var call_with_cast = get_node(^"Node") as Node + +func _init(): + var node := Node.new() + node.name = "Node" + add_child(node) + +func test(): + # Those are expected to be `null` since `_ready()` is never called on tests. + prints("shorthand", shorthand) + prints("call", call) + prints("shorthand_with_cast", shorthand_with_cast) + prints("call_with_cast", call_with_cast) diff --git a/modules/gdscript/tests/scripts/analyzer/features/allow_get_node_with_onready.out b/modules/gdscript/tests/scripts/analyzer/features/allow_get_node_with_onready.out new file mode 100644 index 0000000000..eddc2deec0 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/allow_get_node_with_onready.out @@ -0,0 +1,5 @@ +GDTEST_OK +shorthand <null> +call <null> +shorthand_with_cast <null> +call_with_cast <null> diff --git a/modules/gdscript/tests/scripts/analyzer/features/allow_void_function_to_return_void.gd b/modules/gdscript/tests/scripts/analyzer/features/allow_void_function_to_return_void.gd new file mode 100644 index 0000000000..df89137f40 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/allow_void_function_to_return_void.gd @@ -0,0 +1,20 @@ +func test(): + return_call() + return_nothing() + return_side_effect() + var r = return_side_effect.call() # Untyped call to check return value. + prints(r, typeof(r) == TYPE_NIL) + print("end") + +func side_effect(v): + print("effect") + return v + +func return_call() -> void: + return print("hello") + +func return_nothing() -> void: + return + +func return_side_effect() -> void: + return side_effect("x") diff --git a/modules/gdscript/tests/scripts/analyzer/features/allow_void_function_to_return_void.out b/modules/gdscript/tests/scripts/analyzer/features/allow_void_function_to_return_void.out new file mode 100644 index 0000000000..7c0416371f --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/allow_void_function_to_return_void.out @@ -0,0 +1,10 @@ +GDTEST_OK +>> WARNING +>> Line: 20 +>> UNSAFE_VOID_RETURN +>> The method 'return_side_effect()' returns 'void' but it's trying to return a call to 'side_effect()' that can't be ensured to also be 'void'. +hello +effect +effect +<null> true +end diff --git a/modules/gdscript/tests/scripts/analyzer/features/assign_to_native_enum_property.gd b/modules/gdscript/tests/scripts/analyzer/features/assign_to_native_enum_property.gd new file mode 100644 index 0000000000..02120db868 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/assign_to_native_enum_property.gd @@ -0,0 +1,13 @@ +# https://github.com/godotengine/godot/issues/72501 +extends Node + +func test(): + prints("before", process_mode) + process_mode = PROCESS_MODE_PAUSABLE + prints("after", process_mode) + + var node := Node.new() + add_child(node) + prints("before", node.process_mode) + node.process_mode = PROCESS_MODE_PAUSABLE + prints("after", node.process_mode) diff --git a/modules/gdscript/tests/scripts/analyzer/features/assign_to_native_enum_property.out b/modules/gdscript/tests/scripts/analyzer/features/assign_to_native_enum_property.out new file mode 100644 index 0000000000..1eb045a4e4 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/assign_to_native_enum_property.out @@ -0,0 +1,5 @@ +GDTEST_OK +before 0 +after 1 +before 0 +after 1 diff --git a/modules/gdscript/tests/scripts/analyzer/features/await_type_inference.gd b/modules/gdscript/tests/scripts/analyzer/features/await_type_inference.gd new file mode 100644 index 0000000000..9d8cfc7f99 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/await_type_inference.gd @@ -0,0 +1,15 @@ +func coroutine() -> int: + @warning_ignore("redundant_await") + await 0 + return 1 + +func not_coroutine() -> int: + return 2 + +func test(): + var a := await coroutine() + @warning_ignore("redundant_await") + var b := await not_coroutine() + @warning_ignore("redundant_await") + var c := await 3 + prints(a, b, c) diff --git a/modules/gdscript/tests/scripts/analyzer/features/await_type_inference.out b/modules/gdscript/tests/scripts/analyzer/features/await_type_inference.out new file mode 100644 index 0000000000..2920e2ce9c --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/await_type_inference.out @@ -0,0 +1,2 @@ +GDTEST_OK +1 2 3 diff --git a/modules/gdscript/tests/scripts/analyzer/features/hard_variants.gd b/modules/gdscript/tests/scripts/analyzer/features/hard_variants.gd index 48a804ff54..b447180ea8 100644 --- a/modules/gdscript/tests/scripts/analyzer/features/hard_variants.gd +++ b/modules/gdscript/tests/scripts/analyzer/features/hard_variants.gd @@ -2,16 +2,18 @@ func variant() -> Variant: return null var member_weak = variant() var member_typed: Variant = variant() +@warning_ignore("inference_on_variant") var member_inferred := variant() func param_weak(param = variant()) -> void: print(param) func param_typed(param: Variant = variant()) -> void: print(param) +@warning_ignore("inference_on_variant") func param_inferred(param := variant()) -> void: print(param) func return_untyped(): return variant() func return_typed() -> Variant: return variant() -@warning_ignore("unused_variable") +@warning_ignore("unused_variable", "inference_on_variant") func test() -> void: var weak = variant() var typed: Variant = variant() diff --git a/modules/gdscript/tests/scripts/analyzer/features/onready_on_inner_class_with_non_node_outer.gd b/modules/gdscript/tests/scripts/analyzer/features/onready_on_inner_class_with_non_node_outer.gd new file mode 100644 index 0000000000..1ac03c2181 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/onready_on_inner_class_with_non_node_outer.gd @@ -0,0 +1,7 @@ +extends RefCounted + +func test(): + print("ok") + +class Inner extends Node: + @onready var okay = 0 diff --git a/modules/gdscript/tests/scripts/analyzer/features/onready_on_inner_class_with_non_node_outer.out b/modules/gdscript/tests/scripts/analyzer/features/onready_on_inner_class_with_non_node_outer.out new file mode 100644 index 0000000000..1b47ed10dc --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/onready_on_inner_class_with_non_node_outer.out @@ -0,0 +1,2 @@ +GDTEST_OK +ok diff --git a/modules/gdscript/tests/scripts/analyzer/features/ternary_hard_infer.gd b/modules/gdscript/tests/scripts/analyzer/features/ternary_hard_infer.gd new file mode 100644 index 0000000000..44ca5f4dd0 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/ternary_hard_infer.gd @@ -0,0 +1,15 @@ +func test(): + var left_hard_int := 1 + var right_hard_int := 2 + var result_hard_int := left_hard_int if true else right_hard_int + assert(result_hard_int == 1) + + @warning_ignore("inference_on_variant") + var left_hard_variant := 1 as Variant + @warning_ignore("inference_on_variant") + var right_hard_variant := 2.0 as Variant + @warning_ignore("inference_on_variant") + var result_hard_variant := left_hard_variant if true else right_hard_variant + assert(result_hard_variant == 1) + + print('ok') diff --git a/modules/gdscript/tests/scripts/analyzer/features/ternary_hard_infer.out b/modules/gdscript/tests/scripts/analyzer/features/ternary_hard_infer.out new file mode 100644 index 0000000000..1b47ed10dc --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/ternary_hard_infer.out @@ -0,0 +1,2 @@ +GDTEST_OK +ok diff --git a/modules/gdscript/tests/scripts/analyzer/features/typed_array_as_default_parameter.out b/modules/gdscript/tests/scripts/analyzer/features/typed_array_as_default_parameter.out index 082e3ade19..2729c5b6c7 100644 --- a/modules/gdscript/tests/scripts/analyzer/features/typed_array_as_default_parameter.out +++ b/modules/gdscript/tests/scripts/analyzer/features/typed_array_as_default_parameter.out @@ -2,7 +2,7 @@ GDTEST_OK [0] 0 [1] -2 +0 [2] 2 ok diff --git a/modules/gdscript/tests/scripts/analyzer/features/typed_array_usage.gd b/modules/gdscript/tests/scripts/analyzer/features/typed_array_usage.gd new file mode 100644 index 0000000000..092ae49d00 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/typed_array_usage.gd @@ -0,0 +1,210 @@ +class A: pass +class B extends A: pass + +enum E { E0 = 391 } + +func floats_identity(floats: Array[float]): return floats + +class Members: + var one: Array[int] = [104] + var two: Array[int] = one + + func check_passing() -> bool: + assert(str(one) == '[104]') + assert(str(two) == '[104]') + two.push_back(582) + assert(str(one) == '[104, 582]') + assert(str(two) == '[104, 582]') + two = [486] + assert(str(one) == '[104, 582]') + assert(str(two) == '[486]') + return true + + +@warning_ignore("unsafe_method_access") +@warning_ignore("assert_always_true") +@warning_ignore("return_value_discarded") +func test(): + var untyped_basic = [459] + assert(str(untyped_basic) == '[459]') + assert(untyped_basic.get_typed_builtin() == TYPE_NIL) + + var inferred_basic := [366] + assert(str(inferred_basic) == '[366]') + assert(inferred_basic.get_typed_builtin() == TYPE_NIL) + + var typed_basic: Array = [521] + assert(str(typed_basic) == '[521]') + assert(typed_basic.get_typed_builtin() == TYPE_NIL) + + + var empty_floats: Array[float] = [] + assert(str(empty_floats) == '[]') + assert(empty_floats.get_typed_builtin() == TYPE_FLOAT) + + untyped_basic = empty_floats + assert(untyped_basic.get_typed_builtin() == TYPE_FLOAT) + + inferred_basic = empty_floats + assert(inferred_basic.get_typed_builtin() == TYPE_FLOAT) + + typed_basic = empty_floats + assert(typed_basic.get_typed_builtin() == TYPE_FLOAT) + + empty_floats.push_back(705.0) + untyped_basic.push_back(430.0) + inferred_basic.push_back(263.0) + typed_basic.push_back(518.0) + assert(str(empty_floats) == '[705, 430, 263, 518]') + assert(str(untyped_basic) == '[705, 430, 263, 518]') + assert(str(inferred_basic) == '[705, 430, 263, 518]') + assert(str(typed_basic) == '[705, 430, 263, 518]') + + + const constant_float := 950.0 + const constant_int := 170 + var typed_float := 954.0 + var filled_floats: Array[float] = [constant_float, constant_int, typed_float, empty_floats[1] + empty_floats[2]] + assert(str(filled_floats) == '[950, 170, 954, 693]') + assert(filled_floats.get_typed_builtin() == TYPE_FLOAT) + + var casted_floats := [empty_floats[2] * 2] as Array[float] + assert(str(casted_floats) == '[526]') + assert(casted_floats.get_typed_builtin() == TYPE_FLOAT) + + var returned_floats = (func () -> Array[float]: return [554]).call() + assert(str(returned_floats) == '[554]') + assert(returned_floats.get_typed_builtin() == TYPE_FLOAT) + + var passed_floats = floats_identity([663.0 if randf() > 0.5 else 663.0]) + assert(str(passed_floats) == '[663]') + assert(passed_floats.get_typed_builtin() == TYPE_FLOAT) + + var default_floats = (func (floats: Array[float] = [364.0]): return floats).call() + assert(str(default_floats) == '[364]') + assert(default_floats.get_typed_builtin() == TYPE_FLOAT) + + var typed_int := 556 + var converted_floats: Array[float] = [typed_int] + assert(str(converted_floats) == '[556]') + assert(converted_floats.get_typed_builtin() == TYPE_FLOAT) + + + const constant_basic = [228] + assert(str(constant_basic) == '[228]') + assert(constant_basic.get_typed_builtin() == TYPE_NIL) + + const constant_floats: Array[float] = [constant_float - constant_basic[0] - constant_int] + assert(str(constant_floats) == '[552]') + assert(constant_floats.get_typed_builtin() == TYPE_FLOAT) + + + var source_floats: Array[float] = [999.74] + untyped_basic = source_floats + var destination_floats: Array[float] = untyped_basic + destination_floats[0] -= 0.74 + assert(str(source_floats) == '[999]') + assert(str(untyped_basic) == '[999]') + assert(str(destination_floats) == '[999]') + assert(destination_floats.get_typed_builtin() == TYPE_FLOAT) + + + var duplicated_floats := empty_floats.duplicate().slice(2, 3) + duplicated_floats[0] *= 3 + assert(str(duplicated_floats) == '[789]') + assert(duplicated_floats.get_typed_builtin() == TYPE_FLOAT) + + + var b_objects: Array[B] = [B.new(), null] + assert(b_objects.size() == 2) + assert(b_objects.get_typed_builtin() == TYPE_OBJECT) + assert(b_objects.get_typed_script() == B) + + var a_objects: Array[A] = [A.new(), B.new(), null, b_objects[0]] + assert(a_objects.size() == 4) + assert(a_objects.get_typed_builtin() == TYPE_OBJECT) + assert(a_objects.get_typed_script() == A) + + var a_passed = (func check_a_passing(a_objects: Array[A]): return a_objects.size()).call(a_objects) + assert(a_passed == 4) + + var b_passed = (func check_b_passing(basic: Array): return basic[0] != null).call(b_objects) + assert(b_passed == true) + + + var empty_strings: Array[String] = [] + var empty_bools: Array[bool] = [] + var empty_basic_one := [] + var empty_basic_two := [] + assert(empty_strings == empty_bools) + assert(empty_basic_one == empty_basic_two) + assert(empty_strings.hash() == empty_bools.hash()) + assert(empty_basic_one.hash() == empty_basic_two.hash()) + + + var assign_source: Array[int] = [527] + var assign_target: Array[int] = [] + assign_target.assign(assign_source) + assert(str(assign_source) == '[527]') + assert(str(assign_target) == '[527]') + assign_source.push_back(657) + assert(str(assign_source) == '[527, 657]') + assert(str(assign_target) == '[527]') + + + var defaults_passed = (func check_defaults_passing(one: Array[int] = [], two := one): + one.push_back(887) + two.push_back(198) + assert(str(one) == '[887, 198]') + assert(str(two) == '[887, 198]') + two = [130] + assert(str(one) == '[887, 198]') + assert(str(two) == '[130]') + return true + ).call() + assert(defaults_passed == true) + + + var members := Members.new() + var members_passed := members.check_passing() + assert(members_passed == true) + + + var resized_basic: Array = [] + resized_basic.resize(1) + assert(typeof(resized_basic[0]) == TYPE_NIL) + assert(resized_basic[0] == null) + + var resized_ints: Array[int] = [] + resized_ints.resize(1) + assert(typeof(resized_ints[0]) == TYPE_INT) + assert(resized_ints[0] == 0) + + var resized_arrays: Array[Array] = [] + resized_arrays.resize(1) + assert(typeof(resized_arrays[0]) == TYPE_ARRAY) + resized_arrays[0].resize(1) + resized_arrays[0][0] = 523 + assert(str(resized_arrays) == '[[523]]') + + var resized_objects: Array[Object] = [] + resized_objects.resize(1) + assert(typeof(resized_objects[0]) == TYPE_NIL) + assert(resized_objects[0] == null) + + + var typed_enums: Array[E] = [] + typed_enums.resize(1) + assert(str(typed_enums) == '[0]') + typed_enums[0] = E.E0 + assert(str(typed_enums) == '[391]') + assert(typed_enums.get_typed_builtin() == TYPE_INT) + + + var a := A.new() + var typed_natives: Array[RefCounted] = [a] + var typed_scripts = Array(typed_natives, TYPE_OBJECT, "RefCounted", A) + assert(typed_scripts[0] == a) + + + print('ok') diff --git a/modules/gdscript/tests/scripts/analyzer/features/typed_array_usage.out b/modules/gdscript/tests/scripts/analyzer/features/typed_array_usage.out new file mode 100644 index 0000000000..1b47ed10dc --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/typed_array_usage.out @@ -0,0 +1,2 @@ +GDTEST_OK +ok diff --git a/modules/gdscript/tests/scripts/analyzer/features/vararg_call.gd b/modules/gdscript/tests/scripts/analyzer/features/vararg_call.gd new file mode 100644 index 0000000000..d444250f1e --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/vararg_call.gd @@ -0,0 +1,6 @@ +signal ok() + +@warning_ignore("return_value_discarded") +func test(): + ok.connect(func(): print('ok')) + emit_signal(&'ok') diff --git a/modules/gdscript/tests/scripts/analyzer/features/vararg_call.out b/modules/gdscript/tests/scripts/analyzer/features/vararg_call.out new file mode 100644 index 0000000000..1b47ed10dc --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/vararg_call.out @@ -0,0 +1,2 @@ +GDTEST_OK +ok diff --git a/modules/gdscript/tests/scripts/analyzer/typed_array_assignment.out b/modules/gdscript/tests/scripts/analyzer/typed_array_assignment.out deleted file mode 100644 index ad2e6558d7..0000000000 --- a/modules/gdscript/tests/scripts/analyzer/typed_array_assignment.out +++ /dev/null @@ -1,2 +0,0 @@ -GDTEST_ANALYZER_ERROR -Cannot assign a value of type Array[String] to constant "arr" with specified type Array[int]. diff --git a/modules/gdscript/tests/scripts/analyzer/warnings/get_node_without_onready.gd b/modules/gdscript/tests/scripts/analyzer/warnings/get_node_without_onready.gd new file mode 100644 index 0000000000..849df0921e --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/warnings/get_node_without_onready.gd @@ -0,0 +1,17 @@ +extends Node + +var add_node = do_add_node() # Hack to have one node on init and not fail at runtime. + +var shorthand = $Node +var with_self = self.get_node(^"Node") +var without_self = get_node(^"Node") +var with_cast = get_node(^"Node") as Node +var shorthand_with_cast = $Node as Node + +func test(): + print("warn") + +func do_add_node(): + var node = Node.new() + node.name = "Node" + add_child(node) diff --git a/modules/gdscript/tests/scripts/analyzer/warnings/get_node_without_onready.out b/modules/gdscript/tests/scripts/analyzer/warnings/get_node_without_onready.out new file mode 100644 index 0000000000..62b3ae291f --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/warnings/get_node_without_onready.out @@ -0,0 +1,22 @@ +GDTEST_OK +>> WARNING +>> Line: 5 +>> GET_NODE_DEFAULT_WITHOUT_ONREADY +>> The default value is using "$" which won't return nodes in the scene tree before "_ready()" is called. Use the "@onready" annotation to solve this. +>> WARNING +>> Line: 6 +>> GET_NODE_DEFAULT_WITHOUT_ONREADY +>> The default value is using "get_node()" which won't return nodes in the scene tree before "_ready()" is called. Use the "@onready" annotation to solve this. +>> WARNING +>> Line: 7 +>> GET_NODE_DEFAULT_WITHOUT_ONREADY +>> The default value is using "get_node()" which won't return nodes in the scene tree before "_ready()" is called. Use the "@onready" annotation to solve this. +>> WARNING +>> Line: 8 +>> GET_NODE_DEFAULT_WITHOUT_ONREADY +>> The default value is using "get_node()" which won't return nodes in the scene tree before "_ready()" is called. Use the "@onready" annotation to solve this. +>> WARNING +>> Line: 9 +>> GET_NODE_DEFAULT_WITHOUT_ONREADY +>> The default value is using "$" which won't return nodes in the scene tree before "_ready()" is called. Use the "@onready" annotation to solve this. +warn diff --git a/modules/gdscript/tests/scripts/analyzer/warnings/inference_with_variant.gd b/modules/gdscript/tests/scripts/analyzer/warnings/inference_with_variant.gd new file mode 100644 index 0000000000..024e91b7c6 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/warnings/inference_with_variant.gd @@ -0,0 +1,6 @@ +func test(): + var inferred_with_variant := return_variant() + print(inferred_with_variant) + +func return_variant() -> Variant: + return "warn" diff --git a/modules/gdscript/tests/scripts/analyzer/warnings/inference_with_variant.out b/modules/gdscript/tests/scripts/analyzer/warnings/inference_with_variant.out new file mode 100644 index 0000000000..1d4078d2f7 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/warnings/inference_with_variant.out @@ -0,0 +1,6 @@ +GDTEST_OK +>> WARNING +>> Line: 2 +>> INFERENCE_ON_VARIANT +>> The variable type is being inferred from a Variant value, so it will be typed as Variant. +warn diff --git a/modules/gdscript/tests/scripts/analyzer/warnings/onready_with_export.gd b/modules/gdscript/tests/scripts/analyzer/warnings/onready_with_export.gd new file mode 100644 index 0000000000..0b358ca5f2 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/warnings/onready_with_export.gd @@ -0,0 +1,6 @@ +extends Node + +@onready @export var conflict = "" + +func test(): + print("warn") diff --git a/modules/gdscript/tests/scripts/analyzer/warnings/onready_with_export.out b/modules/gdscript/tests/scripts/analyzer/warnings/onready_with_export.out new file mode 100644 index 0000000000..ff184f9f04 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/warnings/onready_with_export.out @@ -0,0 +1,6 @@ +GDTEST_OK +>> WARNING +>> Line: 3 +>> ONREADY_WITH_EXPORT +>> The "@onready" annotation will make the default value to be set after the "@export" takes effect and will override it. +warn diff --git a/modules/gdscript/tests/scripts/analyzer/warnings/overriding_native_method.gd b/modules/gdscript/tests/scripts/analyzer/warnings/overriding_native_method.gd new file mode 100644 index 0000000000..19d40f8ec8 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/warnings/overriding_native_method.gd @@ -0,0 +1,5 @@ +func test(): + print("warn") + +func get(_property: StringName) -> Variant: + return null diff --git a/modules/gdscript/tests/scripts/analyzer/warnings/overriding_native_method.out b/modules/gdscript/tests/scripts/analyzer/warnings/overriding_native_method.out new file mode 100644 index 0000000000..793faa05d4 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/warnings/overriding_native_method.out @@ -0,0 +1,6 @@ +GDTEST_OK +>> WARNING +>> Line: 4 +>> NATIVE_METHOD_OVERRIDE +>> The method "get" overrides a method from native class "Object". This won't be called by the engine and may not work as expected. +warn diff --git a/modules/gdscript/tests/scripts/parser/errors/class_name_after_annotation.gd b/modules/gdscript/tests/scripts/parser/errors/class_name_after_annotation.gd index 0085b3f367..2470fe978e 100644 --- a/modules/gdscript/tests/scripts/parser/errors/class_name_after_annotation.gd +++ b/modules/gdscript/tests/scripts/parser/errors/class_name_after_annotation.gd @@ -1,5 +1,5 @@ # Error here. Annotations should be used before `class_name`, not after. -class_name HelloWorld +class_name WrongAnnotationPlace @icon("res://path/to/optional/icon.svg") func test(): diff --git a/modules/gdscript/tests/scripts/parser/features/allow_id_similar_to_keyword_in_ascii.gd b/modules/gdscript/tests/scripts/parser/features/allow_id_similar_to_keyword_in_ascii.gd new file mode 100644 index 0000000000..390d314b94 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/features/allow_id_similar_to_keyword_in_ascii.gd @@ -0,0 +1,3 @@ +func test(): + var P1 = "ok" # Technically it is visually similar to keyword "PI" but allowed since it's in ASCII range. + print(P1) diff --git a/modules/gdscript/tests/scripts/parser/features/allow_id_similar_to_keyword_in_ascii.out b/modules/gdscript/tests/scripts/parser/features/allow_id_similar_to_keyword_in_ascii.out new file mode 100644 index 0000000000..1b47ed10dc --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/features/allow_id_similar_to_keyword_in_ascii.out @@ -0,0 +1,2 @@ +GDTEST_OK +ok diff --git a/modules/gdscript/tests/scripts/parser/features/allowed_keywords_as_identifiers.gd b/modules/gdscript/tests/scripts/parser/features/allowed_keywords_as_identifiers.gd new file mode 100644 index 0000000000..7e1982597c --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/features/allowed_keywords_as_identifiers.gd @@ -0,0 +1,16 @@ +func test(): + # The following keywords are allowed as identifiers: + var match = "match" + print(match) + + var PI = "PI" + print(PI) + + var INF = "INF" + print(INF) + + var NAN = "NAN" + print(NAN) + + var TAU = "TAU" + print(TAU) diff --git a/modules/gdscript/tests/scripts/parser/features/allowed_keywords_as_identifiers.out b/modules/gdscript/tests/scripts/parser/features/allowed_keywords_as_identifiers.out new file mode 100644 index 0000000000..aae2ae13d5 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/features/allowed_keywords_as_identifiers.out @@ -0,0 +1,6 @@ +GDTEST_OK +match +PI +INF +NAN +TAU diff --git a/modules/gdscript/tests/scripts/parser/features/export_enum.gd b/modules/gdscript/tests/scripts/parser/features/export_enum.gd new file mode 100644 index 0000000000..9b2c22dea1 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/features/export_enum.gd @@ -0,0 +1,15 @@ +@export_enum("Red", "Green", "Blue") var untyped + +@export_enum("Red", "Green", "Blue") var weak_int = 0 +@export_enum("Red", "Green", "Blue") var weak_string = "" + +@export_enum("Red", "Green", "Blue") var hard_int: int +@export_enum("Red", "Green", "Blue") var hard_string: String + +@export_enum("Red:10", "Green:20", "Blue:30") var with_values + +func test(): + for property in get_property_list(): + if property.name in ["untyped", "weak_int", "weak_string", "hard_int", + "hard_string", "with_values"]: + prints(property.name, property.type, property.hint_string) diff --git a/modules/gdscript/tests/scripts/parser/features/export_enum.out b/modules/gdscript/tests/scripts/parser/features/export_enum.out new file mode 100644 index 0000000000..330b7eaf01 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/features/export_enum.out @@ -0,0 +1,7 @@ +GDTEST_OK +untyped 2 Red,Green,Blue +weak_int 2 Red,Green,Blue +weak_string 4 Red,Green,Blue +hard_int 2 Red,Green,Blue +hard_string 4 Red,Green,Blue +with_values 2 Red:10,Green:20,Blue:30 diff --git a/modules/gdscript/tests/scripts/parser/warnings/confusable_identifier.gd b/modules/gdscript/tests/scripts/parser/warnings/confusable_identifier.gd index e2caac8ffd..41b38c4bba 100644 --- a/modules/gdscript/tests/scripts/parser/warnings/confusable_identifier.gd +++ b/modules/gdscript/tests/scripts/parser/warnings/confusable_identifier.gd @@ -1,5 +1,12 @@ +extends Node + func test(): var port = 0 # Only latin characters. var pοrt = 1 # The "ο" is Greek omicron. prints(port, pοrt) + +# Do not call this since nodes aren't in the tree. It is just a parser check. +func nodes(): + var _node1 = $port # Only latin characters. + var _node2 = $pοrt # The "ο" is Greek omicron. diff --git a/modules/gdscript/tests/scripts/parser/warnings/confusable_identifier.out b/modules/gdscript/tests/scripts/parser/warnings/confusable_identifier.out index c483396443..c189204285 100644 --- a/modules/gdscript/tests/scripts/parser/warnings/confusable_identifier.out +++ b/modules/gdscript/tests/scripts/parser/warnings/confusable_identifier.out @@ -1,6 +1,10 @@ GDTEST_OK >> WARNING ->> Line: 3 +>> Line: 5 +>> CONFUSABLE_IDENTIFIER +>> The identifier "pοrt" has misleading characters and might be confused with something else. +>> WARNING +>> Line: 12 >> CONFUSABLE_IDENTIFIER >> The identifier "pοrt" has misleading characters and might be confused with something else. 0 1 diff --git a/modules/gdscript/tests/scripts/runtime/assign_to_read_only_property.gd b/modules/gdscript/tests/scripts/runtime/assign_to_read_only_property.gd new file mode 100644 index 0000000000..19c4186622 --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/assign_to_read_only_property.gd @@ -0,0 +1,7 @@ +func test(): + var state = PhysicsDirectBodyState3DExtension.new() + assign(state) + state.free() + +func assign(state): + state.center_of_mass.x -= 1.0 diff --git a/modules/gdscript/tests/scripts/runtime/assign_to_read_only_property.out b/modules/gdscript/tests/scripts/runtime/assign_to_read_only_property.out new file mode 100644 index 0000000000..c181c5dd02 --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/assign_to_read_only_property.out @@ -0,0 +1,6 @@ +GDTEST_RUNTIME_ERROR +>> SCRIPT ERROR +>> on function: assign() +>> runtime/assign_to_read_only_property.gd +>> 7 +>> Cannot set value into property "center_of_mass" (on base "PhysicsDirectBodyState3DExtension") because it is read-only. diff --git a/modules/gdscript/tests/scripts/runtime/assign_to_read_only_property_with_variable_index.gd b/modules/gdscript/tests/scripts/runtime/assign_to_read_only_property_with_variable_index.gd new file mode 100644 index 0000000000..f15f580272 --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/assign_to_read_only_property_with_variable_index.gd @@ -0,0 +1,8 @@ +func test(): + var state = PhysicsDirectBodyState3DExtension.new() + var prop = &"center_of_mass" + assign(state, prop) + state.free() + +func assign(state, prop): + state[prop].x = 1.0 diff --git a/modules/gdscript/tests/scripts/runtime/assign_to_read_only_property_with_variable_index.out b/modules/gdscript/tests/scripts/runtime/assign_to_read_only_property_with_variable_index.out new file mode 100644 index 0000000000..2cdc81aacc --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/assign_to_read_only_property_with_variable_index.out @@ -0,0 +1,6 @@ +GDTEST_RUNTIME_ERROR +>> SCRIPT ERROR +>> on function: assign() +>> runtime/assign_to_read_only_property_with_variable_index.gd +>> 8 +>> Cannot set value into property "center_of_mass" (on base "PhysicsDirectBodyState3DExtension") because it is read-only. diff --git a/modules/gdscript/tests/scripts/runtime/errors/typed_array_assign_basic_to_typed.gd b/modules/gdscript/tests/scripts/runtime/errors/typed_array_assign_basic_to_typed.gd new file mode 100644 index 0000000000..e9dbc1b640 --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/errors/typed_array_assign_basic_to_typed.gd @@ -0,0 +1,4 @@ +func test(): + var basic := [1] + var typed: Array[int] = basic + print('not ok') diff --git a/modules/gdscript/tests/scripts/runtime/errors/typed_array_assign_basic_to_typed.out b/modules/gdscript/tests/scripts/runtime/errors/typed_array_assign_basic_to_typed.out new file mode 100644 index 0000000000..bca700b4ec --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/errors/typed_array_assign_basic_to_typed.out @@ -0,0 +1,6 @@ +GDTEST_RUNTIME_ERROR +>> SCRIPT ERROR +>> on function: test() +>> runtime/errors/typed_array_assign_basic_to_typed.gd +>> 3 +>> Trying to assign an array of type "Array" to a variable of type "Array[int]". diff --git a/modules/gdscript/tests/scripts/runtime/errors/typed_array_assign_differently_typed.gd b/modules/gdscript/tests/scripts/runtime/errors/typed_array_assign_differently_typed.gd new file mode 100644 index 0000000000..920352a6ea --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/errors/typed_array_assign_differently_typed.gd @@ -0,0 +1,4 @@ +func test(): + var differently: Variant = [1.0] as Array[float] + var typed: Array[int] = differently + print('not ok') diff --git a/modules/gdscript/tests/scripts/runtime/errors/typed_array_assign_differently_typed.out b/modules/gdscript/tests/scripts/runtime/errors/typed_array_assign_differently_typed.out new file mode 100644 index 0000000000..402ab38fb3 --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/errors/typed_array_assign_differently_typed.out @@ -0,0 +1,6 @@ +GDTEST_RUNTIME_ERROR +>> SCRIPT ERROR +>> on function: test() +>> runtime/errors/typed_array_assign_differently_typed.gd +>> 3 +>> Trying to assign an array of type "Array[float]" to a variable of type "Array[int]". diff --git a/modules/gdscript/tests/scripts/runtime/errors/typed_array_pass_basic_to_typed.gd b/modules/gdscript/tests/scripts/runtime/errors/typed_array_pass_basic_to_typed.gd new file mode 100644 index 0000000000..e1fd0f7168 --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/errors/typed_array_pass_basic_to_typed.gd @@ -0,0 +1,7 @@ +func expect_typed(typed: Array[int]): + print(typed.size()) + +func test(): + var basic := [1] + expect_typed(basic) + print('not ok') diff --git a/modules/gdscript/tests/scripts/runtime/errors/typed_array_pass_basic_to_typed.out b/modules/gdscript/tests/scripts/runtime/errors/typed_array_pass_basic_to_typed.out new file mode 100644 index 0000000000..6f210e944e --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/errors/typed_array_pass_basic_to_typed.out @@ -0,0 +1,6 @@ +GDTEST_RUNTIME_ERROR +>> SCRIPT ERROR +>> on function: test() +>> runtime/errors/typed_array_pass_basic_to_typed.gd +>> 6 +>> Invalid type in function 'expect_typed' in base 'RefCounted ()'. The array of argument 1 (Array) does not have the same element type as the expected typed array argument. diff --git a/modules/gdscript/tests/scripts/runtime/errors/typed_array_pass_differently_to_typed.gd b/modules/gdscript/tests/scripts/runtime/errors/typed_array_pass_differently_to_typed.gd new file mode 100644 index 0000000000..e2d2721e8c --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/errors/typed_array_pass_differently_to_typed.gd @@ -0,0 +1,7 @@ +func expect_typed(typed: Array[int]): + print(typed.size()) + +func test(): + var differently: Variant = [1.0] as Array[float] + expect_typed(differently) + print('not ok') diff --git a/modules/gdscript/tests/scripts/runtime/errors/typed_array_pass_differently_to_typed.out b/modules/gdscript/tests/scripts/runtime/errors/typed_array_pass_differently_to_typed.out new file mode 100644 index 0000000000..3cd4e25bd8 --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/errors/typed_array_pass_differently_to_typed.out @@ -0,0 +1,6 @@ +GDTEST_RUNTIME_ERROR +>> SCRIPT ERROR +>> on function: test() +>> runtime/errors/typed_array_pass_differently_to_typed.gd +>> 6 +>> Invalid type in function 'expect_typed' in base 'RefCounted ()'. The array of argument 1 (Array[float]) does not have the same element type as the expected typed array argument. diff --git a/modules/gdscript/tests/scripts/runtime/features/typed_array_init_with_untyped_in_literal.gd b/modules/gdscript/tests/scripts/runtime/features/typed_array_init_with_untyped_in_literal.gd new file mode 100644 index 0000000000..ec444b4ffa --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/features/typed_array_init_with_untyped_in_literal.gd @@ -0,0 +1,6 @@ +func test(): + var untyped: Variant = 32 + var typed: Array[int] = [untyped] + assert(typed.get_typed_builtin() == TYPE_INT) + assert(str(typed) == '[32]') + print('ok') diff --git a/modules/gdscript/tests/scripts/runtime/features/typed_array_init_with_untyped_in_literal.out b/modules/gdscript/tests/scripts/runtime/features/typed_array_init_with_untyped_in_literal.out new file mode 100644 index 0000000000..1b47ed10dc --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/features/typed_array_init_with_untyped_in_literal.out @@ -0,0 +1,2 @@ +GDTEST_OK +ok |