summaryrefslogtreecommitdiffstats
path: root/modules/gdscript
diff options
context:
space:
mode:
Diffstat (limited to 'modules/gdscript')
-rw-r--r--modules/gdscript/doc_classes/@GDScript.xml16
-rw-r--r--modules/gdscript/doc_classes/GDScript.xml2
-rw-r--r--modules/gdscript/gdscript.cpp5
-rw-r--r--modules/gdscript/gdscript.h1
-rw-r--r--modules/gdscript/gdscript_analyzer.cpp50
-rw-r--r--modules/gdscript/gdscript_byte_codegen.cpp7
-rw-r--r--modules/gdscript/gdscript_compiler.cpp53
-rw-r--r--modules/gdscript/gdscript_parser.cpp13
-rw-r--r--modules/gdscript/gdscript_vm.cpp38
-rw-r--r--modules/gdscript/tests/scripts/analyzer/errors/not_found_type.gd3
-rw-r--r--modules/gdscript/tests/scripts/analyzer/errors/not_found_type.out2
-rw-r--r--modules/gdscript/tests/scripts/analyzer/features/enums_in_range_call.gd9
-rw-r--r--modules/gdscript/tests/scripts/analyzer/features/enums_in_range_call.out2
-rw-r--r--modules/gdscript/tests/scripts/analyzer/features/for_range_usage.gd7
-rw-r--r--modules/gdscript/tests/scripts/analyzer/features/for_range_usage.out2
-rw-r--r--modules/gdscript/tests/scripts/analyzer/features/inheritance_signature_check_no_meta.gd10
-rw-r--r--modules/gdscript/tests/scripts/analyzer/features/inheritance_signature_check_no_meta.out2
-rw-r--r--modules/gdscript/tests/scripts/analyzer/features/script_typed_assign_null.gd18
-rw-r--r--modules/gdscript/tests/scripts/analyzer/features/script_typed_assign_null.out7
-rw-r--r--modules/gdscript/tests/scripts/analyzer/warnings/lambda_unused_arg.gd2
-rw-r--r--modules/gdscript/tests/scripts/parser/errors/bad_continue_in_lambda.gd5
-rw-r--r--modules/gdscript/tests/scripts/parser/errors/bad_continue_in_lambda.out2
-rw-r--r--modules/gdscript/tests/scripts/parser/features/good_continue_in_lambda.gd13
-rw-r--r--modules/gdscript/tests/scripts/parser/features/good_continue_in_lambda.out2
-rw-r--r--modules/gdscript/tests/scripts/parser/features/string_formatting.gd2
-rw-r--r--modules/gdscript/tests/scripts/runtime/features/conversions_from_native_members.gd11
-rw-r--r--modules/gdscript/tests/scripts/runtime/features/conversions_from_native_members.out2
-rw-r--r--modules/gdscript/tests/scripts/runtime/features/default_set_beforehand.gd20
-rw-r--r--modules/gdscript/tests/scripts/runtime/features/default_set_beforehand.out2
29 files changed, 240 insertions, 68 deletions
diff --git a/modules/gdscript/doc_classes/@GDScript.xml b/modules/gdscript/doc_classes/@GDScript.xml
index 08c8763493..124c8be3e0 100644
--- a/modules/gdscript/doc_classes/@GDScript.xml
+++ b/modules/gdscript/doc_classes/@GDScript.xml
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8" ?>
-<class name="@GDScript" version="4.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../doc/class.xsd">
+<class name="@GDScript" version="4.1" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../doc/class.xsd">
<brief_description>
Built-in GDScript functions.
</brief_description>
@@ -126,6 +126,20 @@
<param index="0" name="value" type="Variant" />
<param index="1" name="type" type="Variant" />
<description>
+ Returns [code]true[/code] if [param value] is an instance of [param type]. The [param type] value must be one of the following:
+ - A constant from the [enum Variant.Type] enumeration, for example [constant TYPE_INT].
+ - An [Object]-derived class which exists in [ClassDB], for example [Node].
+ - A [Script] (you can use any class, including inner one).
+ Unlike the right operand of the [code]is[/code] operator, [param type] can be a non-constant value. The [code]is[/code] operator supports more features (such as typed arrays) and is more performant. Use the operator instead of this method if you do not need dynamic type checking.
+ Examples:
+ [codeblock]
+ print(is_instance_of(a, TYPE_INT))
+ print(is_instance_of(a, Node))
+ print(is_instance_of(a, MyClass))
+ print(is_instance_of(a, MyClass.InnerClass))
+ [/codeblock]
+ [b]Note:[/b] If [param value] and/or [param type] are freed objects (see [method @GlobalScope.is_instance_valid]), or [param type] is not one of the above options, this method will raise an runtime error.
+ See also [method @GlobalScope.typeof], [method type_exists], [method Array.is_same_typed] (and other [Array] methods).
</description>
</method>
<method name="len">
diff --git a/modules/gdscript/doc_classes/GDScript.xml b/modules/gdscript/doc_classes/GDScript.xml
index 1a102bd16f..556f747eb3 100644
--- a/modules/gdscript/doc_classes/GDScript.xml
+++ b/modules/gdscript/doc_classes/GDScript.xml
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8" ?>
-<class name="GDScript" inherits="Script" version="4.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../doc/class.xsd">
+<class name="GDScript" inherits="Script" version="4.1" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../doc/class.xsd">
<brief_description>
A script implemented in the GDScript programming language.
</brief_description>
diff --git a/modules/gdscript/gdscript.cpp b/modules/gdscript/gdscript.cpp
index 00f8d2817a..b6caefbdb5 100644
--- a/modules/gdscript/gdscript.cpp
+++ b/modules/gdscript/gdscript.cpp
@@ -2027,11 +2027,6 @@ String GDScriptLanguage::get_extension() const {
return "gd";
}
-Error GDScriptLanguage::execute_file(const String &p_path) {
- // ??
- return OK;
-}
-
void GDScriptLanguage::finish() {
if (_call_stack) {
memdelete_arr(_call_stack);
diff --git a/modules/gdscript/gdscript.h b/modules/gdscript/gdscript.h
index 82d04f641c..0117ed40ab 100644
--- a/modules/gdscript/gdscript.h
+++ b/modules/gdscript/gdscript.h
@@ -473,7 +473,6 @@ public:
virtual void init() override;
virtual String get_type() const override;
virtual String get_extension() const override;
- virtual Error execute_file(const String &p_path) override;
virtual void finish() override;
/* EDITOR FUNCTIONS */
diff --git a/modules/gdscript/gdscript_analyzer.cpp b/modules/gdscript/gdscript_analyzer.cpp
index 38f9163f70..38d5ae6b77 100644
--- a/modules/gdscript/gdscript_analyzer.cpp
+++ b/modules/gdscript/gdscript_analyzer.cpp
@@ -657,6 +657,10 @@ GDScriptParser::DataType GDScriptAnalyzer::resolve_datatype(GDScriptParser::Type
} else if (ProjectSettings::get_singleton()->has_autoload(first) && ProjectSettings::get_singleton()->get_autoload(first).is_singleton) {
const ProjectSettings::AutoloadInfo &autoload = ProjectSettings::get_singleton()->get_autoload(first);
Ref<GDScriptParserRef> ref = get_parser_for(autoload.path);
+ if (ref.is_null()) {
+ push_error(vformat(R"(The referenced autoload "%s" (from "%s") could not be loaded.)", first, autoload.path), p_type);
+ return bad_type;
+ }
if (ref->raise_status(GDScriptParserRef::INHERITANCE_SOLVED) != OK) {
push_error(vformat(R"(Could not parse singleton "%s" from "%s".)", first, autoload.path), p_type);
return bad_type;
@@ -727,7 +731,7 @@ GDScriptParser::DataType GDScriptAnalyzer::resolve_datatype(GDScriptParser::Type
}
}
if (!result.is_set()) {
- push_error(vformat(R"("%s" was not found in the current scope.)", first), p_type);
+ push_error(vformat(R"(Could not find type "%s" in the current scope.)", first), p_type);
return bad_type;
}
@@ -1534,6 +1538,7 @@ void GDScriptAnalyzer::resolve_function_signature(GDScriptParser::FunctionNode *
// Check if the function signature matches the parent. If not it's an error since it breaks polymorphism.
// Not for the constructor which can vary in signature.
GDScriptParser::DataType base_type = parser->current_class->base_type;
+ base_type.is_meta_type = false;
GDScriptParser::DataType parent_return_type;
List<GDScriptParser::DataType> parameters_types;
int default_par_count = 0;
@@ -1852,33 +1857,40 @@ void GDScriptAnalyzer::resolve_for(GDScriptParser::ForNode *p_for) {
push_error(vformat(R"*(Invalid call for "range()" function. Expected at most 3 arguments, %d given.)*", call->arguments.size()), call->callee);
} else {
// Now we can optimize it.
- bool all_is_constant = true;
+ bool can_reduce = true;
Vector<Variant> args;
args.resize(call->arguments.size());
for (int i = 0; i < call->arguments.size(); i++) {
- reduce_expression(call->arguments[i]);
-
- if (!call->arguments[i]->is_constant) {
- all_is_constant = false;
- } else if (all_is_constant) {
- args.write[i] = call->arguments[i]->reduced_value;
- }
+ GDScriptParser::ExpressionNode *argument = call->arguments[i];
+ reduce_expression(argument);
- GDScriptParser::DataType arg_type = call->arguments[i]->get_datatype();
- if (!arg_type.is_variant()) {
- if (arg_type.kind != GDScriptParser::DataType::BUILTIN) {
- all_is_constant = false;
- push_error(vformat(R"*(Invalid argument for "range()" call. Argument %d should be int or float but "%s" was given.)*", i + 1, arg_type.to_string()), call->arguments[i]);
- } else if (arg_type.builtin_type != Variant::INT && arg_type.builtin_type != Variant::FLOAT) {
- all_is_constant = false;
- push_error(vformat(R"*(Invalid argument for "range()" call. Argument %d should be int or float but "%s" was given.)*", i + 1, arg_type.to_string()), call->arguments[i]);
+ if (argument->is_constant) {
+ if (argument->reduced_value.get_type() != Variant::INT && argument->reduced_value.get_type() != Variant::FLOAT) {
+ can_reduce = false;
+ push_error(vformat(R"*(Invalid argument for "range()" call. Argument %d should be int or float but "%s" was given.)*", i + 1, Variant::get_type_name(argument->reduced_value.get_type())), argument);
+ }
+ if (can_reduce) {
+ args.write[i] = argument->reduced_value;
+ }
+ } else {
+ can_reduce = false;
+ GDScriptParser::DataType argument_type = argument->get_datatype();
+ if (argument_type.is_variant() || !argument_type.is_hard_type()) {
+ mark_node_unsafe(argument);
+ }
+ if (!argument_type.is_variant() && (argument_type.builtin_type != Variant::INT && argument_type.builtin_type != Variant::FLOAT)) {
+ if (!argument_type.is_hard_type()) {
+ downgrade_node_type_source(argument);
+ } else {
+ push_error(vformat(R"*(Invalid argument for "range()" call. Argument %d should be int or float but "%s" was given.)*", i + 1, argument_type.to_string()), argument);
+ }
}
}
}
Variant reduced;
- if (all_is_constant) {
+ if (can_reduce) {
switch (args.size()) {
case 1:
reduced = (int32_t)args[0];
@@ -2606,7 +2618,7 @@ void GDScriptAnalyzer::reduce_binary_op(GDScriptParser::BinaryOpNode *p_binary_o
result = get_operation_type(p_binary_op->variant_op, left_type, right_type, valid, p_binary_op);
if (!valid) {
push_error(vformat(R"(Invalid operands "%s" and "%s" for "%s" operator.)", left_type.to_string(), right_type.to_string(), Variant::get_operator_name(p_binary_op->variant_op)), p_binary_op);
- } else if (result.type_source != GDScriptParser::DataType::ANNOTATED_EXPLICIT) {
+ } else if (!result.is_hard_type()) {
mark_node_unsafe(p_binary_op);
}
} else {
diff --git a/modules/gdscript/gdscript_byte_codegen.cpp b/modules/gdscript/gdscript_byte_codegen.cpp
index a13bf8009f..d6f21d297a 100644
--- a/modules/gdscript/gdscript_byte_codegen.cpp
+++ b/modules/gdscript/gdscript_byte_codegen.cpp
@@ -581,7 +581,8 @@ void GDScriptByteCodeGenerator::write_unary_operator(const Address &p_target, Va
}
void GDScriptByteCodeGenerator::write_binary_operator(const Address &p_target, Variant::Operator p_operator, const Address &p_left_operand, const Address &p_right_operand) {
- if (HAS_BUILTIN_TYPE(p_left_operand) && HAS_BUILTIN_TYPE(p_right_operand)) {
+ // Avoid validated evaluator for modulo and division when operands are int, since there's no check for division by zero.
+ if (HAS_BUILTIN_TYPE(p_left_operand) && HAS_BUILTIN_TYPE(p_right_operand) && ((p_operator != Variant::OP_DIVIDE && p_operator != Variant::OP_MODULE) || p_left_operand.type.builtin_type != Variant::INT || p_right_operand.type.builtin_type != Variant::INT)) {
if (p_target.mode == Address::TEMPORARY) {
Variant::Type result_type = Variant::get_operator_return_type(p_operator, p_left_operand.type.builtin_type, p_right_operand.type.builtin_type);
Variant::Type temp_type = temporaries[p_target.address].type;
@@ -1057,7 +1058,7 @@ void GDScriptByteCodeGenerator::write_call_gdscript_utility(const Address &p_tar
void GDScriptByteCodeGenerator::write_call_utility(const Address &p_target, const StringName &p_function, const Vector<Address> &p_arguments) {
bool is_validated = true;
if (Variant::is_utility_function_vararg(p_function)) {
- is_validated = true; // Vararg works fine with any argument, since they can be any type.
+ is_validated = false; // Vararg needs runtime checks, can't use validated call.
} else if (p_arguments.size() == Variant::get_utility_function_argument_count(p_function)) {
bool all_types_exact = true;
for (int i = 0; i < p_arguments.size(); i++) {
@@ -1106,7 +1107,7 @@ void GDScriptByteCodeGenerator::write_call_builtin_type(const Address &p_target,
// Check if all types are correct.
if (Variant::is_builtin_method_vararg(p_type, p_method)) {
- is_validated = true; // Vararg works fine with any argument, since they can be any type.
+ is_validated = false; // Vararg needs runtime checks, can't use validated call.
} else if (p_arguments.size() == Variant::get_builtin_method_argument_count(p_type, p_method)) {
bool all_types_exact = true;
for (int i = 0; i < p_arguments.size(); i++) {
diff --git a/modules/gdscript/gdscript_compiler.cpp b/modules/gdscript/gdscript_compiler.cpp
index efa75528fc..d0c2cb43a6 100644
--- a/modules/gdscript/gdscript_compiler.cpp
+++ b/modules/gdscript/gdscript_compiler.cpp
@@ -85,7 +85,7 @@ void GDScriptCompiler::_set_error(const String &p_error, const GDScriptParser::N
}
GDScriptDataType GDScriptCompiler::_gdtype_from_datatype(const GDScriptParser::DataType &p_datatype, GDScript *p_owner) {
- if (!p_datatype.is_set() || !p_datatype.is_hard_type()) {
+ if (!p_datatype.is_set() || !p_datatype.is_hard_type() || p_datatype.is_coroutine) {
return GDScriptDataType();
}
@@ -238,7 +238,7 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &code
// Try class members.
if (_is_class_member_property(codegen, identifier)) {
// Get property.
- GDScriptCodeGenerator::Address temp = codegen.add_temporary(); // TODO: Could get the type of the class member here.
+ GDScriptCodeGenerator::Address temp = codegen.add_temporary(_gdtype_from_datatype(p_expression->get_datatype(), codegen.script));
gen->write_get_member(temp, identifier);
return temp;
}
@@ -2018,6 +2018,32 @@ GDScriptFunction *GDScriptCompiler::_parse_function(Error &r_error, GDScript *p_
bool is_initializer = p_func && !p_for_lambda && p_func->identifier->name == GDScriptLanguage::get_singleton()->strings._init;
bool is_implicit_ready = !p_func && p_for_ready;
+ if (!p_for_lambda && is_implicit_initializer) {
+ // Initialize the default values for type variables before anything.
+ // This avoids crashes if they are accessed with validated calls before being properly initialized.
+ // It may happen with out-of-order access or with `@onready` variables.
+ for (const GDScriptParser::ClassNode::Member &member : p_class->members) {
+ if (member.type != GDScriptParser::ClassNode::Member::VARIABLE) {
+ continue;
+ }
+
+ const GDScriptParser::VariableNode *field = member.variable;
+ GDScriptDataType field_type = _gdtype_from_datatype(field->get_datatype(), codegen.script);
+ GDScriptCodeGenerator::Address dst_address(GDScriptCodeGenerator::Address::MEMBER, codegen.script->member_indices[field->identifier->name].index, field_type);
+
+ if (field_type.has_type) {
+ codegen.generator->write_newline(field->start_line);
+
+ 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 if (field_type.kind == GDScriptDataType::BUILTIN) {
+ codegen.generator->write_construct(dst_address, field_type.builtin_type, Vector<GDScriptCodeGenerator::Address>());
+ }
+ // The `else` branch is for objects, in such case we leave it as `null`.
+ }
+ }
+ }
+
if (!p_for_lambda && (is_implicit_initializer || is_implicit_ready)) {
// Initialize class fields.
for (int i = 0; i < p_class->members.size(); i++) {
@@ -2051,16 +2077,6 @@ GDScriptFunction *GDScriptCompiler::_parse_function(Error &r_error, GDScript *p_
if (src_address.mode == GDScriptCodeGenerator::Address::TEMPORARY) {
codegen.generator->pop_temporary();
}
- } else if (field_type.has_type) {
- codegen.generator->write_newline(field->start_line);
-
- // Initialize with default for type.
- 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 if (field_type.kind == GDScriptDataType::BUILTIN) {
- codegen.generator->write_construct(dst_address, field_type.builtin_type, Vector<GDScriptCodeGenerator::Address>());
- }
- // The `else` branch is for objects, in such case we leave it as `null`.
}
}
}
@@ -2265,13 +2281,15 @@ Error GDScriptCompiler::_populate_class_members(GDScript *p_script, const GDScri
GDScriptDataType base_type = _gdtype_from_datatype(p_class->base_type, p_script);
+ int native_idx = GDScriptLanguage::get_singleton()->get_global_map()[base_type.native_type];
+ p_script->native = GDScriptLanguage::get_singleton()->get_global_array()[native_idx];
+ ERR_FAIL_COND_V(p_script->native.is_null(), ERR_BUG);
+
// Inheritance
switch (base_type.kind) {
- case GDScriptDataType::NATIVE: {
- int native_idx = GDScriptLanguage::get_singleton()->get_global_map()[base_type.native_type];
- p_script->native = GDScriptLanguage::get_singleton()->get_global_array()[native_idx];
- ERR_FAIL_COND_V(p_script->native.is_null(), ERR_BUG);
- } break;
+ case GDScriptDataType::NATIVE:
+ // Nothing more to do.
+ break;
case GDScriptDataType::GDSCRIPT: {
Ref<GDScript> base = Ref<GDScript>(base_type.script_type);
if (base.is_null()) {
@@ -2303,7 +2321,6 @@ Error GDScriptCompiler::_populate_class_members(GDScript *p_script, const GDScri
p_script->base = base;
p_script->_base = base.ptr();
p_script->member_indices = base->member_indices;
- p_script->native = base->native;
} break;
default: {
_set_error("Parser bug: invalid inheritance.", nullptr);
diff --git a/modules/gdscript/gdscript_parser.cpp b/modules/gdscript/gdscript_parser.cpp
index b32313dad4..c402b63f7b 100644
--- a/modules/gdscript/gdscript_parser.cpp
+++ b/modules/gdscript/gdscript_parser.cpp
@@ -3138,6 +3138,14 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_lambda(ExpressionNode *p_p
bool previous_in_lambda = in_lambda;
in_lambda = true;
+ // Save break/continue state.
+ bool could_break = can_break;
+ bool could_continue = can_continue;
+
+ // Disallow break/continue.
+ can_break = false;
+ can_continue = false;
+
function->body = parse_suite("lambda declaration", body, true);
complete_extents(function);
complete_extents(lambda);
@@ -3155,6 +3163,11 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_lambda(ExpressionNode *p_p
current_function = previous_function;
in_lambda = previous_in_lambda;
lambda->function = function;
+
+ // Reset break/continue state.
+ can_break = could_break;
+ can_continue = could_continue;
+
return lambda;
}
diff --git a/modules/gdscript/gdscript_vm.cpp b/modules/gdscript/gdscript_vm.cpp
index ba400b8e15..83d2ed6010 100644
--- a/modules/gdscript/gdscript_vm.cpp
+++ b/modules/gdscript/gdscript_vm.cpp
@@ -1326,28 +1326,30 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a
OPCODE_BREAK;
}
- ScriptInstance *scr_inst = val_obj->get_script_instance();
- if (!scr_inst) {
- err_text = "Trying to assign value of type '" + val_obj->get_class_name() +
- "' to a variable of type '" + base_type->get_path().get_file() + "'.";
- OPCODE_BREAK;
- }
+ if (val_obj) { // src is not null
+ ScriptInstance *scr_inst = val_obj->get_script_instance();
+ if (!scr_inst) {
+ err_text = "Trying to assign value of type '" + val_obj->get_class_name() +
+ "' to a variable of type '" + base_type->get_path().get_file() + "'.";
+ OPCODE_BREAK;
+ }
- Script *src_type = val_obj->get_script_instance()->get_script().ptr();
- bool valid = false;
+ Script *src_type = scr_inst->get_script().ptr();
+ bool valid = false;
- while (src_type) {
- if (src_type == base_type) {
- valid = true;
- break;
+ while (src_type) {
+ if (src_type == base_type) {
+ valid = true;
+ break;
+ }
+ src_type = src_type->get_base_script().ptr();
}
- src_type = src_type->get_base_script().ptr();
- }
- if (!valid) {
- err_text = "Trying to assign value of type '" + val_obj->get_script_instance()->get_script()->get_path().get_file() +
- "' to a variable of type '" + base_type->get_path().get_file() + "'.";
- OPCODE_BREAK;
+ if (!valid) {
+ err_text = "Trying to assign value of type '" + val_obj->get_script_instance()->get_script()->get_path().get_file() +
+ "' to a variable of type '" + base_type->get_path().get_file() + "'.";
+ OPCODE_BREAK;
+ }
}
}
#endif // DEBUG_ENABLED
diff --git a/modules/gdscript/tests/scripts/analyzer/errors/not_found_type.gd b/modules/gdscript/tests/scripts/analyzer/errors/not_found_type.gd
new file mode 100644
index 0000000000..1644295b38
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/errors/not_found_type.gd
@@ -0,0 +1,3 @@
+func test():
+ var foo: Foo
+ print('not ok')
diff --git a/modules/gdscript/tests/scripts/analyzer/errors/not_found_type.out b/modules/gdscript/tests/scripts/analyzer/errors/not_found_type.out
new file mode 100644
index 0000000000..3f6c2d868d
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/errors/not_found_type.out
@@ -0,0 +1,2 @@
+GDTEST_ANALYZER_ERROR
+Could not find type "Foo" in the current scope.
diff --git a/modules/gdscript/tests/scripts/analyzer/features/enums_in_range_call.gd b/modules/gdscript/tests/scripts/analyzer/features/enums_in_range_call.gd
new file mode 100644
index 0000000000..d2d9d04508
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/features/enums_in_range_call.gd
@@ -0,0 +1,9 @@
+enum E { E0 = 0, E3 = 3 }
+
+func test():
+ var total := 0
+ for value in range(E.E0, E.E3):
+ var inferable := value
+ total += inferable
+ assert(total == 0 + 1 + 2)
+ print('ok')
diff --git a/modules/gdscript/tests/scripts/analyzer/features/enums_in_range_call.out b/modules/gdscript/tests/scripts/analyzer/features/enums_in_range_call.out
new file mode 100644
index 0000000000..1b47ed10dc
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/features/enums_in_range_call.out
@@ -0,0 +1,2 @@
+GDTEST_OK
+ok
diff --git a/modules/gdscript/tests/scripts/analyzer/features/for_range_usage.gd b/modules/gdscript/tests/scripts/analyzer/features/for_range_usage.gd
new file mode 100644
index 0000000000..4a7f10f1ee
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/features/for_range_usage.gd
@@ -0,0 +1,7 @@
+func test():
+ var array := [3, 6, 9]
+ var result := ''
+ for i in range(array.size(), 0, -1):
+ result += str(array[i - 1])
+ assert(result == '963')
+ print('ok')
diff --git a/modules/gdscript/tests/scripts/analyzer/features/for_range_usage.out b/modules/gdscript/tests/scripts/analyzer/features/for_range_usage.out
new file mode 100644
index 0000000000..1b47ed10dc
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/features/for_range_usage.out
@@ -0,0 +1,2 @@
+GDTEST_OK
+ok
diff --git a/modules/gdscript/tests/scripts/analyzer/features/inheritance_signature_check_no_meta.gd b/modules/gdscript/tests/scripts/analyzer/features/inheritance_signature_check_no_meta.gd
new file mode 100644
index 0000000000..6c056530f6
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/features/inheritance_signature_check_no_meta.gd
@@ -0,0 +1,10 @@
+func test():
+ print("ok")
+
+# https://github.com/godotengine/godot/issues/71994
+class A:
+ extends RefCounted
+class B:
+ extends A
+ func duplicate():
+ pass
diff --git a/modules/gdscript/tests/scripts/analyzer/features/inheritance_signature_check_no_meta.out b/modules/gdscript/tests/scripts/analyzer/features/inheritance_signature_check_no_meta.out
new file mode 100644
index 0000000000..1b47ed10dc
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/features/inheritance_signature_check_no_meta.out
@@ -0,0 +1,2 @@
+GDTEST_OK
+ok
diff --git a/modules/gdscript/tests/scripts/analyzer/features/script_typed_assign_null.gd b/modules/gdscript/tests/scripts/analyzer/features/script_typed_assign_null.gd
new file mode 100644
index 0000000000..1b47680a7b
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/features/script_typed_assign_null.gd
@@ -0,0 +1,18 @@
+extends Node
+
+class LocalClass extends Node:
+ pass
+
+func test():
+ var typed: LocalClass = get_node_or_null("does_not_exist")
+ var untyped = null
+ var node_1: LocalClass = typed
+ var node_2: LocalClass = untyped
+ var node_3 = typed
+ var node_4 = untyped
+ print(typed)
+ print(untyped)
+ print(node_1)
+ print(node_2)
+ print(node_3)
+ print(node_4)
diff --git a/modules/gdscript/tests/scripts/analyzer/features/script_typed_assign_null.out b/modules/gdscript/tests/scripts/analyzer/features/script_typed_assign_null.out
new file mode 100644
index 0000000000..d66b72f5c3
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/features/script_typed_assign_null.out
@@ -0,0 +1,7 @@
+GDTEST_OK
+<Object#null>
+<null>
+<Object#null>
+<null>
+<Object#null>
+<null>
diff --git a/modules/gdscript/tests/scripts/analyzer/warnings/lambda_unused_arg.gd b/modules/gdscript/tests/scripts/analyzer/warnings/lambda_unused_arg.gd
index 6fc90ea29c..1e5c10b7d5 100644
--- a/modules/gdscript/tests/scripts/analyzer/warnings/lambda_unused_arg.gd
+++ b/modules/gdscript/tests/scripts/analyzer/warnings/lambda_unused_arg.gd
@@ -1,4 +1,4 @@
func test():
var lambda := func(unused: Variant) -> void:
pass
- lambda.call()
+ lambda.call("something")
diff --git a/modules/gdscript/tests/scripts/parser/errors/bad_continue_in_lambda.gd b/modules/gdscript/tests/scripts/parser/errors/bad_continue_in_lambda.gd
new file mode 100644
index 0000000000..319c1801c0
--- /dev/null
+++ b/modules/gdscript/tests/scripts/parser/errors/bad_continue_in_lambda.gd
@@ -0,0 +1,5 @@
+func test():
+ for index in range(0, 1):
+ var lambda := func():
+ continue
+ print('not ok')
diff --git a/modules/gdscript/tests/scripts/parser/errors/bad_continue_in_lambda.out b/modules/gdscript/tests/scripts/parser/errors/bad_continue_in_lambda.out
new file mode 100644
index 0000000000..262dfbc09b
--- /dev/null
+++ b/modules/gdscript/tests/scripts/parser/errors/bad_continue_in_lambda.out
@@ -0,0 +1,2 @@
+GDTEST_PARSER_ERROR
+Cannot use "continue" outside of a loop.
diff --git a/modules/gdscript/tests/scripts/parser/features/good_continue_in_lambda.gd b/modules/gdscript/tests/scripts/parser/features/good_continue_in_lambda.gd
new file mode 100644
index 0000000000..2fa45c1d7d
--- /dev/null
+++ b/modules/gdscript/tests/scripts/parser/features/good_continue_in_lambda.gd
@@ -0,0 +1,13 @@
+func test():
+ var i_string := ''
+ for i in 3:
+ if i == 1: continue
+ var lambda := func():
+ var j_string := ''
+ for j in 3:
+ if j == 1: continue
+ j_string += str(j)
+ return j_string
+ i_string += lambda.call()
+ assert(i_string == '0202')
+ print('ok')
diff --git a/modules/gdscript/tests/scripts/parser/features/good_continue_in_lambda.out b/modules/gdscript/tests/scripts/parser/features/good_continue_in_lambda.out
new file mode 100644
index 0000000000..1b47ed10dc
--- /dev/null
+++ b/modules/gdscript/tests/scripts/parser/features/good_continue_in_lambda.out
@@ -0,0 +1,2 @@
+GDTEST_OK
+ok
diff --git a/modules/gdscript/tests/scripts/parser/features/string_formatting.gd b/modules/gdscript/tests/scripts/parser/features/string_formatting.gd
index a91837145d..0815915f04 100644
--- a/modules/gdscript/tests/scripts/parser/features/string_formatting.gd
+++ b/modules/gdscript/tests/scripts/parser/features/string_formatting.gd
@@ -13,6 +13,6 @@ func test():
print("hello %.02f" % 0.123456 == "hello 0.12")
# Dynamic padding:
- # <https://docs.godotengine.org/en/stable/getting_started/scripting/gdscript/gdscript_format_string.html#dynamic-padding>
+ # https://docs.godotengine.org/en/latest/getting_started/scripting/gdscript/gdscript_format_string.html#dynamic-padding
print("hello %*.*f" % [7, 3, 0.123456] == "hello 0.123")
print("hello %0*.*f" % [7, 3, 0.123456] == "hello 000.123")
diff --git a/modules/gdscript/tests/scripts/runtime/features/conversions_from_native_members.gd b/modules/gdscript/tests/scripts/runtime/features/conversions_from_native_members.gd
new file mode 100644
index 0000000000..a778fb1a94
--- /dev/null
+++ b/modules/gdscript/tests/scripts/runtime/features/conversions_from_native_members.gd
@@ -0,0 +1,11 @@
+class Foo extends Node:
+ func _init():
+ name = 'f'
+ var string: String = name
+ assert(typeof(string) == TYPE_STRING)
+ assert(string == 'f')
+ print('ok')
+
+func test():
+ var foo := Foo.new()
+ foo.free()
diff --git a/modules/gdscript/tests/scripts/runtime/features/conversions_from_native_members.out b/modules/gdscript/tests/scripts/runtime/features/conversions_from_native_members.out
new file mode 100644
index 0000000000..1b47ed10dc
--- /dev/null
+++ b/modules/gdscript/tests/scripts/runtime/features/conversions_from_native_members.out
@@ -0,0 +1,2 @@
+GDTEST_OK
+ok
diff --git a/modules/gdscript/tests/scripts/runtime/features/default_set_beforehand.gd b/modules/gdscript/tests/scripts/runtime/features/default_set_beforehand.gd
new file mode 100644
index 0000000000..03278e453f
--- /dev/null
+++ b/modules/gdscript/tests/scripts/runtime/features/default_set_beforehand.gd
@@ -0,0 +1,20 @@
+extends Node
+
+@onready var later_inferred := [1]
+@onready var later_static : Array
+@onready var later_static_with_init : Array = [1]
+@onready var later_untyped = [1]
+
+func test():
+ assert(typeof(later_inferred) == TYPE_ARRAY)
+ assert(later_inferred.size() == 0)
+
+ assert(typeof(later_static) == TYPE_ARRAY)
+ assert(later_static.size() == 0)
+
+ assert(typeof(later_static_with_init) == TYPE_ARRAY)
+ assert(later_static_with_init.size() == 0)
+
+ assert(typeof(later_untyped) == TYPE_NIL)
+
+ print("ok")
diff --git a/modules/gdscript/tests/scripts/runtime/features/default_set_beforehand.out b/modules/gdscript/tests/scripts/runtime/features/default_set_beforehand.out
new file mode 100644
index 0000000000..1b47ed10dc
--- /dev/null
+++ b/modules/gdscript/tests/scripts/runtime/features/default_set_beforehand.out
@@ -0,0 +1,2 @@
+GDTEST_OK
+ok