summaryrefslogtreecommitdiff
path: root/modules/gdscript
diff options
context:
space:
mode:
Diffstat (limited to 'modules/gdscript')
-rw-r--r--modules/gdscript/editor/gdscript_highlighter.cpp96
-rw-r--r--modules/gdscript/editor/gdscript_highlighter.h1
-rw-r--r--modules/gdscript/editor_templates/VisualShaderNodeCustom/basic.gd41
-rw-r--r--modules/gdscript/gdscript.cpp1
-rw-r--r--modules/gdscript/gdscript_analyzer.cpp84
-rw-r--r--modules/gdscript/gdscript_byte_codegen.cpp1
-rw-r--r--modules/gdscript/gdscript_compiler.cpp88
-rw-r--r--modules/gdscript/gdscript_editor.cpp62
-rw-r--r--modules/gdscript/gdscript_parser.cpp80
-rw-r--r--modules/gdscript/gdscript_parser.h4
-rw-r--r--modules/gdscript/gdscript_vm.cpp7
-rw-r--r--modules/gdscript/gdscript_warning.cpp2
-rw-r--r--modules/gdscript/icons/GDScriptInternal.svg1
-rw-r--r--modules/gdscript/tests/gdscript_test_runner.cpp8
-rw-r--r--modules/gdscript/tests/scripts/analyzer/errors/params_default_forward_reference.gd9
-rw-r--r--modules/gdscript/tests/scripts/analyzer/errors/params_default_forward_reference.out2
-rw-r--r--modules/gdscript/tests/scripts/analyzer/features/warning_ignore_annotation.gd15
-rw-r--r--modules/gdscript/tests/scripts/analyzer/features/warning_ignore_annotation.out4
-rw-r--r--modules/gdscript/tests/scripts/runtime/features/params_default_values.gd35
-rw-r--r--modules/gdscript/tests/scripts/runtime/features/params_default_values.out6
20 files changed, 443 insertions, 104 deletions
diff --git a/modules/gdscript/editor/gdscript_highlighter.cpp b/modules/gdscript/editor/gdscript_highlighter.cpp
index 6425123e63..5cc295bbab 100644
--- a/modules/gdscript/editor/gdscript_highlighter.cpp
+++ b/modules/gdscript/editor/gdscript_highlighter.cpp
@@ -60,7 +60,9 @@ Dictionary GDScriptSyntaxHighlighter::_get_line_syntax_highlighting_impl(int p_l
bool in_keyword = false;
bool in_word = false;
bool in_function_name = false;
+ bool in_lambda = false;
bool in_variable_declaration = false;
+ bool in_signal_declaration = false;
bool in_function_args = false;
bool in_member_variable = false;
bool in_node_path = false;
@@ -105,12 +107,15 @@ Dictionary GDScriptSyntaxHighlighter::_get_line_syntax_highlighting_impl(int p_l
/* color regions */
if (is_a_symbol || in_region != -1) {
int from = j;
- for (; from < line_length; from++) {
- if (str[from] == '\\') {
- from++;
- continue;
+
+ if (in_region == -1) {
+ for (; from < line_length; from++) {
+ if (str[from] == '\\') {
+ from++;
+ continue;
+ }
+ break;
}
- break;
}
if (from != line_length) {
@@ -142,6 +147,12 @@ Dictionary GDScriptSyntaxHighlighter::_get_line_syntax_highlighting_impl(int p_l
/* check if it's the whole line */
if (end_key_length == 0 || color_regions[c].line_only || from + end_key_length > line_length) {
+ if (from + end_key_length > line_length) {
+ // If it's key length and there is a '\', dont skip to highlight esc chars.
+ if (str.find("\\", from) >= 0) {
+ break;
+ }
+ }
prev_color = color_regions[in_region].color;
highlighter_info["color"] = color_regions[c].color;
color_map[j] = highlighter_info;
@@ -161,13 +172,25 @@ Dictionary GDScriptSyntaxHighlighter::_get_line_syntax_highlighting_impl(int p_l
/* if we are in one find the end key */
if (in_region != -1) {
+ Color region_color = color_regions[in_region].color;
+ if (in_node_path && (color_regions[in_region].start_key == "\"" || color_regions[in_region].start_key == "\'")) {
+ region_color = node_path_color;
+ }
+
+ prev_color = region_color;
+ highlighter_info["color"] = region_color;
+ color_map[j] = highlighter_info;
+
/* search the line */
int region_end_index = -1;
int end_key_length = color_regions[in_region].end_key.length();
const char32_t *end_key = color_regions[in_region].end_key.get_data();
for (; from < line_length; from++) {
if (line_length - from < end_key_length) {
- break;
+ // Don't break if '\' to highlight esc chars.
+ if (str.find("\\", from) < 0) {
+ break;
+ }
}
if (!is_symbol(str[from])) {
@@ -175,7 +198,16 @@ Dictionary GDScriptSyntaxHighlighter::_get_line_syntax_highlighting_impl(int p_l
}
if (str[from] == '\\') {
+ Dictionary escape_char_highlighter_info;
+ escape_char_highlighter_info["color"] = symbol_color;
+ color_map[from] = escape_char_highlighter_info;
+
from++;
+
+ Dictionary region_continue_highlighter_info;
+ prev_color = region_color;
+ region_continue_highlighter_info["color"] = region_color;
+ color_map[from + 1] = region_continue_highlighter_info;
continue;
}
@@ -192,10 +224,6 @@ Dictionary GDScriptSyntaxHighlighter::_get_line_syntax_highlighting_impl(int p_l
}
}
- prev_color = color_regions[in_region].color;
- highlighter_info["color"] = color_regions[in_region].color;
- color_map[j] = highlighter_info;
-
previous_type = REGION;
previous_text = "";
previous_column = j;
@@ -289,20 +317,36 @@ Dictionary GDScriptSyntaxHighlighter::_get_line_syntax_highlighting_impl(int p_l
}
if (!in_function_name && in_word && !in_keyword) {
- int k = j;
- while (k < str.length() && !is_symbol(str[k]) && str[k] != '\t' && str[k] != ' ') {
- k++;
- }
+ if (previous_text == GDScriptTokenizer::get_token_name(GDScriptTokenizer::Token::SIGNAL)) {
+ in_signal_declaration = true;
+ } else {
+ int k = j;
+ while (k < str.length() && !is_symbol(str[k]) && str[k] != '\t' && str[k] != ' ') {
+ k++;
+ }
- // check for space between name and bracket
- while (k < str.length() && (str[k] == '\t' || str[k] == ' ')) {
- k++;
- }
+ // check for space between name and bracket
+ while (k < str.length() && (str[k] == '\t' || str[k] == ' ')) {
+ k++;
+ }
- if (str[k] == '(') {
- in_function_name = true;
- } else if (previous_text == GDScriptTokenizer::get_token_name(GDScriptTokenizer::Token::VAR)) {
- in_variable_declaration = true;
+ if (str[k] == '(') {
+ in_function_name = true;
+ } else if (previous_text == GDScriptTokenizer::get_token_name(GDScriptTokenizer::Token::VAR)) {
+ in_variable_declaration = true;
+ }
+
+ // Check for lambda.
+ if (in_function_name && previous_text == GDScriptTokenizer::get_token_name(GDScriptTokenizer::Token::FUNC)) {
+ k = j - 1;
+ while (k > 0 && (str[k] == '\t' || str[k] == ' ')) {
+ k--;
+ }
+
+ if (str[k] == ':') {
+ in_lambda = true;
+ }
+ }
}
}
@@ -348,7 +392,9 @@ Dictionary GDScriptSyntaxHighlighter::_get_line_syntax_highlighting_impl(int p_l
}
in_variable_declaration = false;
+ in_signal_declaration = false;
in_function_name = false;
+ in_lambda = false;
in_member_variable = false;
}
@@ -376,10 +422,14 @@ Dictionary GDScriptSyntaxHighlighter::_get_line_syntax_highlighting_impl(int p_l
} else if (in_member_variable) {
next_type = MEMBER;
color = member_color;
+ } else if (in_signal_declaration) {
+ next_type = SIGNAL;
+
+ color = member_color;
} else if (in_function_name) {
next_type = FUNCTION;
- if (previous_text == GDScriptTokenizer::get_token_name(GDScriptTokenizer::Token::FUNC)) {
+ if (!in_lambda && previous_text == GDScriptTokenizer::get_token_name(GDScriptTokenizer::Token::FUNC)) {
color = function_definition_color;
} else {
color = function_color;
diff --git a/modules/gdscript/editor/gdscript_highlighter.h b/modules/gdscript/editor/gdscript_highlighter.h
index ac4995bee7..1ae0d72896 100644
--- a/modules/gdscript/editor/gdscript_highlighter.h
+++ b/modules/gdscript/editor/gdscript_highlighter.h
@@ -58,6 +58,7 @@ private:
SYMBOL,
NUMBER,
FUNCTION,
+ SIGNAL,
KEYWORD,
MEMBER,
IDENTIFIER,
diff --git a/modules/gdscript/editor_templates/VisualShaderNodeCustom/basic.gd b/modules/gdscript/editor_templates/VisualShaderNodeCustom/basic.gd
new file mode 100644
index 0000000000..27383b878d
--- /dev/null
+++ b/modules/gdscript/editor_templates/VisualShaderNodeCustom/basic.gd
@@ -0,0 +1,41 @@
+# meta-description: Visual shader's node plugin template
+
+@tool
+extends _BASE_
+class_name VisualShaderNode_CLASS_
+
+func _get_name() -> String:
+ return "_CLASS_"
+
+func _get_category() -> String:
+ return ""
+
+func _get_description() -> String:
+ return ""
+
+func _get_return_icon_type() -> int:
+ return PORT_TYPE_SCALAR
+
+func _get_input_port_count() -> int:
+ return 0
+
+func _get_input_port_name(port: int) -> String:
+ return ""
+
+func _get_input_port_type(port: int) -> int:
+ return PORT_TYPE_SCALAR
+
+func _get_output_port_count() -> int:
+ return 1
+
+func _get_output_port_name(port: int) -> String:
+ return "result"
+
+func _get_output_port_type(port: int) -> int:
+ return PORT_TYPE_SCALAR
+
+func _get_global_code(mode: Shader.Mode) -> String:
+ return ""
+
+func _get_code(input_vars: Array[String], output_vars: Array[String], mode: Shader.Mode, type: VisualShader.Type) -> String:
+ return output_vars[0] + " = 0.0;"
diff --git a/modules/gdscript/gdscript.cpp b/modules/gdscript/gdscript.cpp
index 6f7e913864..84db97625b 100644
--- a/modules/gdscript/gdscript.cpp
+++ b/modules/gdscript/gdscript.cpp
@@ -2206,7 +2206,6 @@ GDScriptLanguage::GDScriptLanguage() {
GLOBAL_DEF("debug/gdscript/warnings/enable", true);
GLOBAL_DEF("debug/gdscript/warnings/treat_warnings_as_errors", false);
GLOBAL_DEF("debug/gdscript/warnings/exclude_addons", true);
- GLOBAL_DEF("debug/gdscript/completion/autocomplete_setters_and_getters", false);
for (int i = 0; i < (int)GDScriptWarning::WARNING_MAX; i++) {
String warning = GDScriptWarning::get_name_from_code((GDScriptWarning::Code)i).to_lower();
bool default_enabled = !warning.begins_with("unsafe_");
diff --git a/modules/gdscript/gdscript_analyzer.cpp b/modules/gdscript/gdscript_analyzer.cpp
index ffd34080e3..0d295c3a51 100644
--- a/modules/gdscript/gdscript_analyzer.cpp
+++ b/modules/gdscript/gdscript_analyzer.cpp
@@ -881,12 +881,23 @@ void GDScriptAnalyzer::resolve_class_body(GDScriptParser::ClassNode *p_class) {
for (int i = 0; i < p_class->members.size(); i++) {
GDScriptParser::ClassNode::Member member = p_class->members[i];
if (member.type == GDScriptParser::ClassNode::Member::FUNCTION) {
- resolve_function_body(member.function);
-
// Apply annotations.
for (GDScriptParser::AnnotationNode *&E : member.function->annotations) {
E->apply(parser, member.function);
}
+
+#ifdef DEBUG_ENABLED
+ Set<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) {
@@ -925,6 +936,10 @@ void GDScriptAnalyzer::resolve_class_body(GDScriptParser::ClassNode *p_class) {
GDScriptParser::ClassNode::Member member = p_class->members[i];
if (member.type == GDScriptParser::ClassNode::Member::VARIABLE) {
#ifdef DEBUG_ENABLED
+ Set<uint32_t> previously_ignored = parser->ignored_warning_codes;
+ for (uint32_t ignored_warning : member.function->ignored_warnings) {
+ parser->ignored_warning_codes.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);
}
@@ -992,6 +1007,9 @@ void GDScriptAnalyzer::resolve_class_body(GDScriptParser::ClassNode *p_class) {
push_error(vformat(R"(Getter with type "%s" cannot be used along with setter of type "%s".)", getter_function->datatype.to_string(), setter_function->parameters[0]->datatype.to_string()), member.variable);
}
}
+#ifdef DEBUG_ENABLED
+ parser->ignored_warning_codes = previously_ignored;
+#endif // DEBUG_ENABLED
}
}
}
@@ -1186,7 +1204,23 @@ void GDScriptAnalyzer::decide_suite_type(GDScriptParser::Node *p_suite, GDScript
void GDScriptAnalyzer::resolve_suite(GDScriptParser::SuiteNode *p_suite) {
for (int i = 0; i < p_suite->statements.size(); i++) {
GDScriptParser::Node *stmt = p_suite->statements[i];
+ for (GDScriptParser::AnnotationNode *&annotation : stmt->annotations) {
+ annotation->apply(parser, stmt);
+ }
+
+#ifdef DEBUG_ENABLED
+ Set<uint32_t> previously_ignored = parser->ignored_warning_codes;
+ for (uint32_t ignored_warning : stmt->ignored_warnings) {
+ parser->ignored_warning_codes.insert(ignored_warning);
+ }
+#endif // DEBUG_ENABLED
+
resolve_node(stmt);
+
+#ifdef DEBUG_ENABLED
+ parser->ignored_warning_codes = previously_ignored;
+#endif // DEBUG_ENABLED
+
decide_suite_type(p_suite, stmt);
}
}
@@ -1834,13 +1868,14 @@ void GDScriptAnalyzer::reduce_assignment(GDScriptParser::AssignmentNode *p_assig
push_error("Cannot assign a new value to a constant.", p_assignment->assignee);
}
- if (!assignee_type.is_variant() && assigned_value_type.is_hard_type()) {
- bool compatible = true;
- GDScriptParser::DataType op_type = assigned_value_type;
- if (p_assignment->operation != GDScriptParser::AssignmentNode::OP_NONE) {
- op_type = get_operation_type(p_assignment->variant_op, assignee_type, assigned_value_type, compatible, p_assignment->assigned_value);
- }
+ bool compatible = true;
+ GDScriptParser::DataType op_type = assigned_value_type;
+ if (p_assignment->operation != GDScriptParser::AssignmentNode::OP_NONE) {
+ op_type = get_operation_type(p_assignment->variant_op, assignee_type, assigned_value_type, compatible, p_assignment->assigned_value);
+ }
+ p_assignment->set_datatype(op_type);
+ if (!assignee_type.is_variant() && assigned_value_type.is_hard_type()) {
if (compatible) {
compatible = is_type_compatible(assignee_type, op_type, true);
if (!compatible) {
@@ -1865,7 +1900,7 @@ void GDScriptAnalyzer::reduce_assignment(GDScriptParser::AssignmentNode *p_assig
if (assignee_type.has_no_type() || assigned_value_type.is_variant()) {
mark_node_unsafe(p_assignment);
- if (assignee_type.is_hard_type()) {
+ if (assignee_type.is_hard_type() && !assignee_type.is_variant()) {
p_assignment->use_conversion_assign = true;
}
}
@@ -2606,7 +2641,8 @@ void GDScriptAnalyzer::reduce_identifier_from_base(GDScriptParser::IdentifierNod
GDScriptParser::DataType result;
result.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT;
result.kind = GDScriptParser::DataType::ENUM_VALUE;
- result.builtin_type = base.builtin_type;
+ result.is_constant = true;
+ result.builtin_type = Variant::INT;
result.native_type = base.native_type;
result.enum_type = name;
p_identifier->set_datatype(result);
@@ -3753,6 +3789,7 @@ GDScriptParser::DataType GDScriptAnalyzer::get_operation_type(Variant::Operator
// Unary version.
GDScriptParser::DataType nil_type;
nil_type.builtin_type = Variant::NIL;
+ nil_type.type_source = GDScriptParser::DataType::ANNOTATED_INFERRED;
return get_operation_type(p_operation, p_a, nil_type, r_valid, p_source);
}
@@ -3762,20 +3799,31 @@ GDScriptParser::DataType GDScriptAnalyzer::get_operation_type(Variant::Operator
Variant::Type a_type = p_a.builtin_type;
Variant::Type b_type = p_b.builtin_type;
-
Variant::ValidatedOperatorEvaluator op_eval = Variant::get_validated_operator_evaluator(p_operation, a_type, b_type);
- if (op_eval == nullptr) {
+ bool hard_operation = p_a.is_hard_type() && p_b.is_hard_type();
+ bool validated = op_eval != nullptr;
+
+ if (hard_operation && !validated) {
r_valid = false;
return result;
+ } else if (hard_operation && validated) {
+ r_valid = true;
+ result.type_source = GDScriptParser::DataType::ANNOTATED_INFERRED;
+ result.kind = GDScriptParser::DataType::BUILTIN;
+ result.builtin_type = Variant::get_operator_return_type(p_operation, a_type, b_type);
+ } else if (!hard_operation && !validated) {
+ r_valid = true;
+ result.type_source = GDScriptParser::DataType::UNDETECTED;
+ result.kind = GDScriptParser::DataType::VARIANT;
+ result.builtin_type = Variant::NIL;
+ } else if (!hard_operation && validated) {
+ r_valid = true;
+ result.type_source = GDScriptParser::DataType::INFERRED;
+ result.kind = GDScriptParser::DataType::BUILTIN;
+ result.builtin_type = Variant::get_operator_return_type(p_operation, a_type, b_type);
}
- r_valid = true;
- result.type_source = GDScriptParser::DataType::ANNOTATED_INFERRED;
-
- result.kind = GDScriptParser::DataType::BUILTIN;
- result.builtin_type = Variant::get_operator_return_type(p_operation, a_type, b_type);
-
return result;
}
diff --git a/modules/gdscript/gdscript_byte_codegen.cpp b/modules/gdscript/gdscript_byte_codegen.cpp
index 8623122edc..82aa14795e 100644
--- a/modules/gdscript/gdscript_byte_codegen.cpp
+++ b/modules/gdscript/gdscript_byte_codegen.cpp
@@ -688,6 +688,7 @@ void GDScriptByteCodeGenerator::write_ternary_false_expr(const Address &p_expr)
void GDScriptByteCodeGenerator::write_end_ternary() {
patch_jump(ternary_jump_skip_pos.back()->get());
ternary_jump_skip_pos.pop_back();
+ ternary_result.pop_back();
}
void GDScriptByteCodeGenerator::write_set(const Address &p_target, const Address &p_index, const Address &p_source) {
diff --git a/modules/gdscript/gdscript_compiler.cpp b/modules/gdscript/gdscript_compiler.cpp
index af5ada513c..ca125d3a07 100644
--- a/modules/gdscript/gdscript_compiler.cpp
+++ b/modules/gdscript/gdscript_compiler.cpp
@@ -882,7 +882,13 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &code
#endif
/* Find chain of sets */
- StringName assign_property;
+ StringName assign_class_member_property;
+
+ GDScriptCodeGenerator::Address target_member_property;
+ bool is_member_property = false;
+ bool member_property_has_setter = false;
+ bool member_property_is_in_setter = false;
+ StringName member_property_setter_function;
List<const GDScriptParser::SubscriptNode *> chain;
@@ -892,11 +898,20 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &code
while (true) {
chain.push_back(n);
if (n->base->type != GDScriptParser::Node::SUBSCRIPT) {
- // Check for a built-in property.
+ // Check for a property.
if (n->base->type == GDScriptParser::Node::IDENTIFIER) {
GDScriptParser::IdentifierNode *identifier = static_cast<GDScriptParser::IdentifierNode *>(n->base);
- if (_is_class_member_property(codegen, identifier->name)) {
- assign_property = identifier->name;
+ StringName var_name = identifier->name;
+ if (_is_class_member_property(codegen, var_name)) {
+ assign_class_member_property = var_name;
+ } else if (!codegen.locals.has(var_name) && codegen.script->member_indices.has(var_name)) {
+ is_member_property = true;
+ member_property_setter_function = codegen.script->member_indices[var_name].setter;
+ member_property_has_setter = member_property_setter_function != StringName();
+ member_property_is_in_setter = member_property_has_setter && member_property_setter_function == codegen.function_name;
+ target_member_property.mode = GDScriptCodeGenerator::Address::MEMBER;
+ target_member_property.address = codegen.script->member_indices[var_name].index;
+ target_member_property.type = codegen.script->member_indices[var_name].data_type;
}
}
break;
@@ -969,17 +984,19 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &code
// Perform operator if any.
if (assignment->operation != GDScriptParser::AssignmentNode::OP_NONE) {
+ GDScriptCodeGenerator::Address op_result = codegen.add_temporary(_gdtype_from_datatype(assignment->get_datatype()));
GDScriptCodeGenerator::Address value = codegen.add_temporary(_gdtype_from_datatype(subscript->get_datatype()));
if (subscript->is_attribute) {
gen->write_get_named(value, name, prev_base);
} else {
gen->write_get(value, key, prev_base);
}
- gen->write_binary_operator(value, assignment->variant_op, value, assigned);
+ gen->write_binary_operator(op_result, assignment->variant_op, value, assigned);
+ gen->pop_temporary();
if (assigned.mode == GDScriptCodeGenerator::Address::TEMPORARY) {
gen->pop_temporary();
}
- assigned = value;
+ assigned = op_result;
}
// Perform assignment.
@@ -1013,10 +1030,20 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &code
assigned = info.base;
}
- // If this is a local member, also assign to it.
+ // If this is a class member property, also assign to it.
// This allow things like: position.x += 2.0
- if (assign_property != StringName()) {
- gen->write_set_member(assigned, assign_property);
+ if (assign_class_member_property != StringName()) {
+ gen->write_set_member(assigned, assign_class_member_property);
+ }
+ // Same as above but for members
+ if (is_member_property) {
+ if (member_property_has_setter && !member_property_is_in_setter) {
+ Vector<GDScriptCodeGenerator::Address> args;
+ args.push_back(assigned);
+ gen->write_call(GDScriptCodeGenerator::Address(), GDScriptCodeGenerator::Address(GDScriptCodeGenerator::Address::SELF), member_property_setter_function, args);
+ } else {
+ gen->write_assign(target_member_property, assigned);
+ }
}
if (assigned.mode == GDScriptCodeGenerator::Address::TEMPORARY) {
@@ -1035,8 +1062,8 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &code
StringName name = static_cast<GDScriptParser::IdentifierNode *>(assignment->assignee)->name;
if (has_operation) {
- GDScriptCodeGenerator::Address op_result = codegen.add_temporary();
- GDScriptCodeGenerator::Address member = codegen.add_temporary();
+ GDScriptCodeGenerator::Address op_result = codegen.add_temporary(_gdtype_from_datatype(assignment->get_datatype()));
+ GDScriptCodeGenerator::Address member = codegen.add_temporary(_gdtype_from_datatype(assignment->assignee->get_datatype()));
gen->write_get_member(member, name);
gen->write_binary_operator(op_result, assignment->variant_op, member, assigned_value);
gen->pop_temporary(); // Pop member temp.
@@ -1053,29 +1080,26 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &code
}
} else {
// Regular assignment.
- GDScriptCodeGenerator::Address target;
-
+ ERR_FAIL_COND_V_MSG(assignment->assignee->type != GDScriptParser::Node::IDENTIFIER, GDScriptCodeGenerator::Address(), "Expected the assignee to be an identifier here.");
+ GDScriptCodeGenerator::Address member;
+ bool is_member = false;
bool has_setter = false;
bool is_in_setter = false;
StringName setter_function;
- if (assignment->assignee->type == GDScriptParser::Node::IDENTIFIER) {
- StringName var_name = static_cast<const GDScriptParser::IdentifierNode *>(assignment->assignee)->name;
- if (!codegen.locals.has(var_name) && codegen.script->member_indices.has(var_name)) {
- setter_function = codegen.script->member_indices[var_name].setter;
- if (setter_function != StringName()) {
- has_setter = true;
- is_in_setter = setter_function == codegen.function_name;
- target.mode = GDScriptCodeGenerator::Address::MEMBER;
- target.address = codegen.script->member_indices[var_name].index;
- }
- }
+ StringName var_name = static_cast<const GDScriptParser::IdentifierNode *>(assignment->assignee)->name;
+ if (!codegen.locals.has(var_name) && codegen.script->member_indices.has(var_name)) {
+ is_member = true;
+ setter_function = codegen.script->member_indices[var_name].setter;
+ has_setter = setter_function != StringName();
+ is_in_setter = has_setter && setter_function == codegen.function_name;
+ member.mode = GDScriptCodeGenerator::Address::MEMBER;
+ member.address = codegen.script->member_indices[var_name].index;
+ member.type = codegen.script->member_indices[var_name].data_type;
}
- if (has_setter) {
- if (!is_in_setter) {
- // Store stack slot for the temp value.
- target = codegen.add_temporary(_gdtype_from_datatype(assignment->assignee->get_datatype()));
- }
+ GDScriptCodeGenerator::Address target;
+ if (is_member) {
+ target = member; // _parse_expression could call its getter, but we want to know the actual address
} else {
target = _parse_expression(codegen, r_error, assignment->assignee);
if (r_error) {
@@ -1092,7 +1116,7 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &code
bool has_operation = assignment->operation != GDScriptParser::AssignmentNode::OP_NONE;
if (has_operation) {
// Perform operation.
- GDScriptCodeGenerator::Address op_result = codegen.add_temporary();
+ GDScriptCodeGenerator::Address op_result = codegen.add_temporary(_gdtype_from_datatype(assignment->get_datatype()));
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;
@@ -1995,7 +2019,7 @@ GDScriptFunction *GDScriptCompiler::_parse_function(Error &r_error, GDScript *p_
codegen.generator->start_parameters();
for (int i = p_func->parameters.size() - optional_parameters; i < p_func->parameters.size(); i++) {
const GDScriptParser::ParameterNode *parameter = p_func->parameters[i];
- GDScriptCodeGenerator::Address src_addr = _parse_expression(codegen, r_error, parameter->default_value, true);
+ GDScriptCodeGenerator::Address src_addr = _parse_expression(codegen, r_error, parameter->default_value);
if (r_error) {
memdelete(codegen.generator);
return nullptr;
@@ -2069,7 +2093,7 @@ GDScriptFunction *GDScriptCompiler::_parse_function(Error &r_error, GDScript *p_
if (p_func) {
// if no return statement -> return type is void not unresolved Variant
if (p_func->body->has_return) {
- gd_function->return_type = _gdtype_from_datatype(p_func->get_datatype());
+ gd_function->return_type = _gdtype_from_datatype(p_func->get_datatype(), p_script);
} else {
gd_function->return_type = GDScriptDataType();
gd_function->return_type.has_type = true;
diff --git a/modules/gdscript/gdscript_editor.cpp b/modules/gdscript/gdscript_editor.cpp
index 7e5780a6b8..d10e120410 100644
--- a/modules/gdscript/gdscript_editor.cpp
+++ b/modules/gdscript/gdscript_editor.cpp
@@ -580,12 +580,50 @@ static String _make_arguments_hint(const GDScriptParser::FunctionNode *p_functio
if (par->default_value) {
String def_val = "<unknown>";
- if (par->default_value->type == GDScriptParser::Node::LITERAL) {
- const GDScriptParser::LiteralNode *literal = static_cast<const GDScriptParser::LiteralNode *>(par->default_value);
- def_val = literal->value.get_construct_string();
- } else if (par->default_value->type == GDScriptParser::Node::IDENTIFIER) {
- const GDScriptParser::IdentifierNode *id = static_cast<const GDScriptParser::IdentifierNode *>(par->default_value);
- def_val = id->name.operator String();
+ switch (par->default_value->type) {
+ case GDScriptParser::Node::LITERAL: {
+ const GDScriptParser::LiteralNode *literal = static_cast<const GDScriptParser::LiteralNode *>(par->default_value);
+ def_val = literal->value.get_construct_string();
+ } break;
+ case GDScriptParser::Node::IDENTIFIER: {
+ const GDScriptParser::IdentifierNode *id = static_cast<const GDScriptParser::IdentifierNode *>(par->default_value);
+ def_val = id->name.operator String();
+ } break;
+ case GDScriptParser::Node::CALL: {
+ const GDScriptParser::CallNode *call = static_cast<const GDScriptParser::CallNode *>(par->default_value);
+ if (call->is_constant && call->reduced) {
+ def_val = call->function_name.operator String() + call->reduced_value.operator String();
+ }
+ } break;
+ case GDScriptParser::Node::ARRAY: {
+ const GDScriptParser::ArrayNode *arr = static_cast<const GDScriptParser::ArrayNode *>(par->default_value);
+ if (arr->is_constant && arr->reduced) {
+ def_val = arr->reduced_value.operator String();
+ }
+ } break;
+ case GDScriptParser::Node::DICTIONARY: {
+ const GDScriptParser::DictionaryNode *dict = static_cast<const GDScriptParser::DictionaryNode *>(par->default_value);
+ if (dict->is_constant && dict->reduced) {
+ def_val = dict->reduced_value.operator String();
+ }
+ } break;
+ case GDScriptParser::Node::SUBSCRIPT: {
+ const GDScriptParser::SubscriptNode *sub = static_cast<const GDScriptParser::SubscriptNode *>(par->default_value);
+ if (sub->is_constant) {
+ if (sub->datatype.kind == GDScriptParser::DataType::ENUM_VALUE) {
+ def_val = sub->get_datatype().to_string();
+ } else if (sub->reduced) {
+ const Variant::Type vt = sub->reduced_value.get_type();
+ if (vt == Variant::Type::NIL || vt == Variant::Type::FLOAT || vt == Variant::Type::INT || vt == Variant::Type::STRING || vt == Variant::Type::STRING_NAME || vt == Variant::Type::BOOL || vt == Variant::Type::NODE_PATH) {
+ def_val = sub->reduced_value.operator String();
+ } else {
+ def_val = sub->get_datatype().to_string() + sub->reduced_value.operator String();
+ }
+ }
+ }
+ } break;
+ default:
+ break;
}
arghint += " = " + def_val;
}
@@ -646,6 +684,11 @@ static void _find_annotation_arguments(const GDScriptParser::AnnotationNode *p_a
ScriptCodeCompletionOption option(E, ScriptCodeCompletionOption::KIND_CLASS);
r_result.insert(option.display, option);
}
+ } else if (p_annotation->name == "@warning_ignore") {
+ for (int warning_code = 0; warning_code < GDScriptWarning::WARNING_MAX; warning_code++) {
+ ScriptCodeCompletionOption warning(GDScriptWarning::get_name_from_code((GDScriptWarning::Code)warning_code).to_lower(), ScriptCodeCompletionOption::KIND_PLAIN_TEXT);
+ r_result.insert(warning.display, warning);
+ }
}
}
@@ -923,8 +966,7 @@ static void _find_identifiers_in_base(const GDScriptCompletionIdentifier &p_base
if (!_static || Engine::get_singleton()->has_singleton(type)) {
List<MethodInfo> methods;
- bool is_autocompleting_getters = GLOBAL_GET("debug/gdscript/completion/autocomplete_setters_and_getters").booleanize();
- ClassDB::get_method_list(type, &methods, false, !is_autocompleting_getters);
+ ClassDB::get_method_list(type, &methods, false, true);
for (const MethodInfo &E : methods) {
if (E.name.begins_with("_")) {
continue;
@@ -1159,6 +1201,10 @@ static bool _guess_method_return_type_from_base(GDScriptParser::CompletionContex
static bool _guess_expression_type(GDScriptParser::CompletionContext &p_context, const GDScriptParser::ExpressionNode *p_expression, GDScriptCompletionIdentifier &r_type) {
bool found = false;
+ if (p_expression == nullptr) {
+ return false;
+ }
+
if (p_expression->is_constant) {
// Already has a value, so just use that.
r_type = _type_from_variant(p_expression->reduced_value);
diff --git a/modules/gdscript/gdscript_parser.cpp b/modules/gdscript/gdscript_parser.cpp
index d12795d320..5e210074ed 100644
--- a/modules/gdscript/gdscript_parser.cpp
+++ b/modules/gdscript/gdscript_parser.cpp
@@ -39,6 +39,7 @@
#ifdef DEBUG_ENABLED
#include "core/os/os.h"
#include "core/string/string_builder.h"
+#include "gdscript_warning.h"
#endif // DEBUG_ENABLED
#ifdef TOOLS_ENABLED
@@ -132,9 +133,9 @@ GDScriptParser::GDScriptParser() {
register_annotation(MethodInfo("@export_flags_3d_render"), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_LAYERS_3D_RENDER, Variant::INT>);
register_annotation(MethodInfo("@export_flags_3d_physics"), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_LAYERS_3D_PHYSICS, Variant::INT>);
register_annotation(MethodInfo("@export_flags_3d_navigation"), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_LAYERS_3D_NAVIGATION, Variant::INT>);
+ register_annotation(MethodInfo("@warning_ignore", { Variant::STRING, "warning" }), AnnotationInfo::CLASS | AnnotationInfo::VARIABLE | AnnotationInfo::SIGNAL | AnnotationInfo::CONSTANT | AnnotationInfo::FUNCTION | AnnotationInfo::STATEMENT, &GDScriptParser::warning_annotations, 0, true);
// Networking.
register_annotation(MethodInfo("@rpc", { Variant::STRING, "mode" }, { Variant::STRING, "sync" }, { Variant::STRING, "transfer_mode" }, { Variant::INT, "transfer_channel" }), AnnotationInfo::FUNCTION, &GDScriptParser::network_annotations<Multiplayer::RPC_MODE_AUTHORITY>, 4, true);
- // TODO: Warning annotations.
}
GDScriptParser::~GDScriptParser() {
@@ -196,6 +197,10 @@ void GDScriptParser::push_warning(const Node *p_source, GDScriptWarning::Code p_
return;
}
+ if (ignored_warning_codes.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;
@@ -701,24 +706,21 @@ void GDScriptParser::parse_extends() {
template <class T>
void GDScriptParser::parse_class_member(T *(GDScriptParser::*p_parse_function)(), AnnotationInfo::TargetKind p_target, const String &p_member_kind) {
advance();
- T *member = (this->*p_parse_function)();
- if (member == nullptr) {
- return;
- }
+
#ifdef TOOLS_ENABLED
- int doc_comment_line = member->start_line - 1;
+ int doc_comment_line = previous.start_line - 1;
#endif // TOOLS_ENABLED
// Consume annotations.
+ List<AnnotationNode *> annotations;
while (!annotation_stack.is_empty()) {
AnnotationNode *last_annotation = annotation_stack.back()->get();
if (last_annotation->applies_to(p_target)) {
- member->annotations.push_front(last_annotation);
+ annotations.push_front(last_annotation);
annotation_stack.pop_back();
} else {
push_error(vformat(R"(Annotation "%s" cannot be applied to a %s.)", last_annotation->name, p_member_kind));
clear_unused_annotations();
- return;
}
#ifdef TOOLS_ENABLED
if (last_annotation->start_line == doc_comment_line) {
@@ -727,6 +729,16 @@ void GDScriptParser::parse_class_member(T *(GDScriptParser::*p_parse_function)()
#endif // TOOLS_ENABLED
}
+ T *member = (this->*p_parse_function)();
+ if (member == nullptr) {
+ return;
+ }
+
+ // Apply annotations.
+ for (AnnotationNode *&annotation : annotations) {
+ member->annotations.push_back(annotation);
+ }
+
#ifdef TOOLS_ENABLED
// Consume doc comments.
class_doc_line = MIN(class_doc_line, doc_comment_line - 1);
@@ -805,6 +817,8 @@ void GDScriptParser::parse_class_body(bool p_is_multiline) {
class_end = true;
break;
default:
+ // Display a completion with identifiers.
+ make_completion_context(COMPLETION_IDENTIFIER, nullptr);
push_error(vformat(R"(Unexpected "%s" in class body.)", current.get_name()));
advance();
break;
@@ -1507,6 +1521,8 @@ GDScriptParser::Node *GDScriptParser::parse_statement() {
bool unreachable = current_suite->has_return && !current_suite->has_unreachable_code;
#endif
+ bool is_annotation = false;
+
switch (current.type) {
case GDScriptTokenizer::Token::PASS:
advance();
@@ -1576,6 +1592,7 @@ GDScriptParser::Node *GDScriptParser::parse_statement() {
break;
case GDScriptTokenizer::Token::ANNOTATION: {
advance();
+ is_annotation = true;
AnnotationNode *annotation = parse_annotation(AnnotationInfo::STATEMENT);
if (annotation != nullptr) {
annotation_stack.push_back(annotation);
@@ -1616,6 +1633,18 @@ GDScriptParser::Node *GDScriptParser::parse_statement() {
}
}
+ // Apply annotations to statement.
+ while (!is_annotation && result != nullptr && !annotation_stack.is_empty()) {
+ AnnotationNode *last_annotation = annotation_stack.back()->get();
+ if (last_annotation->applies_to(AnnotationInfo::STATEMENT)) {
+ result->annotations.push_front(last_annotation);
+ annotation_stack.pop_back();
+ } else {
+ push_error(vformat(R"(Annotation "%s" cannot be applied to a statement.)", last_annotation->name));
+ clear_unused_annotations();
+ }
+ }
+
#ifdef DEBUG_ENABLED
if (unreachable && result != nullptr) {
current_suite->has_unreachable_code = true;
@@ -3468,7 +3497,7 @@ bool GDScriptParser::export_annotations(const AnnotationNode *p_annotation, Node
variable->export_info.hint_string = hint_string;
- // This is called after tne analyzer is done finding the type, so this should be set here.
+ // This is called after the analyzer is done finding the type, so this should be set here.
DataType export_type = variable->get_datatype();
if (p_annotation->name == "@export") {
@@ -3510,12 +3539,12 @@ bool GDScriptParser::export_annotations(const AnnotationNode *p_annotation, Node
variable->export_info.hint = PROPERTY_HINT_ENUM;
String enum_hint_string;
- for (const Map<StringName, int>::Element *E = export_type.enum_values.front(); E; E = E->next()) {
- enum_hint_string += E->key().operator String().capitalize().xml_escape();
+ for (OrderedHashMap<StringName, int>::Element E = export_type.enum_values.front(); E; E = E.next()) {
+ enum_hint_string += E.key().operator String().capitalize().xml_escape();
enum_hint_string += ":";
- enum_hint_string += String::num_int64(E->get()).xml_escape();
+ enum_hint_string += String::num_int64(E.value()).xml_escape();
- if (E->next()) {
+ if (E.next()) {
enum_hint_string += ",";
}
}
@@ -3552,7 +3581,24 @@ bool GDScriptParser::export_annotations(const AnnotationNode *p_annotation, Node
}
bool GDScriptParser::warning_annotations(const AnnotationNode *p_annotation, Node *p_node) {
- ERR_FAIL_V_MSG(false, "Not implemented.");
+#ifdef DEBUG_ENABLED
+ bool has_error = false;
+ for (const Variant &warning_name : p_annotation->resolved_arguments) {
+ GDScriptWarning::Code warning = GDScriptWarning::get_code_from_name(String(warning_name).to_upper());
+ if (warning == GDScriptWarning::WARNING_MAX) {
+ push_error(vformat(R"(Invalid warning name: "%s".)", warning_name), p_annotation);
+ has_error = true;
+ } else {
+ p_node->ignored_warnings.push_back(warning);
+ }
+ }
+
+ return !has_error;
+
+#else // ! DEBUG_ENABLED
+ // Only available in debug builds.
+ return true;
+#endif // DEBUG_ENABLED
}
template <Multiplayer::RPCMode t_mode>
@@ -4190,7 +4236,11 @@ void GDScriptParser::TreePrinter::print_get_node(GetNodeNode *p_get_node) {
}
void GDScriptParser::TreePrinter::print_identifier(IdentifierNode *p_identifier) {
- push_text(p_identifier->name);
+ if (p_identifier != nullptr) {
+ push_text(p_identifier->name);
+ } else {
+ push_text("<invalid identifier>");
+ }
}
void GDScriptParser::TreePrinter::print_if(IfNode *p_if, bool p_is_elif) {
diff --git a/modules/gdscript/gdscript_parser.h b/modules/gdscript/gdscript_parser.h
index b5ff0538eb..e4311d2d5e 100644
--- a/modules/gdscript/gdscript_parser.h
+++ b/modules/gdscript/gdscript_parser.h
@@ -133,7 +133,7 @@ public:
ClassNode *class_type = nullptr;
MethodInfo method_info; // For callable/signals.
- Map<StringName, int> enum_values; // For enums.
+ OrderedHashMap<StringName, int> enum_values; // For enums.
_FORCE_INLINE_ bool is_set() const { return kind != UNRESOLVED; }
_FORCE_INLINE_ bool has_no_type() const { return type_source == UNDETECTED; }
@@ -297,6 +297,7 @@ public:
int leftmost_column = 0, rightmost_column = 0;
Node *next = nullptr;
List<AnnotationNode *> annotations;
+ Vector<uint32_t> ignored_warnings;
DataType datatype;
@@ -1204,6 +1205,7 @@ private:
#ifdef DEBUG_ENABLED
List<GDScriptWarning> warnings;
Set<String> ignored_warnings;
+ Set<uint32_t> ignored_warning_codes;
Set<int> unsafe_lines;
#endif
diff --git a/modules/gdscript/gdscript_vm.cpp b/modules/gdscript/gdscript_vm.cpp
index 014a2ad3b8..e0facaf61d 100644
--- a/modules/gdscript/gdscript_vm.cpp
+++ b/modules/gdscript/gdscript_vm.cpp
@@ -488,7 +488,12 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a
memnew_placement(&stack[i + 3], Variant(*p_args[i]));
continue;
}
-
+ // If types already match, don't call Variant::construct(). Constructors of some types
+ // (e.g. packed arrays) do copies, whereas they pass by reference when inside a Variant.
+ if (argument_types[i].is_type(*p_args[i], false)) {
+ memnew_placement(&stack[i + 3], Variant(*p_args[i]));
+ continue;
+ }
if (!argument_types[i].is_type(*p_args[i], true)) {
r_err.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT;
r_err.argument = i;
diff --git a/modules/gdscript/gdscript_warning.cpp b/modules/gdscript/gdscript_warning.cpp
index fd251b8444..73536f5f8e 100644
--- a/modules/gdscript/gdscript_warning.cpp
+++ b/modules/gdscript/gdscript_warning.cpp
@@ -213,7 +213,7 @@ GDScriptWarning::Code GDScriptWarning::get_code_from_name(const String &p_name)
}
}
- ERR_FAIL_V_MSG(WARNING_MAX, "Invalid GDScript warning name: " + p_name);
+ return WARNING_MAX;
}
#endif // DEBUG_ENABLED
diff --git a/modules/gdscript/icons/GDScriptInternal.svg b/modules/gdscript/icons/GDScriptInternal.svg
new file mode 100644
index 0000000000..fcabaafbd0
--- /dev/null
+++ b/modules/gdscript/icons/GDScriptInternal.svg
@@ -0,0 +1 @@
+<svg height="16" viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg"><path d="m7 1-.56445 2.2578c-.2364329.0758517-.4668872.16921-.68945.2793l-1.9883-1.1934-1.4141 1.4141 1.1953 1.9941c-.1119126.2211335-.2072287.4502818-.28516.68555l-2.2539.5625v2l2.2578.56445c.075942.2357685.1692993.465568.2793.6875l-1.1934 1.9902 1.4141 1.4141 1.9941-1.1953c.2211335.111913.4502818.207229.68555.28516l.5625 2.2539h2l.56445-2.2578c.2357685-.07594.465568-.169299.6875-.2793l1.9902 1.1934 1.4141-1.4141-1.1953-1.9941c.111913-.221133.207229-.4502818.28516-.68555l2.2539-.5625v-2l-2.2578-.56445c-.075942-.2357685-.169299-.4655679-.2793-.6875l1.1934-1.9902-1.4141-1.4141-1.9941 1.1953c-.221133-.1119126-.4502818-.2072287-.68555-.28516l-.5625-2.2539zm1 5c1.1045695 0 2 .8954305 2 2s-.8954305 2-2 2-2-.8954305-2-2 .8954305-2 2-2z" fill="none" stroke="#e0e0e0"/></svg>
diff --git a/modules/gdscript/tests/gdscript_test_runner.cpp b/modules/gdscript/tests/gdscript_test_runner.cpp
index 5845f84605..47772b8039 100644
--- a/modules/gdscript/tests/gdscript_test_runner.cpp
+++ b/modules/gdscript/tests/gdscript_test_runner.cpp
@@ -362,16 +362,16 @@ void GDScriptTest::error_handler(void *p_this, const char *p_function, const cha
}
builder.append("\n>> on function: ");
- builder.append(p_function);
+ builder.append(String::utf8(p_function));
builder.append("()\n>> ");
- builder.append(String(p_file).trim_prefix(self->base_dir));
+ builder.append(String::utf8(p_file).trim_prefix(self->base_dir));
builder.append("\n>> ");
builder.append(itos(p_line));
builder.append("\n>> ");
- builder.append(p_error);
+ builder.append(String::utf8(p_error));
if (strlen(p_explanation) > 0) {
builder.append("\n>> ");
- builder.append(p_explanation);
+ builder.append(String::utf8(p_explanation));
}
builder.append("\n");
diff --git a/modules/gdscript/tests/scripts/analyzer/errors/params_default_forward_reference.gd b/modules/gdscript/tests/scripts/analyzer/errors/params_default_forward_reference.gd
new file mode 100644
index 0000000000..05d9bd6a3d
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/errors/params_default_forward_reference.gd
@@ -0,0 +1,9 @@
+# https://github.com/godotengine/godot/issues/56702
+
+func test():
+ # somewhat obscure feature: referencing parameters in defaults, but only earlier ones!
+ ref_default("non-optional")
+
+
+func ref_default(nondefault1, defa=nondefault1, defb=defc, defc=1):
+ prints(nondefault1, nondefault2, defa, defb, defc)
diff --git a/modules/gdscript/tests/scripts/analyzer/errors/params_default_forward_reference.out b/modules/gdscript/tests/scripts/analyzer/errors/params_default_forward_reference.out
new file mode 100644
index 0000000000..1d5b5bf393
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/errors/params_default_forward_reference.out
@@ -0,0 +1,2 @@
+GDTEST_ANALYZER_ERROR
+Identifier "defc" not declared in the current scope.
diff --git a/modules/gdscript/tests/scripts/analyzer/features/warning_ignore_annotation.gd b/modules/gdscript/tests/scripts/analyzer/features/warning_ignore_annotation.gd
new file mode 100644
index 0000000000..877a4ea221
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/features/warning_ignore_annotation.gd
@@ -0,0 +1,15 @@
+@warning_ignore(unused_private_class_variable)
+var _unused = 2
+
+@warning_ignore(unused_variable)
+func test():
+ print("test")
+ var unused = 3
+
+ @warning_ignore(redundant_await)
+ print(await regular_func())
+
+ print("done")
+
+func regular_func():
+ return 0
diff --git a/modules/gdscript/tests/scripts/analyzer/features/warning_ignore_annotation.out b/modules/gdscript/tests/scripts/analyzer/features/warning_ignore_annotation.out
new file mode 100644
index 0000000000..42292774a0
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/features/warning_ignore_annotation.out
@@ -0,0 +1,4 @@
+GDTEST_OK
+test
+0
+done
diff --git a/modules/gdscript/tests/scripts/runtime/features/params_default_values.gd b/modules/gdscript/tests/scripts/runtime/features/params_default_values.gd
new file mode 100644
index 0000000000..8156b4ec68
--- /dev/null
+++ b/modules/gdscript/tests/scripts/runtime/features/params_default_values.gd
@@ -0,0 +1,35 @@
+# https://github.com/godotengine/godot/issues/56702
+
+func test():
+ const_default()
+ func_result_default()
+ # calling again will run the initializer again,
+ # as the default is not evaluated at time of defining the function (as in python)
+ # but every time the function is called (as in C++)
+ func_result_default()
+ lots_of_defaults("non-optional")
+ # somewhat obscure feature: referencing earlier parameters
+ ref_default("non-optional", 42)
+
+
+func const_default(param=42):
+ print(param)
+
+
+var default_val := 0
+
+func get_default():
+ default_val += 1
+ return default_val
+
+
+func func_result_default(param=get_default()):
+ print(param)
+
+
+func lots_of_defaults(nondefault, one=1, two=2, three=get_default()):
+ prints(nondefault, one, two, three)
+
+
+func ref_default(nondefault1, nondefault2, defa=nondefault1, defb=nondefault2 - 1):
+ prints(nondefault1, nondefault2, defa, defb)
diff --git a/modules/gdscript/tests/scripts/runtime/features/params_default_values.out b/modules/gdscript/tests/scripts/runtime/features/params_default_values.out
new file mode 100644
index 0000000000..50e0885ae5
--- /dev/null
+++ b/modules/gdscript/tests/scripts/runtime/features/params_default_values.out
@@ -0,0 +1,6 @@
+GDTEST_OK
+42
+1
+2
+non-optional 1 2 3
+non-optional 42 non-optional 41