summaryrefslogtreecommitdiff
path: root/modules
diff options
context:
space:
mode:
Diffstat (limited to 'modules')
-rw-r--r--modules/gdscript/doc_classes/@GDScript.xml16
-rw-r--r--modules/gdscript/gdscript_analyzer.cpp205
-rw-r--r--modules/gdscript/gdscript_analyzer.h2
-rw-r--r--modules/gdscript/gdscript_editor.cpp45
-rw-r--r--modules/gdscript/gdscript_parser.cpp77
-rw-r--r--modules/gdscript/gdscript_parser.h11
-rw-r--r--modules/gdscript/gdscript_warning.cpp31
-rw-r--r--modules/gdscript/gdscript_warning.h49
-rw-r--r--modules/gdscript/tests/gdscript_test_runner.cpp6
-rw-r--r--modules/gdscript/tests/scripts/analyzer/errors/await_signal_no_infer.gd4
-rw-r--r--modules/gdscript/tests/scripts/analyzer/errors/await_signal_no_infer.out2
-rw-r--r--modules/gdscript/tests/scripts/analyzer/errors/function_dont_match_parent_signature_parameter_default_values.out2
-rw-r--r--modules/gdscript/tests/scripts/analyzer/errors/get_node_shorthand_within_non_node.gd5
-rw-r--r--modules/gdscript/tests/scripts/analyzer/errors/get_node_shorthand_within_non_node.out2
-rw-r--r--modules/gdscript/tests/scripts/analyzer/errors/onready_within_non_node.gd6
-rw-r--r--modules/gdscript/tests/scripts/analyzer/errors/onready_within_non_node.out2
-rw-r--r--modules/gdscript/tests/scripts/analyzer/errors/onready_within_non_node_inner_class.gd7
-rw-r--r--modules/gdscript/tests/scripts/analyzer/errors/onready_within_non_node_inner_class.out2
-rw-r--r--modules/gdscript/tests/scripts/analyzer/errors/ternary_weak_infer.gd6
-rw-r--r--modules/gdscript/tests/scripts/analyzer/errors/ternary_weak_infer.out2
-rw-r--r--modules/gdscript/tests/scripts/analyzer/features/allow_get_node_with_onready.gd18
-rw-r--r--modules/gdscript/tests/scripts/analyzer/features/allow_get_node_with_onready.out5
-rw-r--r--modules/gdscript/tests/scripts/analyzer/features/assign_to_native_enum_property.gd13
-rw-r--r--modules/gdscript/tests/scripts/analyzer/features/assign_to_native_enum_property.out5
-rw-r--r--modules/gdscript/tests/scripts/analyzer/features/await_type_inference.gd15
-rw-r--r--modules/gdscript/tests/scripts/analyzer/features/await_type_inference.out2
-rw-r--r--modules/gdscript/tests/scripts/analyzer/features/hard_variants.gd4
-rw-r--r--modules/gdscript/tests/scripts/analyzer/features/onready_on_inner_class_with_non_node_outer.gd7
-rw-r--r--modules/gdscript/tests/scripts/analyzer/features/onready_on_inner_class_with_non_node_outer.out2
-rw-r--r--modules/gdscript/tests/scripts/analyzer/features/ternary_hard_infer.gd15
-rw-r--r--modules/gdscript/tests/scripts/analyzer/features/ternary_hard_infer.out2
-rw-r--r--modules/gdscript/tests/scripts/analyzer/features/typed_array_usage.gd6
-rw-r--r--modules/gdscript/tests/scripts/analyzer/warnings/get_node_without_onready.gd17
-rw-r--r--modules/gdscript/tests/scripts/analyzer/warnings/get_node_without_onready.out22
-rw-r--r--modules/gdscript/tests/scripts/analyzer/warnings/inference_with_variant.gd6
-rw-r--r--modules/gdscript/tests/scripts/analyzer/warnings/inference_with_variant.out6
-rw-r--r--modules/gdscript/tests/scripts/analyzer/warnings/onready_with_export.gd6
-rw-r--r--modules/gdscript/tests/scripts/analyzer/warnings/onready_with_export.out6
-rw-r--r--modules/gdscript/tests/scripts/analyzer/warnings/overriding_native_method.gd5
-rw-r--r--modules/gdscript/tests/scripts/analyzer/warnings/overriding_native_method.out6
-rw-r--r--modules/gltf/editor/editor_import_blend_runner.cpp13
-rw-r--r--modules/gltf/gltf_document.cpp63
-rw-r--r--modules/mono/glue/GodotSharp/GodotPlugins/PluginLoadContext.cs2
-rw-r--r--modules/text_server_adv/text_server_adv.cpp50
-rw-r--r--modules/text_server_adv/text_server_adv.h3
45 files changed, 619 insertions, 162 deletions
diff --git a/modules/gdscript/doc_classes/@GDScript.xml b/modules/gdscript/doc_classes/@GDScript.xml
index 32acad76aa..e05b17168d 100644
--- a/modules/gdscript/doc_classes/@GDScript.xml
+++ b/modules/gdscript/doc_classes/@GDScript.xml
@@ -565,14 +565,22 @@
</annotation>
<annotation name="@rpc" qualifiers="vararg">
<return type="void" />
- <param index="0" name="mode" type="String" default="&quot;&quot;" />
- <param index="1" name="sync" type="String" default="&quot;&quot;" />
- <param index="2" name="transfer_mode" type="String" default="&quot;&quot;" />
+ <param index="0" name="mode" type="String" default="&quot;authority&quot;" />
+ <param index="1" name="sync" type="String" default="&quot;call_remote&quot;" />
+ <param index="2" name="transfer_mode" type="String" default="&quot;unreliable&quot;" />
<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_analyzer.cpp b/modules/gdscript/gdscript_analyzer.cpp
index fd04d3c660..cd1dcf9a78 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;
@@ -782,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);
@@ -790,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);
@@ -878,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: {
@@ -931,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;
@@ -1059,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) {
@@ -1102,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);
@@ -1179,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
}
}
@@ -1289,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();
@@ -1355,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;
@@ -1421,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();
@@ -1447,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++;
@@ -1464,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
}
@@ -1472,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;
}
@@ -1481,6 +1564,13 @@ 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;
@@ -1498,6 +1588,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;
}
@@ -1538,16 +1631,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);
@@ -1599,6 +1692,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);
@@ -2373,30 +2471,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
@@ -2953,7 +3048,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) {
@@ -4011,7 +4110,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);
@@ -4019,6 +4117,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);
}
@@ -4334,15 +4433,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;
@@ -4477,6 +4588,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;
}
diff --git a/modules/gdscript/gdscript_analyzer.h b/modules/gdscript/gdscript_analyzer.h
index 75d52509a4..a4c84db6b9 100644
--- a/modules/gdscript/gdscript_analyzer.h
+++ b/modules/gdscript/gdscript_analyzer.h
@@ -117,7 +117,7 @@ class GDScriptAnalyzer {
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, 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);
diff --git a/modules/gdscript/gdscript_editor.cpp b/modules/gdscript/gdscript_editor.cpp
index 12c10642ec..8cfd48b52b 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);
+ }
}
}
@@ -2022,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();
@@ -2133,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.
diff --git a/modules/gdscript/gdscript_parser.cpp b/modules/gdscript/gdscript_parser.cpp
index ed2dce471f..0a1a64cb59 100644
--- a/modules/gdscript/gdscript_parser.cpp
+++ b/modules/gdscript/gdscript_parser.cpp
@@ -109,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");
@@ -158,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;
@@ -180,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;
}
@@ -1837,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)");
@@ -3548,7 +3552,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);
}
@@ -3611,6 +3619,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.)");
@@ -3685,6 +3697,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;
}
}
@@ -3902,26 +3921,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 bc0fe58fa7..0ba0d5b6da 100644
--- a/modules/gdscript/gdscript_parser.h
+++ b/modules/gdscript/gdscript_parser.h
@@ -299,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;
@@ -331,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() {
@@ -1265,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
diff --git a/modules/gdscript/gdscript_warning.cpp b/modules/gdscript/gdscript_warning.cpp
index 9436146bed..ef59a07f1a 100644
--- a/modules/gdscript/gdscript_warning.cpp
+++ b/modules/gdscript/gdscript_warning.cpp
@@ -170,6 +170,21 @@ String GDScriptWarning::get_message() const {
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
}
@@ -179,14 +194,8 @@ 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) {
@@ -240,7 +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"
+ "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 fa2907cdae..f0123c518c 100644
--- a/modules/gdscript/gdscript_warning.h
+++ b/modules/gdscript/gdscript_warning.h
@@ -82,9 +82,58 @@ public:
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/tests/gdscript_test_runner.cpp b/modules/gdscript/tests/gdscript_test_runner.cpp
index 5b8af0ff34..57405aa1ce 100644
--- a/modules/gdscript/tests/gdscript_test_runner.cpp
+++ b/modules/gdscript/tests/gdscript_test_runner.cpp
@@ -146,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
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/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/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/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_usage.gd b/modules/gdscript/tests/scripts/analyzer/features/typed_array_usage.gd
index e1e6134fd4..092ae49d00 100644
--- a/modules/gdscript/tests/scripts/analyzer/features/typed_array_usage.gd
+++ b/modules/gdscript/tests/scripts/analyzer/features/typed_array_usage.gd
@@ -201,4 +201,10 @@ func test():
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/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/gltf/editor/editor_import_blend_runner.cpp b/modules/gltf/editor/editor_import_blend_runner.cpp
index c203a91834..659a60e6a1 100644
--- a/modules/gltf/editor/editor_import_blend_runner.cpp
+++ b/modules/gltf/editor/editor_import_blend_runner.cpp
@@ -181,7 +181,18 @@ Error EditorImportBlendRunner::start_blender(const String &p_python_script, bool
Error EditorImportBlendRunner::do_import(const Dictionary &p_options) {
if (is_using_rpc()) {
- return do_import_rpc(p_options);
+ Error err = do_import_rpc(p_options);
+ if (err != OK) {
+ // Retry without using RPC (slow, but better than the import failing completely).
+ if (err == ERR_CONNECTION_ERROR) {
+ // Disable RPC if the connection could not be established.
+ print_error(vformat("Failed to connect to Blender via RPC, switching to direct imports of .blend files. Check your proxy and firewall settings, then RPC can be re-enabled by changing the editor setting `filesystem/import/blender/rpc_port` to %d.", rpc_port));
+ EditorSettings::get_singleton()->set_manually("filesystem/import/blender/rpc_port", 0);
+ rpc_port = 0;
+ }
+ err = do_import_direct(p_options);
+ }
+ return err;
} else {
return do_import_direct(p_options);
}
diff --git a/modules/gltf/gltf_document.cpp b/modules/gltf/gltf_document.cpp
index 1a09b5bdcc..bd3ee1881f 100644
--- a/modules/gltf/gltf_document.cpp
+++ b/modules/gltf/gltf_document.cpp
@@ -54,6 +54,9 @@
#include "modules/modules_enabled.gen.h" // For csg, gridmap.
+#ifdef TOOLS_ENABLED
+#include "editor/editor_file_system.h"
+#endif
#ifdef MODULE_CSG_ENABLED
#include "modules/csg/csg_shape.h"
#endif // MODULE_CSG_ENABLED
@@ -3232,54 +3235,38 @@ Error GLTFDocument::_parse_images(Ref<GLTFState> p_state, const String &p_base_p
p_state->source_images.push_back(Ref<Image>());
} else {
Error err = OK;
- bool must_import = false;
+ bool must_import = true;
+ Vector<uint8_t> img_data = img->get_data();
+ Dictionary generator_parameters;
String file_path = p_state->get_base_path() + "/" + p_state->filename.get_basename() + "_" + img->get_name() + ".png";
- if (!FileAccess::exists(file_path + ".import")) {
+ if (FileAccess::exists(file_path + ".import")) {
Ref<ConfigFile> config;
config.instantiate();
- config->set_value("remap", "importer", "texture");
- config->set_value("remap", "type", "Texture2D");
- // Currently, it will likely use project defaults of Detect 3D, so textures will be reimported again.
- if (!config->has_section_key("params", "mipmaps/generate")) {
- config->set_value("params", "mipmaps/generate", true);
+ config->load(file_path + ".import");
+ if (config->has_section_key("remap", "generator_parameters")) {
+ generator_parameters = (Dictionary)config->get_value("remap", "generator_parameters");
}
-
- if (ProjectSettings::get_singleton()->has_setting("importer_defaults/texture")) {
- //use defaults if exist
- Dictionary importer_defaults = GLOBAL_GET("importer_defaults/texture");
- List<Variant> importer_def_keys;
- importer_defaults.get_key_list(&importer_def_keys);
- for (const Variant &key : importer_def_keys) {
- if (!config->has_section_key("params", (String)key)) {
- config->set_value("params", (String)key, importer_defaults[key]);
- }
- }
+ if (!generator_parameters.has("md5")) {
+ must_import = false; // Didn't come form a gltf document; don't overwrite.
}
- err = config->save(file_path + ".import");
- ERR_FAIL_COND_V(err != OK, err);
- must_import = true;
- }
- Vector<uint8_t> png_buffer = img->save_png_to_buffer();
- if (ResourceLoader::exists(file_path)) {
- Ref<FileAccess> file = FileAccess::open(file_path, FileAccess::READ, &err);
- if (err == OK && file.is_valid()) {
- Vector<uint8_t> orig_png_buffer = file->get_buffer(file->get_length());
- if (png_buffer != orig_png_buffer) {
- must_import = true;
- }
+ String existing_md5 = generator_parameters["md5"];
+ unsigned char md5_hash[16];
+ CryptoCore::md5(img_data.ptr(), img_data.size(), md5_hash);
+ String new_md5 = String::hex_encode_buffer(md5_hash, 16);
+ generator_parameters["md5"] = new_md5;
+ if (new_md5 == existing_md5) {
+ must_import = false;
}
- } else {
- must_import = true;
}
if (must_import) {
- Ref<FileAccess> file = FileAccess::open(file_path, FileAccess::WRITE, &err);
+ err = img->save_png(file_path);
ERR_FAIL_COND_V(err != OK, err);
- ERR_FAIL_COND_V(file.is_null(), FAILED);
- file->store_buffer(png_buffer);
- file->flush();
- file.unref();
// ResourceLoader::import will crash if not is_editor_hint(), so this case is protected above and will fall through to uncompressed.
- ResourceLoader::import(file_path);
+ HashMap<StringName, Variant> custom_options;
+ custom_options[SNAME("mipmaps/generate")] = true;
+ // Will only use project settings defaults if custom_importer is empty.
+ EditorFileSystem::get_singleton()->update_file(file_path);
+ EditorFileSystem::get_singleton()->reimport_append(file_path, custom_options, String(), generator_parameters);
}
Ref<Texture2D> saved_image = ResourceLoader::load(file_path, "Texture2D");
if (saved_image.is_valid()) {
diff --git a/modules/mono/glue/GodotSharp/GodotPlugins/PluginLoadContext.cs b/modules/mono/glue/GodotSharp/GodotPlugins/PluginLoadContext.cs
index 02d0226e90..93baf4e51c 100644
--- a/modules/mono/glue/GodotSharp/GodotPlugins/PluginLoadContext.cs
+++ b/modules/mono/glue/GodotSharp/GodotPlugins/PluginLoadContext.cs
@@ -30,7 +30,7 @@ namespace GodotPlugins
if (baseDirectory != null)
{
if (!Path.EndsInDirectorySeparator(baseDirectory))
- baseDirectory += Path.PathSeparator;
+ baseDirectory += Path.DirectorySeparatorChar;
// This SetData call effectively sets AppContext.BaseDirectory
// See https://github.com/dotnet/runtime/blob/v6.0.0/src/libraries/System.Private.CoreLib/src/System/AppContext.cs#L21-L25
AppDomain.CurrentDomain.SetData("APP_CONTEXT_BASE_DIRECTORY", baseDirectory);
diff --git a/modules/text_server_adv/text_server_adv.cpp b/modules/text_server_adv/text_server_adv.cpp
index 9b474bf2ce..b55188ce0c 100644
--- a/modules/text_server_adv/text_server_adv.cpp
+++ b/modules/text_server_adv/text_server_adv.cpp
@@ -6118,20 +6118,22 @@ int64_t TextServerAdvanced::_is_confusable(const String &p_string, const PackedS
Vector<UChar *> skeletons;
skeletons.resize(p_dict.size());
- USpoofChecker *sc = uspoof_open(&status);
- uspoof_setChecks(sc, USPOOF_CONFUSABLE, &status);
+ if (sc_conf == nullptr) {
+ sc_conf = uspoof_open(&status);
+ uspoof_setChecks(sc_conf, USPOOF_CONFUSABLE, &status);
+ }
for (int i = 0; i < p_dict.size(); i++) {
Char16String word = p_dict[i].utf16();
- int32_t len = uspoof_getSkeleton(sc, 0, word.get_data(), -1, NULL, 0, &status);
+ int32_t len = uspoof_getSkeleton(sc_conf, 0, word.get_data(), -1, NULL, 0, &status);
skeletons.write[i] = (UChar *)memalloc(++len * sizeof(UChar));
status = U_ZERO_ERROR;
- uspoof_getSkeleton(sc, 0, word.get_data(), -1, skeletons.write[i], len, &status);
+ uspoof_getSkeleton(sc_conf, 0, word.get_data(), -1, skeletons.write[i], len, &status);
}
- int32_t len = uspoof_getSkeleton(sc, 0, utf16.get_data(), -1, NULL, 0, &status);
+ int32_t len = uspoof_getSkeleton(sc_conf, 0, utf16.get_data(), -1, NULL, 0, &status);
UChar *skel = (UChar *)memalloc(++len * sizeof(UChar));
status = U_ZERO_ERROR;
- uspoof_getSkeleton(sc, 0, utf16.get_data(), -1, skel, len, &status);
+ uspoof_getSkeleton(sc_conf, 0, utf16.get_data(), -1, skel, len, &status);
for (int i = 0; i < skeletons.size(); i++) {
if (u_strcmp(skel, skeletons[i]) == 0) {
match_index = i;
@@ -6143,7 +6145,6 @@ int64_t TextServerAdvanced::_is_confusable(const String &p_string, const PackedS
for (int i = 0; i < skeletons.size(); i++) {
memfree(skeletons.write[i]);
}
- uspoof_close(sc);
ERR_FAIL_COND_V_MSG(U_FAILURE(status), -1, u_errorName(status));
@@ -6159,19 +6160,18 @@ bool TextServerAdvanced::_spoof_check(const String &p_string) const {
UErrorCode status = U_ZERO_ERROR;
Char16String utf16 = p_string.utf16();
- USet *allowed = uset_openEmpty();
- uset_addAll(allowed, uspoof_getRecommendedSet(&status));
- uset_addAll(allowed, uspoof_getInclusionSet(&status));
-
- USpoofChecker *sc = uspoof_open(&status);
- uspoof_setAllowedChars(sc, allowed, &status);
- uspoof_setRestrictionLevel(sc, USPOOF_MODERATELY_RESTRICTIVE);
-
- int32_t bitmask = uspoof_check(sc, utf16.get_data(), -1, NULL, &status);
-
- uspoof_close(sc);
- uset_close(allowed);
+ if (allowed == nullptr) {
+ allowed = uset_openEmpty();
+ uset_addAll(allowed, uspoof_getRecommendedSet(&status));
+ uset_addAll(allowed, uspoof_getInclusionSet(&status));
+ }
+ if (sc_spoof == nullptr) {
+ sc_spoof = uspoof_open(&status);
+ uspoof_setAllowedChars(sc_spoof, allowed, &status);
+ uspoof_setRestrictionLevel(sc_spoof, USPOOF_MODERATELY_RESTRICTIVE);
+ }
+ int32_t bitmask = uspoof_check(sc_spoof, utf16.get_data(), -1, NULL, &status);
ERR_FAIL_COND_V_MSG(U_FAILURE(status), false, u_errorName(status));
return (bitmask != 0);
@@ -6587,5 +6587,17 @@ TextServerAdvanced::~TextServerAdvanced() {
FT_Done_FreeType(ft_library);
}
#endif
+ if (sc_spoof != nullptr) {
+ uspoof_close(sc_spoof);
+ sc_spoof = nullptr;
+ }
+ if (sc_conf != nullptr) {
+ uspoof_close(sc_conf);
+ sc_conf = nullptr;
+ }
+ if (allowed != nullptr) {
+ uset_close(allowed);
+ allowed = nullptr;
+ }
u_cleanup();
}
diff --git a/modules/text_server_adv/text_server_adv.h b/modules/text_server_adv/text_server_adv.h
index c7fe46d554..1acf5b21f0 100644
--- a/modules/text_server_adv/text_server_adv.h
+++ b/modules/text_server_adv/text_server_adv.h
@@ -159,6 +159,9 @@ class TextServerAdvanced : public TextServerExtension {
// ICU support data.
bool icu_data_loaded = false;
+ mutable USet *allowed = nullptr;
+ mutable USpoofChecker *sc_spoof = nullptr;
+ mutable USpoofChecker *sc_conf = nullptr;
// Font cache data.