summaryrefslogtreecommitdiff
path: root/modules/gdscript
diff options
context:
space:
mode:
Diffstat (limited to 'modules/gdscript')
-rw-r--r--modules/gdscript/doc_classes/GDScript.xml2
-rw-r--r--modules/gdscript/editor/gdscript_highlighter.cpp8
-rw-r--r--modules/gdscript/editor/gdscript_highlighter.h4
-rw-r--r--modules/gdscript/gdscript_analyzer.cpp6
-rw-r--r--modules/gdscript/gdscript_compiler.cpp36
-rw-r--r--modules/gdscript/gdscript_editor.cpp5
-rw-r--r--modules/gdscript/gdscript_parser.cpp35
-rw-r--r--modules/gdscript/gdscript_parser.h3
-rw-r--r--modules/gdscript/gdscript_vm.cpp12
-rw-r--r--modules/gdscript/language_server/gdscript_language_protocol.cpp17
-rw-r--r--modules/gdscript/language_server/gdscript_language_protocol.h3
-rw-r--r--modules/gdscript/language_server/gdscript_workspace.cpp59
-rw-r--r--modules/gdscript/language_server/gdscript_workspace.h4
-rw-r--r--modules/gdscript/language_server/lsp.hpp22
-rw-r--r--modules/gdscript/tests/scripts/analyzer/errors/bitwise_float_right_operand.gd2
-rw-r--r--modules/gdscript/tests/scripts/analyzer/errors/bitwise_float_right_operand.out2
-rw-r--r--modules/gdscript/tests/scripts/analyzer/errors/class_name_shadows_builtin_type.gd5
-rw-r--r--modules/gdscript/tests/scripts/analyzer/errors/class_name_shadows_builtin_type.out2
-rw-r--r--modules/gdscript/tests/scripts/analyzer/errors/constant_name_shadows_builtin_type.gd4
-rw-r--r--modules/gdscript/tests/scripts/analyzer/errors/constant_name_shadows_builtin_type.out2
-rw-r--r--modules/gdscript/tests/scripts/analyzer/errors/enum_name_shadows_builtin_type.gd4
-rw-r--r--modules/gdscript/tests/scripts/analyzer/errors/enum_name_shadows_builtin_type.out2
-rw-r--r--modules/gdscript/tests/scripts/analyzer/errors/variable_name_shadows_builtin_type.gd4
-rw-r--r--modules/gdscript/tests/scripts/analyzer/errors/variable_name_shadows_builtin_type.out2
-rw-r--r--modules/gdscript/tests/scripts/analyzer/features/static_method_builtin_type.gd2
-rw-r--r--modules/gdscript/tests/scripts/analyzer/features/static_method_builtin_type.out2
-rw-r--r--modules/gdscript/tests/scripts/parser/errors/brace_syntax.gd3
-rw-r--r--modules/gdscript/tests/scripts/parser/errors/brace_syntax.out2
-rw-r--r--modules/gdscript/tests/scripts/parser/errors/invalid_escape_sequence.gd2
-rw-r--r--modules/gdscript/tests/scripts/parser/errors/invalid_escape_sequence.out2
-rw-r--r--modules/gdscript/tests/scripts/parser/errors/invalid_ternary_operator.gd5
-rw-r--r--modules/gdscript/tests/scripts/parser/errors/invalid_ternary_operator.out2
-rw-r--r--modules/gdscript/tests/scripts/parser/errors/vcs_conflict_marker.gd13
-rw-r--r--modules/gdscript/tests/scripts/parser/errors/vcs_conflict_marker.out2
-rw-r--r--modules/gdscript/tests/scripts/parser/errors/yield_instead_of_await.gd6
-rw-r--r--modules/gdscript/tests/scripts/parser/errors/yield_instead_of_await.out2
-rw-r--r--modules/gdscript/tests/scripts/parser/features/export_variable.gd7
-rw-r--r--modules/gdscript/tests/scripts/parser/features/export_variable.out3
-rw-r--r--modules/gdscript/tests/scripts/parser/features/function_default_parameter_type_inference.gd5
-rw-r--r--modules/gdscript/tests/scripts/parser/features/function_default_parameter_type_inference.out2
-rw-r--r--modules/gdscript/tests/scripts/parser/features/function_many_parameters.gd5
-rw-r--r--modules/gdscript/tests/scripts/parser/features/function_many_parameters.out2
-rw-r--r--modules/gdscript/tests/scripts/parser/features/lambda_callable.gd4
-rw-r--r--modules/gdscript/tests/scripts/parser/features/lambda_callable.out2
-rw-r--r--modules/gdscript/tests/scripts/parser/features/lambda_capture_callable.gd4
-rw-r--r--modules/gdscript/tests/scripts/parser/features/lambda_capture_callable.out2
-rw-r--r--modules/gdscript/tests/scripts/parser/features/lambda_named_callable.gd10
-rw-r--r--modules/gdscript/tests/scripts/parser/features/lambda_named_callable.out3
-rw-r--r--modules/gdscript/tests/scripts/parser/features/nested_function_calls.gd5
-rw-r--r--modules/gdscript/tests/scripts/parser/features/nested_function_calls.out2
-rw-r--r--modules/gdscript/tests/scripts/parser/features/semicolon_as_terminator.gd22
-rw-r--r--modules/gdscript/tests/scripts/parser/features/semicolon_as_terminator.out10
-rw-r--r--modules/gdscript/tests/scripts/parser/features/signal_declaration.gd20
-rw-r--r--modules/gdscript/tests/scripts/parser/features/signal_declaration.out2
-rw-r--r--modules/gdscript/tests/scripts/parser/features/single_line_declaration.gd11
-rw-r--r--modules/gdscript/tests/scripts/parser/features/single_line_declaration.out3
-rw-r--r--modules/gdscript/tests/scripts/parser/features/typed_arrays.gd5
-rw-r--r--modules/gdscript/tests/scripts/parser/features/typed_arrays.out3
-rw-r--r--modules/gdscript/tests/scripts/runtime/features/property_with_operator_assignment.gd11
-rw-r--r--modules/gdscript/tests/scripts/runtime/features/property_with_operator_assignment.out8
-rw-r--r--modules/gdscript/tests/scripts/runtime/features/recursion.gd19
-rw-r--r--modules/gdscript/tests/scripts/runtime/features/recursion.out125
62 files changed, 537 insertions, 46 deletions
diff --git a/modules/gdscript/doc_classes/GDScript.xml b/modules/gdscript/doc_classes/GDScript.xml
index 72738f027a..d45202bd40 100644
--- a/modules/gdscript/doc_classes/GDScript.xml
+++ b/modules/gdscript/doc_classes/GDScript.xml
@@ -30,6 +30,4 @@
</description>
</method>
</methods>
- <constants>
- </constants>
</class>
diff --git a/modules/gdscript/editor/gdscript_highlighter.cpp b/modules/gdscript/editor/gdscript_highlighter.cpp
index ab441d194a..6529154e5c 100644
--- a/modules/gdscript/editor/gdscript_highlighter.cpp
+++ b/modules/gdscript/editor/gdscript_highlighter.cpp
@@ -467,7 +467,7 @@ void GDScriptSyntaxHighlighter::_update_cache() {
List<StringName> global_classes;
ScriptServer::get_global_class_list(&global_classes);
for (const StringName &E : global_classes) {
- keywords[String(E)] = usertype_color;
+ keywords[E] = usertype_color;
}
/* Autoloads. */
@@ -486,7 +486,7 @@ void GDScriptSyntaxHighlighter::_update_cache() {
List<String> core_types;
gdscript->get_core_type_words(&core_types);
for (const String &E : core_types) {
- keywords[E] = basetype_color;
+ keywords[StringName(E)] = basetype_color;
}
/* Reserved words. */
@@ -496,9 +496,9 @@ void GDScriptSyntaxHighlighter::_update_cache() {
gdscript->get_reserved_words(&keyword_list);
for (const String &E : keyword_list) {
if (gdscript->is_control_flow_keyword(E)) {
- keywords[E] = control_flow_keyword_color;
+ keywords[StringName(E)] = control_flow_keyword_color;
} else {
- keywords[E] = keyword_color;
+ keywords[StringName(E)] = keyword_color;
}
}
diff --git a/modules/gdscript/editor/gdscript_highlighter.h b/modules/gdscript/editor/gdscript_highlighter.h
index fabd64dab8..07f21b34ae 100644
--- a/modules/gdscript/editor/gdscript_highlighter.h
+++ b/modules/gdscript/editor/gdscript_highlighter.h
@@ -47,8 +47,8 @@ private:
Vector<ColorRegion> color_regions;
Map<int, int> color_region_cache;
- Dictionary keywords;
- Dictionary member_keywords;
+ HashMap<StringName, Color> keywords;
+ HashMap<StringName, Color> member_keywords;
enum Type {
NONE,
diff --git a/modules/gdscript/gdscript_analyzer.cpp b/modules/gdscript/gdscript_analyzer.cpp
index ceb6d5a5f0..aa62ad20ff 100644
--- a/modules/gdscript/gdscript_analyzer.cpp
+++ b/modules/gdscript/gdscript_analyzer.cpp
@@ -175,6 +175,11 @@ Error GDScriptAnalyzer::check_native_member_name_conflict(const StringName &p_me
return ERR_PARSE_ERROR;
}
+ if (GDScriptParser::get_builtin_type(p_member_name) != Variant::VARIANT_MAX) {
+ push_error(vformat(R"(The member "%s" cannot have the same name as a builtin type.)", p_member_name), p_member_node);
+ return ERR_PARSE_ERROR;
+ }
+
return OK;
}
@@ -1754,7 +1759,6 @@ void GDScriptAnalyzer::reduce_assignment(GDScriptParser::AssignmentNode *p_assig
} else {
// TODO: Warning in this case.
mark_node_unsafe(p_assignment);
- p_assignment->use_conversion_assign = true;
}
}
} else {
diff --git a/modules/gdscript/gdscript_compiler.cpp b/modules/gdscript/gdscript_compiler.cpp
index a8aef84db3..947224e93e 100644
--- a/modules/gdscript/gdscript_compiler.cpp
+++ b/modules/gdscript/gdscript_compiler.cpp
@@ -1071,19 +1071,25 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &code
}
}
- GDScriptCodeGenerator::Address assigned = _parse_expression(codegen, r_error, assignment->assigned_value);
- GDScriptCodeGenerator::Address op_result;
+ GDScriptCodeGenerator::Address assigned_value = _parse_expression(codegen, r_error, assignment->assigned_value);
if (r_error) {
return GDScriptCodeGenerator::Address();
}
- if (assignment->operation != GDScriptParser::AssignmentNode::OP_NONE) {
+ GDScriptCodeGenerator::Address to_assign;
+ bool has_operation = assignment->operation != GDScriptParser::AssignmentNode::OP_NONE;
+ if (has_operation) {
// Perform operation.
- op_result = codegen.add_temporary();
- gen->write_binary_operator(op_result, assignment->variant_op, target, assigned);
+ GDScriptCodeGenerator::Address op_result = codegen.add_temporary();
+ GDScriptCodeGenerator::Address og_value = _parse_expression(codegen, r_error, assignment->assignee);
+ gen->write_binary_operator(op_result, assignment->variant_op, og_value, assigned_value);
+ to_assign = op_result;
+
+ if (og_value.mode == GDScriptCodeGenerator::Address::TEMPORARY) {
+ gen->pop_temporary();
+ }
} else {
- op_result = assigned;
- assigned = GDScriptCodeGenerator::Address();
+ to_assign = assigned_value;
}
GDScriptDataType assign_type = _gdtype_from_datatype(assignment->assignee->get_datatype());
@@ -1091,25 +1097,25 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &code
if (has_setter && !is_in_setter) {
// Call setter.
Vector<GDScriptCodeGenerator::Address> args;
- args.push_back(op_result);
+ args.push_back(to_assign);
gen->write_call(GDScriptCodeGenerator::Address(), GDScriptCodeGenerator::Address(GDScriptCodeGenerator::Address::SELF), setter_function, args);
} else {
// Just assign.
if (assignment->use_conversion_assign) {
- gen->write_assign_with_conversion(target, op_result);
+ gen->write_assign_with_conversion(target, to_assign);
} else {
- gen->write_assign(target, op_result);
+ gen->write_assign(target, to_assign);
}
}
- if (op_result.mode == GDScriptCodeGenerator::Address::TEMPORARY) {
- gen->pop_temporary();
+ if (to_assign.mode == GDScriptCodeGenerator::Address::TEMPORARY) {
+ gen->pop_temporary(); // Pop assigned value or temp operation result.
}
- if (assigned.mode == GDScriptCodeGenerator::Address::TEMPORARY) {
- gen->pop_temporary();
+ if (has_operation && assigned_value.mode == GDScriptCodeGenerator::Address::TEMPORARY) {
+ gen->pop_temporary(); // Pop assigned value if not done before.
}
if (target.mode == GDScriptCodeGenerator::Address::TEMPORARY) {
- gen->pop_temporary();
+ gen->pop_temporary(); // Pop the target to assignment.
}
}
return GDScriptCodeGenerator::Address(); // Assignment does not return a value.
diff --git a/modules/gdscript/gdscript_editor.cpp b/modules/gdscript/gdscript_editor.cpp
index f79e5726ce..044ac4b661 100644
--- a/modules/gdscript/gdscript_editor.cpp
+++ b/modules/gdscript/gdscript_editor.cpp
@@ -637,7 +637,7 @@ static void _get_directory_contents(EditorFileSystemDirectory *p_dir, Map<String
}
static void _find_annotation_arguments(const GDScriptParser::AnnotationNode *p_annotation, int p_argument, const String p_quote_style, Map<String, ScriptCodeCompletionOption> &r_result) {
- if (p_annotation->name == "@export_range" || p_annotation->name == "@export_exp_range") {
+ if (p_annotation->name == "@export_range") {
if (p_argument == 3 || p_argument == 4) {
// Slider hint.
ScriptCodeCompletionOption slider1("or_greater", ScriptCodeCompletionOption::KIND_PLAIN_TEXT);
@@ -781,6 +781,9 @@ static void _find_identifiers_in_class(const GDScriptParser::ClassNode *p_class,
if (p_only_functions) {
continue;
}
+ if (r_result.has(member.constant->identifier->name)) {
+ continue;
+ }
option = ScriptCodeCompletionOption(member.constant->identifier->name, ScriptCodeCompletionOption::KIND_CONSTANT);
if (member.constant->initializer) {
option.default_value = member.constant->initializer->reduced_value;
diff --git a/modules/gdscript/gdscript_parser.cpp b/modules/gdscript/gdscript_parser.cpp
index c901d9f68f..025accf4ba 100644
--- a/modules/gdscript/gdscript_parser.cpp
+++ b/modules/gdscript/gdscript_parser.cpp
@@ -579,7 +579,7 @@ void GDScriptParser::parse_program() {
}
}
- parse_class_body();
+ parse_class_body(true);
#ifdef TOOLS_ENABLED
for (Map<int, GDScriptTokenizer::CommentData>::Element *E = tokenizer.get_comments().front(); E; E = E->next()) {
@@ -615,9 +615,10 @@ GDScriptParser::ClassNode *GDScriptParser::parse_class() {
}
consume(GDScriptTokenizer::Token::COLON, R"(Expected ":" after class declaration.)");
- consume(GDScriptTokenizer::Token::NEWLINE, R"(Expected newline after class declaration.)");
- if (!consume(GDScriptTokenizer::Token::INDENT, R"(Expected indented block after class declaration.)")) {
+ bool multiline = match(GDScriptTokenizer::Token::NEWLINE);
+
+ if (multiline && !consume(GDScriptTokenizer::Token::INDENT, R"(Expected indented block after class declaration.)")) {
current_class = previous_class;
return n_class;
}
@@ -630,9 +631,11 @@ GDScriptParser::ClassNode *GDScriptParser::parse_class() {
end_statement("superclass");
}
- parse_class_body();
+ parse_class_body(multiline);
- consume(GDScriptTokenizer::Token::DEDENT, R"(Missing unindent at the end of the class body.)");
+ if (multiline) {
+ consume(GDScriptTokenizer::Token::DEDENT, R"(Missing unindent at the end of the class body.)");
+ }
current_class = previous_class;
return n_class;
@@ -747,7 +750,7 @@ void GDScriptParser::parse_class_member(T *(GDScriptParser::*p_parse_function)()
}
}
-void GDScriptParser::parse_class_body() {
+void GDScriptParser::parse_class_body(bool p_is_multiline) {
bool class_end = false;
while (!class_end && !is_at_end()) {
switch (current.type) {
@@ -793,6 +796,9 @@ void GDScriptParser::parse_class_body() {
if (panic_mode) {
synchronize();
}
+ if (!p_is_multiline) {
+ class_end = true;
+ }
}
}
@@ -1053,7 +1059,9 @@ GDScriptParser::SignalNode *GDScriptParser::parse_signal() {
SignalNode *signal = alloc_node<SignalNode>();
signal->identifier = parse_identifier();
- if (match(GDScriptTokenizer::Token::PARENTHESIS_OPEN)) {
+ if (check(GDScriptTokenizer::Token::PARENTHESIS_OPEN)) {
+ push_multiline(true);
+ advance();
do {
if (check(GDScriptTokenizer::Token::PARENTHESIS_CLOSE)) {
// Allow for trailing comma.
@@ -1076,6 +1084,7 @@ GDScriptParser::SignalNode *GDScriptParser::parse_signal() {
}
} while (match(GDScriptTokenizer::Token::COMMA) && !is_at_end());
+ pop_multiline();
consume(GDScriptTokenizer::Token::PARENTHESIS_CLOSE, R"*(Expected closing ")" after signal parameters.)*");
}
@@ -1358,6 +1367,9 @@ GDScriptParser::SuiteNode *GDScriptParser::parse_suite(const String &p_context,
int error_count = 0;
do {
+ if (!multiline && previous.type == GDScriptTokenizer::Token::SEMICOLON && check(GDScriptTokenizer::Token::NEWLINE)) {
+ break;
+ }
Node *statement = parse_statement();
if (statement == nullptr) {
if (error_count++ > 100) {
@@ -1398,7 +1410,7 @@ GDScriptParser::SuiteNode *GDScriptParser::parse_suite(const String &p_context,
break;
}
- } while (multiline && !check(GDScriptTokenizer::Token::DEDENT) && !lambda_ended && !is_at_end());
+ } while ((multiline || previous.type == GDScriptTokenizer::Token::SEMICOLON) && !check(GDScriptTokenizer::Token::DEDENT) && !lambda_ended && !is_at_end());
if (multiline) {
if (!lambda_ended) {
@@ -2810,6 +2822,11 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_lambda(ExpressionNode *p_p
return lambda;
}
+GDScriptParser::ExpressionNode *GDScriptParser::parse_yield(ExpressionNode *p_previous_operand, bool p_can_assign) {
+ push_error(R"("yield" was removed in Godot 4.0. Use "await" instead.)");
+ return nullptr;
+}
+
GDScriptParser::ExpressionNode *GDScriptParser::parse_invalid_token(ExpressionNode *p_previous_operand, bool p_can_assign) {
// Just for better error messages.
GDScriptTokenizer::Token::Type invalid = previous.type;
@@ -3166,7 +3183,7 @@ GDScriptParser::ParseRule *GDScriptParser::get_rule(GDScriptTokenizer::Token::Ty
{ nullptr, nullptr, PREC_NONE }, // TRAIT,
{ nullptr, nullptr, PREC_NONE }, // VAR,
{ nullptr, nullptr, PREC_NONE }, // VOID,
- { nullptr, nullptr, PREC_NONE }, // YIELD,
+ { &GDScriptParser::parse_yield, nullptr, PREC_NONE }, // YIELD,
// Punctuation
{ &GDScriptParser::parse_array, &GDScriptParser::parse_subscript, PREC_SUBSCRIPT }, // BRACKET_OPEN,
{ nullptr, nullptr, PREC_NONE }, // BRACKET_CLOSE,
diff --git a/modules/gdscript/gdscript_parser.h b/modules/gdscript/gdscript_parser.h
index a641c1052d..593fb0cc5e 100644
--- a/modules/gdscript/gdscript_parser.h
+++ b/modules/gdscript/gdscript_parser.h
@@ -1324,7 +1324,7 @@ private:
ClassNode *parse_class();
void parse_class_name();
void parse_extends();
- void parse_class_body();
+ void parse_class_body(bool p_is_multiline);
template <class T>
void parse_class_member(T *(GDScriptParser::*p_parse_function)(), AnnotationInfo::TargetKind p_target, const String &p_member_kind);
SignalNode *parse_signal();
@@ -1388,6 +1388,7 @@ private:
ExpressionNode *parse_attribute(ExpressionNode *p_previous_operand, bool p_can_assign);
ExpressionNode *parse_subscript(ExpressionNode *p_previous_operand, bool p_can_assign);
ExpressionNode *parse_lambda(ExpressionNode *p_previous_operand, bool p_can_assign);
+ ExpressionNode *parse_yield(ExpressionNode *p_previous_operand, bool p_can_assign);
ExpressionNode *parse_invalid_token(ExpressionNode *p_previous_operand, bool p_can_assign);
TypeNode *parse_type(bool p_allow_void = false);
#ifdef TOOLS_ENABLED
diff --git a/modules/gdscript/gdscript_vm.cpp b/modules/gdscript/gdscript_vm.cpp
index bf21c8510a..6186d0edee 100644
--- a/modules/gdscript/gdscript_vm.cpp
+++ b/modules/gdscript/gdscript_vm.cpp
@@ -88,9 +88,9 @@ static String _get_var_type(const Variant *p_var) {
Object *bobj = p_var->get_validated_object_with_check(was_freed);
if (!bobj) {
if (was_freed) {
- basestr = "null instance";
- } else {
basestr = "previously freed";
+ } else {
+ basestr = "null instance";
}
} else {
basestr = bobj->get_class();
@@ -1233,7 +1233,7 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a
GD_ERR_BREAK(to_type < 0 || to_type >= Variant::VARIANT_MAX);
#ifdef DEBUG_ENABLED
- if (src->get_type() == Variant::OBJECT && !src->operator ObjectID().is_ref_counted() && ObjectDB::get_instance(src->operator ObjectID()) == nullptr) {
+ if (src->operator Object *() && !src->get_validated_object()) {
err_text = "Trying to cast a freed object.";
OPCODE_BREAK;
}
@@ -1263,7 +1263,7 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a
GD_ERR_BREAK(!nc);
#ifdef DEBUG_ENABLED
- if (src->get_type() == Variant::OBJECT && !src->operator ObjectID().is_ref_counted() && ObjectDB::get_instance(src->operator ObjectID()) == nullptr) {
+ if (src->operator Object *() && !src->get_validated_object()) {
err_text = "Trying to cast a freed object.";
OPCODE_BREAK;
}
@@ -1295,7 +1295,7 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a
GD_ERR_BREAK(!base_type);
#ifdef DEBUG_ENABLED
- if (src->get_type() == Variant::OBJECT && !src->operator ObjectID().is_ref_counted() && ObjectDB::get_instance(src->operator ObjectID()) == nullptr) {
+ if (src->operator Object *() && !src->get_validated_object()) {
err_text = "Trying to cast a freed object.";
OPCODE_BREAK;
}
@@ -2138,7 +2138,7 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a
retvalue = gdfs;
- Error err = sig.connect(Callable(gdfs.ptr(), "_signal_callback"), varray(gdfs), Object::CONNECT_ONESHOT);
+ Error err = sig.connect(callable_bind(Callable(gdfs.ptr(), "_signal_callback"), retvalue), Object::CONNECT_ONESHOT);
if (err != OK) {
err_text = "Error connecting to signal: " + sig.get_name() + " during await.";
OPCODE_BREAK;
diff --git a/modules/gdscript/language_server/gdscript_language_protocol.cpp b/modules/gdscript/language_server/gdscript_language_protocol.cpp
index b6c48468f5..bd5a9f01b2 100644
--- a/modules/gdscript/language_server/gdscript_language_protocol.cpp
+++ b/modules/gdscript/language_server/gdscript_language_protocol.cpp
@@ -284,6 +284,23 @@ void GDScriptLanguageProtocol::notify_client(const String &p_method, const Varia
peer->res_queue.push_back(msg.utf8());
}
+void GDScriptLanguageProtocol::request_client(const String &p_method, const Variant &p_params, int p_client_id) {
+ if (p_client_id == -1) {
+ ERR_FAIL_COND_MSG(latest_client_id == -1,
+ "GDScript LSP: Can't notify client as none was connected.");
+ p_client_id = latest_client_id;
+ }
+ ERR_FAIL_COND(!clients.has(p_client_id));
+ Ref<LSPeer> peer = clients.get(p_client_id);
+ ERR_FAIL_COND(peer == nullptr);
+
+ Dictionary message = make_request(p_method, p_params, next_server_id);
+ next_server_id++;
+ String msg = Variant(message).to_json_string();
+ msg = format_output(msg);
+ peer->res_queue.push_back(msg.utf8());
+}
+
bool GDScriptLanguageProtocol::is_smart_resolve_enabled() const {
return bool(_EDITOR_GET("network/language_server/enable_smart_resolve"));
}
diff --git a/modules/gdscript/language_server/gdscript_language_protocol.h b/modules/gdscript/language_server/gdscript_language_protocol.h
index 5a2dd55c46..899446fb42 100644
--- a/modules/gdscript/language_server/gdscript_language_protocol.h
+++ b/modules/gdscript/language_server/gdscript_language_protocol.h
@@ -79,6 +79,8 @@ private:
int latest_client_id = 0;
int next_client_id = 0;
+ int next_server_id = 0;
+
Ref<GDScriptTextDocument> text_document;
Ref<GDScriptWorkspace> workspace;
@@ -107,6 +109,7 @@ public:
void stop();
void notify_client(const String &p_method, const Variant &p_params = Variant(), int p_client_id = -1);
+ void request_client(const String &p_method, const Variant &p_params = Variant(), int p_client_id = -1);
bool is_smart_resolve_enabled() const;
bool is_goto_native_symbols_enabled() const;
diff --git a/modules/gdscript/language_server/gdscript_workspace.cpp b/modules/gdscript/language_server/gdscript_workspace.cpp
index 1512b4bb89..f4b55cac02 100644
--- a/modules/gdscript/language_server/gdscript_workspace.cpp
+++ b/modules/gdscript/language_server/gdscript_workspace.cpp
@@ -42,6 +42,7 @@
#include "scene/resources/packed_scene.h"
void GDScriptWorkspace::_bind_methods() {
+ ClassDB::bind_method(D_METHOD("apply_new_signal"), &GDScriptWorkspace::apply_new_signal);
ClassDB::bind_method(D_METHOD("didDeleteFiles"), &GDScriptWorkspace::did_delete_files);
ClassDB::bind_method(D_METHOD("symbol"), &GDScriptWorkspace::symbol);
ClassDB::bind_method(D_METHOD("parse_script", "path", "content"), &GDScriptWorkspace::parse_script);
@@ -52,6 +53,54 @@ void GDScriptWorkspace::_bind_methods() {
ClassDB::bind_method(D_METHOD("generate_script_api", "path"), &GDScriptWorkspace::generate_script_api);
}
+void GDScriptWorkspace::apply_new_signal(Object *obj, String function, PackedStringArray args) {
+ String function_signature = "func " + function;
+ Ref<Script> script = obj->get_script();
+
+ String source = script->get_source_code();
+
+ if (source.find(function_signature) != -1) {
+ return;
+ }
+
+ int first_class = source.find("\nclass ");
+ int start_line = 0;
+ if (first_class != -1) {
+ start_line = source.substr(0, first_class).split("\n").size();
+ } else {
+ start_line = source.split("\n").size();
+ }
+
+ String function_body = "\n\n" + function_signature + "(";
+ for (int i = 0; i < args.size(); ++i) {
+ function_body += args[i];
+ if (i < args.size() - 1) {
+ function_body += ", ";
+ }
+ }
+ function_body += ")";
+ if (EditorSettings::get_singleton()->get_setting("text_editor/completion/add_type_hints")) {
+ function_body += " -> void";
+ }
+ function_body += ":\n\tpass # Replace with function body.\n";
+
+ lsp::TextEdit text_edit;
+
+ if (first_class != -1) {
+ function_body += "\n\n";
+ }
+ text_edit.range.end.line = text_edit.range.start.line = start_line;
+
+ text_edit.newText = function_body;
+
+ String uri = get_file_uri(script->get_path());
+
+ lsp::ApplyWorkspaceEditParams params;
+ params.edit.add_edit(uri, text_edit);
+
+ GDScriptLanguageProtocol::get_singleton()->request_client("workspace/applyEdit", params.to_json());
+}
+
void GDScriptWorkspace::did_delete_files(const Dictionary &p_params) {
Array files = p_params["files"];
for (int i = 0; i < files.size(); ++i) {
@@ -360,6 +409,9 @@ Error GDScriptWorkspace::initialize() {
}
}
+ EditorNode *editor_node = EditorNode::get_singleton();
+ editor_node->connect("script_add_function_request", callable_mp(this, &GDScriptWorkspace::apply_new_signal));
+
return OK;
}
@@ -551,7 +603,7 @@ void GDScriptWorkspace::completion(const lsp::CompletionParams &p_params, List<S
}
}
-const lsp::DocumentSymbol *GDScriptWorkspace::resolve_symbol(const lsp::TextDocumentPositionParams &p_doc_pos, const String &p_symbol_name, bool p_func_requred) {
+const lsp::DocumentSymbol *GDScriptWorkspace::resolve_symbol(const lsp::TextDocumentPositionParams &p_doc_pos, const String &p_symbol_name, bool p_func_required) {
const lsp::DocumentSymbol *symbol = nullptr;
String path = get_file_path(p_doc_pos.textDocument.uri);
@@ -576,7 +628,10 @@ const lsp::DocumentSymbol *GDScriptWorkspace::resolve_symbol(const lsp::TextDocu
} else {
ScriptLanguage::LookupResult ret;
- if (OK == GDScriptLanguage::get_singleton()->lookup_code(parser->get_text_for_lookup_symbol(pos, symbol_identifier, p_func_requred), symbol_identifier, path, nullptr, ret)) {
+ if (symbol_identifier == "new" && parser->get_lines()[p_doc_pos.position.line].replace(" ", "").replace("\t", "").find("new(") > -1) {
+ symbol_identifier = "_init";
+ }
+ if (OK == GDScriptLanguage::get_singleton()->lookup_code(parser->get_text_for_lookup_symbol(pos, symbol_identifier, p_func_required), symbol_identifier, path, nullptr, ret)) {
if (ret.type == ScriptLanguage::LookupResult::RESULT_SCRIPT_LOCATION) {
String target_script_path = path;
if (!ret.script.is_null()) {
diff --git a/modules/gdscript/language_server/gdscript_workspace.h b/modules/gdscript/language_server/gdscript_workspace.h
index 9496677449..6f5600b5cf 100644
--- a/modules/gdscript/language_server/gdscript_workspace.h
+++ b/modules/gdscript/language_server/gdscript_workspace.h
@@ -62,6 +62,8 @@ protected:
void list_script_files(const String &p_root_dir, List<String> &r_files);
+ void apply_new_signal(Object *obj, String function, PackedStringArray args);
+
public:
String root;
String root_uri;
@@ -85,7 +87,7 @@ public:
void publish_diagnostics(const String &p_path);
void completion(const lsp::CompletionParams &p_params, List<ScriptCodeCompletionOption> *r_options);
- const lsp::DocumentSymbol *resolve_symbol(const lsp::TextDocumentPositionParams &p_doc_pos, const String &p_symbol_name = "", bool p_func_requred = false);
+ const lsp::DocumentSymbol *resolve_symbol(const lsp::TextDocumentPositionParams &p_doc_pos, const String &p_symbol_name = "", bool p_func_required = false);
void resolve_related_symbols(const lsp::TextDocumentPositionParams &p_doc_pos, List<const lsp::DocumentSymbol *> &r_list);
const lsp::DocumentSymbol *resolve_native_symbol(const lsp::NativeSymbolInspectParams &p_params);
void resolve_document_links(const String &p_uri, List<lsp::DocumentLink> &r_list);
diff --git a/modules/gdscript/language_server/lsp.hpp b/modules/gdscript/language_server/lsp.hpp
index 9ac6c6bd4e..662382d279 100644
--- a/modules/gdscript/language_server/lsp.hpp
+++ b/modules/gdscript/language_server/lsp.hpp
@@ -263,6 +263,16 @@ struct WorkspaceEdit {
*/
Map<String, Vector<TextEdit>> changes;
+ _FORCE_INLINE_ void add_edit(const String &uri, const TextEdit &edit) {
+ if (changes.has(uri)) {
+ changes[uri].push_back(edit);
+ } else {
+ Vector<TextEdit> edits;
+ edits.push_back(edit);
+ changes[uri] = edits;
+ }
+ }
+
_FORCE_INLINE_ Dictionary to_json() const {
Dictionary dict;
@@ -1322,6 +1332,18 @@ struct DocumentSymbol {
}
};
+struct ApplyWorkspaceEditParams {
+ WorkspaceEdit edit;
+
+ Dictionary to_json() {
+ Dictionary dict;
+
+ dict["edit"] = edit.to_json();
+
+ return dict;
+ }
+};
+
struct NativeSymbolInspectParams {
String native_class;
String symbol_name;
diff --git a/modules/gdscript/tests/scripts/analyzer/errors/bitwise_float_right_operand.gd b/modules/gdscript/tests/scripts/analyzer/errors/bitwise_float_right_operand.gd
index 4502960105..0a4f647f57 100644
--- a/modules/gdscript/tests/scripts/analyzer/errors/bitwise_float_right_operand.gd
+++ b/modules/gdscript/tests/scripts/analyzer/errors/bitwise_float_right_operand.gd
@@ -1,3 +1,3 @@
func test():
# Error here.
- print(2 << 4.4)
+ print(2 >> 4.4)
diff --git a/modules/gdscript/tests/scripts/analyzer/errors/bitwise_float_right_operand.out b/modules/gdscript/tests/scripts/analyzer/errors/bitwise_float_right_operand.out
index 1879fc1adf..1edbf47ec0 100644
--- a/modules/gdscript/tests/scripts/analyzer/errors/bitwise_float_right_operand.out
+++ b/modules/gdscript/tests/scripts/analyzer/errors/bitwise_float_right_operand.out
@@ -1,2 +1,2 @@
GDTEST_ANALYZER_ERROR
-Invalid operands to operator <<, int and float.
+Invalid operands to operator >>, int and float.
diff --git a/modules/gdscript/tests/scripts/analyzer/errors/class_name_shadows_builtin_type.gd b/modules/gdscript/tests/scripts/analyzer/errors/class_name_shadows_builtin_type.gd
new file mode 100644
index 0000000000..b84ccdce81
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/errors/class_name_shadows_builtin_type.gd
@@ -0,0 +1,5 @@
+class Vector2:
+ pass
+
+func test():
+ pass
diff --git a/modules/gdscript/tests/scripts/analyzer/errors/class_name_shadows_builtin_type.out b/modules/gdscript/tests/scripts/analyzer/errors/class_name_shadows_builtin_type.out
new file mode 100644
index 0000000000..87863baf75
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/errors/class_name_shadows_builtin_type.out
@@ -0,0 +1,2 @@
+GDTEST_ANALYZER_ERROR
+The member "Vector2" cannot have the same name as a builtin type.
diff --git a/modules/gdscript/tests/scripts/analyzer/errors/constant_name_shadows_builtin_type.gd b/modules/gdscript/tests/scripts/analyzer/errors/constant_name_shadows_builtin_type.gd
new file mode 100644
index 0000000000..a7c0a29a69
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/errors/constant_name_shadows_builtin_type.gd
@@ -0,0 +1,4 @@
+const Vector2 = 0
+
+func test():
+ pass
diff --git a/modules/gdscript/tests/scripts/analyzer/errors/constant_name_shadows_builtin_type.out b/modules/gdscript/tests/scripts/analyzer/errors/constant_name_shadows_builtin_type.out
new file mode 100644
index 0000000000..87863baf75
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/errors/constant_name_shadows_builtin_type.out
@@ -0,0 +1,2 @@
+GDTEST_ANALYZER_ERROR
+The member "Vector2" cannot have the same name as a builtin type.
diff --git a/modules/gdscript/tests/scripts/analyzer/errors/enum_name_shadows_builtin_type.gd b/modules/gdscript/tests/scripts/analyzer/errors/enum_name_shadows_builtin_type.gd
new file mode 100644
index 0000000000..930f91b389
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/errors/enum_name_shadows_builtin_type.gd
@@ -0,0 +1,4 @@
+enum Vector2 { A, B }
+
+func test():
+ pass
diff --git a/modules/gdscript/tests/scripts/analyzer/errors/enum_name_shadows_builtin_type.out b/modules/gdscript/tests/scripts/analyzer/errors/enum_name_shadows_builtin_type.out
new file mode 100644
index 0000000000..87863baf75
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/errors/enum_name_shadows_builtin_type.out
@@ -0,0 +1,2 @@
+GDTEST_ANALYZER_ERROR
+The member "Vector2" cannot have the same name as a builtin type.
diff --git a/modules/gdscript/tests/scripts/analyzer/errors/variable_name_shadows_builtin_type.gd b/modules/gdscript/tests/scripts/analyzer/errors/variable_name_shadows_builtin_type.gd
new file mode 100644
index 0000000000..7cba29884c
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/errors/variable_name_shadows_builtin_type.gd
@@ -0,0 +1,4 @@
+var Vector2
+
+func test():
+ pass
diff --git a/modules/gdscript/tests/scripts/analyzer/errors/variable_name_shadows_builtin_type.out b/modules/gdscript/tests/scripts/analyzer/errors/variable_name_shadows_builtin_type.out
new file mode 100644
index 0000000000..87863baf75
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/errors/variable_name_shadows_builtin_type.out
@@ -0,0 +1,2 @@
+GDTEST_ANALYZER_ERROR
+The member "Vector2" cannot have the same name as a builtin type.
diff --git a/modules/gdscript/tests/scripts/analyzer/features/static_method_builtin_type.gd b/modules/gdscript/tests/scripts/analyzer/features/static_method_builtin_type.gd
new file mode 100644
index 0000000000..569f95850f
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/features/static_method_builtin_type.gd
@@ -0,0 +1,2 @@
+func test():
+ print(Color.html_is_valid("00ffff"))
diff --git a/modules/gdscript/tests/scripts/analyzer/features/static_method_builtin_type.out b/modules/gdscript/tests/scripts/analyzer/features/static_method_builtin_type.out
new file mode 100644
index 0000000000..55482c2b52
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/features/static_method_builtin_type.out
@@ -0,0 +1,2 @@
+GDTEST_OK
+true
diff --git a/modules/gdscript/tests/scripts/parser/errors/brace_syntax.gd b/modules/gdscript/tests/scripts/parser/errors/brace_syntax.gd
new file mode 100644
index 0000000000..ab66537c93
--- /dev/null
+++ b/modules/gdscript/tests/scripts/parser/errors/brace_syntax.gd
@@ -0,0 +1,3 @@
+func test() {
+ print("Hello world!");
+}
diff --git a/modules/gdscript/tests/scripts/parser/errors/brace_syntax.out b/modules/gdscript/tests/scripts/parser/errors/brace_syntax.out
new file mode 100644
index 0000000000..2f37a740ab
--- /dev/null
+++ b/modules/gdscript/tests/scripts/parser/errors/brace_syntax.out
@@ -0,0 +1,2 @@
+GDTEST_PARSER_ERROR
+Expected ":" after function declaration.
diff --git a/modules/gdscript/tests/scripts/parser/errors/invalid_escape_sequence.gd b/modules/gdscript/tests/scripts/parser/errors/invalid_escape_sequence.gd
new file mode 100644
index 0000000000..3b52f6e324
--- /dev/null
+++ b/modules/gdscript/tests/scripts/parser/errors/invalid_escape_sequence.gd
@@ -0,0 +1,2 @@
+func test():
+ var escape = "invalid escape \h <- here"
diff --git a/modules/gdscript/tests/scripts/parser/errors/invalid_escape_sequence.out b/modules/gdscript/tests/scripts/parser/errors/invalid_escape_sequence.out
new file mode 100644
index 0000000000..32b4d004db
--- /dev/null
+++ b/modules/gdscript/tests/scripts/parser/errors/invalid_escape_sequence.out
@@ -0,0 +1,2 @@
+GDTEST_PARSER_ERROR
+Invalid escape in string.
diff --git a/modules/gdscript/tests/scripts/parser/errors/invalid_ternary_operator.gd b/modules/gdscript/tests/scripts/parser/errors/invalid_ternary_operator.gd
new file mode 100644
index 0000000000..c835ce15e1
--- /dev/null
+++ b/modules/gdscript/tests/scripts/parser/errors/invalid_ternary_operator.gd
@@ -0,0 +1,5 @@
+func test():
+ var amount = 50
+ # C-style ternary operator is invalid in GDScript.
+ # The valid syntax is `"yes" if amount < 60 else "no"`, like in Python.
+ var ternary = amount < 60 ? "yes" : "no"
diff --git a/modules/gdscript/tests/scripts/parser/errors/invalid_ternary_operator.out b/modules/gdscript/tests/scripts/parser/errors/invalid_ternary_operator.out
new file mode 100644
index 0000000000..ac82d691b7
--- /dev/null
+++ b/modules/gdscript/tests/scripts/parser/errors/invalid_ternary_operator.out
@@ -0,0 +1,2 @@
+GDTEST_PARSER_ERROR
+Unexpected "?" in source. If you want a ternary operator, use "truthy_value if true_condition else falsy_value".
diff --git a/modules/gdscript/tests/scripts/parser/errors/vcs_conflict_marker.gd b/modules/gdscript/tests/scripts/parser/errors/vcs_conflict_marker.gd
new file mode 100644
index 0000000000..8850892f2d
--- /dev/null
+++ b/modules/gdscript/tests/scripts/parser/errors/vcs_conflict_marker.gd
@@ -0,0 +1,13 @@
+# The VCS conflict marker has only 6 `=` signs instead of 7 to prevent editors like
+# Visual Studio Code from recognizing it as an actual VCS conflict marker.
+# Nonetheless, the GDScript parser is still expected to find and report the VCS
+# conflict marker error correctly.
+
+<<<<<<< HEAD
+Hello world
+======
+Goodbye
+>>>>>>> 77976da35a11db4580b80ae27e8d65caf5208086
+
+func test():
+ pass
diff --git a/modules/gdscript/tests/scripts/parser/errors/vcs_conflict_marker.out b/modules/gdscript/tests/scripts/parser/errors/vcs_conflict_marker.out
new file mode 100644
index 0000000000..df9dab2223
--- /dev/null
+++ b/modules/gdscript/tests/scripts/parser/errors/vcs_conflict_marker.out
@@ -0,0 +1,2 @@
+GDTEST_PARSER_ERROR
+Unexpected "VCS conflict marker" in class body.
diff --git a/modules/gdscript/tests/scripts/parser/errors/yield_instead_of_await.gd b/modules/gdscript/tests/scripts/parser/errors/yield_instead_of_await.gd
new file mode 100644
index 0000000000..7862eff6ec
--- /dev/null
+++ b/modules/gdscript/tests/scripts/parser/errors/yield_instead_of_await.gd
@@ -0,0 +1,6 @@
+#GDTEST_PARSER_ERROR
+
+signal event
+
+func test():
+ yield("event")
diff --git a/modules/gdscript/tests/scripts/parser/errors/yield_instead_of_await.out b/modules/gdscript/tests/scripts/parser/errors/yield_instead_of_await.out
new file mode 100644
index 0000000000..36cb699e92
--- /dev/null
+++ b/modules/gdscript/tests/scripts/parser/errors/yield_instead_of_await.out
@@ -0,0 +1,2 @@
+GDTEST_PARSER_ERROR
+"yield" was removed in Godot 4.0. Use "await" instead.
diff --git a/modules/gdscript/tests/scripts/parser/features/export_variable.gd b/modules/gdscript/tests/scripts/parser/features/export_variable.gd
index 51e7d4a8ed..1e072728fc 100644
--- a/modules/gdscript/tests/scripts/parser/features/export_variable.gd
+++ b/modules/gdscript/tests/scripts/parser/features/export_variable.gd
@@ -3,9 +3,16 @@
@export_range(0, 100, 1) var example_range_step = 101
@export_range(0, 100, 1, "or_greater") var example_range_step_or_greater = 102
+@export var color: Color
+@export_color_no_alpha var color_no_alpha: Color
+@export_node_path(Sprite2D, Sprite3D, Control, Node) var nodepath := ^"hello"
+
func test():
print(example)
print(example_range)
print(example_range_step)
print(example_range_step_or_greater)
+ print(color)
+ print(color_no_alpha)
+ print(nodepath)
diff --git a/modules/gdscript/tests/scripts/parser/features/export_variable.out b/modules/gdscript/tests/scripts/parser/features/export_variable.out
index b455196359..bae35e75c6 100644
--- a/modules/gdscript/tests/scripts/parser/features/export_variable.out
+++ b/modules/gdscript/tests/scripts/parser/features/export_variable.out
@@ -3,3 +3,6 @@ GDTEST_OK
100
101
102
+(0, 0, 0, 1)
+(0, 0, 0, 1)
+hello
diff --git a/modules/gdscript/tests/scripts/parser/features/function_default_parameter_type_inference.gd b/modules/gdscript/tests/scripts/parser/features/function_default_parameter_type_inference.gd
new file mode 100644
index 0000000000..f5098b00ae
--- /dev/null
+++ b/modules/gdscript/tests/scripts/parser/features/function_default_parameter_type_inference.gd
@@ -0,0 +1,5 @@
+func example(_number: int, _number2: int = 5, number3 := 10):
+ return number3
+
+func test():
+ print(example(3))
diff --git a/modules/gdscript/tests/scripts/parser/features/function_default_parameter_type_inference.out b/modules/gdscript/tests/scripts/parser/features/function_default_parameter_type_inference.out
new file mode 100644
index 0000000000..404cd41fe5
--- /dev/null
+++ b/modules/gdscript/tests/scripts/parser/features/function_default_parameter_type_inference.out
@@ -0,0 +1,2 @@
+GDTEST_OK
+10
diff --git a/modules/gdscript/tests/scripts/parser/features/function_many_parameters.gd b/modules/gdscript/tests/scripts/parser/features/function_many_parameters.gd
new file mode 100644
index 0000000000..01edb37cec
--- /dev/null
+++ b/modules/gdscript/tests/scripts/parser/features/function_many_parameters.gd
@@ -0,0 +1,5 @@
+func example(arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, arg13, arg14, arg15, arg16, arg17, arg18, arg19, arg20, arg21, arg22, arg23, arg24, arg25, arg26, arg27, arg28, arg29, arg30, arg31, arg32, arg33, arg34, arg35, arg36, arg37, arg38, arg39, arg40, arg41, arg42, arg43, arg44, arg45, arg46, arg47, arg48 = false, arg49 = true, arg50 = null):
+ print(arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, arg13, arg14, arg15, arg16, arg17, arg18, arg19, arg20, arg21, arg22, arg23, arg24, arg25, arg26, arg27, arg28, arg29, arg30, arg31, arg32, arg33, arg34, arg35, arg36, arg37, arg38, arg39, arg40, arg41, arg42, arg43, arg44, arg45, arg46, arg47, arg48, arg49, arg50)
+
+func test():
+ example(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 2, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47)
diff --git a/modules/gdscript/tests/scripts/parser/features/function_many_parameters.out b/modules/gdscript/tests/scripts/parser/features/function_many_parameters.out
new file mode 100644
index 0000000000..3a979227d4
--- /dev/null
+++ b/modules/gdscript/tests/scripts/parser/features/function_many_parameters.out
@@ -0,0 +1,2 @@
+GDTEST_OK
+123456789101112131415161718192212223242526272829303132333435363738394041424344454647falsetruenull
diff --git a/modules/gdscript/tests/scripts/parser/features/lambda_callable.gd b/modules/gdscript/tests/scripts/parser/features/lambda_callable.gd
new file mode 100644
index 0000000000..c3b2506156
--- /dev/null
+++ b/modules/gdscript/tests/scripts/parser/features/lambda_callable.gd
@@ -0,0 +1,4 @@
+func test():
+ var my_lambda = func(x):
+ print(x)
+ my_lambda.call("hello")
diff --git a/modules/gdscript/tests/scripts/parser/features/lambda_callable.out b/modules/gdscript/tests/scripts/parser/features/lambda_callable.out
new file mode 100644
index 0000000000..58774d2d3f
--- /dev/null
+++ b/modules/gdscript/tests/scripts/parser/features/lambda_callable.out
@@ -0,0 +1,2 @@
+GDTEST_OK
+hello
diff --git a/modules/gdscript/tests/scripts/parser/features/lambda_capture_callable.gd b/modules/gdscript/tests/scripts/parser/features/lambda_capture_callable.gd
new file mode 100644
index 0000000000..f081a0b6a7
--- /dev/null
+++ b/modules/gdscript/tests/scripts/parser/features/lambda_capture_callable.gd
@@ -0,0 +1,4 @@
+func test():
+ var x = 42
+ var my_lambda = func(): print(x)
+ my_lambda.call() # Prints "42".
diff --git a/modules/gdscript/tests/scripts/parser/features/lambda_capture_callable.out b/modules/gdscript/tests/scripts/parser/features/lambda_capture_callable.out
new file mode 100644
index 0000000000..0982f3718c
--- /dev/null
+++ b/modules/gdscript/tests/scripts/parser/features/lambda_capture_callable.out
@@ -0,0 +1,2 @@
+GDTEST_OK
+42
diff --git a/modules/gdscript/tests/scripts/parser/features/lambda_named_callable.gd b/modules/gdscript/tests/scripts/parser/features/lambda_named_callable.gd
new file mode 100644
index 0000000000..7971ca72a6
--- /dev/null
+++ b/modules/gdscript/tests/scripts/parser/features/lambda_named_callable.gd
@@ -0,0 +1,10 @@
+func i_take_lambda(lambda: Callable, param: String):
+ lambda.call(param)
+
+
+func test():
+ var my_lambda := func this_is_lambda(x):
+ print("Hello")
+ print("This is %s" % x)
+
+ i_take_lambda(my_lambda, "a lambda")
diff --git a/modules/gdscript/tests/scripts/parser/features/lambda_named_callable.out b/modules/gdscript/tests/scripts/parser/features/lambda_named_callable.out
new file mode 100644
index 0000000000..c627187d82
--- /dev/null
+++ b/modules/gdscript/tests/scripts/parser/features/lambda_named_callable.out
@@ -0,0 +1,3 @@
+GDTEST_OK
+Hello
+This is a lambda
diff --git a/modules/gdscript/tests/scripts/parser/features/nested_function_calls.gd b/modules/gdscript/tests/scripts/parser/features/nested_function_calls.gd
new file mode 100644
index 0000000000..59cdc7d6c2
--- /dev/null
+++ b/modules/gdscript/tests/scripts/parser/features/nested_function_calls.gd
@@ -0,0 +1,5 @@
+func foo(x):
+ return x + 1
+
+func test():
+ print(foo(foo(foo(foo(foo(foo(foo(foo(foo(foo(foo(foo(foo(foo(foo(foo(foo(foo(foo(foo(foo(foo(foo(foo(0)))))))))))))))))))))))))
diff --git a/modules/gdscript/tests/scripts/parser/features/nested_function_calls.out b/modules/gdscript/tests/scripts/parser/features/nested_function_calls.out
new file mode 100644
index 0000000000..28a6636a7b
--- /dev/null
+++ b/modules/gdscript/tests/scripts/parser/features/nested_function_calls.out
@@ -0,0 +1,2 @@
+GDTEST_OK
+24
diff --git a/modules/gdscript/tests/scripts/parser/features/semicolon_as_terminator.gd b/modules/gdscript/tests/scripts/parser/features/semicolon_as_terminator.gd
new file mode 100644
index 0000000000..0f4aebb459
--- /dev/null
+++ b/modules/gdscript/tests/scripts/parser/features/semicolon_as_terminator.gd
@@ -0,0 +1,22 @@
+#GDTEST_OK
+
+func test():
+ a();
+ b();
+ c();
+ d();
+ e();
+
+func a(): print("a");
+
+func b(): print("b1"); print("b2")
+
+func c(): print("c1"); print("c2");
+
+func d():
+ print("d1");
+ print("d2")
+
+func e():
+ print("e1");
+ print("e2");
diff --git a/modules/gdscript/tests/scripts/parser/features/semicolon_as_terminator.out b/modules/gdscript/tests/scripts/parser/features/semicolon_as_terminator.out
new file mode 100644
index 0000000000..387cbd8fc2
--- /dev/null
+++ b/modules/gdscript/tests/scripts/parser/features/semicolon_as_terminator.out
@@ -0,0 +1,10 @@
+GDTEST_OK
+a
+b1
+b2
+c1
+c2
+d1
+d2
+e1
+e2
diff --git a/modules/gdscript/tests/scripts/parser/features/signal_declaration.gd b/modules/gdscript/tests/scripts/parser/features/signal_declaration.gd
new file mode 100644
index 0000000000..9ad98b78a8
--- /dev/null
+++ b/modules/gdscript/tests/scripts/parser/features/signal_declaration.gd
@@ -0,0 +1,20 @@
+#GDTEST_OK
+
+# No parentheses.
+signal a
+
+# No parameters.
+signal b()
+
+# With paramters.
+signal c(a, b, c)
+
+# With parameters multiline.
+signal d(
+ a,
+ b,
+ c,
+)
+
+func test():
+ print("Ok")
diff --git a/modules/gdscript/tests/scripts/parser/features/signal_declaration.out b/modules/gdscript/tests/scripts/parser/features/signal_declaration.out
new file mode 100644
index 0000000000..0e9f482af4
--- /dev/null
+++ b/modules/gdscript/tests/scripts/parser/features/signal_declaration.out
@@ -0,0 +1,2 @@
+GDTEST_OK
+Ok
diff --git a/modules/gdscript/tests/scripts/parser/features/single_line_declaration.gd b/modules/gdscript/tests/scripts/parser/features/single_line_declaration.gd
new file mode 100644
index 0000000000..650500663b
--- /dev/null
+++ b/modules/gdscript/tests/scripts/parser/features/single_line_declaration.gd
@@ -0,0 +1,11 @@
+#GDTEST_OK
+
+func test(): C.new().test("Ok"); test2()
+
+func test2(): print("Ok 2")
+
+class A: pass
+
+class B extends RefCounted: pass
+
+class C extends RefCounted: func test(x): print(x)
diff --git a/modules/gdscript/tests/scripts/parser/features/single_line_declaration.out b/modules/gdscript/tests/scripts/parser/features/single_line_declaration.out
new file mode 100644
index 0000000000..e021923dc2
--- /dev/null
+++ b/modules/gdscript/tests/scripts/parser/features/single_line_declaration.out
@@ -0,0 +1,3 @@
+GDTEST_OK
+Ok
+Ok 2
diff --git a/modules/gdscript/tests/scripts/parser/features/typed_arrays.gd b/modules/gdscript/tests/scripts/parser/features/typed_arrays.gd
new file mode 100644
index 0000000000..21bf3fdfcf
--- /dev/null
+++ b/modules/gdscript/tests/scripts/parser/features/typed_arrays.gd
@@ -0,0 +1,5 @@
+func test():
+ var my_array: Array[int] = [1, 2, 3]
+ var inferred_array := [1, 2, 3] # This is Array[int].
+ print(my_array)
+ print(inferred_array)
diff --git a/modules/gdscript/tests/scripts/parser/features/typed_arrays.out b/modules/gdscript/tests/scripts/parser/features/typed_arrays.out
new file mode 100644
index 0000000000..953d54d5e0
--- /dev/null
+++ b/modules/gdscript/tests/scripts/parser/features/typed_arrays.out
@@ -0,0 +1,3 @@
+GDTEST_OK
+[1, 2, 3]
+[1, 2, 3]
diff --git a/modules/gdscript/tests/scripts/runtime/features/property_with_operator_assignment.gd b/modules/gdscript/tests/scripts/runtime/features/property_with_operator_assignment.gd
new file mode 100644
index 0000000000..3eb02816ed
--- /dev/null
+++ b/modules/gdscript/tests/scripts/runtime/features/property_with_operator_assignment.gd
@@ -0,0 +1,11 @@
+#GDTEST_OK
+var prop : int = 0:
+ get:
+ return prop
+ set(value):
+ prop = value % 7
+
+func test():
+ for i in 7:
+ prop += 1
+ print(prop)
diff --git a/modules/gdscript/tests/scripts/runtime/features/property_with_operator_assignment.out b/modules/gdscript/tests/scripts/runtime/features/property_with_operator_assignment.out
new file mode 100644
index 0000000000..76157853f2
--- /dev/null
+++ b/modules/gdscript/tests/scripts/runtime/features/property_with_operator_assignment.out
@@ -0,0 +1,8 @@
+GDTEST_OK
+1
+2
+3
+4
+5
+6
+0
diff --git a/modules/gdscript/tests/scripts/runtime/features/recursion.gd b/modules/gdscript/tests/scripts/runtime/features/recursion.gd
new file mode 100644
index 0000000000..a35485022e
--- /dev/null
+++ b/modules/gdscript/tests/scripts/runtime/features/recursion.gd
@@ -0,0 +1,19 @@
+func is_prime(number: int, divisor: int = 2) -> bool:
+ print(divisor)
+ if number <= 2:
+ return (number == 2)
+ elif number % divisor == 0:
+ return false
+ elif divisor * divisor > number:
+ return true
+
+ return is_prime(number, divisor + 1)
+
+func test():
+ # Not a prime number.
+ print(is_prime(989))
+
+ print()
+
+ # Largest prime number below 10000.
+ print(is_prime(9973))
diff --git a/modules/gdscript/tests/scripts/runtime/features/recursion.out b/modules/gdscript/tests/scripts/runtime/features/recursion.out
new file mode 100644
index 0000000000..2bd8f24988
--- /dev/null
+++ b/modules/gdscript/tests/scripts/runtime/features/recursion.out
@@ -0,0 +1,125 @@
+GDTEST_OK
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+false
+
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+77
+78
+79
+80
+81
+82
+83
+84
+85
+86
+87
+88
+89
+90
+91
+92
+93
+94
+95
+96
+97
+98
+99
+100
+true