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.cpp33
-rw-r--r--modules/gdscript/gdscript.h7
-rw-r--r--modules/gdscript/gdscript_analyzer.cpp190
-rw-r--r--modules/gdscript/gdscript_analyzer.h6
-rw-r--r--modules/gdscript/gdscript_byte_codegen.cpp8
-rw-r--r--modules/gdscript/gdscript_byte_codegen.h3
-rw-r--r--modules/gdscript/gdscript_cache.cpp12
-rw-r--r--modules/gdscript/gdscript_cache.h1
-rw-r--r--modules/gdscript/gdscript_codegen.h5
-rw-r--r--modules/gdscript/gdscript_compiler.cpp56
-rw-r--r--modules/gdscript/gdscript_disassembler.cpp8
-rw-r--r--modules/gdscript/gdscript_editor.cpp14
-rw-r--r--modules/gdscript/gdscript_function.h5
-rw-r--r--modules/gdscript/gdscript_parser.cpp291
-rw-r--r--modules/gdscript/gdscript_parser.h13
-rw-r--r--modules/gdscript/gdscript_vm.cpp34
-rw-r--r--modules/gdscript/gdscript_warning.cpp4
-rw-r--r--modules/gdscript/gdscript_warning.h1
-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_language_server.cpp7
-rw-r--r--modules/gdscript/language_server/gdscript_language_server.h1
-rw-r--r--modules/gdscript/language_server/gdscript_workspace.cpp52
-rw-r--r--modules/gdscript/language_server/gdscript_workspace.h2
-rw-r--r--modules/gdscript/language_server/lsp.hpp22
-rw-r--r--modules/gdscript/tests/gdscript_test_runner.cpp8
-rw-r--r--modules/gdscript/tests/scripts/analyzer/errors/bitwise_float_left_operand.gd3
-rw-r--r--modules/gdscript/tests/scripts/analyzer/errors/bitwise_float_left_operand.out2
-rw-r--r--modules/gdscript/tests/scripts/analyzer/errors/bitwise_float_right_operand.gd3
-rw-r--r--modules/gdscript/tests/scripts/analyzer/errors/bitwise_float_right_operand.out2
-rw-r--r--modules/gdscript/tests/scripts/analyzer/errors/constant_used_as_function.gd5
-rw-r--r--modules/gdscript/tests/scripts/analyzer/errors/constant_used_as_function.out2
-rw-r--r--modules/gdscript/tests/scripts/analyzer/errors/dictionary_duplicate_key_lua.gd6
-rw-r--r--modules/gdscript/tests/scripts/analyzer/errors/dictionary_duplicate_key_lua.out2
-rw-r--r--modules/gdscript/tests/scripts/analyzer/errors/dictionary_duplicate_key_lua_with_string.gd6
-rw-r--r--modules/gdscript/tests/scripts/analyzer/errors/dictionary_duplicate_key_lua_with_string.out2
-rw-r--r--modules/gdscript/tests/scripts/analyzer/errors/dictionary_duplicate_key_python.gd6
-rw-r--r--modules/gdscript/tests/scripts/analyzer/errors/dictionary_duplicate_key_python.out2
-rw-r--r--modules/gdscript/tests/scripts/analyzer/errors/enum_float_value.gd7
-rw-r--r--modules/gdscript/tests/scripts/analyzer/errors/enum_float_value.out2
-rw-r--r--modules/gdscript/tests/scripts/analyzer/errors/enum_string_value.gd7
-rw-r--r--modules/gdscript/tests/scripts/analyzer/errors/enum_string_value.out2
-rw-r--r--modules/gdscript/tests/scripts/analyzer/errors/function_used_as_property.gd6
-rw-r--r--modules/gdscript/tests/scripts/analyzer/errors/function_used_as_property.out2
-rw-r--r--modules/gdscript/tests/scripts/analyzer/errors/invalid_array_index.gd3
-rw-r--r--modules/gdscript/tests/scripts/analyzer/errors/invalid_array_index.out2
-rw-r--r--modules/gdscript/tests/scripts/analyzer/errors/invalid_concatenation_bool.gd2
-rw-r--r--modules/gdscript/tests/scripts/analyzer/errors/invalid_concatenation_bool.out2
-rw-r--r--modules/gdscript/tests/scripts/analyzer/errors/invalid_concatenation_dictionary.gd2
-rw-r--r--modules/gdscript/tests/scripts/analyzer/errors/invalid_concatenation_dictionary.out2
-rw-r--r--modules/gdscript/tests/scripts/analyzer/errors/invalid_concatenation_mixed.gd2
-rw-r--r--modules/gdscript/tests/scripts/analyzer/errors/invalid_concatenation_mixed.out2
-rw-r--r--modules/gdscript/tests/scripts/analyzer/errors/invalid_constant.gd5
-rw-r--r--modules/gdscript/tests/scripts/analyzer/errors/invalid_constant.out2
-rw-r--r--modules/gdscript/tests/scripts/analyzer/errors/leading_number_separator.gd3
-rw-r--r--modules/gdscript/tests/scripts/analyzer/errors/leading_number_separator.out2
-rw-r--r--modules/gdscript/tests/scripts/analyzer/errors/missing_argument.gd6
-rw-r--r--modules/gdscript/tests/scripts/analyzer/errors/missing_argument.out (renamed from modules/gdscript/tests/scripts/parser/errors/missing_argument.out)0
-rw-r--r--modules/gdscript/tests/scripts/analyzer/errors/property_used_as_function.gd4
-rw-r--r--modules/gdscript/tests/scripts/analyzer/errors/property_used_as_function.out2
-rw-r--r--modules/gdscript/tests/scripts/analyzer/errors/redefine_class_constant.gd7
-rw-r--r--modules/gdscript/tests/scripts/analyzer/errors/redefine_class_constant.out2
-rw-r--r--modules/gdscript/tests/scripts/analyzer/errors/redefine_local_constant.gd5
-rw-r--r--modules/gdscript/tests/scripts/analyzer/errors/redefine_local_constant.out2
-rw-r--r--modules/gdscript/tests/scripts/analyzer/errors/super_nonexistent_base_method.gd11
-rw-r--r--modules/gdscript/tests/scripts/analyzer/errors/super_nonexistent_base_method.out2
-rw-r--r--modules/gdscript/tests/scripts/analyzer/features/as.gd16
-rw-r--r--modules/gdscript/tests/scripts/analyzer/features/as.out8
-rw-r--r--modules/gdscript/tests/scripts/analyzer/features/auto_inferred_type_dont_error.gd9
-rw-r--r--modules/gdscript/tests/scripts/analyzer/features/auto_inferred_type_dont_error.out2
-rw-r--r--modules/gdscript/tests/scripts/analyzer/features/constants_from_parent.gd16
-rw-r--r--modules/gdscript/tests/scripts/analyzer/features/constants_from_parent.out4
-rw-r--r--modules/gdscript/tests/scripts/analyzer/features/enum_from_parent.gd14
-rw-r--r--modules/gdscript/tests/scripts/analyzer/features/enum_from_parent.out4
-rw-r--r--modules/gdscript/tests/scripts/analyzer/features/enum_value_from_parent.gd14
-rw-r--r--modules/gdscript/tests/scripts/analyzer/features/enum_value_from_parent.out4
-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/array_consecutive_commas.gd3
-rw-r--r--modules/gdscript/tests/scripts/parser/errors/array_consecutive_commas.out2
-rw-r--r--modules/gdscript/tests/scripts/parser/errors/assignment_2_equal_signs.gd2
-rw-r--r--modules/gdscript/tests/scripts/parser/errors/assignment_2_equal_signs.out2
-rw-r--r--modules/gdscript/tests/scripts/parser/errors/assignment_3_equal_signs.gd2
-rw-r--r--modules/gdscript/tests/scripts/parser/errors/assignment_3_equal_signs.out2
-rw-r--r--modules/gdscript/tests/scripts/parser/errors/assignment_in_if.gd4
-rw-r--r--modules/gdscript/tests/scripts/parser/errors/assignment_in_if.out2
-rw-r--r--modules/gdscript/tests/scripts/parser/errors/assignment_in_var.gd2
-rw-r--r--modules/gdscript/tests/scripts/parser/errors/assignment_in_var.out2
-rw-r--r--modules/gdscript/tests/scripts/parser/errors/assignment_in_var_if.gd4
-rw-r--r--modules/gdscript/tests/scripts/parser/errors/assignment_in_var_if.out2
-rw-r--r--modules/gdscript/tests/scripts/parser/errors/assignment_without_identifier.gd2
-rw-r--r--modules/gdscript/tests/scripts/parser/errors/assignment_without_identifier.out2
-rw-r--r--modules/gdscript/tests/scripts/parser/errors/binary_complement_without_argument.gd2
-rw-r--r--modules/gdscript/tests/scripts/parser/errors/binary_complement_without_argument.out2
-rw-r--r--modules/gdscript/tests/scripts/parser/errors/boolean_negation_without_argument.gd2
-rw-r--r--modules/gdscript/tests/scripts/parser/errors/boolean_negation_without_argument.out2
-rw-r--r--modules/gdscript/tests/scripts/parser/errors/boolean_negation_without_argument_using_bang.gd2
-rw-r--r--modules/gdscript/tests/scripts/parser/errors/boolean_negation_without_argument_using_bang.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/class_name_after_annotation.gd6
-rw-r--r--modules/gdscript/tests/scripts/parser/errors/class_name_after_annotation.out2
-rw-r--r--modules/gdscript/tests/scripts/parser/errors/constant_conflicts_variable.gd3
-rw-r--r--modules/gdscript/tests/scripts/parser/errors/constant_conflicts_variable.out2
-rw-r--r--modules/gdscript/tests/scripts/parser/errors/default_value_in_function_call.gd7
-rw-r--r--modules/gdscript/tests/scripts/parser/errors/default_value_in_function_call.out2
-rw-r--r--modules/gdscript/tests/scripts/parser/errors/double_dictionary_comma.gd2
-rw-r--r--modules/gdscript/tests/scripts/parser/errors/double_dictionary_comma.out2
-rw-r--r--modules/gdscript/tests/scripts/parser/errors/function_conflicts_constant.gd5
-rw-r--r--modules/gdscript/tests/scripts/parser/errors/function_conflicts_constant.out2
-rw-r--r--modules/gdscript/tests/scripts/parser/errors/function_conflicts_variable.gd7
-rw-r--r--modules/gdscript/tests/scripts/parser/errors/function_conflicts_variable.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_identifier_number.gd3
-rw-r--r--modules/gdscript/tests/scripts/parser/errors/invalid_identifier_number.out2
-rw-r--r--modules/gdscript/tests/scripts/parser/errors/invalid_identifier_string.gd3
-rw-r--r--modules/gdscript/tests/scripts/parser/errors/invalid_identifier_string.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/missing_argument.gd6
-rw-r--r--modules/gdscript/tests/scripts/parser/errors/missing_closing_expr_paren.gd2
-rw-r--r--modules/gdscript/tests/scripts/parser/errors/missing_colon.gd4
-rw-r--r--modules/gdscript/tests/scripts/parser/errors/missing_expression_after_ternary_else.gd4
-rw-r--r--modules/gdscript/tests/scripts/parser/errors/missing_expression_after_ternary_else.out2
-rw-r--r--modules/gdscript/tests/scripts/parser/errors/missing_paren_after_args.gd6
-rw-r--r--modules/gdscript/tests/scripts/parser/errors/mistaken_decrement_operator.gd3
-rw-r--r--modules/gdscript/tests/scripts/parser/errors/mistaken_decrement_operator.out2
-rw-r--r--modules/gdscript/tests/scripts/parser/errors/mistaken_increment_operator.gd3
-rw-r--r--modules/gdscript/tests/scripts/parser/errors/mistaken_increment_operator.out2
-rw-r--r--modules/gdscript/tests/scripts/parser/errors/multiple_number_separators.gd3
-rw-r--r--modules/gdscript/tests/scripts/parser/errors/multiple_number_separators.out2
-rw-r--r--modules/gdscript/tests/scripts/parser/errors/nothing_after_dollar.gd4
-rw-r--r--modules/gdscript/tests/scripts/parser/errors/redefine_keyword.gd2
-rw-r--r--modules/gdscript/tests/scripts/parser/errors/redefine_keyword.out2
-rw-r--r--modules/gdscript/tests/scripts/parser/errors/redefine_local_constant_with_keyword.gd5
-rw-r--r--modules/gdscript/tests/scripts/parser/errors/redefine_local_constant_with_keyword.out2
-rw-r--r--modules/gdscript/tests/scripts/parser/errors/subscript_without_index.gd3
-rw-r--r--modules/gdscript/tests/scripts/parser/errors/subscript_without_index.out2
-rw-r--r--modules/gdscript/tests/scripts/parser/errors/variable_conflicts_constant.gd3
-rw-r--r--modules/gdscript/tests/scripts/parser/errors/variable_conflicts_constant.out2
-rw-r--r--modules/gdscript/tests/scripts/parser/errors/variable_conflicts_function.gd6
-rw-r--r--modules/gdscript/tests/scripts/parser/errors/variable_conflicts_function.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/wrong_value_after_dollar.gd4
-rw-r--r--modules/gdscript/tests/scripts/parser/errors/wrong_value_after_dollar_slash.gd4
-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/advanced_expression_matching.gd34
-rw-r--r--modules/gdscript/tests/scripts/parser/features/advanced_expression_matching.out14
-rw-r--r--modules/gdscript/tests/scripts/parser/features/array.gd16
-rw-r--r--modules/gdscript/tests/scripts/parser/features/array.out11
-rw-r--r--modules/gdscript/tests/scripts/parser/features/basic_expression_matching.gd27
-rw-r--r--modules/gdscript/tests/scripts/parser/features/basic_expression_matching.out10
-rw-r--r--modules/gdscript/tests/scripts/parser/features/bitwise_operators.gd50
-rw-r--r--modules/gdscript/tests/scripts/parser/features/bitwise_operators.out14
-rw-r--r--modules/gdscript/tests/scripts/parser/features/class.gd25
-rw-r--r--modules/gdscript/tests/scripts/parser/features/class.out3
-rw-r--r--modules/gdscript/tests/scripts/parser/features/class_inheritance.gd33
-rw-r--r--modules/gdscript/tests/scripts/parser/features/class_inheritance.out10
-rw-r--r--modules/gdscript/tests/scripts/parser/features/class_name.gd5
-rw-r--r--modules/gdscript/tests/scripts/parser/features/class_name.out1
-rw-r--r--modules/gdscript/tests/scripts/parser/features/concatenation.gd4
-rw-r--r--modules/gdscript/tests/scripts/parser/features/concatenation.out4
-rw-r--r--modules/gdscript/tests/scripts/parser/features/constants.gd11
-rw-r--r--modules/gdscript/tests/scripts/parser/features/constants.out33
-rw-r--r--modules/gdscript/tests/scripts/parser/features/dictionary.gd37
-rw-r--r--modules/gdscript/tests/scripts/parser/features/dictionary.out14
-rw-r--r--modules/gdscript/tests/scripts/parser/features/dictionary_lua_style.gd9
-rw-r--r--modules/gdscript/tests/scripts/parser/features/dictionary_lua_style.out2
-rw-r--r--modules/gdscript/tests/scripts/parser/features/dictionary_mixed_syntax.gd12
-rw-r--r--modules/gdscript/tests/scripts/parser/features/dictionary_mixed_syntax.out2
-rw-r--r--modules/gdscript/tests/scripts/parser/features/dollar_node_paths.gd20
-rw-r--r--modules/gdscript/tests/scripts/parser/features/dollar_node_paths.out1
-rw-r--r--modules/gdscript/tests/scripts/parser/features/enum.gd14
-rw-r--r--modules/gdscript/tests/scripts/parser/features/enum.out6
-rw-r--r--modules/gdscript/tests/scripts/parser/features/export_variable.gd18
-rw-r--r--modules/gdscript/tests/scripts/parser/features/export_variable.out8
-rw-r--r--modules/gdscript/tests/scripts/parser/features/float_notation.gd24
-rw-r--r--modules/gdscript/tests/scripts/parser/features/float_notation.out19
-rw-r--r--modules/gdscript/tests/scripts/parser/features/for_range.gd39
-rw-r--r--modules/gdscript/tests/scripts/parser/features/for_range.out58
-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/in.gd14
-rw-r--r--modules/gdscript/tests/scripts/parser/features/in.out11
-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/match.gd18
-rw-r--r--modules/gdscript/tests/scripts/parser/features/match.out4
-rw-r--r--modules/gdscript/tests/scripts/parser/features/multiline_arrays.gd7
-rw-r--r--modules/gdscript/tests/scripts/parser/features/multiline_arrays.out1
-rw-r--r--modules/gdscript/tests/scripts/parser/features/multiline_dictionaries.gd10
-rw-r--r--modules/gdscript/tests/scripts/parser/features/multiline_dictionaries.out1
-rw-r--r--modules/gdscript/tests/scripts/parser/features/multiline_if.gd14
-rw-r--r--modules/gdscript/tests/scripts/parser/features/multiline_if.out1
-rw-r--r--modules/gdscript/tests/scripts/parser/features/multiline_strings.gd15
-rw-r--r--modules/gdscript/tests/scripts/parser/features/multiline_strings.out1
-rw-r--r--modules/gdscript/tests/scripts/parser/features/multiline_vector.gd17
-rw-r--r--modules/gdscript/tests/scripts/parser/features/multiline_vector.out1
-rw-r--r--modules/gdscript/tests/scripts/parser/features/nested_arithmetic.gd22
-rw-r--r--modules/gdscript/tests/scripts/parser/features/nested_arithmetic.out82
-rw-r--r--modules/gdscript/tests/scripts/parser/features/nested_array.gd5
-rw-r--r--modules/gdscript/tests/scripts/parser/features/nested_array.out4
-rw-r--r--modules/gdscript/tests/scripts/parser/features/nested_dictionary.gd6
-rw-r--r--modules/gdscript/tests/scripts/parser/features/nested_dictionary.out5
-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/nested_if.gd57
-rw-r--r--modules/gdscript/tests/scripts/parser/features/nested_if.out21
-rw-r--r--modules/gdscript/tests/scripts/parser/features/nested_match.gd79
-rw-r--r--modules/gdscript/tests/scripts/parser/features/nested_match.out22
-rw-r--r--modules/gdscript/tests/scripts/parser/features/nested_parentheses.gd65
-rw-r--r--modules/gdscript/tests/scripts/parser/features/nested_parentheses.out4
-rw-r--r--modules/gdscript/tests/scripts/parser/features/number_separators.gd12
-rw-r--r--modules/gdscript/tests/scripts/parser/features/number_separators.out1
-rw-r--r--modules/gdscript/tests/scripts/parser/features/operator_assign.gd8
-rw-r--r--modules/gdscript/tests/scripts/parser/features/operator_assign.out2
-rw-r--r--modules/gdscript/tests/scripts/parser/features/property_setter_getter.gd37
-rw-r--r--modules/gdscript/tests/scripts/parser/features/property_setter_getter.out19
-rw-r--r--modules/gdscript/tests/scripts/parser/features/semicolon_as_end_statement.gd5
-rw-r--r--modules/gdscript/tests/scripts/parser/features/semicolon_as_end_statement.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/space_indentation.gd4
-rw-r--r--modules/gdscript/tests/scripts/parser/features/space_indentation.out1
-rw-r--r--modules/gdscript/tests/scripts/parser/features/static_typing.gd13
-rw-r--r--modules/gdscript/tests/scripts/parser/features/static_typing.out21
-rw-r--r--modules/gdscript/tests/scripts/parser/features/str_preserves_case.gd4
-rw-r--r--modules/gdscript/tests/scripts/parser/features/string_formatting.gd18
-rw-r--r--modules/gdscript/tests/scripts/parser/features/string_formatting.out11
-rw-r--r--modules/gdscript/tests/scripts/parser/features/super.gd60
-rw-r--r--modules/gdscript/tests/scripts/parser/features/super.out13
-rw-r--r--modules/gdscript/tests/scripts/parser/features/truthiness.gd30
-rw-r--r--modules/gdscript/tests/scripts/parser/features/truthiness.out65
-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/parser/features/variable_declaration.gd22
-rw-r--r--modules/gdscript/tests/scripts/parser/features/variable_declaration.out6
-rw-r--r--modules/gdscript/tests/scripts/parser/features/while.gd5
-rw-r--r--modules/gdscript/tests/scripts/parser/features/while.out6
-rw-r--r--modules/gdscript/tests/scripts/parser/warnings/assert_always_true.gd5
-rw-r--r--modules/gdscript/tests/scripts/parser/warnings/assert_always_true.out13
-rw-r--r--modules/gdscript/tests/scripts/parser/warnings/deprecated_operators.gd8
-rw-r--r--modules/gdscript/tests/scripts/parser/warnings/deprecated_operators.out1
-rw-r--r--modules/gdscript/tests/scripts/parser/warnings/empty_file.notest.gd1
-rw-r--r--modules/gdscript/tests/scripts/parser/warnings/empty_file.notest.out4
-rw-r--r--modules/gdscript/tests/scripts/parser/warnings/empty_file_comment.notest.gd1
-rw-r--r--modules/gdscript/tests/scripts/parser/warnings/empty_file_comment.notest.out4
-rw-r--r--modules/gdscript/tests/scripts/parser/warnings/empty_file_newline.notest.gd3
-rw-r--r--modules/gdscript/tests/scripts/parser/warnings/empty_file_newline.notest.out4
-rw-r--r--modules/gdscript/tests/scripts/parser/warnings/empty_file_newline_comment.notest.gd4
-rw-r--r--modules/gdscript/tests/scripts/parser/warnings/empty_file_newline_comment.notest.out4
-rw-r--r--modules/gdscript/tests/scripts/parser/warnings/incompatible_ternary.gd8
-rw-r--r--modules/gdscript/tests/scripts/parser/warnings/incompatible_ternary.out5
-rw-r--r--modules/gdscript/tests/scripts/parser/warnings/integer_division.gd10
-rw-r--r--modules/gdscript/tests/scripts/parser/warnings/integer_division.out5
-rw-r--r--modules/gdscript/tests/scripts/parser/warnings/match_default_not_at_end.gd9
-rw-r--r--modules/gdscript/tests/scripts/parser/warnings/match_default_not_at_end.out6
-rw-r--r--modules/gdscript/tests/scripts/parser/warnings/narrowing_conversion.gd5
-rw-r--r--modules/gdscript/tests/scripts/parser/warnings/narrowing_conversion.out5
-rw-r--r--modules/gdscript/tests/scripts/parser/warnings/return_value_discarded.gd6
-rw-r--r--modules/gdscript/tests/scripts/parser/warnings/return_value_discarded.out1
-rw-r--r--modules/gdscript/tests/scripts/parser/warnings/shadowed_constant.gd8
-rw-r--r--modules/gdscript/tests/scripts/parser/warnings/shadowed_constant.out9
-rw-r--r--modules/gdscript/tests/scripts/parser/warnings/shadowed_variable_class.gd8
-rw-r--r--modules/gdscript/tests/scripts/parser/warnings/shadowed_variable_class.out9
-rw-r--r--modules/gdscript/tests/scripts/parser/warnings/shadowed_variable_function.gd2
-rw-r--r--modules/gdscript/tests/scripts/parser/warnings/shadowed_variable_function.out9
-rw-r--r--modules/gdscript/tests/scripts/parser/warnings/standalone_expression.gd9
-rw-r--r--modules/gdscript/tests/scripts/parser/warnings/standalone_expression.out21
-rw-r--r--modules/gdscript/tests/scripts/parser/warnings/unassigned_variable.gd2
-rw-r--r--modules/gdscript/tests/scripts/parser/warnings/unassigned_variable.out5
-rw-r--r--modules/gdscript/tests/scripts/parser/warnings/unassigned_variable_op_assign.gd4
-rw-r--r--modules/gdscript/tests/scripts/parser/warnings/unassigned_variable_op_assign.out5
-rw-r--r--modules/gdscript/tests/scripts/parser/warnings/unreachable_code_after_return.gd7
-rw-r--r--modules/gdscript/tests/scripts/parser/warnings/unreachable_code_after_return.out5
-rw-r--r--modules/gdscript/tests/scripts/parser/warnings/unused_argument.gd12
-rw-r--r--modules/gdscript/tests/scripts/parser/warnings/unused_argument.out5
-rw-r--r--modules/gdscript/tests/scripts/parser/warnings/unused_variable.gd4
-rw-r--r--modules/gdscript/tests/scripts/parser/warnings/void_assignment.gd6
-rw-r--r--modules/gdscript/tests/scripts/parser/warnings/void_assignment.out5
-rw-r--r--modules/gdscript/tests/scripts/runtime/features/compare-builtin-equals-null.gd138
-rw-r--r--modules/gdscript/tests/scripts/runtime/features/compare-builtin-equals-null.out35
-rw-r--r--modules/gdscript/tests/scripts/runtime/features/compare-builtin-not-equals-null.gd138
-rw-r--r--modules/gdscript/tests/scripts/runtime/features/compare-builtin-not-equals-null.out35
-rw-r--r--modules/gdscript/tests/scripts/runtime/features/compare-null-equals-builtin.gd138
-rw-r--r--modules/gdscript/tests/scripts/runtime/features/compare-null-equals-builtin.out35
-rw-r--r--modules/gdscript/tests/scripts/runtime/features/compare-null-not-equals-builtin.gd138
-rw-r--r--modules/gdscript/tests/scripts/runtime/features/compare-null-not-equals-builtin.out35
-rw-r--r--modules/gdscript/tests/scripts/runtime/features/recursion.gd19
-rw-r--r--modules/gdscript/tests/scripts/runtime/features/recursion.out125
306 files changed, 3687 insertions, 223 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.cpp b/modules/gdscript/gdscript.cpp
index 4589cf1a36..bc8801b8b9 100644
--- a/modules/gdscript/gdscript.cpp
+++ b/modules/gdscript/gdscript.cpp
@@ -87,6 +87,19 @@ Object *GDScriptNativeClass::instantiate() {
return ClassDB::instantiate(name);
}
+GDScriptFunction *GDScript::_super_constructor(GDScript *p_script) {
+ if (p_script->initializer) {
+ return p_script->initializer;
+ } else {
+ GDScript *base = p_script->_base;
+ if (base != nullptr) {
+ return _super_constructor(base);
+ } else {
+ return nullptr;
+ }
+ }
+}
+
void GDScript::_super_implicit_constructor(GDScript *p_script, GDScriptInstance *p_instance, Callable::CallError &r_error) {
GDScript *base = p_script->_base;
if (base != nullptr) {
@@ -135,6 +148,8 @@ GDScriptInstance *GDScript::_create_instance(const Variant **p_args, int p_argco
if (p_argcount < 0) {
return instance;
}
+
+ initializer = _super_constructor(this);
if (initializer != nullptr) {
initializer->call(instance, p_args, p_argcount, r_error);
if (r_error.error != Callable::CallError::CALL_OK) {
@@ -625,9 +640,9 @@ bool GDScript::_update_exports(bool *r_err, bool p_recursive_call, PlaceHolderSc
String path = "";
if (String(c->extends_path) != "" && String(c->extends_path) != get_path()) {
path = c->extends_path;
- if (path.is_rel_path()) {
+ if (path.is_relative_path()) {
String base = get_path();
- if (base == "" || base.is_rel_path()) {
+ if (base == "" || base.is_relative_path()) {
ERR_PRINT(("Could not resolve relative path for parent class: " + path).utf8().get_data());
} else {
path = base.get_base_dir().plus_file(path);
@@ -900,7 +915,7 @@ void GDScript::get_members(Set<StringName> *p_members) {
}
}
-const Vector<MultiplayerAPI::RPCConfig> GDScript::get_rpc_methods() const {
+const Vector<Multiplayer::RPCConfig> GDScript::get_rpc_methods() const {
return rpc_functions;
}
@@ -1164,8 +1179,8 @@ void GDScript::_init_rpc_methods_properties() {
while (cscript) {
// RPC Methods
for (Map<StringName, GDScriptFunction *>::Element *E = cscript->member_functions.front(); E; E = E->next()) {
- MultiplayerAPI::RPCConfig config = E->get()->get_rpc_config();
- if (config.rpc_mode != MultiplayerAPI::RPC_MODE_DISABLED) {
+ Multiplayer::RPCConfig config = E->get()->get_rpc_config();
+ if (config.rpc_mode != Multiplayer::RPC_MODE_DISABLED) {
config.name = E->get()->get_name();
if (rpc_functions.find(config) == -1) {
rpc_functions.push_back(config);
@@ -1185,7 +1200,7 @@ void GDScript::_init_rpc_methods_properties() {
}
// Sort so we are 100% that they are always the same.
- rpc_functions.sort_custom<MultiplayerAPI::SortRPCConfig>();
+ rpc_functions.sort_custom<Multiplayer::SortRPCConfig>();
}
GDScript::~GDScript() {
@@ -1541,7 +1556,7 @@ ScriptLanguage *GDScriptInstance::get_language() {
return GDScriptLanguage::get_singleton();
}
-const Vector<MultiplayerAPI::RPCConfig> GDScriptInstance::get_rpc_methods() const {
+const Vector<Multiplayer::RPCConfig> GDScriptInstance::get_rpc_methods() const {
return script->get_rpc_methods();
}
@@ -2059,7 +2074,7 @@ String GDScriptLanguage::get_global_class_name(const String &p_path, String *r_b
if (r_icon_path) {
if (c->icon_path.is_empty() || c->icon_path.is_absolute_path()) {
*r_icon_path = c->icon_path;
- } else if (c->icon_path.is_rel_path()) {
+ } else if (c->icon_path.is_relative_path()) {
*r_icon_path = p_path.get_base_dir().plus_file(c->icon_path).simplify_path();
}
}
@@ -2087,7 +2102,7 @@ String GDScriptLanguage::get_global_class_name(const String &p_path, String *r_b
break;
}
String subpath = subclass->extends_path;
- if (subpath.is_rel_path()) {
+ if (subpath.is_relative_path()) {
subpath = path.get_base_dir().plus_file(subpath).simplify_path();
}
diff --git a/modules/gdscript/gdscript.h b/modules/gdscript/gdscript.h
index 24809ad5fd..791f8a1431 100644
--- a/modules/gdscript/gdscript.h
+++ b/modules/gdscript/gdscript.h
@@ -85,7 +85,7 @@ class GDScript : public Script {
Map<StringName, MemberInfo> member_indices; //members are just indices to the instantiated script.
Map<StringName, Ref<GDScript>> subclasses;
Map<StringName, Vector<StringName>> _signals;
- Vector<MultiplayerAPI::RPCConfig> rpc_functions;
+ Vector<Multiplayer::RPCConfig> rpc_functions;
#ifdef TOOLS_ENABLED
@@ -130,6 +130,7 @@ class GDScript : public Script {
SelfList<GDScriptFunctionState>::List pending_func_states;
+ GDScriptFunction *_super_constructor(GDScript *p_script);
void _super_implicit_constructor(GDScript *p_script, GDScriptInstance *p_instance, Callable::CallError &r_error);
GDScriptInstance *_create_instance(const Variant **p_args, int p_argcount, Object *p_owner, bool p_is_ref_counted, Callable::CallError &r_error);
@@ -245,7 +246,7 @@ public:
virtual void get_constants(Map<StringName, Variant> *p_constants) override;
virtual void get_members(Set<StringName> *p_members) override;
- virtual const Vector<MultiplayerAPI::RPCConfig> get_rpc_methods() const override;
+ virtual const Vector<Multiplayer::RPCConfig> get_rpc_methods() const override;
#ifdef TOOLS_ENABLED
virtual bool is_placeholder_fallback_enabled() const override { return placeholder_fallback_enabled; }
@@ -298,7 +299,7 @@ public:
void reload_members();
- virtual const Vector<MultiplayerAPI::RPCConfig> get_rpc_methods() const;
+ virtual const Vector<Multiplayer::RPCConfig> get_rpc_methods() const;
GDScriptInstance();
~GDScriptInstance();
diff --git a/modules/gdscript/gdscript_analyzer.cpp b/modules/gdscript/gdscript_analyzer.cpp
index 6b7403d854..23e88ae059 100644
--- a/modules/gdscript/gdscript_analyzer.cpp
+++ b/modules/gdscript/gdscript_analyzer.cpp
@@ -132,6 +132,76 @@ static GDScriptParser::DataType make_builtin_meta_type(Variant::Type p_type) {
return type;
}
+bool GDScriptAnalyzer::has_member_name_conflict_in_script_class(const StringName &p_member_name, const GDScriptParser::ClassNode *p_class) {
+ if (p_class->members_indices.has(p_member_name)) {
+ int index = p_class->members_indices[p_member_name];
+ const GDScriptParser::ClassNode::Member *member = &p_class->members[index];
+
+ if (member->type == GDScriptParser::ClassNode::Member::VARIABLE ||
+ member->type == GDScriptParser::ClassNode::Member::CONSTANT ||
+ member->type == GDScriptParser::ClassNode::Member::ENUM ||
+ member->type == GDScriptParser::ClassNode::Member::ENUM_VALUE ||
+ member->type == GDScriptParser::ClassNode::Member::CLASS ||
+ member->type == GDScriptParser::ClassNode::Member::SIGNAL) {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+bool GDScriptAnalyzer::has_member_name_conflict_in_native_type(const StringName &p_member_name, const StringName &p_native_type_string) {
+ if (ClassDB::has_signal(p_native_type_string, p_member_name)) {
+ return true;
+ }
+ if (ClassDB::has_property(p_native_type_string, p_member_name)) {
+ return true;
+ }
+ if (ClassDB::has_integer_constant(p_native_type_string, p_member_name)) {
+ return true;
+ }
+
+ return false;
+}
+
+Error GDScriptAnalyzer::check_native_member_name_conflict(const StringName &p_member_name, const GDScriptParser::Node *p_member_node, const StringName &p_native_type_string) {
+ if (has_member_name_conflict_in_native_type(p_member_name, p_native_type_string)) {
+ push_error(vformat(R"(Member "%s" redefined (original in native class '%s'))", p_member_name, p_native_type_string), p_member_node);
+ return ERR_PARSE_ERROR;
+ }
+
+ if (class_exists(p_member_name)) {
+ push_error(vformat(R"(The member "%s" shadows a native class.)", p_member_name), p_member_node);
+ return ERR_PARSE_ERROR;
+ }
+
+ return OK;
+}
+
+Error GDScriptAnalyzer::check_class_member_name_conflict(const GDScriptParser::ClassNode *p_class_node, const StringName &p_member_name, const GDScriptParser::Node *p_member_node) {
+ const GDScriptParser::DataType *current_data_type = &p_class_node->base_type;
+ while (current_data_type && current_data_type->kind == GDScriptParser::DataType::Kind::CLASS) {
+ GDScriptParser::ClassNode *current_class_node = current_data_type->class_type;
+ if (has_member_name_conflict_in_script_class(p_member_name, current_class_node)) {
+ push_error(vformat(R"(The member "%s" already exists in a parent class.)", p_member_name),
+ p_member_node);
+ return ERR_PARSE_ERROR;
+ }
+ current_data_type = &current_class_node->base_type;
+ }
+
+ if (current_data_type && current_data_type->kind == GDScriptParser::DataType::Kind::NATIVE) {
+ if (current_data_type->native_type != StringName("")) {
+ return check_native_member_name_conflict(
+ p_member_name,
+ p_member_node,
+ current_data_type->native_type);
+ }
+ }
+
+ return OK;
+}
+
Error GDScriptAnalyzer::resolve_inheritance(GDScriptParser::ClassNode *p_class, bool p_recursive) {
if (p_class->base_type.is_set()) {
// Already resolved
@@ -148,6 +218,17 @@ Error GDScriptAnalyzer::resolve_inheritance(GDScriptParser::ClassNode *p_class,
p_class->fqcn = p_class->outer->fqcn + "::" + String(p_class->identifier->name);
}
+ if (p_class->identifier) {
+ StringName class_name = p_class->identifier->name;
+ if (class_exists(class_name)) {
+ push_error(vformat(R"(Class "%s" hides a native class.)", class_name), p_class->identifier);
+ } else if (ScriptServer::is_global_class(class_name) && (ScriptServer::get_global_class_path(class_name) != parser->script_path || p_class != parser->head)) {
+ push_error(vformat(R"(Class "%s" hides a global script class.)", class_name), p_class->identifier);
+ } else if (ProjectSettings::get_singleton()->has_autoload(class_name) && ProjectSettings::get_singleton()->get_autoload(class_name).is_singleton) {
+ push_error(vformat(R"(Class "%s" hides an autoload singleton.)", class_name), p_class->identifier);
+ }
+ }
+
GDScriptParser::DataType result;
// Set datatype for class.
@@ -172,6 +253,9 @@ Error GDScriptAnalyzer::resolve_inheritance(GDScriptParser::ClassNode *p_class,
int extends_index = 0;
if (!p_class->extends_path.is_empty()) {
+ if (p_class->extends_path.is_relative_path()) {
+ p_class->extends_path = class_type.script_path.get_base_dir().plus_file(p_class->extends_path).simplify_path();
+ }
Ref<GDScriptParserRef> parser = get_parser_for(p_class->extends_path);
if (parser.is_null()) {
push_error(vformat(R"(Could not resolve super class path "%s".)", p_class->extends_path), p_class);
@@ -525,6 +609,8 @@ void GDScriptAnalyzer::resolve_class_interface(GDScriptParser::ClassNode *p_clas
switch (member.type) {
case GDScriptParser::ClassNode::Member::VARIABLE: {
+ check_class_member_name_conflict(p_class, member.variable->identifier->name, member.variable);
+
GDScriptParser::DataType datatype;
datatype.kind = GDScriptParser::DataType::VARIANT;
datatype.type_source = GDScriptParser::DataType::UNDETECTED;
@@ -598,6 +684,8 @@ void GDScriptAnalyzer::resolve_class_interface(GDScriptParser::ClassNode *p_clas
}
} break;
case GDScriptParser::ClassNode::Member::CONSTANT: {
+ check_class_member_name_conflict(p_class, member.constant->identifier->name, member.constant);
+
reduce_expression(member.constant->initializer);
GDScriptParser::DataType specified_type;
@@ -607,8 +695,9 @@ void GDScriptAnalyzer::resolve_class_interface(GDScriptParser::ClassNode *p_clas
specified_type.is_meta_type = false;
}
- GDScriptParser::DataType datatype = member.constant->get_datatype();
+ GDScriptParser::DataType datatype;
if (member.constant->initializer) {
+ datatype = member.constant->initializer->get_datatype();
if (member.constant->initializer->type == GDScriptParser::Node::ARRAY) {
GDScriptParser::ArrayNode *array = static_cast<GDScriptParser::ArrayNode *>(member.constant->initializer);
const_fold_array(array);
@@ -647,6 +736,8 @@ void GDScriptAnalyzer::resolve_class_interface(GDScriptParser::ClassNode *p_clas
}
} break;
case GDScriptParser::ClassNode::Member::SIGNAL: {
+ check_class_member_name_conflict(p_class, member.signal->identifier->name, member.signal);
+
for (int j = 0; j < member.signal->parameters.size(); j++) {
GDScriptParser::DataType signal_type = resolve_datatype(member.signal->parameters[j]->datatype_specifier);
signal_type.is_meta_type = false;
@@ -666,6 +757,8 @@ void GDScriptAnalyzer::resolve_class_interface(GDScriptParser::ClassNode *p_clas
}
} break;
case GDScriptParser::ClassNode::Member::ENUM: {
+ check_class_member_name_conflict(p_class, member.m_enum->identifier->name, member.m_enum);
+
GDScriptParser::DataType enum_type;
enum_type.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT;
enum_type.kind = GDScriptParser::DataType::ENUM;
@@ -717,6 +810,8 @@ void GDScriptAnalyzer::resolve_class_interface(GDScriptParser::ClassNode *p_clas
break;
case GDScriptParser::ClassNode::Member::ENUM_VALUE: {
if (member.enum_value.custom_value) {
+ check_class_member_name_conflict(p_class, member.enum_value.identifier->name, member.enum_value.custom_value);
+
current_enum = member.enum_value.parent_enum;
reduce_expression(member.enum_value.custom_value);
current_enum = nullptr;
@@ -730,6 +825,8 @@ void GDScriptAnalyzer::resolve_class_interface(GDScriptParser::ClassNode *p_clas
member.enum_value.resolved = true;
}
} else {
+ check_class_member_name_conflict(p_class, member.enum_value.identifier->name, member.enum_value.parent_enum);
+
if (member.enum_value.index > 0) {
member.enum_value.value = member.enum_value.parent_enum->values[member.enum_value.index - 1].value + 1;
} else {
@@ -742,7 +839,8 @@ void GDScriptAnalyzer::resolve_class_interface(GDScriptParser::ClassNode *p_clas
p_class->members.write[i].enum_value = member.enum_value;
} break;
case GDScriptParser::ClassNode::Member::CLASS:
- break; // Done later.
+ check_class_member_name_conflict(p_class, member.m_class->identifier->name, member.m_class);
+ break;
case GDScriptParser::ClassNode::Member::UNDEFINED:
ERR_PRINT("Trying to resolve undefined member.");
break;
@@ -1089,12 +1187,28 @@ void GDScriptAnalyzer::resolve_for(GDScriptParser::ForNode *p_for) {
}
}
- if (!list_resolved) {
+ GDScriptParser::DataType variable_type;
+ if (list_resolved) {
+ variable_type.type_source = GDScriptParser::DataType::ANNOTATED_INFERRED;
+ variable_type.kind = GDScriptParser::DataType::BUILTIN;
+ variable_type.builtin_type = Variant::INT; // Can this ever be a float or something else?
+ p_for->variable->set_datatype(variable_type);
+ } else {
resolve_node(p_for->list);
+ if (p_for->list->datatype.has_container_element_type()) {
+ variable_type = p_for->list->datatype.get_container_element_type();
+ variable_type.type_source = GDScriptParser::DataType::ANNOTATED_INFERRED;
+ } else if (p_for->list->datatype.is_typed_container_type()) {
+ variable_type = p_for->list->datatype.get_typed_container_type();
+ variable_type.type_source = GDScriptParser::DataType::ANNOTATED_INFERRED;
+ } else {
+ // Last resort
+ // TODO: Must other cases be handled? Must we mark as unsafe?
+ variable_type.type_source = GDScriptParser::DataType::UNDETECTED;
+ variable_type.kind = GDScriptParser::DataType::VARIANT;
+ }
}
-
- // TODO: If list is a typed array, the variable should be an element.
- // Also applicable for constant range() (so variable is int or float).
+ p_for->variable->set_datatype(variable_type);
resolve_suite(p_for->loop);
p_for->set_datatype(p_for->loop->get_datatype());
@@ -1382,8 +1496,7 @@ void GDScriptAnalyzer::resolve_parameter(GDScriptParser::ParameterNode *p_parame
}
if (p_parameter->datatype_specifier != nullptr) {
- resolve_datatype(p_parameter->datatype_specifier);
- result = p_parameter->datatype_specifier->get_datatype();
+ result = resolve_datatype(p_parameter->datatype_specifier);
result.is_meta_type = false;
if (p_parameter->default_value != nullptr) {
@@ -1395,6 +1508,10 @@ void GDScriptAnalyzer::resolve_parameter(GDScriptParser::ParameterNode *p_parame
}
}
+ if (result.builtin_type == Variant::Type::NIL && result.type_source == GDScriptParser::DataType::ANNOTATED_INFERRED && p_parameter->datatype_specifier == nullptr) {
+ push_error(vformat(R"(Could not infer the type of the variable "%s" because the initial value is "null".)", p_parameter->identifier->name), p_parameter->default_value);
+ }
+
p_parameter->set_datatype(result);
}
@@ -1637,7 +1754,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 {
@@ -1665,6 +1781,15 @@ void GDScriptAnalyzer::reduce_assignment(GDScriptParser::AssignmentNode *p_assig
identifier->variable_source->set_datatype(id_type);
}
} break;
+ case GDScriptParser::IdentifierNode::FUNCTION_PARAMETER: {
+ GDScriptParser::DataType id_type = identifier->parameter_source->get_datatype();
+ if (!id_type.is_hard_type()) {
+ id_type = assigned_value_type;
+ id_type.type_source = GDScriptParser::DataType::INFERRED;
+ id_type.is_constant = false;
+ identifier->parameter_source->set_datatype(id_type);
+ }
+ } break;
case GDScriptParser::IdentifierNode::LOCAL_VARIABLE: {
GDScriptParser::DataType id_type = identifier->variable_source->get_datatype();
if (!id_type.is_hard_type()) {
@@ -2326,13 +2451,15 @@ void GDScriptAnalyzer::reduce_identifier_from_base(GDScriptParser::IdentifierNod
p_identifier->is_constant = true;
p_identifier->reduced_value = result;
p_identifier->set_datatype(type_from_variant(result, p_identifier));
- } else {
+ } else if (base.is_hard_type()) {
push_error(vformat(R"(Cannot find constant "%s" on type "%s".)", name, base.to_string()), p_identifier);
}
} else {
switch (base.builtin_type) {
case Variant::NIL: {
- push_error(vformat(R"(Invalid get index "%s" on base Nil)", name), p_identifier);
+ if (base.is_hard_type()) {
+ push_error(vformat(R"(Invalid get index "%s" on base Nil)", name), p_identifier);
+ }
return;
}
case Variant::DICTIONARY: {
@@ -2353,7 +2480,9 @@ void GDScriptAnalyzer::reduce_identifier_from_base(GDScriptParser::IdentifierNod
return;
}
}
- push_error(vformat(R"(Cannot find property "%s" on base "%s".)", name, base.to_string()), p_identifier);
+ if (base.is_hard_type()) {
+ push_error(vformat(R"(Cannot find property "%s" on base "%s".)", name, base.to_string()), p_identifier);
+ }
}
}
}
@@ -2411,6 +2540,7 @@ void GDScriptAnalyzer::reduce_identifier_from_base(GDScriptParser::IdentifierNod
case GDScriptParser::ClassNode::Member::VARIABLE:
p_identifier->source = GDScriptParser::IdentifierNode::MEMBER_VARIABLE;
p_identifier->variable_source = member.variable;
+ member.variable->usages += 1;
break;
case GDScriptParser::ClassNode::Member::FUNCTION:
resolve_function_signature(member.function);
@@ -2432,14 +2562,29 @@ void GDScriptAnalyzer::reduce_identifier_from_base(GDScriptParser::IdentifierNod
while (outer != nullptr) {
if (outer->has_member(name)) {
const GDScriptParser::ClassNode::Member &member = outer->get_member(name);
- if (member.type == GDScriptParser::ClassNode::Member::CONSTANT) {
- // TODO: Make sure loops won't cause problem. And make special error message for those.
- // For out-of-order resolution:
- reduce_expression(member.constant->initializer);
- p_identifier->set_datatype(member.get_datatype());
- p_identifier->is_constant = true;
- p_identifier->reduced_value = member.constant->initializer->reduced_value;
- return;
+ switch (member.type) {
+ case GDScriptParser::ClassNode::Member::CONSTANT: {
+ // TODO: Make sure loops won't cause problem. And make special error message for those.
+ // For out-of-order resolution:
+ reduce_expression(member.constant->initializer);
+ p_identifier->set_datatype(member.get_datatype());
+ p_identifier->is_constant = true;
+ p_identifier->reduced_value = member.constant->initializer->reduced_value;
+ return;
+ } break;
+ case GDScriptParser::ClassNode::Member::ENUM_VALUE: {
+ p_identifier->set_datatype(member.get_datatype());
+ p_identifier->is_constant = true;
+ p_identifier->reduced_value = member.enum_value.value;
+ return;
+ } break;
+ case GDScriptParser::ClassNode::Member::ENUM: {
+ p_identifier->set_datatype(member.get_datatype());
+ p_identifier->is_constant = false;
+ return;
+ } break;
+ default:
+ break;
}
}
outer = outer->outer;
@@ -2727,7 +2872,7 @@ void GDScriptAnalyzer::reduce_preload(GDScriptParser::PreloadNode *p_preload) {
} else {
p_preload->resolved_path = p_preload->path->reduced_value;
// TODO: Save this as script dependency.
- if (p_preload->resolved_path.is_rel_path()) {
+ if (p_preload->resolved_path.is_relative_path()) {
p_preload->resolved_path = parser->script_path.get_base_dir().plus_file(p_preload->resolved_path);
}
p_preload->resolved_path = p_preload->resolved_path.simplify_path();
@@ -2753,6 +2898,9 @@ void GDScriptAnalyzer::reduce_self(GDScriptParser::SelfNode *p_self) {
}
void GDScriptAnalyzer::reduce_subscript(GDScriptParser::SubscriptNode *p_subscript) {
+ if (p_subscript->base == nullptr) {
+ return;
+ }
if (p_subscript->base->type == GDScriptParser::Node::IDENTIFIER) {
reduce_identifier(static_cast<GDScriptParser::IdentifierNode *>(p_subscript->base), true);
} else {
diff --git a/modules/gdscript/gdscript_analyzer.h b/modules/gdscript/gdscript_analyzer.h
index 32bf049fa1..2e17e15452 100644
--- a/modules/gdscript/gdscript_analyzer.h
+++ b/modules/gdscript/gdscript_analyzer.h
@@ -44,6 +44,12 @@ class GDScriptAnalyzer {
const GDScriptParser::EnumNode *current_enum = nullptr;
List<const GDScriptParser::LambdaNode *> lambda_stack;
+ // Tests for detecting invalid overloading of script members
+ static _FORCE_INLINE_ bool has_member_name_conflict_in_script_class(const StringName &p_name, const GDScriptParser::ClassNode *p_current_class_node);
+ static _FORCE_INLINE_ bool has_member_name_conflict_in_native_type(const StringName &p_name, const StringName &p_native_type_string);
+ Error check_native_member_name_conflict(const StringName &p_member_name, const GDScriptParser::Node *p_member_node, const StringName &p_native_type_string);
+ Error check_class_member_name_conflict(const GDScriptParser::ClassNode *p_class_node, const StringName &p_member_name, const GDScriptParser::Node *p_member_node);
+
Error resolve_inheritance(GDScriptParser::ClassNode *p_class, bool p_recursive = true);
GDScriptParser::DataType resolve_datatype(GDScriptParser::TypeNode *p_type);
diff --git a/modules/gdscript/gdscript_byte_codegen.cpp b/modules/gdscript/gdscript_byte_codegen.cpp
index 5958326315..1127488db8 100644
--- a/modules/gdscript/gdscript_byte_codegen.cpp
+++ b/modules/gdscript/gdscript_byte_codegen.cpp
@@ -155,7 +155,7 @@ void GDScriptByteCodeGenerator::end_parameters() {
function->default_arguments.reverse();
}
-void GDScriptByteCodeGenerator::write_start(GDScript *p_script, const StringName &p_function_name, bool p_static, MultiplayerAPI::RPCConfig p_rpc_config, const GDScriptDataType &p_return_type) {
+void GDScriptByteCodeGenerator::write_start(GDScript *p_script, const StringName &p_function_name, bool p_static, Multiplayer::RPCConfig p_rpc_config, const GDScriptDataType &p_return_type) {
function = memnew(GDScriptFunction);
debug_stack = EngineDebugger::is_active();
@@ -864,6 +864,12 @@ void GDScriptByteCodeGenerator::write_assign_default_parameter(const Address &p_
function->default_arguments.push_back(opcodes.size());
}
+void GDScriptByteCodeGenerator::write_store_global(const Address &p_dst, int p_global_index) {
+ append(GDScriptFunction::OPCODE_STORE_GLOBAL, 1);
+ append(p_dst);
+ append(p_global_index);
+}
+
void GDScriptByteCodeGenerator::write_store_named_global(const Address &p_dst, const StringName &p_global) {
append(GDScriptFunction::OPCODE_STORE_NAMED_GLOBAL, 1);
append(p_dst);
diff --git a/modules/gdscript/gdscript_byte_codegen.h b/modules/gdscript/gdscript_byte_codegen.h
index 3d6fb291ad..dcc11ebdce 100644
--- a/modules/gdscript/gdscript_byte_codegen.h
+++ b/modules/gdscript/gdscript_byte_codegen.h
@@ -419,7 +419,7 @@ public:
virtual void start_block() override;
virtual void end_block() override;
- virtual void write_start(GDScript *p_script, const StringName &p_function_name, bool p_static, MultiplayerAPI::RPCConfig p_rpc_config, const GDScriptDataType &p_return_type) override;
+ virtual void write_start(GDScript *p_script, const StringName &p_function_name, bool p_static, Multiplayer::RPCConfig p_rpc_config, const GDScriptDataType &p_return_type) override;
virtual GDScriptFunction *write_end() override;
#ifdef DEBUG_ENABLED
@@ -454,6 +454,7 @@ public:
virtual void write_assign_true(const Address &p_target) override;
virtual void write_assign_false(const Address &p_target) override;
virtual void write_assign_default_parameter(const Address &p_dst, const Address &p_src) override;
+ virtual void write_store_global(const Address &p_dst, int p_global_index) override;
virtual void write_store_named_global(const Address &p_dst, const StringName &p_global) override;
virtual void write_cast(const Address &p_target, const Address &p_source, const GDScriptDataType &p_type) override;
virtual void write_call(const Address &p_target, const Address &p_base, const StringName &p_function_name, const Vector<Address> &p_arguments) override;
diff --git a/modules/gdscript/gdscript_cache.cpp b/modules/gdscript/gdscript_cache.cpp
index 07f50d14dc..8121053245 100644
--- a/modules/gdscript/gdscript_cache.cpp
+++ b/modules/gdscript/gdscript_cache.cpp
@@ -51,7 +51,9 @@ GDScriptParser *GDScriptParserRef::get_parser() const {
Error GDScriptParserRef::raise_status(Status p_new_status) {
ERR_FAIL_COND_V(parser == nullptr, ERR_INVALID_DATA);
- Error result = OK;
+ if (result != OK) {
+ return result;
+ }
while (p_new_status > status) {
switch (status) {
@@ -86,14 +88,6 @@ Error GDScriptParserRef::raise_status(Status p_new_status) {
}
}
if (result != OK) {
- if (parser != nullptr) {
- memdelete(parser);
- parser = nullptr;
- }
- if (analyzer != nullptr) {
- memdelete(analyzer);
- analyzer = nullptr;
- }
return result;
}
}
diff --git a/modules/gdscript/gdscript_cache.h b/modules/gdscript/gdscript_cache.h
index 943638d29f..9fb661d031 100644
--- a/modules/gdscript/gdscript_cache.h
+++ b/modules/gdscript/gdscript_cache.h
@@ -54,6 +54,7 @@ private:
GDScriptParser *parser = nullptr;
GDScriptAnalyzer *analyzer = nullptr;
Status status = EMPTY;
+ Error result = OK;
String path;
friend class GDScriptCache;
diff --git a/modules/gdscript/gdscript_codegen.h b/modules/gdscript/gdscript_codegen.h
index ecc86c37f3..e6ecc92d55 100644
--- a/modules/gdscript/gdscript_codegen.h
+++ b/modules/gdscript/gdscript_codegen.h
@@ -31,7 +31,7 @@
#ifndef GDSCRIPT_CODEGEN
#define GDSCRIPT_CODEGEN
-#include "core/io/multiplayer_api.h"
+#include "core/multiplayer/multiplayer.h"
#include "core/string/string_name.h"
#include "core/variant/variant.h"
#include "gdscript_function.h"
@@ -80,7 +80,7 @@ public:
virtual void start_block() = 0;
virtual void end_block() = 0;
- virtual void write_start(GDScript *p_script, const StringName &p_function_name, bool p_static, MultiplayerAPI::RPCConfig p_rpc_config, const GDScriptDataType &p_return_type) = 0;
+ virtual void write_start(GDScript *p_script, const StringName &p_function_name, bool p_static, Multiplayer::RPCConfig p_rpc_config, const GDScriptDataType &p_return_type) = 0;
virtual GDScriptFunction *write_end() = 0;
#ifdef DEBUG_ENABLED
@@ -115,6 +115,7 @@ public:
virtual void write_assign_true(const Address &p_target) = 0;
virtual void write_assign_false(const Address &p_target) = 0;
virtual void write_assign_default_parameter(const Address &dst, const Address &src) = 0;
+ virtual void write_store_global(const Address &p_dst, int p_global_index) = 0;
virtual void write_store_named_global(const Address &p_dst, const StringName &p_global) = 0;
virtual void write_cast(const Address &p_target, const Address &p_source, const GDScriptDataType &p_type) = 0;
virtual void write_call(const Address &p_target, const Address &p_base, const StringName &p_function_name, const Vector<Address> &p_arguments) = 0;
diff --git a/modules/gdscript/gdscript_compiler.cpp b/modules/gdscript/gdscript_compiler.cpp
index ddf4f281b4..a8aef84db3 100644
--- a/modules/gdscript/gdscript_compiler.cpp
+++ b/modules/gdscript/gdscript_compiler.cpp
@@ -35,6 +35,8 @@
#include "gdscript_cache.h"
#include "gdscript_utility_functions.h"
+#include "core/config/project_settings.h"
+
bool GDScriptCompiler::_is_class_member_property(CodeGen &codegen, const StringName &p_name) {
if (codegen.function_node && codegen.function_node->is_static) {
return false;
@@ -316,10 +318,21 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &code
}
}
+ // Try globals.
if (GDScriptLanguage::get_singleton()->get_global_map().has(identifier)) {
- int idx = GDScriptLanguage::get_singleton()->get_global_map()[identifier];
- Variant global = GDScriptLanguage::get_singleton()->get_global_array()[idx];
- return codegen.add_constant(global); // TODO: Get type.
+ // If it's an autoload singleton, we postpone to load it at runtime.
+ // This is so one autoload doesn't try to load another before it's compiled.
+ OrderedHashMap<StringName, ProjectSettings::AutoloadInfo> autoloads = ProjectSettings::get_singleton()->get_autoload_list();
+ if (autoloads.has(identifier) && autoloads[identifier].is_singleton) {
+ GDScriptCodeGenerator::Address global = codegen.add_temporary(_gdtype_from_datatype(in->get_datatype()));
+ int idx = GDScriptLanguage::get_singleton()->get_global_map()[identifier];
+ gen->write_store_global(global, idx);
+ return global;
+ } else {
+ int idx = GDScriptLanguage::get_singleton()->get_global_map()[identifier];
+ Variant global = GDScriptLanguage::get_singleton()->get_global_array()[idx];
+ return codegen.add_constant(global);
+ }
}
// Try global classes.
@@ -427,7 +440,7 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &code
break;
case GDScriptParser::DictionaryNode::LUA_TABLE:
// Lua-style: key is an identifier interpreted as StringName.
- StringName key = static_cast<const GDScriptParser::IdentifierNode *>(dn->elements[i].key)->name;
+ StringName key = dn->elements[i].key->reduced_value.operator StringName();
element = codegen.add_constant(key);
break;
}
@@ -951,7 +964,7 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &code
// Perform operator if any.
if (assignment->operation != GDScriptParser::AssignmentNode::OP_NONE) {
- GDScriptCodeGenerator::Address value = codegen.add_temporary();
+ 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 {
@@ -1857,7 +1870,7 @@ GDScriptFunction *GDScriptCompiler::_parse_function(Error &r_error, GDScript *p_
StringName func_name;
bool is_static = false;
- MultiplayerAPI::RPCConfig rpc_config;
+ Multiplayer::RPCConfig rpc_config;
GDScriptDataType return_type;
return_type.has_type = true;
return_type.kind = GDScriptDataType::BUILTIN;
@@ -2086,7 +2099,7 @@ Error GDScriptCompiler::_parse_setter_getter(GDScript *p_script, const GDScriptP
return_type = _gdtype_from_datatype(p_variable->get_datatype(), p_script);
}
- codegen.generator->write_start(p_script, func_name, false, MultiplayerAPI::RPCConfig(), return_type);
+ codegen.generator->write_start(p_script, func_name, false, Multiplayer::RPCConfig(), return_type);
if (p_is_setter) {
uint32_t par_addr = codegen.generator->add_parameter(p_variable->setter_parameter->name, false, _gdtype_from_datatype(p_variable->get_datatype()));
@@ -2186,6 +2199,13 @@ Error GDScriptCompiler::_parse_class_level(GDScript *p_script, const GDScriptPar
p_script->tool = parser->is_tool();
p_script->name = p_class->identifier ? p_class->identifier->name : "";
+ if (p_script->name != "") {
+ if (ClassDB::class_exists(p_script->name) && ClassDB::is_class_exposed(p_script->name)) {
+ _set_error("The class '" + p_script->name + "' shadows a native class", p_class);
+ return ERR_ALREADY_EXISTS;
+ }
+ }
+
Ref<GDScriptNativeClass> native;
GDScriptDataType base_type = _gdtype_from_datatype(p_class->base_type);
@@ -2337,28 +2357,6 @@ Error GDScriptCompiler::_parse_class_level(GDScript *p_script, const GDScriptPar
const GDScriptParser::SignalNode *signal = member.signal;
StringName name = signal->identifier->name;
- GDScript *c = p_script;
-
- while (c) {
- if (c->_signals.has(name)) {
- _set_error("Signal '" + name + "' redefined (in current or parent class)", p_class);
- return ERR_ALREADY_EXISTS;
- }
-
- if (c->base.is_valid()) {
- c = c->base.ptr();
- } else {
- c = nullptr;
- }
- }
-
- if (native.is_valid()) {
- if (ClassDB::has_signal(native->get_name(), name)) {
- _set_error("Signal '" + name + "' redefined (original in native class '" + String(native->get_name()) + "')", p_class);
- return ERR_ALREADY_EXISTS;
- }
- }
-
Vector<StringName> parameters_names;
parameters_names.resize(signal->parameters.size());
for (int j = 0; j < signal->parameters.size(); j++) {
diff --git a/modules/gdscript/gdscript_disassembler.cpp b/modules/gdscript/gdscript_disassembler.cpp
index 1acb9ceddc..9287df2ea0 100644
--- a/modules/gdscript/gdscript_disassembler.cpp
+++ b/modules/gdscript/gdscript_disassembler.cpp
@@ -914,6 +914,14 @@ void GDScriptFunction::disassemble(const Vector<String> &p_code_lines) const {
incr += 5;
} break;
DISASSEMBLE_ITERATE_TYPES(DISASSEMBLE_ITERATE);
+ case OPCODE_STORE_GLOBAL: {
+ text += "store global ";
+ text += DADDR(1);
+ text += " = ";
+ text += String::num_int64(_code_ptr[ip + 2]);
+
+ incr += 3;
+ } break;
case OPCODE_STORE_NAMED_GLOBAL: {
text += "store named global ";
text += DADDR(1);
diff --git a/modules/gdscript/gdscript_editor.cpp b/modules/gdscript/gdscript_editor.cpp
index 70e18c6e6c..2f8a054b2a 100644
--- a/modules/gdscript/gdscript_editor.cpp
+++ b/modules/gdscript/gdscript_editor.cpp
@@ -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;
@@ -1733,7 +1736,7 @@ static bool _guess_identifier_type(GDScriptParser::CompletionContext &p_context,
}
}
- if (is_function_parameter && p_context.current_function && p_context.current_class) {
+ if (is_function_parameter && p_context.current_function && p_context.current_function->source_lambda == nullptr && p_context.current_class) {
// Check if it's override of native function, then we can assume the type from the signature.
GDScriptParser::DataType base_type = p_context.current_class->base_type;
while (base_type.is_set()) {
@@ -3078,6 +3081,15 @@ static Error _lookup_symbol_from_base(const GDScriptParser::DataType &p_base, co
r_result.class_member = p_symbol;
return OK;
}
+ } else {
+ List<StringName> utility_functions;
+ Variant::get_utility_function_list(&utility_functions);
+ if (utility_functions.find(p_symbol) != nullptr) {
+ r_result.type = ScriptLanguage::LookupResult::RESULT_CLASS_TBD_GLOBALSCOPE;
+ r_result.class_name = "@GlobalScope";
+ r_result.class_member = p_symbol;
+ return OK;
+ }
}
}
} break;
diff --git a/modules/gdscript/gdscript_function.h b/modules/gdscript/gdscript_function.h
index 87d8c03494..9d076a8e4c 100644
--- a/modules/gdscript/gdscript_function.h
+++ b/modules/gdscript/gdscript_function.h
@@ -348,6 +348,7 @@ public:
OPCODE_ITERATE_PACKED_VECTOR3_ARRAY,
OPCODE_ITERATE_PACKED_COLOR_ARRAY,
OPCODE_ITERATE_OBJECT,
+ OPCODE_STORE_GLOBAL,
OPCODE_STORE_NAMED_GLOBAL,
OPCODE_TYPE_ADJUST_BOOL,
OPCODE_TYPE_ADJUST_INT,
@@ -468,7 +469,7 @@ private:
int _initial_line = 0;
bool _static = false;
- MultiplayerAPI::RPCConfig rpc_config;
+ Multiplayer::RPCConfig rpc_config;
GDScript *_script = nullptr;
@@ -588,7 +589,7 @@ public:
void disassemble(const Vector<String> &p_code_lines) const;
#endif
- _FORCE_INLINE_ MultiplayerAPI::RPCConfig get_rpc_config() const { return rpc_config; }
+ _FORCE_INLINE_ Multiplayer::RPCConfig get_rpc_config() const { return rpc_config; }
GDScriptFunction();
~GDScriptFunction();
};
diff --git a/modules/gdscript/gdscript_parser.cpp b/modules/gdscript/gdscript_parser.cpp
index 029e30080b..025accf4ba 100644
--- a/modules/gdscript/gdscript_parser.cpp
+++ b/modules/gdscript/gdscript_parser.cpp
@@ -133,7 +133,7 @@ GDScriptParser::GDScriptParser() {
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>);
// Networking.
- register_annotation(MethodInfo("@rpc", { Variant::STRING, "mode" }, { Variant::STRING, "sync" }, { Variant::STRING, "transfer_mode" }, { Variant::INT, "transfer_channel" }), AnnotationInfo::FUNCTION, &GDScriptParser::network_annotations<MultiplayerAPI::RPC_MODE_PUPPET>, 4, true);
+ 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.
}
@@ -337,12 +337,29 @@ Error GDScriptParser::parse(const String &p_source_code, const String &p_script_
tokenizer.set_cursor_position(cursor_line, cursor_column);
script_path = p_script_path;
current = tokenizer.scan();
- // Avoid error as the first token.
- while (current.type == GDScriptTokenizer::Token::ERROR) {
- push_error(current.literal);
+ // Avoid error or newline as the first token.
+ // The latter can mess with the parser when opening files filled exclusively with comments and newlines.
+ while (current.type == GDScriptTokenizer::Token::ERROR || current.type == GDScriptTokenizer::Token::NEWLINE) {
+ if (current.type == GDScriptTokenizer::Token::ERROR) {
+ push_error(current.literal);
+ }
current = tokenizer.scan();
}
+#ifdef DEBUG_ENABLED
+ // Warn about parsing an empty script file:
+ if (current.type == GDScriptTokenizer::Token::TK_EOF) {
+ // Create a dummy Node for the warning, pointing to the very beginning of the file
+ Node *nd = alloc_node<PassNode>();
+ nd->start_line = 1;
+ nd->start_column = 0;
+ nd->end_line = 1;
+ nd->leftmost_column = 0;
+ nd->rightmost_column = 0;
+ push_warning(nd, GDScriptWarning::EMPTY_FILE);
+ }
+#endif
+
push_multiline(false); // Keep one for the whole parsing.
parse_program();
pop_multiline();
@@ -562,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()) {
@@ -598,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;
}
@@ -613,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;
@@ -730,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) {
@@ -776,6 +796,9 @@ void GDScriptParser::parse_class_body() {
if (panic_mode) {
synchronize();
}
+ if (!p_is_multiline) {
+ class_end = true;
+ }
}
}
@@ -1036,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.
@@ -1059,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.)*");
}
@@ -1341,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) {
@@ -1381,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) {
@@ -1682,6 +1711,7 @@ GDScriptParser::MatchNode *GDScriptParser::parse_match() {
while (!check(GDScriptTokenizer::Token::DEDENT) && !is_at_end()) {
MatchBranchNode *branch = parse_match_branch();
if (branch == nullptr) {
+ advance();
continue;
}
@@ -1745,7 +1775,9 @@ GDScriptParser::MatchBranchNode *GDScriptParser::parse_match_branch() {
push_error(R"(No pattern found for "match" branch.)");
}
- consume(GDScriptTokenizer::Token::COLON, R"(Expected ":" after "match" patterns.)");
+ if (!consume(GDScriptTokenizer::Token::COLON, R"(Expected ":" after "match" patterns.)")) {
+ return nullptr;
+ }
// Save continue state.
bool could_continue = can_continue;
@@ -1778,15 +1810,6 @@ GDScriptParser::PatternNode *GDScriptParser::parse_match_pattern(PatternNode *p_
PatternNode *pattern = alloc_node<PatternNode>();
switch (current.type) {
- case GDScriptTokenizer::Token::LITERAL:
- advance();
- pattern->pattern_type = PatternNode::PT_LITERAL;
- pattern->literal = parse_literal();
- if (pattern->literal == nullptr) {
- // Error happened.
- return nullptr;
- }
- break;
case GDScriptTokenizer::Token::VAR: {
// Bind.
advance();
@@ -1849,44 +1872,44 @@ GDScriptParser::PatternNode *GDScriptParser::parse_match_pattern(PatternNode *p_
// Dictionary.
advance();
pattern->pattern_type = PatternNode::PT_DICTIONARY;
-
- if (!check(GDScriptTokenizer::Token::BRACE_CLOSE) && !is_at_end()) {
- do {
- if (match(GDScriptTokenizer::Token::PERIOD_PERIOD)) {
- // Rest.
+ do {
+ if (check(GDScriptTokenizer::Token::BRACE_CLOSE) || is_at_end()) {
+ break;
+ }
+ if (match(GDScriptTokenizer::Token::PERIOD_PERIOD)) {
+ // Rest.
+ if (pattern->rest_used) {
+ push_error(R"(The ".." pattern must be the last element in the pattern dictionary.)");
+ } else {
+ PatternNode *sub_pattern = alloc_node<PatternNode>();
+ sub_pattern->pattern_type = PatternNode::PT_REST;
+ pattern->dictionary.push_back({ nullptr, sub_pattern });
+ pattern->rest_used = true;
+ }
+ } else {
+ ExpressionNode *key = parse_expression(false);
+ if (key == nullptr) {
+ push_error(R"(Expected expression as key for dictionary pattern.)");
+ }
+ if (match(GDScriptTokenizer::Token::COLON)) {
+ // Value pattern.
+ PatternNode *sub_pattern = parse_match_pattern(p_root_pattern != nullptr ? p_root_pattern : pattern);
+ if (sub_pattern == nullptr) {
+ continue;
+ }
if (pattern->rest_used) {
push_error(R"(The ".." pattern must be the last element in the pattern dictionary.)");
+ } else if (sub_pattern->pattern_type == PatternNode::PT_REST) {
+ push_error(R"(The ".." pattern cannot be used as a value.)");
} else {
- PatternNode *sub_pattern = alloc_node<PatternNode>();
- sub_pattern->pattern_type = PatternNode::PT_REST;
- pattern->dictionary.push_back({ nullptr, sub_pattern });
- pattern->rest_used = true;
+ pattern->dictionary.push_back({ key, sub_pattern });
}
} else {
- ExpressionNode *key = parse_expression(false);
- if (key == nullptr) {
- push_error(R"(Expected expression as key for dictionary pattern.)");
- }
- if (match(GDScriptTokenizer::Token::COLON)) {
- // Value pattern.
- PatternNode *sub_pattern = parse_match_pattern(p_root_pattern != nullptr ? p_root_pattern : pattern);
- if (sub_pattern == nullptr) {
- continue;
- }
- if (pattern->rest_used) {
- push_error(R"(The ".." pattern must be the last element in the pattern dictionary.)");
- } else if (sub_pattern->pattern_type == PatternNode::PT_REST) {
- push_error(R"(The ".." pattern cannot be used as a value.)");
- } else {
- pattern->dictionary.push_back({ key, sub_pattern });
- }
- } else {
- // Key match only.
- pattern->dictionary.push_back({ key, nullptr });
- }
+ // Key match only.
+ pattern->dictionary.push_back({ key, nullptr });
}
- } while (match(GDScriptTokenizer::Token::COMMA));
- }
+ }
+ } while (match(GDScriptTokenizer::Token::COMMA));
consume(GDScriptTokenizer::Token::BRACE_CLOSE, R"(Expected "}" to close the dictionary pattern.)");
break;
}
@@ -1895,8 +1918,13 @@ GDScriptParser::PatternNode *GDScriptParser::parse_match_pattern(PatternNode *p_
ExpressionNode *expression = parse_expression(false);
if (expression == nullptr) {
push_error(R"(Expected expression for match pattern.)");
+ return nullptr;
} else {
- pattern->pattern_type = PatternNode::PT_EXPRESSION;
+ if (expression->type == GDScriptParser::Node::LITERAL) {
+ pattern->pattern_type = PatternNode::PT_LITERAL;
+ } else {
+ pattern->pattern_type = PatternNode::PT_EXPRESSION;
+ }
pattern->expression = expression;
}
break;
@@ -2107,22 +2135,34 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_unary_operator(ExpressionN
operation->operation = UnaryOpNode::OP_NEGATIVE;
operation->variant_op = Variant::OP_NEGATE;
operation->operand = parse_precedence(PREC_SIGN, false);
+ if (operation->operand == nullptr) {
+ push_error(R"(Expected expression after "-" operator.)");
+ }
break;
case GDScriptTokenizer::Token::PLUS:
operation->operation = UnaryOpNode::OP_POSITIVE;
operation->variant_op = Variant::OP_POSITIVE;
operation->operand = parse_precedence(PREC_SIGN, false);
+ if (operation->operand == nullptr) {
+ push_error(R"(Expected expression after "+" operator.)");
+ }
break;
case GDScriptTokenizer::Token::TILDE:
operation->operation = UnaryOpNode::OP_COMPLEMENT;
operation->variant_op = Variant::OP_BIT_NEGATE;
operation->operand = parse_precedence(PREC_BIT_NOT, false);
+ if (operation->operand == nullptr) {
+ push_error(R"(Expected expression after "~" operator.)");
+ }
break;
case GDScriptTokenizer::Token::NOT:
case GDScriptTokenizer::Token::BANG:
operation->operation = UnaryOpNode::OP_LOGIC_NOT;
operation->variant_op = Variant::OP_NOT;
operation->operand = parse_precedence(PREC_LOGIC_NOT, false);
+ if (operation->operand == nullptr) {
+ push_error(vformat(R"(Expected expression after "%s" operator.)", op_type == GDScriptTokenizer::Token::NOT ? "not" : "!"));
+ }
break;
default:
return nullptr; // Unreachable.
@@ -2259,6 +2299,10 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_ternary_operator(Expressio
operation->false_expr = parse_precedence(PREC_TERNARY, false);
+ if (operation->false_expr == nullptr) {
+ push_error(R"(Expected expression after "else".)");
+ }
+
return operation;
}
@@ -2370,8 +2414,12 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_assignment(ExpressionNode
}
#ifdef DEBUG_ENABLED
- if (has_operator && source_variable != nullptr && source_variable->assignments == 0) {
- push_warning(assignment, GDScriptWarning::UNASSIGNED_VARIABLE_OP_ASSIGN, source_variable->identifier->name);
+ if (source_variable != nullptr) {
+ if (has_operator && source_variable->assignments == 0) {
+ push_warning(assignment, GDScriptWarning::UNASSIGNED_VARIABLE_OP_ASSIGN, source_variable->identifier->name);
+ }
+
+ source_variable->assignments += 1;
}
#endif
@@ -2380,9 +2428,15 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_assignment(ExpressionNode
GDScriptParser::ExpressionNode *GDScriptParser::parse_await(ExpressionNode *p_previous_operand, bool p_can_assign) {
AwaitNode *await = alloc_node<AwaitNode>();
- await->to_await = parse_precedence(PREC_AWAIT, false);
+ ExpressionNode *element = parse_precedence(PREC_AWAIT, false);
+ if (element == nullptr) {
+ push_error(R"(Expected signal or coroutine after "await".)");
+ }
+ await->to_await = element;
- current_function->is_coroutine = true;
+ if (current_function) { // Might be null in a getter or setter.
+ current_function->is_coroutine = true;
+ }
return await;
}
@@ -2446,8 +2500,13 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_dictionary(ExpressionNode
switch (dictionary->style) {
case DictionaryNode::LUA_TABLE:
- if (key != nullptr && key->type != Node::IDENTIFIER) {
- push_error("Expected identifier as LUA-style dictionary key.");
+ if (key != nullptr && key->type != Node::IDENTIFIER && key->type != Node::LITERAL) {
+ push_error("Expected identifier or string as LUA-style dictionary key.");
+ advance();
+ break;
+ }
+ if (key != nullptr && key->type == Node::LITERAL && static_cast<LiteralNode *>(key)->value.get_type() != Variant::STRING) {
+ push_error("Expected identifier or string as LUA-style dictionary key.");
advance();
break;
}
@@ -2459,8 +2518,14 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_dictionary(ExpressionNode
push_error(R"(Expected "=" after dictionary key.)");
}
}
- key->is_constant = true;
- key->reduced_value = static_cast<IdentifierNode *>(key)->name;
+ if (key != nullptr) {
+ key->is_constant = true;
+ if (key->type == Node::IDENTIFIER) {
+ key->reduced_value = static_cast<IdentifierNode *>(key)->name;
+ } else if (key->type == Node::LITERAL) {
+ key->reduced_value = StringName(static_cast<LiteralNode *>(key)->value.operator String());
+ }
+ }
break;
case DictionaryNode::PYTHON_DICT:
if (!match(GDScriptTokenizer::Token::COLON)) {
@@ -2539,6 +2604,10 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_subscript(ExpressionNode *
subscript->base = p_previous_operand;
subscript->index = parse_expression(false);
+ if (subscript->index == nullptr) {
+ push_error(R"(Expected expression after "[".)");
+ }
+
pop_multiline();
consume(GDScriptTokenizer::Token::BRACKET_CLOSE, R"(Expected "]" after subscription index.)");
@@ -2753,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;
@@ -3109,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,
@@ -3389,55 +3463,47 @@ bool GDScriptParser::warning_annotations(const AnnotationNode *p_annotation, Nod
ERR_FAIL_V_MSG(false, "Not implemented.");
}
-template <MultiplayerAPI::RPCMode t_mode>
+template <Multiplayer::RPCMode t_mode>
bool GDScriptParser::network_annotations(const AnnotationNode *p_annotation, Node *p_node) {
ERR_FAIL_COND_V_MSG(p_node->type != Node::VARIABLE && p_node->type != Node::FUNCTION, false, vformat(R"("%s" annotation can only be applied to variables and functions.)", p_annotation->name));
- MultiplayerAPI::RPCConfig rpc_config;
+ Multiplayer::RPCConfig rpc_config;
rpc_config.rpc_mode = t_mode;
- for (int i = 0; i < p_annotation->resolved_arguments.size(); i++) {
- if (i == 0) {
+ if (p_annotation->resolved_arguments.size()) {
+ int last = p_annotation->resolved_arguments.size() - 1;
+ if (p_annotation->resolved_arguments[last].get_type() == Variant::INT) {
+ rpc_config.channel = p_annotation->resolved_arguments[last].operator int();
+ last -= 1;
+ }
+ if (last > 3) {
+ push_error(R"(Invalid RPC arguments. At most 4 arguments are allowed, where only the last argument can be an integer to specify the channel.')", p_annotation);
+ return false;
+ }
+ for (int i = last; i >= 0; i--) {
String mode = p_annotation->resolved_arguments[i].operator String();
if (mode == "any") {
- rpc_config.rpc_mode = MultiplayerAPI::RPC_MODE_REMOTE;
- } else if (mode == "master") {
- rpc_config.rpc_mode = MultiplayerAPI::RPC_MODE_MASTER;
- } else if (mode == "puppet") {
- rpc_config.rpc_mode = MultiplayerAPI::RPC_MODE_PUPPET;
- } else {
- push_error(R"(Invalid RPC mode. Must be one of: 'any', 'master', or 'puppet')", p_annotation);
- return false;
- }
- } else if (i == 1) {
- String sync = p_annotation->resolved_arguments[i].operator String();
- if (sync == "sync") {
+ rpc_config.rpc_mode = Multiplayer::RPC_MODE_ANY;
+ } else if (mode == "auth") {
+ rpc_config.rpc_mode = Multiplayer::RPC_MODE_AUTHORITY;
+ } else if (mode == "sync") {
rpc_config.sync = true;
- } else if (sync == "nosync") {
+ } else if (mode == "nosync") {
rpc_config.sync = false;
- } else {
- push_error(R"(Invalid RPC sync mode. Must be one of: 'sync' or 'nosync')", p_annotation);
- return false;
- }
- } else if (i == 2) {
- String mode = p_annotation->resolved_arguments[i].operator String();
- if (mode == "reliable") {
- rpc_config.transfer_mode = MultiplayerPeer::TRANSFER_MODE_RELIABLE;
+ } else if (mode == "reliable") {
+ rpc_config.transfer_mode = Multiplayer::TRANSFER_MODE_RELIABLE;
} else if (mode == "unreliable") {
- rpc_config.transfer_mode = MultiplayerPeer::TRANSFER_MODE_UNRELIABLE;
+ rpc_config.transfer_mode = Multiplayer::TRANSFER_MODE_UNRELIABLE;
} else if (mode == "ordered") {
- rpc_config.transfer_mode = MultiplayerPeer::TRANSFER_MODE_UNRELIABLE_ORDERED;
+ rpc_config.transfer_mode = Multiplayer::TRANSFER_MODE_ORDERED;
} else {
- push_error(R"(Invalid RPC transfer mode. Must be one of: 'reliable', 'unreliable', 'ordered')", p_annotation);
- return false;
+ push_error(R"(Invalid RPC argument. Must be one of: 'sync'/'nosync' (local calls), 'any'/'auth' (permission), 'reliable'/'unreliable'/'ordered' (transfer mode).)", p_annotation);
}
- } else if (i == 3) {
- rpc_config.channel = p_annotation->resolved_arguments[i].operator int();
}
}
switch (p_node->type) {
case Node::FUNCTION: {
FunctionNode *function = static_cast<FunctionNode *>(p_node);
- if (function->rpc_config.rpc_mode != MultiplayerAPI::RPC_MODE_DISABLED) {
+ if (function->rpc_config.rpc_mode != Multiplayer::RPC_MODE_DISABLED) {
push_error(R"(RPC annotations can only be used once per function.)", p_annotation);
return false;
}
@@ -3542,6 +3608,39 @@ String GDScriptParser::DataType::to_string() const {
ERR_FAIL_V_MSG("<unresolved type", "Kind set outside the enum range.");
}
+static Variant::Type _variant_type_to_typed_array_element_type(Variant::Type p_type) {
+ switch (p_type) {
+ case Variant::PACKED_BYTE_ARRAY:
+ case Variant::PACKED_INT32_ARRAY:
+ case Variant::PACKED_INT64_ARRAY:
+ return Variant::INT;
+ case Variant::PACKED_FLOAT32_ARRAY:
+ case Variant::PACKED_FLOAT64_ARRAY:
+ return Variant::FLOAT;
+ case Variant::PACKED_STRING_ARRAY:
+ return Variant::STRING;
+ case Variant::PACKED_VECTOR2_ARRAY:
+ return Variant::VECTOR2;
+ case Variant::PACKED_VECTOR3_ARRAY:
+ return Variant::VECTOR3;
+ case Variant::PACKED_COLOR_ARRAY:
+ return Variant::COLOR;
+ default:
+ return Variant::NIL;
+ }
+}
+
+bool GDScriptParser::DataType::is_typed_container_type() const {
+ return kind == GDScriptParser::DataType::BUILTIN && _variant_type_to_typed_array_element_type(builtin_type) != Variant::NIL;
+}
+
+GDScriptParser::DataType GDScriptParser::DataType::get_typed_container_type() const {
+ GDScriptParser::DataType type;
+ type.kind = GDScriptParser::DataType::BUILTIN;
+ type.builtin_type = _variant_type_to_typed_array_element_type(builtin_type);
+ return type;
+}
+
/*---------- PRETTY PRINT FOR DEBUG ----------*/
#ifdef DEBUG_ENABLED
diff --git a/modules/gdscript/gdscript_parser.h b/modules/gdscript/gdscript_parser.h
index 0bce8d7ddd..593fb0cc5e 100644
--- a/modules/gdscript/gdscript_parser.h
+++ b/modules/gdscript/gdscript_parser.h
@@ -31,8 +31,8 @@
#ifndef GDSCRIPT_PARSER_H
#define GDSCRIPT_PARSER_H
-#include "core/io/multiplayer_api.h"
#include "core/io/resource.h"
+#include "core/multiplayer/multiplayer.h"
#include "core/object/ref_counted.h"
#include "core/object/script_language.h"
#include "core/string/string_name.h"
@@ -161,6 +161,10 @@ public:
container_element_type = nullptr;
}
+ bool is_typed_container_type() const;
+
+ GDScriptParser::DataType get_typed_container_type() const;
+
bool operator==(const DataType &p_other) const {
if (type_source == UNDETECTED || p_other.type_source == UNDETECTED) {
return true; // Can be consireded equal for parsing purposes.
@@ -729,7 +733,7 @@ public:
SuiteNode *body = nullptr;
bool is_static = false;
bool is_coroutine = false;
- MultiplayerAPI::RPCConfig rpc_config;
+ Multiplayer::RPCConfig rpc_config;
MethodInfo info;
LambdaNode *source_lambda = nullptr;
#ifdef TOOLS_ENABLED
@@ -1320,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();
@@ -1340,7 +1344,7 @@ private:
template <PropertyHint t_hint, Variant::Type t_type>
bool export_annotations(const AnnotationNode *p_annotation, Node *p_target);
bool warning_annotations(const AnnotationNode *p_annotation, Node *p_target);
- template <MultiplayerAPI::RPCMode t_mode>
+ template <Multiplayer::RPCMode t_mode>
bool network_annotations(const AnnotationNode *p_annotation, Node *p_target);
// Statements.
Node *parse_statement();
@@ -1384,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 882256b7e3..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();
@@ -322,6 +322,7 @@ void (*type_init_function_table[])(Variant *) = {
&&OPCODE_ITERATE_PACKED_VECTOR3_ARRAY, \
&&OPCODE_ITERATE_PACKED_COLOR_ARRAY, \
&&OPCODE_ITERATE_OBJECT, \
+ &&OPCODE_STORE_GLOBAL, \
&&OPCODE_STORE_NAMED_GLOBAL, \
&&OPCODE_TYPE_ADJUST_BOOL, \
&&OPCODE_TYPE_ADJUST_INT, \
@@ -1231,6 +1232,13 @@ 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->operator Object *() && !src->get_validated_object()) {
+ err_text = "Trying to cast a freed object.";
+ OPCODE_BREAK;
+ }
+#endif
+
Callable::CallError err;
Variant::construct(to_type, *dst, (const Variant **)&src, 1, err);
@@ -1255,6 +1263,10 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a
GD_ERR_BREAK(!nc);
#ifdef DEBUG_ENABLED
+ if (src->operator Object *() && !src->get_validated_object()) {
+ err_text = "Trying to cast a freed object.";
+ OPCODE_BREAK;
+ }
if (src->get_type() != Variant::OBJECT && src->get_type() != Variant::NIL) {
err_text = "Invalid cast: can't convert a non-object value to an object type.";
OPCODE_BREAK;
@@ -1283,6 +1295,10 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a
GD_ERR_BREAK(!base_type);
#ifdef DEBUG_ENABLED
+ if (src->operator Object *() && !src->get_validated_object()) {
+ err_text = "Trying to cast a freed object.";
+ OPCODE_BREAK;
+ }
if (src->get_type() != Variant::OBJECT && src->get_type() != Variant::NIL) {
err_text = "Trying to assign a non-object value to a variable of type '" + base_type->get_path().get_file() + "'.";
OPCODE_BREAK;
@@ -2122,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;
@@ -3116,6 +3132,18 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a
}
DISPATCH_OPCODE;
+ OPCODE(OPCODE_STORE_GLOBAL) {
+ CHECK_SPACE(3);
+ int global_idx = _code_ptr[ip + 2];
+ GD_ERR_BREAK(global_idx < 0 || global_idx >= GDScriptLanguage::get_singleton()->get_global_array_size());
+
+ GET_INSTRUCTION_ARG(dst, 0);
+ *dst = GDScriptLanguage::get_singleton()->get_global_array()[global_idx];
+
+ ip += 3;
+ }
+ DISPATCH_OPCODE;
+
OPCODE(OPCODE_STORE_NAMED_GLOBAL) {
CHECK_SPACE(3);
int globalname_idx = _code_ptr[ip + 2];
diff --git a/modules/gdscript/gdscript_warning.cpp b/modules/gdscript/gdscript_warning.cpp
index ad41b60a4e..7a483a16ba 100644
--- a/modules/gdscript/gdscript_warning.cpp
+++ b/modules/gdscript/gdscript_warning.cpp
@@ -145,6 +145,9 @@ String GDScriptWarning::get_message() const {
case REDUNDANT_AWAIT: {
return R"("await" keyword not needed in this case, because the expression isn't a coroutine nor a signal.)";
}
+ case EMPTY_FILE: {
+ return "Empty script file.";
+ }
case WARNING_MAX:
break; // Can't happen, but silences warning
}
@@ -190,6 +193,7 @@ String GDScriptWarning::get_name_from_code(Code p_code) {
"ASSERT_ALWAYS_TRUE",
"ASSERT_ALWAYS_FALSE",
"REDUNDANT_AWAIT",
+ "EMPTY_FILE",
};
static_assert((sizeof(names) / sizeof(*names)) == WARNING_MAX, "Amount of warning types don't match the amount of warning names.");
diff --git a/modules/gdscript/gdscript_warning.h b/modules/gdscript/gdscript_warning.h
index 4b295b5eb8..8de46b08c1 100644
--- a/modules/gdscript/gdscript_warning.h
+++ b/modules/gdscript/gdscript_warning.h
@@ -68,6 +68,7 @@ public:
ASSERT_ALWAYS_TRUE, // Expression for assert argument is always true.
ASSERT_ALWAYS_FALSE, // Expression for assert argument is always false.
REDUNDANT_AWAIT, // await is used but expression is synchronous (not a signal nor a coroutine).
+ EMPTY_FILE, // A script file is empty.
WARNING_MAX,
};
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_language_server.cpp b/modules/gdscript/language_server/gdscript_language_server.cpp
index c47164d95b..41a2f9e4ad 100644
--- a/modules/gdscript/language_server/gdscript_language_server.cpp
+++ b/modules/gdscript/language_server/gdscript_language_server.cpp
@@ -36,6 +36,7 @@
#include "editor/editor_node.h"
GDScriptLanguageServer::GDScriptLanguageServer() {
+ _EDITOR_DEF("network/language_server/remote_host", host);
_EDITOR_DEF("network/language_server/remote_port", port);
_EDITOR_DEF("network/language_server/enable_smart_resolve", true);
_EDITOR_DEF("network/language_server/show_native_symbols_in_editor", false);
@@ -56,9 +57,10 @@ void GDScriptLanguageServer::_notification(int p_what) {
}
} break;
case EditorSettings::NOTIFICATION_EDITOR_SETTINGS_CHANGED: {
+ String host = String(_EDITOR_GET("network/language_server/remote_host"));
int port = (int)_EDITOR_GET("network/language_server/remote_port");
bool use_thread = (bool)_EDITOR_GET("network/language_server/use_thread");
- if (port != this->port || use_thread != this->use_thread) {
+ if (host != this->host || port != this->port || use_thread != this->use_thread) {
this->stop();
this->start();
}
@@ -76,9 +78,10 @@ void GDScriptLanguageServer::thread_main(void *p_userdata) {
}
void GDScriptLanguageServer::start() {
+ host = String(_EDITOR_GET("network/language_server/remote_host"));
port = (int)_EDITOR_GET("network/language_server/remote_port");
use_thread = (bool)_EDITOR_GET("network/language_server/use_thread");
- if (protocol.start(port, IPAddress("127.0.0.1")) == OK) {
+ if (protocol.start(port, IPAddress(host)) == OK) {
EditorNode::get_log()->add_message("--- GDScript language server started ---", EditorLog::MSG_TYPE_EDITOR);
if (use_thread) {
thread_running = true;
diff --git a/modules/gdscript/language_server/gdscript_language_server.h b/modules/gdscript/language_server/gdscript_language_server.h
index 29c5ddd70e..85a44a8cc1 100644
--- a/modules/gdscript/language_server/gdscript_language_server.h
+++ b/modules/gdscript/language_server/gdscript_language_server.h
@@ -44,6 +44,7 @@ class GDScriptLanguageServer : public EditorPlugin {
bool thread_running = false;
bool started = false;
bool use_thread = false;
+ String host = "127.0.0.1";
int port = 6008;
static void thread_main(void *p_userdata);
diff --git a/modules/gdscript/language_server/gdscript_workspace.cpp b/modules/gdscript/language_server/gdscript_workspace.cpp
index 1512b4bb89..86b3a3a326 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;
}
diff --git a/modules/gdscript/language_server/gdscript_workspace.h b/modules/gdscript/language_server/gdscript_workspace.h
index 9496677449..e5cd4d9824 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;
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/gdscript_test_runner.cpp b/modules/gdscript/tests/gdscript_test_runner.cpp
index 6225e5d1eb..c383830c82 100644
--- a/modules/gdscript/tests/gdscript_test_runner.cpp
+++ b/modules/gdscript/tests/gdscript_test_runner.cpp
@@ -415,6 +415,7 @@ GDScriptTest::TestResult GDScriptTest::execute_test_code(bool p_is_generating) {
TestResult result;
result.status = GDTEST_OK;
result.output = String();
+ result.passed = false;
Error err = OK;
@@ -496,7 +497,12 @@ GDScriptTest::TestResult GDScriptTest::execute_test_code(bool p_is_generating) {
}
return result;
}
-
+ // Script files matching this pattern are allowed to not contain a test() function.
+ if (source_file.match("*.notest.gd")) {
+ enable_stdout();
+ result.passed = check_output(result.output);
+ return result;
+ }
// Test running.
const Map<StringName, GDScriptFunction *>::Element *test_function_element = script->get_member_functions().find(GDScriptTestRunner::test_function_name);
if (test_function_element == nullptr) {
diff --git a/modules/gdscript/tests/scripts/analyzer/errors/bitwise_float_left_operand.gd b/modules/gdscript/tests/scripts/analyzer/errors/bitwise_float_left_operand.gd
new file mode 100644
index 0000000000..9b722ea50a
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/errors/bitwise_float_left_operand.gd
@@ -0,0 +1,3 @@
+func test():
+ # Error here.
+ print(2.2 << 4)
diff --git a/modules/gdscript/tests/scripts/analyzer/errors/bitwise_float_left_operand.out b/modules/gdscript/tests/scripts/analyzer/errors/bitwise_float_left_operand.out
new file mode 100644
index 0000000000..7dee854d1a
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/errors/bitwise_float_left_operand.out
@@ -0,0 +1,2 @@
+GDTEST_ANALYZER_ERROR
+Invalid operands to operator <<, float and int.
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
new file mode 100644
index 0000000000..0a4f647f57
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/errors/bitwise_float_right_operand.gd
@@ -0,0 +1,3 @@
+func test():
+ # Error here.
+ 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
new file mode 100644
index 0000000000..1edbf47ec0
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/errors/bitwise_float_right_operand.out
@@ -0,0 +1,2 @@
+GDTEST_ANALYZER_ERROR
+Invalid operands to operator >>, int and float.
diff --git a/modules/gdscript/tests/scripts/analyzer/errors/constant_used_as_function.gd b/modules/gdscript/tests/scripts/analyzer/errors/constant_used_as_function.gd
new file mode 100644
index 0000000000..0ad2337c15
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/errors/constant_used_as_function.gd
@@ -0,0 +1,5 @@
+const CONSTANT = 25
+
+
+func test():
+ CONSTANT(123)
diff --git a/modules/gdscript/tests/scripts/analyzer/errors/constant_used_as_function.out b/modules/gdscript/tests/scripts/analyzer/errors/constant_used_as_function.out
new file mode 100644
index 0000000000..f4051cd02c
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/errors/constant_used_as_function.out
@@ -0,0 +1,2 @@
+GDTEST_ANALYZER_ERROR
+Member "CONSTANT" is not a function.
diff --git a/modules/gdscript/tests/scripts/analyzer/errors/dictionary_duplicate_key_lua.gd b/modules/gdscript/tests/scripts/analyzer/errors/dictionary_duplicate_key_lua.gd
new file mode 100644
index 0000000000..7a922cd73e
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/errors/dictionary_duplicate_key_lua.gd
@@ -0,0 +1,6 @@
+func test():
+ var lua_dict = {
+ a = 1,
+ b = 2,
+ a = 3, # Duplicate isn't allowed.
+ }
diff --git a/modules/gdscript/tests/scripts/analyzer/errors/dictionary_duplicate_key_lua.out b/modules/gdscript/tests/scripts/analyzer/errors/dictionary_duplicate_key_lua.out
new file mode 100644
index 0000000000..ffdfa56645
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/errors/dictionary_duplicate_key_lua.out
@@ -0,0 +1,2 @@
+GDTEST_ANALYZER_ERROR
+Key "a" was already used in this dictionary (at line 3).
diff --git a/modules/gdscript/tests/scripts/analyzer/errors/dictionary_duplicate_key_lua_with_string.gd b/modules/gdscript/tests/scripts/analyzer/errors/dictionary_duplicate_key_lua_with_string.gd
new file mode 100644
index 0000000000..933e737ac7
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/errors/dictionary_duplicate_key_lua_with_string.gd
@@ -0,0 +1,6 @@
+func test():
+ var lua_dict_with_string = {
+ a = 1,
+ b = 2,
+ "a" = 3, # Duplicate isn't allowed.
+ }
diff --git a/modules/gdscript/tests/scripts/analyzer/errors/dictionary_duplicate_key_lua_with_string.out b/modules/gdscript/tests/scripts/analyzer/errors/dictionary_duplicate_key_lua_with_string.out
new file mode 100644
index 0000000000..ffdfa56645
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/errors/dictionary_duplicate_key_lua_with_string.out
@@ -0,0 +1,2 @@
+GDTEST_ANALYZER_ERROR
+Key "a" was already used in this dictionary (at line 3).
diff --git a/modules/gdscript/tests/scripts/analyzer/errors/dictionary_duplicate_key_python.gd b/modules/gdscript/tests/scripts/analyzer/errors/dictionary_duplicate_key_python.gd
new file mode 100644
index 0000000000..3b8c83e9cb
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/errors/dictionary_duplicate_key_python.gd
@@ -0,0 +1,6 @@
+func test():
+ var python_dict = {
+ "a": 1,
+ "b": 2,
+ "a": 3, # Duplicate isn't allowed.
+ }
diff --git a/modules/gdscript/tests/scripts/analyzer/errors/dictionary_duplicate_key_python.out b/modules/gdscript/tests/scripts/analyzer/errors/dictionary_duplicate_key_python.out
new file mode 100644
index 0000000000..ffdfa56645
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/errors/dictionary_duplicate_key_python.out
@@ -0,0 +1,2 @@
+GDTEST_ANALYZER_ERROR
+Key "a" was already used in this dictionary (at line 3).
diff --git a/modules/gdscript/tests/scripts/analyzer/errors/enum_float_value.gd b/modules/gdscript/tests/scripts/analyzer/errors/enum_float_value.gd
new file mode 100644
index 0000000000..cf9a0ce038
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/errors/enum_float_value.gd
@@ -0,0 +1,7 @@
+enum Size {
+ # Error here. Enum values must be integers.
+ S = 0.0,
+}
+
+func test():
+ pass
diff --git a/modules/gdscript/tests/scripts/analyzer/errors/enum_float_value.out b/modules/gdscript/tests/scripts/analyzer/errors/enum_float_value.out
new file mode 100644
index 0000000000..b315d20508
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/errors/enum_float_value.out
@@ -0,0 +1,2 @@
+GDTEST_ANALYZER_ERROR
+Enum values must be integers.
diff --git a/modules/gdscript/tests/scripts/analyzer/errors/enum_string_value.gd b/modules/gdscript/tests/scripts/analyzer/errors/enum_string_value.gd
new file mode 100644
index 0000000000..cd9b8fabc4
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/errors/enum_string_value.gd
@@ -0,0 +1,7 @@
+enum Size {
+ # Error here. Enum values must be integers.
+ S = "hello",
+}
+
+func test():
+ pass
diff --git a/modules/gdscript/tests/scripts/analyzer/errors/enum_string_value.out b/modules/gdscript/tests/scripts/analyzer/errors/enum_string_value.out
new file mode 100644
index 0000000000..b315d20508
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/errors/enum_string_value.out
@@ -0,0 +1,2 @@
+GDTEST_ANALYZER_ERROR
+Enum values must be integers.
diff --git a/modules/gdscript/tests/scripts/analyzer/errors/function_used_as_property.gd b/modules/gdscript/tests/scripts/analyzer/errors/function_used_as_property.gd
new file mode 100644
index 0000000000..4346503fc2
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/errors/function_used_as_property.gd
@@ -0,0 +1,6 @@
+func function():
+ pass
+
+
+func test():
+ function = 25
diff --git a/modules/gdscript/tests/scripts/analyzer/errors/function_used_as_property.out b/modules/gdscript/tests/scripts/analyzer/errors/function_used_as_property.out
new file mode 100644
index 0000000000..5275183da2
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/errors/function_used_as_property.out
@@ -0,0 +1,2 @@
+GDTEST_ANALYZER_ERROR
+Cannot assign a new value to a constant.
diff --git a/modules/gdscript/tests/scripts/analyzer/errors/invalid_array_index.gd b/modules/gdscript/tests/scripts/analyzer/errors/invalid_array_index.gd
new file mode 100644
index 0000000000..b8c0b7a8d3
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/errors/invalid_array_index.gd
@@ -0,0 +1,3 @@
+func test():
+ # Error here. Array indices must be integers.
+ print([0, 1][true])
diff --git a/modules/gdscript/tests/scripts/analyzer/errors/invalid_array_index.out b/modules/gdscript/tests/scripts/analyzer/errors/invalid_array_index.out
new file mode 100644
index 0000000000..015ad756f8
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/errors/invalid_array_index.out
@@ -0,0 +1,2 @@
+GDTEST_ANALYZER_ERROR
+Invalid index type "bool" for a base of type "Array".
diff --git a/modules/gdscript/tests/scripts/analyzer/errors/invalid_concatenation_bool.gd b/modules/gdscript/tests/scripts/analyzer/errors/invalid_concatenation_bool.gd
new file mode 100644
index 0000000000..c159e03140
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/errors/invalid_concatenation_bool.gd
@@ -0,0 +1,2 @@
+func test():
+ print(true + true)
diff --git a/modules/gdscript/tests/scripts/analyzer/errors/invalid_concatenation_bool.out b/modules/gdscript/tests/scripts/analyzer/errors/invalid_concatenation_bool.out
new file mode 100644
index 0000000000..c1dc7c7d08
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/errors/invalid_concatenation_bool.out
@@ -0,0 +1,2 @@
+GDTEST_ANALYZER_ERROR
+Invalid operands to operator +, bool and bool.
diff --git a/modules/gdscript/tests/scripts/analyzer/errors/invalid_concatenation_dictionary.gd b/modules/gdscript/tests/scripts/analyzer/errors/invalid_concatenation_dictionary.gd
new file mode 100644
index 0000000000..6aec2e0796
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/errors/invalid_concatenation_dictionary.gd
@@ -0,0 +1,2 @@
+func test():
+ print({"hello": "world"} + {"godot": "engine"})
diff --git a/modules/gdscript/tests/scripts/analyzer/errors/invalid_concatenation_dictionary.out b/modules/gdscript/tests/scripts/analyzer/errors/invalid_concatenation_dictionary.out
new file mode 100644
index 0000000000..1b4451edbe
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/errors/invalid_concatenation_dictionary.out
@@ -0,0 +1,2 @@
+GDTEST_ANALYZER_ERROR
+Invalid operands "Dictionary" and "Dictionary" for "+" operator.
diff --git a/modules/gdscript/tests/scripts/analyzer/errors/invalid_concatenation_mixed.gd b/modules/gdscript/tests/scripts/analyzer/errors/invalid_concatenation_mixed.gd
new file mode 100644
index 0000000000..eb2a6a0ce7
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/errors/invalid_concatenation_mixed.gd
@@ -0,0 +1,2 @@
+func test():
+ print("hello" + ["world"])
diff --git a/modules/gdscript/tests/scripts/analyzer/errors/invalid_concatenation_mixed.out b/modules/gdscript/tests/scripts/analyzer/errors/invalid_concatenation_mixed.out
new file mode 100644
index 0000000000..6d44c6c1bd
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/errors/invalid_concatenation_mixed.out
@@ -0,0 +1,2 @@
+GDTEST_ANALYZER_ERROR
+Invalid operands "String" and "Array" for "+" operator.
diff --git a/modules/gdscript/tests/scripts/analyzer/errors/invalid_constant.gd b/modules/gdscript/tests/scripts/analyzer/errors/invalid_constant.gd
new file mode 100644
index 0000000000..a7426e88da
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/errors/invalid_constant.gd
@@ -0,0 +1,5 @@
+func test():
+ var i = 12
+ # Constants must be made of a constant, deterministic expression.
+ # A constant that depends on a variable's value is not a constant expression.
+ const TEST = 13 + i
diff --git a/modules/gdscript/tests/scripts/analyzer/errors/invalid_constant.out b/modules/gdscript/tests/scripts/analyzer/errors/invalid_constant.out
new file mode 100644
index 0000000000..c40830f123
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/errors/invalid_constant.out
@@ -0,0 +1,2 @@
+GDTEST_ANALYZER_ERROR
+Assigned value for constant "TEST" isn't a constant expression.
diff --git a/modules/gdscript/tests/scripts/analyzer/errors/leading_number_separator.gd b/modules/gdscript/tests/scripts/analyzer/errors/leading_number_separator.gd
new file mode 100644
index 0000000000..d88c02d6ee
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/errors/leading_number_separator.gd
@@ -0,0 +1,3 @@
+func test():
+ # Number separators may not be placed at the beginning of a number.
+ var __ = _123
diff --git a/modules/gdscript/tests/scripts/analyzer/errors/leading_number_separator.out b/modules/gdscript/tests/scripts/analyzer/errors/leading_number_separator.out
new file mode 100644
index 0000000000..cfb558bf45
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/errors/leading_number_separator.out
@@ -0,0 +1,2 @@
+GDTEST_ANALYZER_ERROR
+Identifier "_123" not declared in the current scope.
diff --git a/modules/gdscript/tests/scripts/analyzer/errors/missing_argument.gd b/modules/gdscript/tests/scripts/analyzer/errors/missing_argument.gd
new file mode 100644
index 0000000000..70bdadf291
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/errors/missing_argument.gd
@@ -0,0 +1,6 @@
+func args(a, b):
+ print(a)
+ print(b)
+
+func test():
+ args(1,)
diff --git a/modules/gdscript/tests/scripts/parser/errors/missing_argument.out b/modules/gdscript/tests/scripts/analyzer/errors/missing_argument.out
index fc2a891109..fc2a891109 100644
--- a/modules/gdscript/tests/scripts/parser/errors/missing_argument.out
+++ b/modules/gdscript/tests/scripts/analyzer/errors/missing_argument.out
diff --git a/modules/gdscript/tests/scripts/analyzer/errors/property_used_as_function.gd b/modules/gdscript/tests/scripts/analyzer/errors/property_used_as_function.gd
new file mode 100644
index 0000000000..059d774927
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/errors/property_used_as_function.gd
@@ -0,0 +1,4 @@
+var property = 25
+
+func test():
+ property()
diff --git a/modules/gdscript/tests/scripts/analyzer/errors/property_used_as_function.out b/modules/gdscript/tests/scripts/analyzer/errors/property_used_as_function.out
new file mode 100644
index 0000000000..94d6c26a1a
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/errors/property_used_as_function.out
@@ -0,0 +1,2 @@
+GDTEST_ANALYZER_ERROR
+Member "property" is not a function.
diff --git a/modules/gdscript/tests/scripts/analyzer/errors/redefine_class_constant.gd b/modules/gdscript/tests/scripts/analyzer/errors/redefine_class_constant.gd
new file mode 100644
index 0000000000..91401d32fc
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/errors/redefine_class_constant.gd
@@ -0,0 +1,7 @@
+# See also `parser-warnings/shadowed-constant.gd`.
+const TEST = 25
+
+
+func test():
+ # Error here (trying to set a new value to a constant).
+ TEST = 50
diff --git a/modules/gdscript/tests/scripts/analyzer/errors/redefine_class_constant.out b/modules/gdscript/tests/scripts/analyzer/errors/redefine_class_constant.out
new file mode 100644
index 0000000000..5275183da2
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/errors/redefine_class_constant.out
@@ -0,0 +1,2 @@
+GDTEST_ANALYZER_ERROR
+Cannot assign a new value to a constant.
diff --git a/modules/gdscript/tests/scripts/analyzer/errors/redefine_local_constant.gd b/modules/gdscript/tests/scripts/analyzer/errors/redefine_local_constant.gd
new file mode 100644
index 0000000000..97f3e55e81
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/errors/redefine_local_constant.gd
@@ -0,0 +1,5 @@
+func test():
+ const TEST = 25
+
+ # Error here (can't assign a new value to a constant).
+ TEST = 50
diff --git a/modules/gdscript/tests/scripts/analyzer/errors/redefine_local_constant.out b/modules/gdscript/tests/scripts/analyzer/errors/redefine_local_constant.out
new file mode 100644
index 0000000000..5275183da2
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/errors/redefine_local_constant.out
@@ -0,0 +1,2 @@
+GDTEST_ANALYZER_ERROR
+Cannot assign a new value to a constant.
diff --git a/modules/gdscript/tests/scripts/analyzer/errors/super_nonexistent_base_method.gd b/modules/gdscript/tests/scripts/analyzer/errors/super_nonexistent_base_method.gd
new file mode 100644
index 0000000000..722a8fcdb7
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/errors/super_nonexistent_base_method.gd
@@ -0,0 +1,11 @@
+# `class` extends RefCounted by default.
+class Say:
+ func say():
+ super()
+ print("say something")
+
+
+func test():
+ # RefCounted doesn't have a `say()` method, so the `super()` call in the method
+ # definition will cause a run-time error.
+ Say.new().say()
diff --git a/modules/gdscript/tests/scripts/analyzer/errors/super_nonexistent_base_method.out b/modules/gdscript/tests/scripts/analyzer/errors/super_nonexistent_base_method.out
new file mode 100644
index 0000000000..e3dbf81850
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/errors/super_nonexistent_base_method.out
@@ -0,0 +1,2 @@
+GDTEST_ANALYZER_ERROR
+Function "say()" not found in base RefCounted.
diff --git a/modules/gdscript/tests/scripts/analyzer/features/as.gd b/modules/gdscript/tests/scripts/analyzer/features/as.gd
new file mode 100644
index 0000000000..13a36147c0
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/features/as.gd
@@ -0,0 +1,16 @@
+func test():
+ var some_bool = 5 as bool
+ var some_int = 5 as int
+ var some_float = 5 as float
+ print(typeof(some_bool))
+ print(typeof(some_int))
+ print(typeof(some_float))
+
+ print()
+
+ var some_bool_typed := 5 as bool
+ var some_int_typed := 5 as int
+ var some_float_typed := 5 as float
+ print(typeof(some_bool_typed))
+ print(typeof(some_int_typed))
+ print(typeof(some_float_typed))
diff --git a/modules/gdscript/tests/scripts/analyzer/features/as.out b/modules/gdscript/tests/scripts/analyzer/features/as.out
new file mode 100644
index 0000000000..bcf84aa6f6
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/features/as.out
@@ -0,0 +1,8 @@
+GDTEST_OK
+1
+2
+3
+
+1
+2
+3
diff --git a/modules/gdscript/tests/scripts/analyzer/features/auto_inferred_type_dont_error.gd b/modules/gdscript/tests/scripts/analyzer/features/auto_inferred_type_dont_error.gd
new file mode 100644
index 0000000000..f64dce26c9
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/features/auto_inferred_type_dont_error.gd
@@ -0,0 +1,9 @@
+func inferred_parameter(param = null):
+ if param == null:
+ param = Node.new()
+ param.name = "Ok"
+ print(param.name)
+ param.free()
+
+func test():
+ inferred_parameter()
diff --git a/modules/gdscript/tests/scripts/analyzer/features/auto_inferred_type_dont_error.out b/modules/gdscript/tests/scripts/analyzer/features/auto_inferred_type_dont_error.out
new file mode 100644
index 0000000000..0e9f482af4
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/features/auto_inferred_type_dont_error.out
@@ -0,0 +1,2 @@
+GDTEST_OK
+Ok
diff --git a/modules/gdscript/tests/scripts/analyzer/features/constants_from_parent.gd b/modules/gdscript/tests/scripts/analyzer/features/constants_from_parent.gd
new file mode 100644
index 0000000000..135b6c3d85
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/features/constants_from_parent.gd
@@ -0,0 +1,16 @@
+extends Node
+
+const NO_TYPE_CONST = 0
+const TYPE_CONST: int = 1
+const GUESS_TYPE_CONST := 2
+
+class Test:
+ var a = NO_TYPE_CONST
+ var b = TYPE_CONST
+ var c = GUESS_TYPE_CONST
+
+func test():
+ var test_instance = Test.new()
+ prints("a", test_instance.a, test_instance.a == NO_TYPE_CONST)
+ prints("b", test_instance.b, test_instance.b == TYPE_CONST)
+ prints("c", test_instance.c, test_instance.c == GUESS_TYPE_CONST)
diff --git a/modules/gdscript/tests/scripts/analyzer/features/constants_from_parent.out b/modules/gdscript/tests/scripts/analyzer/features/constants_from_parent.out
new file mode 100644
index 0000000000..a96bb84246
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/features/constants_from_parent.out
@@ -0,0 +1,4 @@
+GDTEST_OK
+a 0 true
+b 1 true
+c 2 true
diff --git a/modules/gdscript/tests/scripts/analyzer/features/enum_from_parent.gd b/modules/gdscript/tests/scripts/analyzer/features/enum_from_parent.gd
new file mode 100644
index 0000000000..5f57c5b8c2
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/features/enum_from_parent.gd
@@ -0,0 +1,14 @@
+extends Node
+
+enum Named { VALUE_A, VALUE_B, VALUE_C = 42 }
+
+class Test:
+ var a = Named.VALUE_A
+ var b = Named.VALUE_B
+ var c = Named.VALUE_C
+
+func test():
+ var test_instance = Test.new()
+ prints("a", test_instance.a, test_instance.a == Named.VALUE_A)
+ prints("b", test_instance.b, test_instance.b == Named.VALUE_B)
+ prints("c", test_instance.c, test_instance.c == Named.VALUE_C)
diff --git a/modules/gdscript/tests/scripts/analyzer/features/enum_from_parent.out b/modules/gdscript/tests/scripts/analyzer/features/enum_from_parent.out
new file mode 100644
index 0000000000..c160839da3
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/features/enum_from_parent.out
@@ -0,0 +1,4 @@
+GDTEST_OK
+a 0 true
+b 1 true
+c 42 true
diff --git a/modules/gdscript/tests/scripts/analyzer/features/enum_value_from_parent.gd b/modules/gdscript/tests/scripts/analyzer/features/enum_value_from_parent.gd
new file mode 100644
index 0000000000..26edce353d
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/features/enum_value_from_parent.gd
@@ -0,0 +1,14 @@
+extends Node
+
+enum { VALUE_A, VALUE_B, VALUE_C = 42 }
+
+class Test:
+ var a = VALUE_A
+ var b = VALUE_B
+ var c = VALUE_C
+
+func test():
+ var test_instance = Test.new()
+ prints("a", test_instance.a, test_instance.a == VALUE_A)
+ prints("b", test_instance.b, test_instance.b == VALUE_B)
+ prints("c", test_instance.c, test_instance.c == VALUE_C)
diff --git a/modules/gdscript/tests/scripts/analyzer/features/enum_value_from_parent.out b/modules/gdscript/tests/scripts/analyzer/features/enum_value_from_parent.out
new file mode 100644
index 0000000000..c160839da3
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/features/enum_value_from_parent.out
@@ -0,0 +1,4 @@
+GDTEST_OK
+a 0 true
+b 1 true
+c 42 true
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/array_consecutive_commas.gd b/modules/gdscript/tests/scripts/parser/errors/array_consecutive_commas.gd
new file mode 100644
index 0000000000..b45f99fdd0
--- /dev/null
+++ b/modules/gdscript/tests/scripts/parser/errors/array_consecutive_commas.gd
@@ -0,0 +1,3 @@
+func test():
+ # Arrays with consecutive commas are not allowed.
+ var array = ["arrays",,,,]
diff --git a/modules/gdscript/tests/scripts/parser/errors/array_consecutive_commas.out b/modules/gdscript/tests/scripts/parser/errors/array_consecutive_commas.out
new file mode 100644
index 0000000000..4ef8526065
--- /dev/null
+++ b/modules/gdscript/tests/scripts/parser/errors/array_consecutive_commas.out
@@ -0,0 +1,2 @@
+GDTEST_PARSER_ERROR
+Expected expression as array element.
diff --git a/modules/gdscript/tests/scripts/parser/errors/assignment_2_equal_signs.gd b/modules/gdscript/tests/scripts/parser/errors/assignment_2_equal_signs.gd
new file mode 100644
index 0000000000..17d5e078e5
--- /dev/null
+++ b/modules/gdscript/tests/scripts/parser/errors/assignment_2_equal_signs.gd
@@ -0,0 +1,2 @@
+func test():
+ var hello == "world"
diff --git a/modules/gdscript/tests/scripts/parser/errors/assignment_2_equal_signs.out b/modules/gdscript/tests/scripts/parser/errors/assignment_2_equal_signs.out
new file mode 100644
index 0000000000..b150fc0d16
--- /dev/null
+++ b/modules/gdscript/tests/scripts/parser/errors/assignment_2_equal_signs.out
@@ -0,0 +1,2 @@
+GDTEST_PARSER_ERROR
+Expected end of statement after variable declaration, found "==" instead.
diff --git a/modules/gdscript/tests/scripts/parser/errors/assignment_3_equal_signs.gd b/modules/gdscript/tests/scripts/parser/errors/assignment_3_equal_signs.gd
new file mode 100644
index 0000000000..8b5f620889
--- /dev/null
+++ b/modules/gdscript/tests/scripts/parser/errors/assignment_3_equal_signs.gd
@@ -0,0 +1,2 @@
+func test():
+ var hello === "world"
diff --git a/modules/gdscript/tests/scripts/parser/errors/assignment_3_equal_signs.out b/modules/gdscript/tests/scripts/parser/errors/assignment_3_equal_signs.out
new file mode 100644
index 0000000000..b150fc0d16
--- /dev/null
+++ b/modules/gdscript/tests/scripts/parser/errors/assignment_3_equal_signs.out
@@ -0,0 +1,2 @@
+GDTEST_PARSER_ERROR
+Expected end of statement after variable declaration, found "==" instead.
diff --git a/modules/gdscript/tests/scripts/parser/errors/assignment_in_if.gd b/modules/gdscript/tests/scripts/parser/errors/assignment_in_if.gd
new file mode 100644
index 0000000000..8c3a908532
--- /dev/null
+++ b/modules/gdscript/tests/scripts/parser/errors/assignment_in_if.gd
@@ -0,0 +1,4 @@
+func test():
+ # Error here.
+ if foo = 25:
+ print(foo)
diff --git a/modules/gdscript/tests/scripts/parser/errors/assignment_in_if.out b/modules/gdscript/tests/scripts/parser/errors/assignment_in_if.out
new file mode 100644
index 0000000000..e8f9130706
--- /dev/null
+++ b/modules/gdscript/tests/scripts/parser/errors/assignment_in_if.out
@@ -0,0 +1,2 @@
+GDTEST_PARSER_ERROR
+Assignment is not allowed inside an expression.
diff --git a/modules/gdscript/tests/scripts/parser/errors/assignment_in_var.gd b/modules/gdscript/tests/scripts/parser/errors/assignment_in_var.gd
new file mode 100644
index 0000000000..126a3227ea
--- /dev/null
+++ b/modules/gdscript/tests/scripts/parser/errors/assignment_in_var.gd
@@ -0,0 +1,2 @@
+func test():
+ var hello = "world" = "test"
diff --git a/modules/gdscript/tests/scripts/parser/errors/assignment_in_var.out b/modules/gdscript/tests/scripts/parser/errors/assignment_in_var.out
new file mode 100644
index 0000000000..e8f9130706
--- /dev/null
+++ b/modules/gdscript/tests/scripts/parser/errors/assignment_in_var.out
@@ -0,0 +1,2 @@
+GDTEST_PARSER_ERROR
+Assignment is not allowed inside an expression.
diff --git a/modules/gdscript/tests/scripts/parser/errors/assignment_in_var_if.gd b/modules/gdscript/tests/scripts/parser/errors/assignment_in_var_if.gd
new file mode 100644
index 0000000000..a99557fa3c
--- /dev/null
+++ b/modules/gdscript/tests/scripts/parser/errors/assignment_in_var_if.gd
@@ -0,0 +1,4 @@
+func test():
+ # Error here.
+ if var foo = 25:
+ print(foo)
diff --git a/modules/gdscript/tests/scripts/parser/errors/assignment_in_var_if.out b/modules/gdscript/tests/scripts/parser/errors/assignment_in_var_if.out
new file mode 100644
index 0000000000..e84f4652ac
--- /dev/null
+++ b/modules/gdscript/tests/scripts/parser/errors/assignment_in_var_if.out
@@ -0,0 +1,2 @@
+GDTEST_PARSER_ERROR
+Expected conditional expression after "if".
diff --git a/modules/gdscript/tests/scripts/parser/errors/assignment_without_identifier.gd b/modules/gdscript/tests/scripts/parser/errors/assignment_without_identifier.gd
new file mode 100644
index 0000000000..031ea523c8
--- /dev/null
+++ b/modules/gdscript/tests/scripts/parser/errors/assignment_without_identifier.gd
@@ -0,0 +1,2 @@
+func test():
+ var = "world"
diff --git a/modules/gdscript/tests/scripts/parser/errors/assignment_without_identifier.out b/modules/gdscript/tests/scripts/parser/errors/assignment_without_identifier.out
new file mode 100644
index 0000000000..a4bd8beef1
--- /dev/null
+++ b/modules/gdscript/tests/scripts/parser/errors/assignment_without_identifier.out
@@ -0,0 +1,2 @@
+GDTEST_PARSER_ERROR
+Expected variable name after "var".
diff --git a/modules/gdscript/tests/scripts/parser/errors/binary_complement_without_argument.gd b/modules/gdscript/tests/scripts/parser/errors/binary_complement_without_argument.gd
new file mode 100644
index 0000000000..b52a6defcb
--- /dev/null
+++ b/modules/gdscript/tests/scripts/parser/errors/binary_complement_without_argument.gd
@@ -0,0 +1,2 @@
+func test():
+ print(~)
diff --git a/modules/gdscript/tests/scripts/parser/errors/binary_complement_without_argument.out b/modules/gdscript/tests/scripts/parser/errors/binary_complement_without_argument.out
new file mode 100644
index 0000000000..ceabe42d3c
--- /dev/null
+++ b/modules/gdscript/tests/scripts/parser/errors/binary_complement_without_argument.out
@@ -0,0 +1,2 @@
+GDTEST_PARSER_ERROR
+Expected expression after "~" operator.
diff --git a/modules/gdscript/tests/scripts/parser/errors/boolean_negation_without_argument.gd b/modules/gdscript/tests/scripts/parser/errors/boolean_negation_without_argument.gd
new file mode 100644
index 0000000000..b3ea1ba1f6
--- /dev/null
+++ b/modules/gdscript/tests/scripts/parser/errors/boolean_negation_without_argument.gd
@@ -0,0 +1,2 @@
+func test():
+ print(not)
diff --git a/modules/gdscript/tests/scripts/parser/errors/boolean_negation_without_argument.out b/modules/gdscript/tests/scripts/parser/errors/boolean_negation_without_argument.out
new file mode 100644
index 0000000000..6cf191ea98
--- /dev/null
+++ b/modules/gdscript/tests/scripts/parser/errors/boolean_negation_without_argument.out
@@ -0,0 +1,2 @@
+GDTEST_PARSER_ERROR
+Expected expression after "not" operator.
diff --git a/modules/gdscript/tests/scripts/parser/errors/boolean_negation_without_argument_using_bang.gd b/modules/gdscript/tests/scripts/parser/errors/boolean_negation_without_argument_using_bang.gd
new file mode 100644
index 0000000000..8a33079193
--- /dev/null
+++ b/modules/gdscript/tests/scripts/parser/errors/boolean_negation_without_argument_using_bang.gd
@@ -0,0 +1,2 @@
+func test():
+ print(!)
diff --git a/modules/gdscript/tests/scripts/parser/errors/boolean_negation_without_argument_using_bang.out b/modules/gdscript/tests/scripts/parser/errors/boolean_negation_without_argument_using_bang.out
new file mode 100644
index 0000000000..87fcc5e2b0
--- /dev/null
+++ b/modules/gdscript/tests/scripts/parser/errors/boolean_negation_without_argument_using_bang.out
@@ -0,0 +1,2 @@
+GDTEST_PARSER_ERROR
+Expected expression after "!" operator.
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/class_name_after_annotation.gd b/modules/gdscript/tests/scripts/parser/errors/class_name_after_annotation.gd
new file mode 100644
index 0000000000..d13d713454
--- /dev/null
+++ b/modules/gdscript/tests/scripts/parser/errors/class_name_after_annotation.gd
@@ -0,0 +1,6 @@
+# Error here. `class_name` should be used *before* annotations, not after.
+@icon("res://path/to/optional/icon.svg")
+class_name HelloWorld
+
+func test():
+ pass
diff --git a/modules/gdscript/tests/scripts/parser/errors/class_name_after_annotation.out b/modules/gdscript/tests/scripts/parser/errors/class_name_after_annotation.out
new file mode 100644
index 0000000000..0bcc8acc55
--- /dev/null
+++ b/modules/gdscript/tests/scripts/parser/errors/class_name_after_annotation.out
@@ -0,0 +1,2 @@
+GDTEST_PARSER_ERROR
+"class_name" should be used before annotations.
diff --git a/modules/gdscript/tests/scripts/parser/errors/constant_conflicts_variable.gd b/modules/gdscript/tests/scripts/parser/errors/constant_conflicts_variable.gd
new file mode 100644
index 0000000000..49fb4ffedf
--- /dev/null
+++ b/modules/gdscript/tests/scripts/parser/errors/constant_conflicts_variable.gd
@@ -0,0 +1,3 @@
+func test():
+ var TEST = 50
+ const TEST = 25
diff --git a/modules/gdscript/tests/scripts/parser/errors/constant_conflicts_variable.out b/modules/gdscript/tests/scripts/parser/errors/constant_conflicts_variable.out
new file mode 100644
index 0000000000..407f094ca0
--- /dev/null
+++ b/modules/gdscript/tests/scripts/parser/errors/constant_conflicts_variable.out
@@ -0,0 +1,2 @@
+GDTEST_PARSER_ERROR
+There is already a variable named "TEST" declared in this scope.
diff --git a/modules/gdscript/tests/scripts/parser/errors/default_value_in_function_call.gd b/modules/gdscript/tests/scripts/parser/errors/default_value_in_function_call.gd
new file mode 100644
index 0000000000..2581d873dd
--- /dev/null
+++ b/modules/gdscript/tests/scripts/parser/errors/default_value_in_function_call.gd
@@ -0,0 +1,7 @@
+func hello(arg1):
+ print(arg1)
+
+
+func test():
+ # Error here.
+ hello(arg1 = 25)
diff --git a/modules/gdscript/tests/scripts/parser/errors/default_value_in_function_call.out b/modules/gdscript/tests/scripts/parser/errors/default_value_in_function_call.out
new file mode 100644
index 0000000000..e8f9130706
--- /dev/null
+++ b/modules/gdscript/tests/scripts/parser/errors/default_value_in_function_call.out
@@ -0,0 +1,2 @@
+GDTEST_PARSER_ERROR
+Assignment is not allowed inside an expression.
diff --git a/modules/gdscript/tests/scripts/parser/errors/double_dictionary_comma.gd b/modules/gdscript/tests/scripts/parser/errors/double_dictionary_comma.gd
new file mode 100644
index 0000000000..92dfb2366d
--- /dev/null
+++ b/modules/gdscript/tests/scripts/parser/errors/double_dictionary_comma.gd
@@ -0,0 +1,2 @@
+func test():
+ var dictionary = { hello = "world",, }
diff --git a/modules/gdscript/tests/scripts/parser/errors/double_dictionary_comma.out b/modules/gdscript/tests/scripts/parser/errors/double_dictionary_comma.out
new file mode 100644
index 0000000000..d1dcd1cb4b
--- /dev/null
+++ b/modules/gdscript/tests/scripts/parser/errors/double_dictionary_comma.out
@@ -0,0 +1,2 @@
+GDTEST_PARSER_ERROR
+Expected expression as dictionary key.
diff --git a/modules/gdscript/tests/scripts/parser/errors/function_conflicts_constant.gd b/modules/gdscript/tests/scripts/parser/errors/function_conflicts_constant.gd
new file mode 100644
index 0000000000..a8f7cf1810
--- /dev/null
+++ b/modules/gdscript/tests/scripts/parser/errors/function_conflicts_constant.gd
@@ -0,0 +1,5 @@
+const test = 25
+
+
+func test():
+ pass
diff --git a/modules/gdscript/tests/scripts/parser/errors/function_conflicts_constant.out b/modules/gdscript/tests/scripts/parser/errors/function_conflicts_constant.out
new file mode 100644
index 0000000000..c614acd094
--- /dev/null
+++ b/modules/gdscript/tests/scripts/parser/errors/function_conflicts_constant.out
@@ -0,0 +1,2 @@
+GDTEST_PARSER_ERROR
+Function "test" has the same name as a previously declared constant.
diff --git a/modules/gdscript/tests/scripts/parser/errors/function_conflicts_variable.gd b/modules/gdscript/tests/scripts/parser/errors/function_conflicts_variable.gd
new file mode 100644
index 0000000000..5c86710a40
--- /dev/null
+++ b/modules/gdscript/tests/scripts/parser/errors/function_conflicts_variable.gd
@@ -0,0 +1,7 @@
+func test():
+ pass
+
+
+# Error here. The difference with `variable-conflicts-function.gd` is that here,
+# the function is defined *before* the variable.
+var test = 25
diff --git a/modules/gdscript/tests/scripts/parser/errors/function_conflicts_variable.out b/modules/gdscript/tests/scripts/parser/errors/function_conflicts_variable.out
new file mode 100644
index 0000000000..551db61531
--- /dev/null
+++ b/modules/gdscript/tests/scripts/parser/errors/function_conflicts_variable.out
@@ -0,0 +1,2 @@
+GDTEST_PARSER_ERROR
+Variable "test" has the same name as a previously declared function.
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_identifier_number.gd b/modules/gdscript/tests/scripts/parser/errors/invalid_identifier_number.gd
new file mode 100644
index 0000000000..081b9faf4b
--- /dev/null
+++ b/modules/gdscript/tests/scripts/parser/errors/invalid_identifier_number.gd
@@ -0,0 +1,3 @@
+func test():
+ # Error here.
+ var 23test = "is not a valid identifier"
diff --git a/modules/gdscript/tests/scripts/parser/errors/invalid_identifier_number.out b/modules/gdscript/tests/scripts/parser/errors/invalid_identifier_number.out
new file mode 100644
index 0000000000..a4bd8beef1
--- /dev/null
+++ b/modules/gdscript/tests/scripts/parser/errors/invalid_identifier_number.out
@@ -0,0 +1,2 @@
+GDTEST_PARSER_ERROR
+Expected variable name after "var".
diff --git a/modules/gdscript/tests/scripts/parser/errors/invalid_identifier_string.gd b/modules/gdscript/tests/scripts/parser/errors/invalid_identifier_string.gd
new file mode 100644
index 0000000000..fa4d6b5cac
--- /dev/null
+++ b/modules/gdscript/tests/scripts/parser/errors/invalid_identifier_string.gd
@@ -0,0 +1,3 @@
+func test():
+ # Error here.
+ var "yes" = "is not a valid identifier"
diff --git a/modules/gdscript/tests/scripts/parser/errors/invalid_identifier_string.out b/modules/gdscript/tests/scripts/parser/errors/invalid_identifier_string.out
new file mode 100644
index 0000000000..a4bd8beef1
--- /dev/null
+++ b/modules/gdscript/tests/scripts/parser/errors/invalid_identifier_string.out
@@ -0,0 +1,2 @@
+GDTEST_PARSER_ERROR
+Expected variable name after "var".
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/missing_argument.gd b/modules/gdscript/tests/scripts/parser/errors/missing_argument.gd
deleted file mode 100644
index c56ad94095..0000000000
--- a/modules/gdscript/tests/scripts/parser/errors/missing_argument.gd
+++ /dev/null
@@ -1,6 +0,0 @@
-func args(a, b):
- print(a)
- print(b)
-
-func test():
- args(1,)
diff --git a/modules/gdscript/tests/scripts/parser/errors/missing_closing_expr_paren.gd b/modules/gdscript/tests/scripts/parser/errors/missing_closing_expr_paren.gd
index a1077e1985..8af5f123cc 100644
--- a/modules/gdscript/tests/scripts/parser/errors/missing_closing_expr_paren.gd
+++ b/modules/gdscript/tests/scripts/parser/errors/missing_closing_expr_paren.gd
@@ -1,2 +1,2 @@
func test():
- var a = ("missing paren ->"
+ var a = ("missing paren ->"
diff --git a/modules/gdscript/tests/scripts/parser/errors/missing_colon.gd b/modules/gdscript/tests/scripts/parser/errors/missing_colon.gd
index 62cb633e9e..0e5e5ce060 100644
--- a/modules/gdscript/tests/scripts/parser/errors/missing_colon.gd
+++ b/modules/gdscript/tests/scripts/parser/errors/missing_colon.gd
@@ -1,3 +1,3 @@
func test():
- if true # Missing colon here.
- print("true")
+ if true # Missing colon here.
+ print("true")
diff --git a/modules/gdscript/tests/scripts/parser/errors/missing_expression_after_ternary_else.gd b/modules/gdscript/tests/scripts/parser/errors/missing_expression_after_ternary_else.gd
new file mode 100644
index 0000000000..1f66935329
--- /dev/null
+++ b/modules/gdscript/tests/scripts/parser/errors/missing_expression_after_ternary_else.gd
@@ -0,0 +1,4 @@
+func test():
+ var x = 1 if false else
+ print("oops")
+ print(x)
diff --git a/modules/gdscript/tests/scripts/parser/errors/missing_expression_after_ternary_else.out b/modules/gdscript/tests/scripts/parser/errors/missing_expression_after_ternary_else.out
new file mode 100644
index 0000000000..dab6b0a1ad
--- /dev/null
+++ b/modules/gdscript/tests/scripts/parser/errors/missing_expression_after_ternary_else.out
@@ -0,0 +1,2 @@
+GDTEST_PARSER_ERROR
+Expected expression after "else".
diff --git a/modules/gdscript/tests/scripts/parser/errors/missing_paren_after_args.gd b/modules/gdscript/tests/scripts/parser/errors/missing_paren_after_args.gd
index 116b0151da..7a35bf688c 100644
--- a/modules/gdscript/tests/scripts/parser/errors/missing_paren_after_args.gd
+++ b/modules/gdscript/tests/scripts/parser/errors/missing_paren_after_args.gd
@@ -1,6 +1,6 @@
func args(a, b):
- print(a)
- print(b)
+ print(a)
+ print(b)
func test():
- args(1,2
+ args(1,2
diff --git a/modules/gdscript/tests/scripts/parser/errors/mistaken_decrement_operator.gd b/modules/gdscript/tests/scripts/parser/errors/mistaken_decrement_operator.gd
new file mode 100644
index 0000000000..193f824702
--- /dev/null
+++ b/modules/gdscript/tests/scripts/parser/errors/mistaken_decrement_operator.gd
@@ -0,0 +1,3 @@
+func test():
+ var a = 0
+ print(a--)
diff --git a/modules/gdscript/tests/scripts/parser/errors/mistaken_decrement_operator.out b/modules/gdscript/tests/scripts/parser/errors/mistaken_decrement_operator.out
new file mode 100644
index 0000000000..b6b577a277
--- /dev/null
+++ b/modules/gdscript/tests/scripts/parser/errors/mistaken_decrement_operator.out
@@ -0,0 +1,2 @@
+GDTEST_PARSER_ERROR
+Expected expression after "-" operator.
diff --git a/modules/gdscript/tests/scripts/parser/errors/mistaken_increment_operator.gd b/modules/gdscript/tests/scripts/parser/errors/mistaken_increment_operator.gd
new file mode 100644
index 0000000000..035d27638c
--- /dev/null
+++ b/modules/gdscript/tests/scripts/parser/errors/mistaken_increment_operator.gd
@@ -0,0 +1,3 @@
+func test():
+ var a = 0
+ print(a++)
diff --git a/modules/gdscript/tests/scripts/parser/errors/mistaken_increment_operator.out b/modules/gdscript/tests/scripts/parser/errors/mistaken_increment_operator.out
new file mode 100644
index 0000000000..24eb76593a
--- /dev/null
+++ b/modules/gdscript/tests/scripts/parser/errors/mistaken_increment_operator.out
@@ -0,0 +1,2 @@
+GDTEST_PARSER_ERROR
+Expected expression after "+" operator.
diff --git a/modules/gdscript/tests/scripts/parser/errors/multiple_number_separators.gd b/modules/gdscript/tests/scripts/parser/errors/multiple_number_separators.gd
new file mode 100644
index 0000000000..71a03fbc0d
--- /dev/null
+++ b/modules/gdscript/tests/scripts/parser/errors/multiple_number_separators.gd
@@ -0,0 +1,3 @@
+func test():
+ # Number separators may not be placed right next to each other.
+ var __ = 1__23
diff --git a/modules/gdscript/tests/scripts/parser/errors/multiple_number_separators.out b/modules/gdscript/tests/scripts/parser/errors/multiple_number_separators.out
new file mode 100644
index 0000000000..71a3c2fd6a
--- /dev/null
+++ b/modules/gdscript/tests/scripts/parser/errors/multiple_number_separators.out
@@ -0,0 +1,2 @@
+GDTEST_PARSER_ERROR
+Only one underscore can be used as a numeric separator.
diff --git a/modules/gdscript/tests/scripts/parser/errors/nothing_after_dollar.gd b/modules/gdscript/tests/scripts/parser/errors/nothing_after_dollar.gd
index 3875ce3936..df388a21de 100644
--- a/modules/gdscript/tests/scripts/parser/errors/nothing_after_dollar.gd
+++ b/modules/gdscript/tests/scripts/parser/errors/nothing_after_dollar.gd
@@ -1,3 +1,5 @@
extends Node
+
+
func test():
- var a = $ # Expected some node path.
+ var a = $ # Expected some node path.
diff --git a/modules/gdscript/tests/scripts/parser/errors/redefine_keyword.gd b/modules/gdscript/tests/scripts/parser/errors/redefine_keyword.gd
new file mode 100644
index 0000000000..c289c9d976
--- /dev/null
+++ b/modules/gdscript/tests/scripts/parser/errors/redefine_keyword.gd
@@ -0,0 +1,2 @@
+func test():
+ var while = "it's been a while"
diff --git a/modules/gdscript/tests/scripts/parser/errors/redefine_keyword.out b/modules/gdscript/tests/scripts/parser/errors/redefine_keyword.out
new file mode 100644
index 0000000000..a4bd8beef1
--- /dev/null
+++ b/modules/gdscript/tests/scripts/parser/errors/redefine_keyword.out
@@ -0,0 +1,2 @@
+GDTEST_PARSER_ERROR
+Expected variable name after "var".
diff --git a/modules/gdscript/tests/scripts/parser/errors/redefine_local_constant_with_keyword.gd b/modules/gdscript/tests/scripts/parser/errors/redefine_local_constant_with_keyword.gd
new file mode 100644
index 0000000000..204259f981
--- /dev/null
+++ b/modules/gdscript/tests/scripts/parser/errors/redefine_local_constant_with_keyword.gd
@@ -0,0 +1,5 @@
+func test():
+ const TEST = 25
+
+ # Error here (can't redeclare a constant on the same scope).
+ const TEST = 50
diff --git a/modules/gdscript/tests/scripts/parser/errors/redefine_local_constant_with_keyword.out b/modules/gdscript/tests/scripts/parser/errors/redefine_local_constant_with_keyword.out
new file mode 100644
index 0000000000..d67cc92953
--- /dev/null
+++ b/modules/gdscript/tests/scripts/parser/errors/redefine_local_constant_with_keyword.out
@@ -0,0 +1,2 @@
+GDTEST_PARSER_ERROR
+There is already a constant named "TEST" declared in this scope.
diff --git a/modules/gdscript/tests/scripts/parser/errors/subscript_without_index.gd b/modules/gdscript/tests/scripts/parser/errors/subscript_without_index.gd
new file mode 100644
index 0000000000..c30c05e4da
--- /dev/null
+++ b/modules/gdscript/tests/scripts/parser/errors/subscript_without_index.gd
@@ -0,0 +1,3 @@
+func test():
+ var array = [1, 2, 3]
+ array[] = 4
diff --git a/modules/gdscript/tests/scripts/parser/errors/subscript_without_index.out b/modules/gdscript/tests/scripts/parser/errors/subscript_without_index.out
new file mode 100644
index 0000000000..7017c7b4aa
--- /dev/null
+++ b/modules/gdscript/tests/scripts/parser/errors/subscript_without_index.out
@@ -0,0 +1,2 @@
+GDTEST_PARSER_ERROR
+Expected expression after "[".
diff --git a/modules/gdscript/tests/scripts/parser/errors/variable_conflicts_constant.gd b/modules/gdscript/tests/scripts/parser/errors/variable_conflicts_constant.gd
new file mode 100644
index 0000000000..0d8843df20
--- /dev/null
+++ b/modules/gdscript/tests/scripts/parser/errors/variable_conflicts_constant.gd
@@ -0,0 +1,3 @@
+func test():
+ const TEST = 25
+ var TEST = 50
diff --git a/modules/gdscript/tests/scripts/parser/errors/variable_conflicts_constant.out b/modules/gdscript/tests/scripts/parser/errors/variable_conflicts_constant.out
new file mode 100644
index 0000000000..d67cc92953
--- /dev/null
+++ b/modules/gdscript/tests/scripts/parser/errors/variable_conflicts_constant.out
@@ -0,0 +1,2 @@
+GDTEST_PARSER_ERROR
+There is already a constant named "TEST" declared in this scope.
diff --git a/modules/gdscript/tests/scripts/parser/errors/variable_conflicts_function.gd b/modules/gdscript/tests/scripts/parser/errors/variable_conflicts_function.gd
new file mode 100644
index 0000000000..ce2c8784d6
--- /dev/null
+++ b/modules/gdscript/tests/scripts/parser/errors/variable_conflicts_function.gd
@@ -0,0 +1,6 @@
+var test = 25
+
+# Error here. The difference with `variable-conflicts-function.gd` is that here,
+# the function is defined *before* the variable.
+func test():
+ pass
diff --git a/modules/gdscript/tests/scripts/parser/errors/variable_conflicts_function.out b/modules/gdscript/tests/scripts/parser/errors/variable_conflicts_function.out
new file mode 100644
index 0000000000..daeaca40ec
--- /dev/null
+++ b/modules/gdscript/tests/scripts/parser/errors/variable_conflicts_function.out
@@ -0,0 +1,2 @@
+GDTEST_PARSER_ERROR
+Function "test" has the same name as a previously declared variable.
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/wrong_value_after_dollar.gd b/modules/gdscript/tests/scripts/parser/errors/wrong_value_after_dollar.gd
index 6fd2692d47..babe39068c 100644
--- a/modules/gdscript/tests/scripts/parser/errors/wrong_value_after_dollar.gd
+++ b/modules/gdscript/tests/scripts/parser/errors/wrong_value_after_dollar.gd
@@ -1,3 +1,5 @@
extends Node
+
+
func test():
- $23 # Can't use number here.
+ $23 # Can't use number here.
diff --git a/modules/gdscript/tests/scripts/parser/errors/wrong_value_after_dollar_slash.gd b/modules/gdscript/tests/scripts/parser/errors/wrong_value_after_dollar_slash.gd
index 1836d42226..b6b1cf3e52 100644
--- a/modules/gdscript/tests/scripts/parser/errors/wrong_value_after_dollar_slash.gd
+++ b/modules/gdscript/tests/scripts/parser/errors/wrong_value_after_dollar_slash.gd
@@ -1,3 +1,5 @@
extends Node
+
+
func test():
- $MyNode/23 # Can't use number here.
+ $MyNode/23 # Can't use number here.
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/advanced_expression_matching.gd b/modules/gdscript/tests/scripts/parser/features/advanced_expression_matching.gd
new file mode 100644
index 0000000000..43b513045b
--- /dev/null
+++ b/modules/gdscript/tests/scripts/parser/features/advanced_expression_matching.gd
@@ -0,0 +1,34 @@
+func foo(x):
+ match x:
+ 1 + 1:
+ print("1+1")
+ [1,2,[1,{1:2,2:var z,..}]]:
+ print("[1,2,[1,{1:2,2:var z,..}]]")
+ print(z)
+ 1 if true else 2:
+ print("1 if true else 2")
+ 1 < 2:
+ print("1 < 2")
+ 1 or 2 and 1:
+ print("1 or 2 and 1")
+ 6 | 1:
+ print("1 | 1")
+ 1 >> 1:
+ print("1 >> 1")
+ 1, 2 or 3, 4:
+ print("1, 2 or 3, 4")
+ _:
+ print("wildcard")
+
+func test():
+ foo(6 | 1)
+ foo(1 >> 1)
+ foo(2)
+ foo(1)
+ foo(1+1)
+ foo(1 < 2)
+ foo([2, 1])
+ foo(4)
+ foo([1, 2, [1, {1 : 2, 2:3}]])
+ foo([1, 2, [1, {1 : 2, 2:[1,3,5, "123"], 4:2}]])
+ foo([1, 2, [1, {1 : 2}]])
diff --git a/modules/gdscript/tests/scripts/parser/features/advanced_expression_matching.out b/modules/gdscript/tests/scripts/parser/features/advanced_expression_matching.out
new file mode 100644
index 0000000000..67c7e28046
--- /dev/null
+++ b/modules/gdscript/tests/scripts/parser/features/advanced_expression_matching.out
@@ -0,0 +1,14 @@
+GDTEST_OK
+1 | 1
+1 >> 1
+1+1
+1 if true else 2
+1+1
+1 < 2
+wildcard
+1, 2 or 3, 4
+[1,2,[1,{1:2,2:var z,..}]]
+3
+[1,2,[1,{1:2,2:var z,..}]]
+[1, 3, 5, 123]
+wildcard
diff --git a/modules/gdscript/tests/scripts/parser/features/array.gd b/modules/gdscript/tests/scripts/parser/features/array.gd
new file mode 100644
index 0000000000..828ce8d134
--- /dev/null
+++ b/modules/gdscript/tests/scripts/parser/features/array.gd
@@ -0,0 +1,16 @@
+func test():
+ # Indexing from the beginning:
+ print([1, 2, 3][0])
+ print([1, 2, 3][1])
+ print([1, 2, 3][2])
+
+ # Indexing from the end:
+ print([1, 2, 3][-1])
+ print([1, 2, 3][-2])
+ print([1, 2, 3][-3])
+
+ # Float indices are currently allowed, but should probably be an error?
+ print([1, 2, 3][0.4])
+ print([1, 2, 3][0.8])
+ print([1, 2, 3][1.0])
+ print([1, 2, 3][-1.0])
diff --git a/modules/gdscript/tests/scripts/parser/features/array.out b/modules/gdscript/tests/scripts/parser/features/array.out
new file mode 100644
index 0000000000..cf576c59e0
--- /dev/null
+++ b/modules/gdscript/tests/scripts/parser/features/array.out
@@ -0,0 +1,11 @@
+GDTEST_OK
+1
+2
+3
+3
+2
+1
+1
+1
+2
+3
diff --git a/modules/gdscript/tests/scripts/parser/features/basic_expression_matching.gd b/modules/gdscript/tests/scripts/parser/features/basic_expression_matching.gd
new file mode 100644
index 0000000000..2b46f1e88a
--- /dev/null
+++ b/modules/gdscript/tests/scripts/parser/features/basic_expression_matching.gd
@@ -0,0 +1,27 @@
+func foo(x):
+ match x:
+ 1:
+ print("1")
+ 2:
+ print("2")
+ [1, 2]:
+ print("[1, 2]")
+ 3 or 4:
+ print("3 or 4")
+ 4:
+ print("4")
+ {1 : 2, 2 : 3}:
+ print("{1 : 2, 2 : 3}")
+ _:
+ print("wildcard")
+
+func test():
+ foo(0)
+ foo(1)
+ foo(2)
+ foo([1, 2])
+ foo(3)
+ foo(4)
+ foo([4,4])
+ foo({1 : 2, 2 : 3})
+ foo({1 : 2, 4 : 3})
diff --git a/modules/gdscript/tests/scripts/parser/features/basic_expression_matching.out b/modules/gdscript/tests/scripts/parser/features/basic_expression_matching.out
new file mode 100644
index 0000000000..46ee4b04da
--- /dev/null
+++ b/modules/gdscript/tests/scripts/parser/features/basic_expression_matching.out
@@ -0,0 +1,10 @@
+GDTEST_OK
+wildcard
+1
+2
+[1, 2]
+wildcard
+4
+wildcard
+{1 : 2, 2 : 3}
+wildcard
diff --git a/modules/gdscript/tests/scripts/parser/features/bitwise_operators.gd b/modules/gdscript/tests/scripts/parser/features/bitwise_operators.gd
new file mode 100644
index 0000000000..de502c6ed1
--- /dev/null
+++ b/modules/gdscript/tests/scripts/parser/features/bitwise_operators.gd
@@ -0,0 +1,50 @@
+enum Flags {
+ FIRE = 1 << 1,
+ ICE = 1 << 2,
+ SLIPPERY = 1 << 3,
+ STICKY = 1 << 4,
+ NONSOLID = 1 << 5,
+
+ ALL = FIRE | ICE | SLIPPERY | STICKY | NONSOLID,
+}
+
+
+func test():
+ var flags = Flags.FIRE | Flags.SLIPPERY
+ print(flags)
+
+ flags = Flags.FIRE & Flags.SLIPPERY
+ print(flags)
+
+ flags = Flags.FIRE ^ Flags.SLIPPERY
+ print(flags)
+
+ flags = Flags.ALL & (Flags.FIRE | Flags.ICE)
+ print(flags)
+
+ flags = (Flags.ALL & Flags.FIRE) | Flags.ICE
+ print(flags)
+
+ flags = Flags.ALL & Flags.FIRE | Flags.ICE
+ print(flags)
+
+ # Enum value must be casted to an integer. Otherwise, a parser error is emitted.
+ flags &= int(Flags.ICE)
+ print(flags)
+
+ flags ^= int(Flags.ICE)
+ print(flags)
+
+ flags |= int(Flags.STICKY | Flags.SLIPPERY)
+ print(flags)
+
+ print()
+
+ var num = 2 << 4
+ print(num)
+
+ num <<= 2
+ print(num)
+
+ num >>= 2
+ print(num)
diff --git a/modules/gdscript/tests/scripts/parser/features/bitwise_operators.out b/modules/gdscript/tests/scripts/parser/features/bitwise_operators.out
new file mode 100644
index 0000000000..410e358a05
--- /dev/null
+++ b/modules/gdscript/tests/scripts/parser/features/bitwise_operators.out
@@ -0,0 +1,14 @@
+GDTEST_OK
+10
+0
+10
+6
+6
+6
+4
+0
+24
+
+32
+128
+32
diff --git a/modules/gdscript/tests/scripts/parser/features/class.gd b/modules/gdscript/tests/scripts/parser/features/class.gd
new file mode 100644
index 0000000000..6652f85ad9
--- /dev/null
+++ b/modules/gdscript/tests/scripts/parser/features/class.gd
@@ -0,0 +1,25 @@
+# Test non-nested/slightly nested class architecture.
+class Test:
+ var number = 25
+ var string = "hello"
+
+
+class TestSub extends Test:
+ var other_string = "bye"
+
+
+class TestConstructor:
+ func _init(argument = 10):
+ print(str("constructor with argument ", argument))
+
+
+func test():
+ var test_instance = Test.new()
+ test_instance.number = 42
+
+ var test_sub = TestSub.new()
+ assert(test_sub.number == 25) # From Test.
+ assert(test_sub.other_string == "bye") # From TestSub.
+
+ TestConstructor.new()
+ TestConstructor.new(500)
diff --git a/modules/gdscript/tests/scripts/parser/features/class.out b/modules/gdscript/tests/scripts/parser/features/class.out
new file mode 100644
index 0000000000..94dc2d6003
--- /dev/null
+++ b/modules/gdscript/tests/scripts/parser/features/class.out
@@ -0,0 +1,3 @@
+GDTEST_OK
+constructor with argument 10
+constructor with argument 500
diff --git a/modules/gdscript/tests/scripts/parser/features/class_inheritance.gd b/modules/gdscript/tests/scripts/parser/features/class_inheritance.gd
new file mode 100644
index 0000000000..3f9b4ea86e
--- /dev/null
+++ b/modules/gdscript/tests/scripts/parser/features/class_inheritance.gd
@@ -0,0 +1,33 @@
+# Test deeply nested class architectures.
+class Test:
+ var depth = 1
+
+ class Nested:
+ var depth_nested = 10
+
+
+class Test2 extends Test:
+ var depth2 = 2
+
+
+class Test3 extends Test2:
+ var depth3 = 3
+
+
+class Test4 extends Test3:
+ var depth4 = 4
+
+ class Nested2:
+ var depth4_nested = 100
+
+
+func test():
+ print(Test.new().depth)
+ print(Test2.new().depth)
+ print(Test2.new().depth2)
+ print(Test3.new().depth)
+ print(Test3.new().depth3)
+ print(Test4.new().depth)
+ print(Test4.new().depth4)
+ print(Test.Nested.new().depth_nested)
+ print(Test4.Nested2.new().depth4_nested)
diff --git a/modules/gdscript/tests/scripts/parser/features/class_inheritance.out b/modules/gdscript/tests/scripts/parser/features/class_inheritance.out
new file mode 100644
index 0000000000..75bdde3d94
--- /dev/null
+++ b/modules/gdscript/tests/scripts/parser/features/class_inheritance.out
@@ -0,0 +1,10 @@
+GDTEST_OK
+1
+1
+2
+1
+3
+1
+4
+10
+100
diff --git a/modules/gdscript/tests/scripts/parser/features/class_name.gd b/modules/gdscript/tests/scripts/parser/features/class_name.gd
new file mode 100644
index 0000000000..8bd188e247
--- /dev/null
+++ b/modules/gdscript/tests/scripts/parser/features/class_name.gd
@@ -0,0 +1,5 @@
+class_name HelloWorld
+@icon("res://path/to/optional/icon.svg")
+
+func test():
+ pass
diff --git a/modules/gdscript/tests/scripts/parser/features/class_name.out b/modules/gdscript/tests/scripts/parser/features/class_name.out
new file mode 100644
index 0000000000..d73c5eb7cd
--- /dev/null
+++ b/modules/gdscript/tests/scripts/parser/features/class_name.out
@@ -0,0 +1 @@
+GDTEST_OK
diff --git a/modules/gdscript/tests/scripts/parser/features/concatenation.gd b/modules/gdscript/tests/scripts/parser/features/concatenation.gd
new file mode 100644
index 0000000000..e8335c9823
--- /dev/null
+++ b/modules/gdscript/tests/scripts/parser/features/concatenation.gd
@@ -0,0 +1,4 @@
+func test():
+ print(20 + 20)
+ print("hello" + "world")
+ print([1, 2] + [3, 4])
diff --git a/modules/gdscript/tests/scripts/parser/features/concatenation.out b/modules/gdscript/tests/scripts/parser/features/concatenation.out
new file mode 100644
index 0000000000..23bff08f49
--- /dev/null
+++ b/modules/gdscript/tests/scripts/parser/features/concatenation.out
@@ -0,0 +1,4 @@
+GDTEST_OK
+40
+helloworld
+[1, 2, 3, 4]
diff --git a/modules/gdscript/tests/scripts/parser/features/constants.gd b/modules/gdscript/tests/scripts/parser/features/constants.gd
new file mode 100644
index 0000000000..013c9c074f
--- /dev/null
+++ b/modules/gdscript/tests/scripts/parser/features/constants.gd
@@ -0,0 +1,11 @@
+func test():
+ const _TEST = 12 + 34 - 56 * 78
+ const _STRING = "yes"
+ const _VECTOR = Vector2(5, 6)
+ const _ARRAY = []
+ const _DICTIONARY = {"this": "dictionary"}
+
+ # Create user constants from built-in constants.
+ const _HELLO = PI + TAU
+ const _INFINITY = INF
+ const _NOT_A_NUMBER = NAN
diff --git a/modules/gdscript/tests/scripts/parser/features/constants.out b/modules/gdscript/tests/scripts/parser/features/constants.out
new file mode 100644
index 0000000000..6093e4a6ca
--- /dev/null
+++ b/modules/gdscript/tests/scripts/parser/features/constants.out
@@ -0,0 +1,33 @@
+GDTEST_OK
+>> WARNING
+>> Line: 2
+>> UNUSED_LOCAL_CONSTANT
+>> The local constant '_TEST' is declared but never used in the block. If this is intended, prefix it with an underscore: '__TEST'
+>> WARNING
+>> Line: 3
+>> UNUSED_LOCAL_CONSTANT
+>> The local constant '_STRING' is declared but never used in the block. If this is intended, prefix it with an underscore: '__STRING'
+>> WARNING
+>> Line: 4
+>> UNUSED_LOCAL_CONSTANT
+>> The local constant '_VECTOR' is declared but never used in the block. If this is intended, prefix it with an underscore: '__VECTOR'
+>> WARNING
+>> Line: 5
+>> UNUSED_LOCAL_CONSTANT
+>> The local constant '_ARRAY' is declared but never used in the block. If this is intended, prefix it with an underscore: '__ARRAY'
+>> WARNING
+>> Line: 6
+>> UNUSED_LOCAL_CONSTANT
+>> The local constant '_DICTIONARY' is declared but never used in the block. If this is intended, prefix it with an underscore: '__DICTIONARY'
+>> WARNING
+>> Line: 9
+>> UNUSED_LOCAL_CONSTANT
+>> The local constant '_HELLO' is declared but never used in the block. If this is intended, prefix it with an underscore: '__HELLO'
+>> WARNING
+>> Line: 10
+>> UNUSED_LOCAL_CONSTANT
+>> The local constant '_INFINITY' is declared but never used in the block. If this is intended, prefix it with an underscore: '__INFINITY'
+>> WARNING
+>> Line: 11
+>> UNUSED_LOCAL_CONSTANT
+>> The local constant '_NOT_A_NUMBER' is declared but never used in the block. If this is intended, prefix it with an underscore: '__NOT_A_NUMBER'
diff --git a/modules/gdscript/tests/scripts/parser/features/dictionary.gd b/modules/gdscript/tests/scripts/parser/features/dictionary.gd
new file mode 100644
index 0000000000..99afe166c7
--- /dev/null
+++ b/modules/gdscript/tests/scripts/parser/features/dictionary.gd
@@ -0,0 +1,37 @@
+func test():
+ # Non-string keys are valid.
+ print({ 12: "world" }[12])
+
+ var contents = {
+ 0: "zero",
+ 0.0: "zero point zero",
+ null: "null",
+ false: "false",
+ []: "empty array",
+ Vector2i(): "zero Vector2i",
+ 15: {
+ 22: {
+ 4: ["nesting", "arrays"],
+ },
+ },
+ }
+
+ print(contents[0.0])
+ # Making sure declaration order doesn't affect things...
+ print({ 0.0: "zero point zero", 0: "zero", null: "null", false: "false", []: "empty array" }[0])
+ print({ 0.0: "zero point zero", 0: "zero", null: "null", false: "false", []: "empty array" }[0.0])
+
+ print(contents[null])
+ print(contents[false])
+ print(contents[[]])
+ print(contents[Vector2i()])
+ print(contents[15])
+ print(contents[15][22])
+ print(contents[15][22][4])
+ print(contents[15][22][4][0])
+ print(contents[15][22][4][1])
+
+ # Currently fails with "invalid get index 'hello' on base Dictionary".
+ # Both syntaxes are valid however.
+ #print({ "hello": "world" }["hello"])
+ #print({ "hello": "world" }.hello)
diff --git a/modules/gdscript/tests/scripts/parser/features/dictionary.out b/modules/gdscript/tests/scripts/parser/features/dictionary.out
new file mode 100644
index 0000000000..54083c1afc
--- /dev/null
+++ b/modules/gdscript/tests/scripts/parser/features/dictionary.out
@@ -0,0 +1,14 @@
+GDTEST_OK
+world
+zero point zero
+zero
+zero point zero
+null
+false
+empty array
+zero Vector2i
+{22:{4:[nesting, arrays]}}
+{4:[nesting, arrays]}
+[nesting, arrays]
+nesting
+arrays
diff --git a/modules/gdscript/tests/scripts/parser/features/dictionary_lua_style.gd b/modules/gdscript/tests/scripts/parser/features/dictionary_lua_style.gd
new file mode 100644
index 0000000000..fdd6de2348
--- /dev/null
+++ b/modules/gdscript/tests/scripts/parser/features/dictionary_lua_style.gd
@@ -0,0 +1,9 @@
+func test():
+ var lua_dict = {
+ a = 1,
+ "b" = 2, # Using strings are allowed too.
+ "with spaces" = 3, # Especially useful when key has spaces...
+ "2" = 4, # ... or invalid identifiers.
+ }
+
+ print(lua_dict)
diff --git a/modules/gdscript/tests/scripts/parser/features/dictionary_lua_style.out b/modules/gdscript/tests/scripts/parser/features/dictionary_lua_style.out
new file mode 100644
index 0000000000..447d7e223c
--- /dev/null
+++ b/modules/gdscript/tests/scripts/parser/features/dictionary_lua_style.out
@@ -0,0 +1,2 @@
+GDTEST_OK
+{2:4, a:1, b:2, with spaces:3}
diff --git a/modules/gdscript/tests/scripts/parser/features/dictionary_mixed_syntax.gd b/modules/gdscript/tests/scripts/parser/features/dictionary_mixed_syntax.gd
new file mode 100644
index 0000000000..cce8538ddd
--- /dev/null
+++ b/modules/gdscript/tests/scripts/parser/features/dictionary_mixed_syntax.gd
@@ -0,0 +1,12 @@
+func test():
+ # Mixing Python-style and Lua-style syntax in the same dictionary declaration
+ # is allowed.
+ var dict = {
+ "hello": {
+ world = {
+ "is": "beautiful",
+ },
+ },
+ }
+
+ print(dict)
diff --git a/modules/gdscript/tests/scripts/parser/features/dictionary_mixed_syntax.out b/modules/gdscript/tests/scripts/parser/features/dictionary_mixed_syntax.out
new file mode 100644
index 0000000000..62be807a1f
--- /dev/null
+++ b/modules/gdscript/tests/scripts/parser/features/dictionary_mixed_syntax.out
@@ -0,0 +1,2 @@
+GDTEST_OK
+{hello:{world:{is:beautiful}}}
diff --git a/modules/gdscript/tests/scripts/parser/features/dollar_node_paths.gd b/modules/gdscript/tests/scripts/parser/features/dollar_node_paths.gd
new file mode 100644
index 0000000000..8ba558e91d
--- /dev/null
+++ b/modules/gdscript/tests/scripts/parser/features/dollar_node_paths.gd
@@ -0,0 +1,20 @@
+extends Node
+
+
+func test():
+ # Create the required node structure.
+ var hello = Node.new()
+ hello.name = "Hello"
+ add_child(hello)
+ var world = Node.new()
+ world.name = "World"
+ hello.add_child(world)
+
+ # All the ways of writing node paths below with the `$` operator are valid.
+ # Results are assigned to variables to avoid warnings.
+ var __ = $Hello
+ __ = $"Hello"
+ __ = $Hello/World
+ __ = $"Hello/World"
+ __ = $"Hello/.."
+ __ = $"Hello/../Hello/World"
diff --git a/modules/gdscript/tests/scripts/parser/features/dollar_node_paths.out b/modules/gdscript/tests/scripts/parser/features/dollar_node_paths.out
new file mode 100644
index 0000000000..d73c5eb7cd
--- /dev/null
+++ b/modules/gdscript/tests/scripts/parser/features/dollar_node_paths.out
@@ -0,0 +1 @@
+GDTEST_OK
diff --git a/modules/gdscript/tests/scripts/parser/features/enum.gd b/modules/gdscript/tests/scripts/parser/features/enum.gd
new file mode 100644
index 0000000000..bbc66f6f3d
--- /dev/null
+++ b/modules/gdscript/tests/scripts/parser/features/enum.gd
@@ -0,0 +1,14 @@
+enum Size {
+ S = -10,
+ M,
+ L = 0,
+ XL = 10,
+ XXL,
+}
+
+func test():
+ print(Size.S)
+ print(Size.M)
+ print(Size.L)
+ print(Size.XL)
+ print(Size.XXL)
diff --git a/modules/gdscript/tests/scripts/parser/features/enum.out b/modules/gdscript/tests/scripts/parser/features/enum.out
new file mode 100644
index 0000000000..6f3a4a3e49
--- /dev/null
+++ b/modules/gdscript/tests/scripts/parser/features/enum.out
@@ -0,0 +1,6 @@
+GDTEST_OK
+-10
+-9
+0
+10
+11
diff --git a/modules/gdscript/tests/scripts/parser/features/export_variable.gd b/modules/gdscript/tests/scripts/parser/features/export_variable.gd
new file mode 100644
index 0000000000..1e072728fc
--- /dev/null
+++ b/modules/gdscript/tests/scripts/parser/features/export_variable.gd
@@ -0,0 +1,18 @@
+@export var example = 99
+@export_range(0, 100) var example_range = 100
+@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
new file mode 100644
index 0000000000..bae35e75c6
--- /dev/null
+++ b/modules/gdscript/tests/scripts/parser/features/export_variable.out
@@ -0,0 +1,8 @@
+GDTEST_OK
+99
+100
+101
+102
+(0, 0, 0, 1)
+(0, 0, 0, 1)
+hello
diff --git a/modules/gdscript/tests/scripts/parser/features/float_notation.gd b/modules/gdscript/tests/scripts/parser/features/float_notation.gd
new file mode 100644
index 0000000000..b207b88820
--- /dev/null
+++ b/modules/gdscript/tests/scripts/parser/features/float_notation.gd
@@ -0,0 +1,24 @@
+func test():
+ # The following floating-point notations are all valid:
+ print(is_equal_approx(123., 123))
+ print(is_equal_approx(.123, 0.123))
+ print(is_equal_approx(.123e4, 1230))
+ print(is_equal_approx(123.e4, 1.23e6))
+ print(is_equal_approx(.123e-1, 0.0123))
+ print(is_equal_approx(123.e-1, 12.3))
+
+ # Same as above, but with negative numbers.
+ print(is_equal_approx(-123., -123))
+ print(is_equal_approx(-.123, -0.123))
+ print(is_equal_approx(-.123e4, -1230))
+ print(is_equal_approx(-123.e4, -1.23e6))
+ print(is_equal_approx(-.123e-1, -0.0123))
+ print(is_equal_approx(-123.e-1, -12.3))
+
+ # Same as above, but with explicit positive numbers (which is redundant).
+ print(is_equal_approx(+123., +123))
+ print(is_equal_approx(+.123, +0.123))
+ print(is_equal_approx(+.123e4, +1230))
+ print(is_equal_approx(+123.e4, +1.23e6))
+ print(is_equal_approx(+.123e-1, +0.0123))
+ print(is_equal_approx(+123.e-1, +12.3))
diff --git a/modules/gdscript/tests/scripts/parser/features/float_notation.out b/modules/gdscript/tests/scripts/parser/features/float_notation.out
new file mode 100644
index 0000000000..041c4439b0
--- /dev/null
+++ b/modules/gdscript/tests/scripts/parser/features/float_notation.out
@@ -0,0 +1,19 @@
+GDTEST_OK
+true
+true
+true
+true
+true
+true
+true
+true
+true
+true
+true
+true
+true
+true
+true
+true
+true
+true
diff --git a/modules/gdscript/tests/scripts/parser/features/for_range.gd b/modules/gdscript/tests/scripts/parser/features/for_range.gd
new file mode 100644
index 0000000000..fd1d002b82
--- /dev/null
+++ b/modules/gdscript/tests/scripts/parser/features/for_range.gd
@@ -0,0 +1,39 @@
+func test():
+ for i in range(5):
+ print(i)
+
+ print()
+
+ # Equivalent to the above `for` loop:
+ for i in 5:
+ print(i)
+
+ print()
+
+ for i in range(1, 5):
+ print(i)
+
+ print()
+
+ for i in range(1, -5, -1):
+ print(i)
+
+ print()
+
+ for i in [2, 4, 6, -8]:
+ print(i)
+
+ print()
+
+ for i in [true, false]:
+ print(i)
+
+ print()
+
+ for i in [Vector2i(10, 20), Vector2i(-30, -40)]:
+ print(i)
+
+ print()
+
+ for i in "Hello_Unicôde_world!_🦄":
+ print(i)
diff --git a/modules/gdscript/tests/scripts/parser/features/for_range.out b/modules/gdscript/tests/scripts/parser/features/for_range.out
new file mode 100644
index 0000000000..50b2c856c5
--- /dev/null
+++ b/modules/gdscript/tests/scripts/parser/features/for_range.out
@@ -0,0 +1,58 @@
+GDTEST_OK
+0
+1
+2
+3
+4
+
+0
+1
+2
+3
+4
+
+1
+2
+3
+4
+
+1
+0
+-1
+-2
+-3
+-4
+
+2
+4
+6
+-8
+
+true
+false
+
+(10, 20)
+(-30, -40)
+
+H
+e
+l
+l
+o
+_
+U
+n
+i
+c
+ô
+d
+e
+_
+w
+o
+r
+l
+d
+!
+_
+🦄
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/in.gd b/modules/gdscript/tests/scripts/parser/features/in.gd
new file mode 100644
index 0000000000..f7296017c5
--- /dev/null
+++ b/modules/gdscript/tests/scripts/parser/features/in.gd
@@ -0,0 +1,14 @@
+func test():
+ print("dot" in "Godot")
+ print(not "i" in "team")
+
+ print(true in [true, false])
+ print(not null in [true, false])
+ print(null in [null])
+
+ print(26 in [8, 26, 64, 100])
+ print(not Vector2i(10, 20) in [Vector2i(20, 10)])
+
+ print("apple" in { "apple": "fruit" })
+ print("apple" in { "apple": null })
+ print(not "apple" in { "fruit": "apple" })
diff --git a/modules/gdscript/tests/scripts/parser/features/in.out b/modules/gdscript/tests/scripts/parser/features/in.out
new file mode 100644
index 0000000000..7533f6ff54
--- /dev/null
+++ b/modules/gdscript/tests/scripts/parser/features/in.out
@@ -0,0 +1,11 @@
+GDTEST_OK
+true
+true
+true
+true
+true
+true
+true
+true
+true
+true
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/match.gd b/modules/gdscript/tests/scripts/parser/features/match.gd
new file mode 100644
index 0000000000..4d05490aa5
--- /dev/null
+++ b/modules/gdscript/tests/scripts/parser/features/match.gd
@@ -0,0 +1,18 @@
+func test():
+ var i = "Hello"
+ match i:
+ "Hello":
+ print("hello")
+ # This will fall through to the default case below.
+ continue
+ "Good bye":
+ print("bye")
+ _:
+ print("default")
+
+ var j = 25
+ match j:
+ 26:
+ print("This won't match")
+ _:
+ print("This will match")
diff --git a/modules/gdscript/tests/scripts/parser/features/match.out b/modules/gdscript/tests/scripts/parser/features/match.out
new file mode 100644
index 0000000000..732885c7a2
--- /dev/null
+++ b/modules/gdscript/tests/scripts/parser/features/match.out
@@ -0,0 +1,4 @@
+GDTEST_OK
+hello
+default
+This will match
diff --git a/modules/gdscript/tests/scripts/parser/features/multiline_arrays.gd b/modules/gdscript/tests/scripts/parser/features/multiline_arrays.gd
new file mode 100644
index 0000000000..3b30998853
--- /dev/null
+++ b/modules/gdscript/tests/scripts/parser/features/multiline_arrays.gd
@@ -0,0 +1,7 @@
+func test():
+ var __ = [
+ "this",
+ "is", "a","multiline",
+
+ "array", "with mixed indentation and trailing comma",
+ ]
diff --git a/modules/gdscript/tests/scripts/parser/features/multiline_arrays.out b/modules/gdscript/tests/scripts/parser/features/multiline_arrays.out
new file mode 100644
index 0000000000..d73c5eb7cd
--- /dev/null
+++ b/modules/gdscript/tests/scripts/parser/features/multiline_arrays.out
@@ -0,0 +1 @@
+GDTEST_OK
diff --git a/modules/gdscript/tests/scripts/parser/features/multiline_dictionaries.gd b/modules/gdscript/tests/scripts/parser/features/multiline_dictionaries.gd
new file mode 100644
index 0000000000..e108cd23d4
--- /dev/null
+++ b/modules/gdscript/tests/scripts/parser/features/multiline_dictionaries.gd
@@ -0,0 +1,10 @@
+func test():
+ var __ = {
+ "multiline": "dictionary","should": "work",
+ "even with": "a trailing comma",
+ }
+
+ __ = {
+ this_also_applies = "to the",
+ lua_style_syntax = null, foo = null,
+ }
diff --git a/modules/gdscript/tests/scripts/parser/features/multiline_dictionaries.out b/modules/gdscript/tests/scripts/parser/features/multiline_dictionaries.out
new file mode 100644
index 0000000000..d73c5eb7cd
--- /dev/null
+++ b/modules/gdscript/tests/scripts/parser/features/multiline_dictionaries.out
@@ -0,0 +1 @@
+GDTEST_OK
diff --git a/modules/gdscript/tests/scripts/parser/features/multiline_if.gd b/modules/gdscript/tests/scripts/parser/features/multiline_if.gd
new file mode 100644
index 0000000000..86152f4543
--- /dev/null
+++ b/modules/gdscript/tests/scripts/parser/features/multiline_if.gd
@@ -0,0 +1,14 @@
+func test():
+ # Line breaks are allowed within parentheses.
+ if (
+ 1 == 1
+ and 2 == 2 and
+ 3 == 3
+ ):
+ pass
+
+ # Alternatively, backslashes can be used.
+ if 1 == 1 \
+ and 2 == 2 and \
+ 3 == 3:
+ pass
diff --git a/modules/gdscript/tests/scripts/parser/features/multiline_if.out b/modules/gdscript/tests/scripts/parser/features/multiline_if.out
new file mode 100644
index 0000000000..d73c5eb7cd
--- /dev/null
+++ b/modules/gdscript/tests/scripts/parser/features/multiline_if.out
@@ -0,0 +1 @@
+GDTEST_OK
diff --git a/modules/gdscript/tests/scripts/parser/features/multiline_strings.gd b/modules/gdscript/tests/scripts/parser/features/multiline_strings.gd
new file mode 100644
index 0000000000..7f5bba85e7
--- /dev/null
+++ b/modules/gdscript/tests/scripts/parser/features/multiline_strings.gd
@@ -0,0 +1,15 @@
+func test():
+ var __ = """
+ This is a standalone string, not a multiline comment.
+ Writing both "double" quotes and 'simple' quotes is fine as
+ long as there is only ""one"" or ''two'' of those in a row, not more.
+
+ If you have more quotes, they need to be escaped like this: \"\"\"
+ """
+ __ = '''
+ Another standalone string, this time with single quotes.
+ Writing both "double" quotes and 'simple' quotes is fine as
+ long as there is only ""one"" or ''two'' of those in a row, not more.
+
+ If you have more quotes, they need to be escaped like this: \'\'\'
+ '''
diff --git a/modules/gdscript/tests/scripts/parser/features/multiline_strings.out b/modules/gdscript/tests/scripts/parser/features/multiline_strings.out
new file mode 100644
index 0000000000..d73c5eb7cd
--- /dev/null
+++ b/modules/gdscript/tests/scripts/parser/features/multiline_strings.out
@@ -0,0 +1 @@
+GDTEST_OK
diff --git a/modules/gdscript/tests/scripts/parser/features/multiline_vector.gd b/modules/gdscript/tests/scripts/parser/features/multiline_vector.gd
new file mode 100644
index 0000000000..11a40fc00e
--- /dev/null
+++ b/modules/gdscript/tests/scripts/parser/features/multiline_vector.gd
@@ -0,0 +1,17 @@
+func test():
+ Vector2(
+ 1,
+ 2
+ )
+
+ Vector3(
+ 3,
+ 3.5,
+ 4, # Trailing comma should work.
+ )
+
+ Vector2i(1, 2,) # Trailing comma should work.
+
+ Vector3i(6,
+ 9,
+ 12)
diff --git a/modules/gdscript/tests/scripts/parser/features/multiline_vector.out b/modules/gdscript/tests/scripts/parser/features/multiline_vector.out
new file mode 100644
index 0000000000..d73c5eb7cd
--- /dev/null
+++ b/modules/gdscript/tests/scripts/parser/features/multiline_vector.out
@@ -0,0 +1 @@
+GDTEST_OK
diff --git a/modules/gdscript/tests/scripts/parser/features/nested_arithmetic.gd b/modules/gdscript/tests/scripts/parser/features/nested_arithmetic.gd
new file mode 100644
index 0000000000..b9bd19c9c5
--- /dev/null
+++ b/modules/gdscript/tests/scripts/parser/features/nested_arithmetic.gd
@@ -0,0 +1,22 @@
+func test():
+ print(+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++2.718)
+
+ print()
+
+ print(------------------------------------------------------------------2.718)
+ print(-------------------------------------------------------------------2.718)
+
+ print()
+
+ print(~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~999)
+
+ print()
+
+ print(+-+-+-----+------------+++++++---++--++--+--+---+--++2.718)
+
+ print()
+
+ print(2 * 2 * 2 * 2 * 2 * 2 * 2 * 2 * 2 * 2 * 2 * 2 * 2 * 2 * 2 * 2 * 2 * 2 * 2 * 2 * 2 * 2 * 2 * 2 * 2 * 2 * 2 * 2 * 2 * 2 * 2 / 2 / 2 / 2 / 2 / 2 / 2 / 2 / 2 / 2 / 2 / 2 / 2 / 2 / 2 / 2 / 2 / 2 / 0.444)
+ print(153023902390239 % 550 % 29 % 27 % 23 % 17)
+ print(2 << 2 << 2 << 2 << 2 << 2 << 2 << 2 << 2 << 2 << 2 << 2 << 2 << 2 << 2 << 2 << 2 << 2 << 2 << 2 << 2 << 2 << 2 << 2 << 2 >> 2 >> 2 >> 2 >> 2 >> 2 >> 2 >> 2 >> 2 >> 2 >> 2 >> 2 >> 2 >> 2 >> 2 >> 2 >> 2 >> 2 >> 2)
+ print(8 ^ 8 ^ 8 ^ 8 ^ 8 ^ 8 ^ 8 ^ 8 ^ 8 ^ 8 ^ 8 ^ 8 ^ 8 ^ 8 ^ 8 ^ 8 ^ 8 ^ 8 ^ 8 ^ 8 ^ 8 ^ -8 ^ 8 ^ 8 ^ 8 ^ 8 ^ 8)
diff --git a/modules/gdscript/tests/scripts/parser/features/nested_arithmetic.out b/modules/gdscript/tests/scripts/parser/features/nested_arithmetic.out
new file mode 100644
index 0000000000..048cfbdfae
--- /dev/null
+++ b/modules/gdscript/tests/scripts/parser/features/nested_arithmetic.out
@@ -0,0 +1,82 @@
+GDTEST_OK
+>> WARNING
+>> Line: 19
+>> INTEGER_DIVISION
+>> Integer division, decimal part will be discarded.
+>> WARNING
+>> Line: 19
+>> INTEGER_DIVISION
+>> Integer division, decimal part will be discarded.
+>> WARNING
+>> Line: 19
+>> INTEGER_DIVISION
+>> Integer division, decimal part will be discarded.
+>> WARNING
+>> Line: 19
+>> INTEGER_DIVISION
+>> Integer division, decimal part will be discarded.
+>> WARNING
+>> Line: 19
+>> INTEGER_DIVISION
+>> Integer division, decimal part will be discarded.
+>> WARNING
+>> Line: 19
+>> INTEGER_DIVISION
+>> Integer division, decimal part will be discarded.
+>> WARNING
+>> Line: 19
+>> INTEGER_DIVISION
+>> Integer division, decimal part will be discarded.
+>> WARNING
+>> Line: 19
+>> INTEGER_DIVISION
+>> Integer division, decimal part will be discarded.
+>> WARNING
+>> Line: 19
+>> INTEGER_DIVISION
+>> Integer division, decimal part will be discarded.
+>> WARNING
+>> Line: 19
+>> INTEGER_DIVISION
+>> Integer division, decimal part will be discarded.
+>> WARNING
+>> Line: 19
+>> INTEGER_DIVISION
+>> Integer division, decimal part will be discarded.
+>> WARNING
+>> Line: 19
+>> INTEGER_DIVISION
+>> Integer division, decimal part will be discarded.
+>> WARNING
+>> Line: 19
+>> INTEGER_DIVISION
+>> Integer division, decimal part will be discarded.
+>> WARNING
+>> Line: 19
+>> INTEGER_DIVISION
+>> Integer division, decimal part will be discarded.
+>> WARNING
+>> Line: 19
+>> INTEGER_DIVISION
+>> Integer division, decimal part will be discarded.
+>> WARNING
+>> Line: 19
+>> INTEGER_DIVISION
+>> Integer division, decimal part will be discarded.
+>> WARNING
+>> Line: 19
+>> INTEGER_DIVISION
+>> Integer division, decimal part will be discarded.
+2.718
+
+2.718
+-2.718
+
+-1000
+
+-2.718
+
+36900.9009009009
+2
+8192
+-8
diff --git a/modules/gdscript/tests/scripts/parser/features/nested_array.gd b/modules/gdscript/tests/scripts/parser/features/nested_array.gd
new file mode 100644
index 0000000000..3caef96391
--- /dev/null
+++ b/modules/gdscript/tests/scripts/parser/features/nested_array.gd
@@ -0,0 +1,5 @@
+func test():
+ var array = [[[[[[[[[[15]]]]]]]]]]
+ print(array[0][0][0][0][0][0][0][0])
+ print(array[0][0][0][0][0][0][0][0][0])
+ print(array[0][0][0][0][0][0][0][0][0][0])
diff --git a/modules/gdscript/tests/scripts/parser/features/nested_array.out b/modules/gdscript/tests/scripts/parser/features/nested_array.out
new file mode 100644
index 0000000000..46c6ce3874
--- /dev/null
+++ b/modules/gdscript/tests/scripts/parser/features/nested_array.out
@@ -0,0 +1,4 @@
+GDTEST_OK
+[[15]]
+[15]
+15
diff --git a/modules/gdscript/tests/scripts/parser/features/nested_dictionary.gd b/modules/gdscript/tests/scripts/parser/features/nested_dictionary.gd
new file mode 100644
index 0000000000..d67e142156
--- /dev/null
+++ b/modules/gdscript/tests/scripts/parser/features/nested_dictionary.gd
@@ -0,0 +1,6 @@
+func test():
+ var dictionary = {1: {2: {3: {4: {5: {6: {7: {8: {"key": "value"}}}}}}}}}
+ print(dictionary[1][2][3][4][5][6][7])
+ print(dictionary[1][2][3][4][5][6][7][8])
+ print(dictionary[1][2][3][4][5][6][7][8].key)
+ print(dictionary[1][2][3][4][5][6][7][8]["key"])
diff --git a/modules/gdscript/tests/scripts/parser/features/nested_dictionary.out b/modules/gdscript/tests/scripts/parser/features/nested_dictionary.out
new file mode 100644
index 0000000000..4009160439
--- /dev/null
+++ b/modules/gdscript/tests/scripts/parser/features/nested_dictionary.out
@@ -0,0 +1,5 @@
+GDTEST_OK
+{8:{key:value}}
+{key:value}
+value
+value
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/nested_if.gd b/modules/gdscript/tests/scripts/parser/features/nested_if.gd
new file mode 100644
index 0000000000..7282d08497
--- /dev/null
+++ b/modules/gdscript/tests/scripts/parser/features/nested_if.gd
@@ -0,0 +1,57 @@
+func test():
+ # 20 levels of nesting (and then some).
+ if true:
+ print("1")
+ if true:
+ print("2")
+ if true:
+ print("3")
+ if true:
+ print("4")
+ if true:
+ print("5")
+ if true:
+ print("6")
+ if true:
+ print("7")
+ if true:
+ print("8")
+ if true:
+ print("9")
+ if true:
+ print("10")
+ if true:
+ print("11")
+ if true:
+ print("12")
+ if true:
+ print("13")
+ if true:
+ print("14")
+ if true:
+ print("15")
+ if true:
+ print("16")
+ if true:
+ print("17")
+ if true:
+ print("18")
+ if true:
+ print("19")
+ if true:
+ print("20")
+ if false:
+ print("End")
+ if true:
+ if true:
+ if true:
+ if true:
+ if true:
+ if true:
+ if true:
+ if true:
+ if true:
+ if true:
+ if true:
+ if true:
+ print("This won't be printed")
diff --git a/modules/gdscript/tests/scripts/parser/features/nested_if.out b/modules/gdscript/tests/scripts/parser/features/nested_if.out
new file mode 100644
index 0000000000..c2d2e29a06
--- /dev/null
+++ b/modules/gdscript/tests/scripts/parser/features/nested_if.out
@@ -0,0 +1,21 @@
+GDTEST_OK
+1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
diff --git a/modules/gdscript/tests/scripts/parser/features/nested_match.gd b/modules/gdscript/tests/scripts/parser/features/nested_match.gd
new file mode 100644
index 0000000000..aaddcc7e83
--- /dev/null
+++ b/modules/gdscript/tests/scripts/parser/features/nested_match.gd
@@ -0,0 +1,79 @@
+func test():
+ # 20 levels of nesting (and then some).
+ var number = 1234
+ match number:
+ 1234:
+ print("1")
+ match number:
+ 1234:
+ print("2")
+ match number:
+ 1234:
+ print("3")
+ continue
+ _:
+ print("Should also be printed")
+ match number:
+ 1234:
+ print("4")
+ match number:
+ _:
+ print("5")
+ match number:
+ false:
+ print("Should not be printed")
+ true:
+ print("Should not be printed")
+ "hello":
+ print("Should not be printed")
+ 1234:
+ print("6")
+ match number:
+ _:
+ print("7")
+ match number:
+ 1234:
+ print("8")
+ match number:
+ _:
+ print("9")
+ match number:
+ 1234:
+ print("10")
+ match number:
+ _:
+ print("11")
+ match number:
+ 1234:
+ print("12")
+ match number:
+ _:
+ print("13")
+ match number:
+ 1234:
+ print("14")
+ match number:
+ _:
+ print("15")
+ match number:
+ _:
+ print("16")
+ match number:
+ 1234:
+ print("17")
+ match number:
+ _:
+ print("18")
+ match number:
+ 1234:
+ print("19")
+ match number:
+ _:
+ print("20")
+ match number:
+ []:
+ print("Should not be printed")
+ _:
+ print("Should not be printed")
+ 5678:
+ print("Should not be printed either")
diff --git a/modules/gdscript/tests/scripts/parser/features/nested_match.out b/modules/gdscript/tests/scripts/parser/features/nested_match.out
new file mode 100644
index 0000000000..651d76cc59
--- /dev/null
+++ b/modules/gdscript/tests/scripts/parser/features/nested_match.out
@@ -0,0 +1,22 @@
+GDTEST_OK
+1
+2
+3
+Should also be printed
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
diff --git a/modules/gdscript/tests/scripts/parser/features/nested_parentheses.gd b/modules/gdscript/tests/scripts/parser/features/nested_parentheses.gd
new file mode 100644
index 0000000000..3fef73b9be
--- /dev/null
+++ b/modules/gdscript/tests/scripts/parser/features/nested_parentheses.gd
@@ -0,0 +1,65 @@
+func test():
+ (((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((print("Hello world!"))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))
+
+ print(((((((((((((((((((((((((((((((((((((((((((((((((((((((((("Hello world 2!"))))))))))))))))))))))))))))))))))))))))))))))))))))))))))
+
+ print(
+ (
+ (
+ (
+ (
+ (
+ (
+ (
+ (
+ (
+ (
+ (
+ (
+ (
+ (
+ (
+ (
+ (
+ (
+ (
+ (
+ (
+ (
+ (
+ (
+ (
+ (
+ (
+ (
+ (
+ (
+ (
+ (
+ (
+ (
+ (
+ (
+ (
+ (
+ (
+ (
+ (
+ (
+ (
+ (
+ (
+ (
+ (
+ (
+ (
+ (
+ (
+ (
+ (
+ (
+ (
+ (
+ (
+ (
+ (2)) + ((4))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))
diff --git a/modules/gdscript/tests/scripts/parser/features/nested_parentheses.out b/modules/gdscript/tests/scripts/parser/features/nested_parentheses.out
new file mode 100644
index 0000000000..27221a56bb
--- /dev/null
+++ b/modules/gdscript/tests/scripts/parser/features/nested_parentheses.out
@@ -0,0 +1,4 @@
+GDTEST_OK
+Hello world!
+Hello world 2!
+6
diff --git a/modules/gdscript/tests/scripts/parser/features/number_separators.gd b/modules/gdscript/tests/scripts/parser/features/number_separators.gd
new file mode 100644
index 0000000000..f5f5661cae
--- /dev/null
+++ b/modules/gdscript/tests/scripts/parser/features/number_separators.gd
@@ -0,0 +1,12 @@
+func test():
+ # `_` can be used as a separator for numbers in GDScript.
+ # It can be placed anywhere in the number, except at the beginning.
+ # Currently, GDScript in the `master` branch only allows using one separator
+ # per number.
+ # Results are assigned to variables to avoid warnings.
+ var __ = 1_23
+ __ = 123_ # Trailing number separators are OK.
+ __ = 12_3
+ __ = 123_456
+ __ = 0x1234_5678
+ __ = 0b1001_0101
diff --git a/modules/gdscript/tests/scripts/parser/features/number_separators.out b/modules/gdscript/tests/scripts/parser/features/number_separators.out
new file mode 100644
index 0000000000..d73c5eb7cd
--- /dev/null
+++ b/modules/gdscript/tests/scripts/parser/features/number_separators.out
@@ -0,0 +1 @@
+GDTEST_OK
diff --git a/modules/gdscript/tests/scripts/parser/features/operator_assign.gd b/modules/gdscript/tests/scripts/parser/features/operator_assign.gd
new file mode 100644
index 0000000000..b5f07675ca
--- /dev/null
+++ b/modules/gdscript/tests/scripts/parser/features/operator_assign.gd
@@ -0,0 +1,8 @@
+func test():
+ var i = 0
+ i += 5
+ i -= 4
+ i *= 10
+ i %= 8
+ i /= 0.25
+ print(round(i))
diff --git a/modules/gdscript/tests/scripts/parser/features/operator_assign.out b/modules/gdscript/tests/scripts/parser/features/operator_assign.out
new file mode 100644
index 0000000000..b0cb63ef59
--- /dev/null
+++ b/modules/gdscript/tests/scripts/parser/features/operator_assign.out
@@ -0,0 +1,2 @@
+GDTEST_OK
+8
diff --git a/modules/gdscript/tests/scripts/parser/features/property_setter_getter.gd b/modules/gdscript/tests/scripts/parser/features/property_setter_getter.gd
new file mode 100644
index 0000000000..9e4b360fb2
--- /dev/null
+++ b/modules/gdscript/tests/scripts/parser/features/property_setter_getter.gd
@@ -0,0 +1,37 @@
+# 4.0+ replacement for `setget`:
+var _backing: int = 0
+var property:
+ get:
+ return _backing + 1000
+ set(value):
+ _backing = value - 1000
+
+
+func test():
+ print("Not using self:")
+ print(property)
+ print(_backing)
+ property = 5000
+ print(property)
+ print(_backing)
+ _backing = -50
+ print(property)
+ print(_backing)
+ property = 5000
+ print(property)
+ print(_backing)
+
+ # In Godot 4.0 and later, using `self` no longer makes a difference for
+ # getter/setter execution in GDScript.
+ print("Using self:")
+ print(self.property)
+ print(self._backing)
+ self.property = 5000
+ print(self.property)
+ print(self._backing)
+ self._backing = -50
+ print(self.property)
+ print(self._backing)
+ self.property = 5000
+ print(self.property)
+ print(self._backing)
diff --git a/modules/gdscript/tests/scripts/parser/features/property_setter_getter.out b/modules/gdscript/tests/scripts/parser/features/property_setter_getter.out
new file mode 100644
index 0000000000..560e0c3bd7
--- /dev/null
+++ b/modules/gdscript/tests/scripts/parser/features/property_setter_getter.out
@@ -0,0 +1,19 @@
+GDTEST_OK
+Not using self:
+1000
+0
+5000
+4000
+950
+-50
+5000
+4000
+Using self:
+5000
+4000
+5000
+4000
+950
+-50
+5000
+4000
diff --git a/modules/gdscript/tests/scripts/parser/features/semicolon_as_end_statement.gd b/modules/gdscript/tests/scripts/parser/features/semicolon_as_end_statement.gd
index 08f2eedb2d..d50776c25c 100644
--- a/modules/gdscript/tests/scripts/parser/features/semicolon_as_end_statement.gd
+++ b/modules/gdscript/tests/scripts/parser/features/semicolon_as_end_statement.gd
@@ -1,2 +1,5 @@
func test():
- print("A"); print("B")
+ print("A"); print("B")
+
+ # Multiple semicolons and whitespace between them is also valid.
+ print("A"); ;;;;; ; print("B");;
diff --git a/modules/gdscript/tests/scripts/parser/features/semicolon_as_end_statement.out b/modules/gdscript/tests/scripts/parser/features/semicolon_as_end_statement.out
index fc03f3efe8..bd7f38f516 100644
--- a/modules/gdscript/tests/scripts/parser/features/semicolon_as_end_statement.out
+++ b/modules/gdscript/tests/scripts/parser/features/semicolon_as_end_statement.out
@@ -1,3 +1,5 @@
GDTEST_OK
A
B
+A
+B
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/space_indentation.gd b/modules/gdscript/tests/scripts/parser/features/space_indentation.gd
new file mode 100644
index 0000000000..0a4887c199
--- /dev/null
+++ b/modules/gdscript/tests/scripts/parser/features/space_indentation.gd
@@ -0,0 +1,4 @@
+func test():
+ # 2-space indentation should work, even though the GDScript style guide recommends tabs.
+ if true:
+ pass
diff --git a/modules/gdscript/tests/scripts/parser/features/space_indentation.out b/modules/gdscript/tests/scripts/parser/features/space_indentation.out
new file mode 100644
index 0000000000..d73c5eb7cd
--- /dev/null
+++ b/modules/gdscript/tests/scripts/parser/features/space_indentation.out
@@ -0,0 +1 @@
+GDTEST_OK
diff --git a/modules/gdscript/tests/scripts/parser/features/static_typing.gd b/modules/gdscript/tests/scripts/parser/features/static_typing.gd
new file mode 100644
index 0000000000..d42632c82d
--- /dev/null
+++ b/modules/gdscript/tests/scripts/parser/features/static_typing.gd
@@ -0,0 +1,13 @@
+func test():
+ # The following lines are equivalent:
+ var _integer: int = 1
+ var _integer2 : int = 1
+ var _inferred := 1
+ var _inferred2 : = 1
+
+ # Type inference is automatic for constants.
+ const _INTEGER = 1
+ const _INTEGER_REDUNDANT_TYPED : int = 1
+ const _INTEGER_REDUNDANT_TYPED2 : int = 1
+ const _INTEGER_REDUNDANT_INFERRED := 1
+ const _INTEGER_REDUNDANT_INFERRED2 : = 1
diff --git a/modules/gdscript/tests/scripts/parser/features/static_typing.out b/modules/gdscript/tests/scripts/parser/features/static_typing.out
new file mode 100644
index 0000000000..92ce7bc0e0
--- /dev/null
+++ b/modules/gdscript/tests/scripts/parser/features/static_typing.out
@@ -0,0 +1,21 @@
+GDTEST_OK
+>> WARNING
+>> Line: 9
+>> UNUSED_LOCAL_CONSTANT
+>> The local constant '_INTEGER' is declared but never used in the block. If this is intended, prefix it with an underscore: '__INTEGER'
+>> WARNING
+>> Line: 10
+>> UNUSED_LOCAL_CONSTANT
+>> The local constant '_INTEGER_REDUNDANT_TYPED' is declared but never used in the block. If this is intended, prefix it with an underscore: '__INTEGER_REDUNDANT_TYPED'
+>> WARNING
+>> Line: 11
+>> UNUSED_LOCAL_CONSTANT
+>> The local constant '_INTEGER_REDUNDANT_TYPED2' is declared but never used in the block. If this is intended, prefix it with an underscore: '__INTEGER_REDUNDANT_TYPED2'
+>> WARNING
+>> Line: 12
+>> UNUSED_LOCAL_CONSTANT
+>> The local constant '_INTEGER_REDUNDANT_INFERRED' is declared but never used in the block. If this is intended, prefix it with an underscore: '__INTEGER_REDUNDANT_INFERRED'
+>> WARNING
+>> Line: 13
+>> UNUSED_LOCAL_CONSTANT
+>> The local constant '_INTEGER_REDUNDANT_INFERRED2' is declared but never used in the block. If this is intended, prefix it with an underscore: '__INTEGER_REDUNDANT_INFERRED2'
diff --git a/modules/gdscript/tests/scripts/parser/features/str_preserves_case.gd b/modules/gdscript/tests/scripts/parser/features/str_preserves_case.gd
index ad1694de81..9e48a7f0da 100644
--- a/modules/gdscript/tests/scripts/parser/features/str_preserves_case.gd
+++ b/modules/gdscript/tests/scripts/parser/features/str_preserves_case.gd
@@ -1,7 +1,7 @@
func test():
var null_var = null
- var true_var:bool = true
- var false_var:bool = false
+ var true_var: bool = true
+ var false_var: bool = false
print(str(null_var))
print(str(true_var))
print(str(false_var))
diff --git a/modules/gdscript/tests/scripts/parser/features/string_formatting.gd b/modules/gdscript/tests/scripts/parser/features/string_formatting.gd
new file mode 100644
index 0000000000..a91837145d
--- /dev/null
+++ b/modules/gdscript/tests/scripts/parser/features/string_formatting.gd
@@ -0,0 +1,18 @@
+func test():
+ print("hello %s" % "world" == "hello world")
+ print("hello %s" % true == "hello true")
+ print("hello %s" % false == "hello false")
+
+ print("hello %d" % 25 == "hello 25")
+ print("hello %d %d" % [25, 42] == "hello 25 42")
+ # Pad with spaces.
+ print("hello %3d" % 25 == "hello 25")
+ # Pad with zeroes.
+ print("hello %03d" % 25 == "hello 025")
+
+ print("hello %.02f" % 0.123456 == "hello 0.12")
+
+ # Dynamic padding:
+ # <https://docs.godotengine.org/en/stable/getting_started/scripting/gdscript/gdscript_format_string.html#dynamic-padding>
+ print("hello %*.*f" % [7, 3, 0.123456] == "hello 0.123")
+ print("hello %0*.*f" % [7, 3, 0.123456] == "hello 000.123")
diff --git a/modules/gdscript/tests/scripts/parser/features/string_formatting.out b/modules/gdscript/tests/scripts/parser/features/string_formatting.out
new file mode 100644
index 0000000000..7533f6ff54
--- /dev/null
+++ b/modules/gdscript/tests/scripts/parser/features/string_formatting.out
@@ -0,0 +1,11 @@
+GDTEST_OK
+true
+true
+true
+true
+true
+true
+true
+true
+true
+true
diff --git a/modules/gdscript/tests/scripts/parser/features/super.gd b/modules/gdscript/tests/scripts/parser/features/super.gd
new file mode 100644
index 0000000000..f5ae2a74a7
--- /dev/null
+++ b/modules/gdscript/tests/scripts/parser/features/super.gd
@@ -0,0 +1,60 @@
+class Say:
+ var prefix = "S"
+
+ func greet():
+ prefix = "S Greeted"
+ print("hello")
+
+ func say(name):
+ print(prefix, " say something ", name)
+
+
+class SayAnotherThing extends Say:
+ # This currently crashes the engine.
+ #var prefix = "SAT"
+
+ func greet():
+ prefix = "SAT Greeted"
+ print("hi")
+
+ func say(name):
+ print(prefix, " say another thing ", name)
+
+
+class SayNothing extends Say:
+ # This currently crashes the engine.
+ #var prefix = "SN"
+
+ func greet():
+ super()
+ prefix = "SN Greeted"
+ print("howdy, see above")
+
+ func greet_prefix_before_super():
+ prefix = "SN Greeted"
+ super.greet()
+ print("howdy, see above")
+
+ func say(name):
+ super(name + " super'd")
+ print(prefix, " say nothing... or not? ", name)
+
+
+func test():
+ var say = Say.new()
+ say.greet()
+ say.say("foo")
+ print()
+
+ var say_another_thing = SayAnotherThing.new()
+ say_another_thing.greet()
+ say_another_thing.say("bar")
+ print()
+
+ var say_nothing = SayNothing.new()
+ say_nothing.greet()
+ print(say_nothing.prefix)
+ say_nothing.greet_prefix_before_super()
+ print(say_nothing.prefix)
+ # This currently triggers a compiler bug: "compiler bug, function name not found"
+ #say_nothing.say("baz")
diff --git a/modules/gdscript/tests/scripts/parser/features/super.out b/modules/gdscript/tests/scripts/parser/features/super.out
new file mode 100644
index 0000000000..e0d4f4f098
--- /dev/null
+++ b/modules/gdscript/tests/scripts/parser/features/super.out
@@ -0,0 +1,13 @@
+GDTEST_OK
+hello
+S Greeted say something foo
+
+hi
+SAT Greeted say another thing bar
+
+hello
+howdy, see above
+SN Greeted
+hello
+howdy, see above
+S Greeted
diff --git a/modules/gdscript/tests/scripts/parser/features/truthiness.gd b/modules/gdscript/tests/scripts/parser/features/truthiness.gd
new file mode 100644
index 0000000000..9c67a152f5
--- /dev/null
+++ b/modules/gdscript/tests/scripts/parser/features/truthiness.gd
@@ -0,0 +1,30 @@
+func test():
+ # The assertions below should all evaluate to `true` for this test to pass.
+ assert(true)
+ assert(not false)
+ assert(500)
+ assert(not 0)
+ assert(500.5)
+ assert(not 0.0)
+ assert("non-empty string")
+ assert(["non-empty array"])
+ assert({"non-empty": "dictionary"})
+ assert(Vector2(1, 0))
+ assert(Vector2i(-1, -1))
+ assert(Vector3(0, 0, 0.0001))
+ assert(Vector3i(0, 0, 10000))
+
+ # Zero position is `true` only if the Rect2's size is non-zero.
+ assert(Rect2(0, 0, 0, 1))
+
+ # Zero size is `true` only if the position is non-zero.
+ assert(Rect2(1, 1, 0, 0))
+
+ # Zero position is `true` only if the Rect2's size is non-zero.
+ assert(Rect2i(0, 0, 0, 1))
+
+ # Zero size is `true` only if the position is non-zero.
+ assert(Rect2i(1, 1, 0, 0))
+
+ # A fully black color is only truthy if its alpha component is not equal to `1`.
+ assert(Color(0, 0, 0, 0.5))
diff --git a/modules/gdscript/tests/scripts/parser/features/truthiness.out b/modules/gdscript/tests/scripts/parser/features/truthiness.out
new file mode 100644
index 0000000000..705524857b
--- /dev/null
+++ b/modules/gdscript/tests/scripts/parser/features/truthiness.out
@@ -0,0 +1,65 @@
+GDTEST_OK
+>> WARNING
+>> Line: 3
+>> ASSERT_ALWAYS_TRUE
+>> Assert statement is redundant because the expression is always true.
+>> WARNING
+>> Line: 4
+>> ASSERT_ALWAYS_TRUE
+>> Assert statement is redundant because the expression is always true.
+>> WARNING
+>> Line: 5
+>> ASSERT_ALWAYS_TRUE
+>> Assert statement is redundant because the expression is always true.
+>> WARNING
+>> Line: 6
+>> ASSERT_ALWAYS_TRUE
+>> Assert statement is redundant because the expression is always true.
+>> WARNING
+>> Line: 7
+>> ASSERT_ALWAYS_TRUE
+>> Assert statement is redundant because the expression is always true.
+>> WARNING
+>> Line: 8
+>> ASSERT_ALWAYS_TRUE
+>> Assert statement is redundant because the expression is always true.
+>> WARNING
+>> Line: 9
+>> ASSERT_ALWAYS_TRUE
+>> Assert statement is redundant because the expression is always true.
+>> WARNING
+>> Line: 12
+>> ASSERT_ALWAYS_TRUE
+>> Assert statement is redundant because the expression is always true.
+>> WARNING
+>> Line: 13
+>> ASSERT_ALWAYS_TRUE
+>> Assert statement is redundant because the expression is always true.
+>> WARNING
+>> Line: 14
+>> ASSERT_ALWAYS_TRUE
+>> Assert statement is redundant because the expression is always true.
+>> WARNING
+>> Line: 15
+>> ASSERT_ALWAYS_TRUE
+>> Assert statement is redundant because the expression is always true.
+>> WARNING
+>> Line: 18
+>> ASSERT_ALWAYS_TRUE
+>> Assert statement is redundant because the expression is always true.
+>> WARNING
+>> Line: 21
+>> ASSERT_ALWAYS_TRUE
+>> Assert statement is redundant because the expression is always true.
+>> WARNING
+>> Line: 24
+>> ASSERT_ALWAYS_TRUE
+>> Assert statement is redundant because the expression is always true.
+>> WARNING
+>> Line: 27
+>> ASSERT_ALWAYS_TRUE
+>> Assert statement is redundant because the expression is always true.
+>> WARNING
+>> Line: 30
+>> ASSERT_ALWAYS_TRUE
+>> Assert statement is redundant because the expression is always true.
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/parser/features/variable_declaration.gd b/modules/gdscript/tests/scripts/parser/features/variable_declaration.gd
index 3b48f10ca7..65013c4301 100644
--- a/modules/gdscript/tests/scripts/parser/features/variable_declaration.gd
+++ b/modules/gdscript/tests/scripts/parser/features/variable_declaration.gd
@@ -1,12 +1,20 @@
-var a # No init.
-var b = 42 # Init.
+var m1 # No init.
+var m2 = 22 # Init.
+var m3: String # No init, typed.
+var m4: String = "44" # Init, typed.
+
func test():
- var c # No init, local.
- var d = 23 # Init, local.
+ var loc5 # No init, local.
+ var loc6 = 66 # Init, local.
+ var loc7: String # No init, typed.
+ var loc8: String = "88" # Init, typed.
+
+ m1 = 11
+ m3 = "33"
- a = 1
- c = 2
+ loc5 = 55
+ loc7 = "77"
- prints(a, b, c, d)
+ prints(m1, m2, m3, m4, loc5, loc6, loc7, loc8)
print("OK")
diff --git a/modules/gdscript/tests/scripts/parser/features/variable_declaration.out b/modules/gdscript/tests/scripts/parser/features/variable_declaration.out
index 2e0a63c024..7817dd3169 100644
--- a/modules/gdscript/tests/scripts/parser/features/variable_declaration.out
+++ b/modules/gdscript/tests/scripts/parser/features/variable_declaration.out
@@ -1,7 +1,3 @@
GDTEST_OK
->> WARNING
->> Line: 5
->> UNASSIGNED_VARIABLE
->> The variable 'c' was used but never assigned a value.
-1 42 2 23
+11 22 33 44 55 66 77 88
OK
diff --git a/modules/gdscript/tests/scripts/parser/features/while.gd b/modules/gdscript/tests/scripts/parser/features/while.gd
new file mode 100644
index 0000000000..17dd4fbad2
--- /dev/null
+++ b/modules/gdscript/tests/scripts/parser/features/while.gd
@@ -0,0 +1,5 @@
+func test():
+ var i = 0
+ while i < 5:
+ print(i)
+ i += 1
diff --git a/modules/gdscript/tests/scripts/parser/features/while.out b/modules/gdscript/tests/scripts/parser/features/while.out
new file mode 100644
index 0000000000..b4a50885c7
--- /dev/null
+++ b/modules/gdscript/tests/scripts/parser/features/while.out
@@ -0,0 +1,6 @@
+GDTEST_OK
+0
+1
+2
+3
+4
diff --git a/modules/gdscript/tests/scripts/parser/warnings/assert_always_true.gd b/modules/gdscript/tests/scripts/parser/warnings/assert_always_true.gd
new file mode 100644
index 0000000000..8feaed899f
--- /dev/null
+++ b/modules/gdscript/tests/scripts/parser/warnings/assert_always_true.gd
@@ -0,0 +1,5 @@
+func test():
+ # These statements always evaluate to `true`, and therefore emit a warning.
+ assert(true)
+ assert(-1.234)
+ assert(2 + 3 == 5)
diff --git a/modules/gdscript/tests/scripts/parser/warnings/assert_always_true.out b/modules/gdscript/tests/scripts/parser/warnings/assert_always_true.out
new file mode 100644
index 0000000000..5132792cb7
--- /dev/null
+++ b/modules/gdscript/tests/scripts/parser/warnings/assert_always_true.out
@@ -0,0 +1,13 @@
+GDTEST_OK
+>> WARNING
+>> Line: 3
+>> ASSERT_ALWAYS_TRUE
+>> Assert statement is redundant because the expression is always true.
+>> WARNING
+>> Line: 4
+>> ASSERT_ALWAYS_TRUE
+>> Assert statement is redundant because the expression is always true.
+>> WARNING
+>> Line: 5
+>> ASSERT_ALWAYS_TRUE
+>> Assert statement is redundant because the expression is always true.
diff --git a/modules/gdscript/tests/scripts/parser/warnings/deprecated_operators.gd b/modules/gdscript/tests/scripts/parser/warnings/deprecated_operators.gd
new file mode 100644
index 0000000000..f72b10213f
--- /dev/null
+++ b/modules/gdscript/tests/scripts/parser/warnings/deprecated_operators.gd
@@ -0,0 +1,8 @@
+func test():
+ # `and` should be used instead.
+ if true && true:
+ pass
+
+ # `or` should be used instead.
+ if false || true:
+ pass
diff --git a/modules/gdscript/tests/scripts/parser/warnings/deprecated_operators.out b/modules/gdscript/tests/scripts/parser/warnings/deprecated_operators.out
new file mode 100644
index 0000000000..d73c5eb7cd
--- /dev/null
+++ b/modules/gdscript/tests/scripts/parser/warnings/deprecated_operators.out
@@ -0,0 +1 @@
+GDTEST_OK
diff --git a/modules/gdscript/tests/scripts/parser/warnings/empty_file.notest.gd b/modules/gdscript/tests/scripts/parser/warnings/empty_file.notest.gd
new file mode 100644
index 0000000000..8b13789179
--- /dev/null
+++ b/modules/gdscript/tests/scripts/parser/warnings/empty_file.notest.gd
@@ -0,0 +1 @@
+
diff --git a/modules/gdscript/tests/scripts/parser/warnings/empty_file.notest.out b/modules/gdscript/tests/scripts/parser/warnings/empty_file.notest.out
new file mode 100644
index 0000000000..20eec212ba
--- /dev/null
+++ b/modules/gdscript/tests/scripts/parser/warnings/empty_file.notest.out
@@ -0,0 +1,4 @@
+>> WARNING
+>> Line: 1
+>> EMPTY_FILE
+>> Empty script file.
diff --git a/modules/gdscript/tests/scripts/parser/warnings/empty_file_comment.notest.gd b/modules/gdscript/tests/scripts/parser/warnings/empty_file_comment.notest.gd
new file mode 100644
index 0000000000..15cd95ff2b
--- /dev/null
+++ b/modules/gdscript/tests/scripts/parser/warnings/empty_file_comment.notest.gd
@@ -0,0 +1 @@
+#a comment
diff --git a/modules/gdscript/tests/scripts/parser/warnings/empty_file_comment.notest.out b/modules/gdscript/tests/scripts/parser/warnings/empty_file_comment.notest.out
new file mode 100644
index 0000000000..20eec212ba
--- /dev/null
+++ b/modules/gdscript/tests/scripts/parser/warnings/empty_file_comment.notest.out
@@ -0,0 +1,4 @@
+>> WARNING
+>> Line: 1
+>> EMPTY_FILE
+>> Empty script file.
diff --git a/modules/gdscript/tests/scripts/parser/warnings/empty_file_newline.notest.gd b/modules/gdscript/tests/scripts/parser/warnings/empty_file_newline.notest.gd
new file mode 100644
index 0000000000..b28b04f643
--- /dev/null
+++ b/modules/gdscript/tests/scripts/parser/warnings/empty_file_newline.notest.gd
@@ -0,0 +1,3 @@
+
+
+
diff --git a/modules/gdscript/tests/scripts/parser/warnings/empty_file_newline.notest.out b/modules/gdscript/tests/scripts/parser/warnings/empty_file_newline.notest.out
new file mode 100644
index 0000000000..20eec212ba
--- /dev/null
+++ b/modules/gdscript/tests/scripts/parser/warnings/empty_file_newline.notest.out
@@ -0,0 +1,4 @@
+>> WARNING
+>> Line: 1
+>> EMPTY_FILE
+>> Empty script file.
diff --git a/modules/gdscript/tests/scripts/parser/warnings/empty_file_newline_comment.notest.gd b/modules/gdscript/tests/scripts/parser/warnings/empty_file_newline_comment.notest.gd
new file mode 100644
index 0000000000..ecdba44d21
--- /dev/null
+++ b/modules/gdscript/tests/scripts/parser/warnings/empty_file_newline_comment.notest.gd
@@ -0,0 +1,4 @@
+#a comment, followed by a bunch of newlines
+
+
+
diff --git a/modules/gdscript/tests/scripts/parser/warnings/empty_file_newline_comment.notest.out b/modules/gdscript/tests/scripts/parser/warnings/empty_file_newline_comment.notest.out
new file mode 100644
index 0000000000..20eec212ba
--- /dev/null
+++ b/modules/gdscript/tests/scripts/parser/warnings/empty_file_newline_comment.notest.out
@@ -0,0 +1,4 @@
+>> WARNING
+>> Line: 1
+>> EMPTY_FILE
+>> Empty script file.
diff --git a/modules/gdscript/tests/scripts/parser/warnings/incompatible_ternary.gd b/modules/gdscript/tests/scripts/parser/warnings/incompatible_ternary.gd
new file mode 100644
index 0000000000..a93ecb66b1
--- /dev/null
+++ b/modules/gdscript/tests/scripts/parser/warnings/incompatible_ternary.gd
@@ -0,0 +1,8 @@
+func test():
+ # The ternary operator below returns values of different types and the
+ # result is assigned to a typed variable. This will cause a run-time error
+ # if the branch with the incompatible type is picked. Here, it won't happen
+ # since the `false` condition never evaluates to `true`. Instead, a warning
+ # will be emitted.
+ var __: int = 25
+ __ = "hello" if false else -2
diff --git a/modules/gdscript/tests/scripts/parser/warnings/incompatible_ternary.out b/modules/gdscript/tests/scripts/parser/warnings/incompatible_ternary.out
new file mode 100644
index 0000000000..7d1558c6fc
--- /dev/null
+++ b/modules/gdscript/tests/scripts/parser/warnings/incompatible_ternary.out
@@ -0,0 +1,5 @@
+GDTEST_OK
+>> WARNING
+>> Line: 8
+>> INCOMPATIBLE_TERNARY
+>> Values of the ternary conditional are not mutually compatible.
diff --git a/modules/gdscript/tests/scripts/parser/warnings/integer_division.gd b/modules/gdscript/tests/scripts/parser/warnings/integer_division.gd
new file mode 100644
index 0000000000..6117425528
--- /dev/null
+++ b/modules/gdscript/tests/scripts/parser/warnings/integer_division.gd
@@ -0,0 +1,10 @@
+func test():
+ # This should emit a warning.
+ var __ = 5 / 2
+
+ # These should not emit warnings.
+ __ = float(5) / 2
+ __ = 5 / float(2)
+ __ = 5.0 / 2
+ __ = 5 / 2.0
+ __ = 5.0 / 2.0
diff --git a/modules/gdscript/tests/scripts/parser/warnings/integer_division.out b/modules/gdscript/tests/scripts/parser/warnings/integer_division.out
new file mode 100644
index 0000000000..40eb63ffcb
--- /dev/null
+++ b/modules/gdscript/tests/scripts/parser/warnings/integer_division.out
@@ -0,0 +1,5 @@
+GDTEST_OK
+>> WARNING
+>> Line: 3
+>> INTEGER_DIVISION
+>> Integer division, decimal part will be discarded.
diff --git a/modules/gdscript/tests/scripts/parser/warnings/match_default_not_at_end.gd b/modules/gdscript/tests/scripts/parser/warnings/match_default_not_at_end.gd
new file mode 100644
index 0000000000..1eb54059dd
--- /dev/null
+++ b/modules/gdscript/tests/scripts/parser/warnings/match_default_not_at_end.gd
@@ -0,0 +1,9 @@
+func test():
+ var i = 25
+ # The default branch (`_`) should be at the end of the `match` statement.
+ # Otherwise, a warning will be emitted
+ match i:
+ _:
+ print("default")
+ 25:
+ print("is 25")
diff --git a/modules/gdscript/tests/scripts/parser/warnings/match_default_not_at_end.out b/modules/gdscript/tests/scripts/parser/warnings/match_default_not_at_end.out
new file mode 100644
index 0000000000..8630fab420
--- /dev/null
+++ b/modules/gdscript/tests/scripts/parser/warnings/match_default_not_at_end.out
@@ -0,0 +1,6 @@
+GDTEST_OK
+>> WARNING
+>> Line: 8
+>> UNREACHABLE_PATTERN
+>> Unreachable pattern (pattern after wildcard or bind).
+default
diff --git a/modules/gdscript/tests/scripts/parser/warnings/narrowing_conversion.gd b/modules/gdscript/tests/scripts/parser/warnings/narrowing_conversion.gd
new file mode 100644
index 0000000000..954e697145
--- /dev/null
+++ b/modules/gdscript/tests/scripts/parser/warnings/narrowing_conversion.gd
@@ -0,0 +1,5 @@
+func i_accept_ints_only(_i: int):
+ pass
+
+func test():
+ i_accept_ints_only(12.345)
diff --git a/modules/gdscript/tests/scripts/parser/warnings/narrowing_conversion.out b/modules/gdscript/tests/scripts/parser/warnings/narrowing_conversion.out
new file mode 100644
index 0000000000..6fb592117b
--- /dev/null
+++ b/modules/gdscript/tests/scripts/parser/warnings/narrowing_conversion.out
@@ -0,0 +1,5 @@
+GDTEST_OK
+>> WARNING
+>> Line: 5
+>> NARROWING_CONVERSION
+>> Narrowing conversion (float is converted to int and loses precision).
diff --git a/modules/gdscript/tests/scripts/parser/warnings/return_value_discarded.gd b/modules/gdscript/tests/scripts/parser/warnings/return_value_discarded.gd
new file mode 100644
index 0000000000..00598e4d50
--- /dev/null
+++ b/modules/gdscript/tests/scripts/parser/warnings/return_value_discarded.gd
@@ -0,0 +1,6 @@
+func i_return_int() -> int:
+ return 4
+
+
+func test():
+ i_return_int()
diff --git a/modules/gdscript/tests/scripts/parser/warnings/return_value_discarded.out b/modules/gdscript/tests/scripts/parser/warnings/return_value_discarded.out
new file mode 100644
index 0000000000..d73c5eb7cd
--- /dev/null
+++ b/modules/gdscript/tests/scripts/parser/warnings/return_value_discarded.out
@@ -0,0 +1 @@
+GDTEST_OK
diff --git a/modules/gdscript/tests/scripts/parser/warnings/shadowed_constant.gd b/modules/gdscript/tests/scripts/parser/warnings/shadowed_constant.gd
new file mode 100644
index 0000000000..d565d38365
--- /dev/null
+++ b/modules/gdscript/tests/scripts/parser/warnings/shadowed_constant.gd
@@ -0,0 +1,8 @@
+# See also `parser-errors/redefine-class-constant.gd`.
+const TEST = 25
+
+
+func test():
+ # Warning here. This is not an error because a new constant is created,
+ # rather than attempting to set the value of an existing constant.
+ const TEST = 50
diff --git a/modules/gdscript/tests/scripts/parser/warnings/shadowed_constant.out b/modules/gdscript/tests/scripts/parser/warnings/shadowed_constant.out
new file mode 100644
index 0000000000..9c9417e11d
--- /dev/null
+++ b/modules/gdscript/tests/scripts/parser/warnings/shadowed_constant.out
@@ -0,0 +1,9 @@
+GDTEST_OK
+>> WARNING
+>> Line: 8
+>> UNUSED_LOCAL_CONSTANT
+>> The local constant 'TEST' is declared but never used in the block. If this is intended, prefix it with an underscore: '_TEST'
+>> WARNING
+>> Line: 8
+>> SHADOWED_VARIABLE
+>> The local constant "TEST" is shadowing an already-declared constant at line 2.
diff --git a/modules/gdscript/tests/scripts/parser/warnings/shadowed_variable_class.gd b/modules/gdscript/tests/scripts/parser/warnings/shadowed_variable_class.gd
new file mode 100644
index 0000000000..66dcf309e8
--- /dev/null
+++ b/modules/gdscript/tests/scripts/parser/warnings/shadowed_variable_class.gd
@@ -0,0 +1,8 @@
+var foo = 123
+
+
+func test():
+ # Notice the `var` keyword. Without this keyword, no warning would be emitted
+ # because no new variable would be created. Instead, the class variable's value
+ # would be overwritten.
+ var foo = 456
diff --git a/modules/gdscript/tests/scripts/parser/warnings/shadowed_variable_class.out b/modules/gdscript/tests/scripts/parser/warnings/shadowed_variable_class.out
new file mode 100644
index 0000000000..82e467b368
--- /dev/null
+++ b/modules/gdscript/tests/scripts/parser/warnings/shadowed_variable_class.out
@@ -0,0 +1,9 @@
+GDTEST_OK
+>> WARNING
+>> Line: 8
+>> UNUSED_VARIABLE
+>> The local variable 'foo' is declared but never used in the block. If this is intended, prefix it with an underscore: '_foo'
+>> WARNING
+>> Line: 8
+>> SHADOWED_VARIABLE
+>> The local variable "foo" is shadowing an already-declared variable at line 1.
diff --git a/modules/gdscript/tests/scripts/parser/warnings/shadowed_variable_function.gd b/modules/gdscript/tests/scripts/parser/warnings/shadowed_variable_function.gd
new file mode 100644
index 0000000000..2c55d68be8
--- /dev/null
+++ b/modules/gdscript/tests/scripts/parser/warnings/shadowed_variable_function.gd
@@ -0,0 +1,2 @@
+func test():
+ var test = "This variable has the same name as the test() function."
diff --git a/modules/gdscript/tests/scripts/parser/warnings/shadowed_variable_function.out b/modules/gdscript/tests/scripts/parser/warnings/shadowed_variable_function.out
new file mode 100644
index 0000000000..26ce0465b1
--- /dev/null
+++ b/modules/gdscript/tests/scripts/parser/warnings/shadowed_variable_function.out
@@ -0,0 +1,9 @@
+GDTEST_OK
+>> WARNING
+>> Line: 2
+>> UNUSED_VARIABLE
+>> The local variable 'test' is declared but never used in the block. If this is intended, prefix it with an underscore: '_test'
+>> WARNING
+>> Line: 2
+>> SHADOWED_VARIABLE
+>> The local variable "test" is shadowing an already-declared function at line 1.
diff --git a/modules/gdscript/tests/scripts/parser/warnings/standalone_expression.gd b/modules/gdscript/tests/scripts/parser/warnings/standalone_expression.gd
new file mode 100644
index 0000000000..18ea260fa2
--- /dev/null
+++ b/modules/gdscript/tests/scripts/parser/warnings/standalone_expression.gd
@@ -0,0 +1,9 @@
+func test():
+ # The following statements should all be reported as standalone expressions:
+ "This is a standalone expression"
+ 1234
+ 0.0 + 0.0
+ Color(1, 1, 1)
+ Vector3.ZERO
+ [true, false]
+ float(125)
diff --git a/modules/gdscript/tests/scripts/parser/warnings/standalone_expression.out b/modules/gdscript/tests/scripts/parser/warnings/standalone_expression.out
new file mode 100644
index 0000000000..99ec87438e
--- /dev/null
+++ b/modules/gdscript/tests/scripts/parser/warnings/standalone_expression.out
@@ -0,0 +1,21 @@
+GDTEST_OK
+>> WARNING
+>> Line: 3
+>> STANDALONE_EXPRESSION
+>> Standalone expression (the line has no effect).
+>> WARNING
+>> Line: 4
+>> STANDALONE_EXPRESSION
+>> Standalone expression (the line has no effect).
+>> WARNING
+>> Line: 5
+>> STANDALONE_EXPRESSION
+>> Standalone expression (the line has no effect).
+>> WARNING
+>> Line: 7
+>> STANDALONE_EXPRESSION
+>> Standalone expression (the line has no effect).
+>> WARNING
+>> Line: 8
+>> STANDALONE_EXPRESSION
+>> Standalone expression (the line has no effect).
diff --git a/modules/gdscript/tests/scripts/parser/warnings/unassigned_variable.gd b/modules/gdscript/tests/scripts/parser/warnings/unassigned_variable.gd
new file mode 100644
index 0000000000..afb5059eea
--- /dev/null
+++ b/modules/gdscript/tests/scripts/parser/warnings/unassigned_variable.gd
@@ -0,0 +1,2 @@
+func test():
+ var __
diff --git a/modules/gdscript/tests/scripts/parser/warnings/unassigned_variable.out b/modules/gdscript/tests/scripts/parser/warnings/unassigned_variable.out
new file mode 100644
index 0000000000..cf14502e9a
--- /dev/null
+++ b/modules/gdscript/tests/scripts/parser/warnings/unassigned_variable.out
@@ -0,0 +1,5 @@
+GDTEST_OK
+>> WARNING
+>> Line: 2
+>> UNASSIGNED_VARIABLE
+>> The variable '__' was used but never assigned a value.
diff --git a/modules/gdscript/tests/scripts/parser/warnings/unassigned_variable_op_assign.gd b/modules/gdscript/tests/scripts/parser/warnings/unassigned_variable_op_assign.gd
new file mode 100644
index 0000000000..d77791f4c5
--- /dev/null
+++ b/modules/gdscript/tests/scripts/parser/warnings/unassigned_variable_op_assign.gd
@@ -0,0 +1,4 @@
+func test():
+ var __: int
+ # Variable has no set value at this point (even though it's implicitly `0` here).
+ __ += 15
diff --git a/modules/gdscript/tests/scripts/parser/warnings/unassigned_variable_op_assign.out b/modules/gdscript/tests/scripts/parser/warnings/unassigned_variable_op_assign.out
new file mode 100644
index 0000000000..ba55a4e0f8
--- /dev/null
+++ b/modules/gdscript/tests/scripts/parser/warnings/unassigned_variable_op_assign.out
@@ -0,0 +1,5 @@
+GDTEST_OK
+>> WARNING
+>> Line: 4
+>> UNASSIGNED_VARIABLE_OP_ASSIGN
+>> Using assignment with operation but the variable '__' was not previously assigned a value.
diff --git a/modules/gdscript/tests/scripts/parser/warnings/unreachable_code_after_return.gd b/modules/gdscript/tests/scripts/parser/warnings/unreachable_code_after_return.gd
new file mode 100644
index 0000000000..3311f342ab
--- /dev/null
+++ b/modules/gdscript/tests/scripts/parser/warnings/unreachable_code_after_return.gd
@@ -0,0 +1,7 @@
+func test():
+ var i = 25
+
+ return
+
+ # This will never be run due to the `return` statement above.
+ print(i)
diff --git a/modules/gdscript/tests/scripts/parser/warnings/unreachable_code_after_return.out b/modules/gdscript/tests/scripts/parser/warnings/unreachable_code_after_return.out
new file mode 100644
index 0000000000..9316abd5eb
--- /dev/null
+++ b/modules/gdscript/tests/scripts/parser/warnings/unreachable_code_after_return.out
@@ -0,0 +1,5 @@
+GDTEST_OK
+>> WARNING
+>> Line: 7
+>> UNREACHABLE_CODE
+>> Unreachable code (statement after return) in function 'test()'.
diff --git a/modules/gdscript/tests/scripts/parser/warnings/unused_argument.gd b/modules/gdscript/tests/scripts/parser/warnings/unused_argument.gd
new file mode 100644
index 0000000000..e6e24dc6f2
--- /dev/null
+++ b/modules/gdscript/tests/scripts/parser/warnings/unused_argument.gd
@@ -0,0 +1,12 @@
+# This should emit a warning since the unused argument is not prefixed with an underscore.
+func function_with_unused_argument(p_arg1, p_arg2):
+ print(p_arg1)
+
+
+# This shouldn't emit a warning since the unused argument is prefixed with an underscore.
+func function_with_ignored_unused_argument(p_arg1, _p_arg2):
+ print(p_arg1)
+
+
+func test():
+ pass
diff --git a/modules/gdscript/tests/scripts/parser/warnings/unused_argument.out b/modules/gdscript/tests/scripts/parser/warnings/unused_argument.out
new file mode 100644
index 0000000000..92f3308f85
--- /dev/null
+++ b/modules/gdscript/tests/scripts/parser/warnings/unused_argument.out
@@ -0,0 +1,5 @@
+GDTEST_OK
+>> WARNING
+>> Line: 2
+>> UNUSED_PARAMETER
+>> The parameter 'p_arg2' is never used in the function 'function_with_unused_argument'. If this is intended, prefix it with an underscore: '_p_arg2'
diff --git a/modules/gdscript/tests/scripts/parser/warnings/unused_variable.gd b/modules/gdscript/tests/scripts/parser/warnings/unused_variable.gd
index 68e3bd424f..013a2e4beb 100644
--- a/modules/gdscript/tests/scripts/parser/warnings/unused_variable.gd
+++ b/modules/gdscript/tests/scripts/parser/warnings/unused_variable.gd
@@ -1,2 +1,4 @@
func test():
- var unused = "not used"
+ var unused = "not used"
+
+ var _unused = "not used, but no warning since the variable name starts with an underscore"
diff --git a/modules/gdscript/tests/scripts/parser/warnings/void_assignment.gd b/modules/gdscript/tests/scripts/parser/warnings/void_assignment.gd
new file mode 100644
index 0000000000..b4a42b3e3d
--- /dev/null
+++ b/modules/gdscript/tests/scripts/parser/warnings/void_assignment.gd
@@ -0,0 +1,6 @@
+func i_return_void() -> void:
+ return
+
+
+func test():
+ var __ = i_return_void()
diff --git a/modules/gdscript/tests/scripts/parser/warnings/void_assignment.out b/modules/gdscript/tests/scripts/parser/warnings/void_assignment.out
new file mode 100644
index 0000000000..84c9598f9a
--- /dev/null
+++ b/modules/gdscript/tests/scripts/parser/warnings/void_assignment.out
@@ -0,0 +1,5 @@
+GDTEST_OK
+>> WARNING
+>> Line: 6
+>> VOID_ASSIGNMENT
+>> Assignment operation, but the function 'i_return_void()' returns void.
diff --git a/modules/gdscript/tests/scripts/runtime/features/compare-builtin-equals-null.gd b/modules/gdscript/tests/scripts/runtime/features/compare-builtin-equals-null.gd
new file mode 100644
index 0000000000..c6645c2c34
--- /dev/null
+++ b/modules/gdscript/tests/scripts/runtime/features/compare-builtin-equals-null.gd
@@ -0,0 +1,138 @@
+func test():
+ var value
+
+ # null
+ value = null
+ print(value == null)
+
+ # bool
+ value = false
+ print(value == null)
+
+ # int
+ value = 0
+ print(value == null)
+
+ # float
+ value = 0.0
+ print(value == null)
+
+ # String
+ value = ""
+ print(value == null)
+
+ # Vector2
+ value = Vector2()
+ print(value == null)
+
+ # Vector2i
+ value = Vector2i()
+ print(value == null)
+
+ # Rect2
+ value = Rect2()
+ print(value == null)
+
+ # Rect2i
+ value = Rect2i()
+ print(value == null)
+
+ # Vector3
+ value = Vector3()
+ print(value == null)
+
+ # Vector3i
+ value = Vector3i()
+ print(value == null)
+
+ # Transform2D
+ value = Transform2D()
+ print(value == null)
+
+ # Plane
+ value = Plane()
+ print(value == null)
+
+ # Quaternion
+ value = Quaternion()
+ print(value == null)
+
+ # AABB
+ value = AABB()
+ print(value == null)
+
+ # Basis
+ value = Basis()
+ print(value == null)
+
+ # Transform3D
+ value = Transform3D()
+ print(value == null)
+
+ # Color
+ value = Color()
+ print(value == null)
+
+ # StringName
+ value = &""
+ print(value == null)
+
+ # NodePath
+ value = ^""
+ print(value == null)
+
+ # RID
+ value = RID()
+ print(value == null)
+
+ # Callable
+ value = Callable()
+ print(value == null)
+
+ # Signal
+ value = Signal()
+ print(value == null)
+
+ # Dictionary
+ value = {}
+ print(value == null)
+
+ # Array
+ value = []
+ print(value == null)
+
+ # PackedByteArray
+ value = PackedByteArray()
+ print(value == null)
+
+ # PackedInt32Array
+ value = PackedInt32Array()
+ print(value == null)
+
+ # PackedInt64Array
+ value = PackedInt64Array()
+ print(value == null)
+
+ # PackedFloat32Array
+ value = PackedFloat32Array()
+ print(value == null)
+
+ # PackedFloat64Array
+ value = PackedFloat64Array()
+ print(value == null)
+
+ # PackedStringArray
+ value = PackedStringArray()
+ print(value == null)
+
+ # PackedVector2Array
+ value = PackedVector2Array()
+ print(value == null)
+
+ # PackedVector3Array
+ value = PackedVector3Array()
+ print(value == null)
+
+ # PackedColorArray
+ value = PackedColorArray()
+ print(value == null)
diff --git a/modules/gdscript/tests/scripts/runtime/features/compare-builtin-equals-null.out b/modules/gdscript/tests/scripts/runtime/features/compare-builtin-equals-null.out
new file mode 100644
index 0000000000..639f6027b9
--- /dev/null
+++ b/modules/gdscript/tests/scripts/runtime/features/compare-builtin-equals-null.out
@@ -0,0 +1,35 @@
+GDTEST_OK
+true
+false
+false
+false
+false
+false
+false
+false
+false
+false
+false
+false
+false
+false
+false
+false
+false
+false
+false
+false
+false
+false
+false
+false
+false
+false
+false
+false
+false
+false
+false
+false
+false
+false
diff --git a/modules/gdscript/tests/scripts/runtime/features/compare-builtin-not-equals-null.gd b/modules/gdscript/tests/scripts/runtime/features/compare-builtin-not-equals-null.gd
new file mode 100644
index 0000000000..ee622bf22f
--- /dev/null
+++ b/modules/gdscript/tests/scripts/runtime/features/compare-builtin-not-equals-null.gd
@@ -0,0 +1,138 @@
+func test():
+ var value
+
+ # null
+ value = null
+ print(value != null)
+
+ # bool
+ value = false
+ print(value != null)
+
+ # int
+ value = 0
+ print(value != null)
+
+ # float
+ value = 0.0
+ print(value != null)
+
+ # String
+ value = ""
+ print(value != null)
+
+ # Vector2
+ value = Vector2()
+ print(value != null)
+
+ # Vector2i
+ value = Vector2i()
+ print(value != null)
+
+ # Rect2
+ value = Rect2()
+ print(value != null)
+
+ # Rect2i
+ value = Rect2i()
+ print(value != null)
+
+ # Vector3
+ value = Vector3()
+ print(value != null)
+
+ # Vector3i
+ value = Vector3i()
+ print(value != null)
+
+ # Transform2D
+ value = Transform2D()
+ print(value != null)
+
+ # Plane
+ value = Plane()
+ print(value != null)
+
+ # Quaternion
+ value = Quaternion()
+ print(value != null)
+
+ # AABB
+ value = AABB()
+ print(value != null)
+
+ # Basis
+ value = Basis()
+ print(value != null)
+
+ # Transform3D
+ value = Transform3D()
+ print(value != null)
+
+ # Color
+ value = Color()
+ print(value != null)
+
+ # StringName
+ value = &""
+ print(value != null)
+
+ # NodePath
+ value = ^""
+ print(value != null)
+
+ # RID
+ value = RID()
+ print(value != null)
+
+ # Callable
+ value = Callable()
+ print(value != null)
+
+ # Signal
+ value = Signal()
+ print(value != null)
+
+ # Dictionary
+ value = {}
+ print(value != null)
+
+ # Array
+ value = []
+ print(value != null)
+
+ # PackedByteArray
+ value = PackedByteArray()
+ print(value != null)
+
+ # PackedInt32Array
+ value = PackedInt32Array()
+ print(value != null)
+
+ # PackedInt64Array
+ value = PackedInt64Array()
+ print(value != null)
+
+ # PackedFloat32Array
+ value = PackedFloat32Array()
+ print(value != null)
+
+ # PackedFloat64Array
+ value = PackedFloat64Array()
+ print(value != null)
+
+ # PackedStringArray
+ value = PackedStringArray()
+ print(value != null)
+
+ # PackedVector2Array
+ value = PackedVector2Array()
+ print(value != null)
+
+ # PackedVector3Array
+ value = PackedVector3Array()
+ print(value != null)
+
+ # PackedColorArray
+ value = PackedColorArray()
+ print(value != null)
diff --git a/modules/gdscript/tests/scripts/runtime/features/compare-builtin-not-equals-null.out b/modules/gdscript/tests/scripts/runtime/features/compare-builtin-not-equals-null.out
new file mode 100644
index 0000000000..d1e332afba
--- /dev/null
+++ b/modules/gdscript/tests/scripts/runtime/features/compare-builtin-not-equals-null.out
@@ -0,0 +1,35 @@
+GDTEST_OK
+false
+true
+true
+true
+true
+true
+true
+true
+true
+true
+true
+true
+true
+true
+true
+true
+true
+true
+true
+true
+true
+true
+true
+true
+true
+true
+true
+true
+true
+true
+true
+true
+true
+true
diff --git a/modules/gdscript/tests/scripts/runtime/features/compare-null-equals-builtin.gd b/modules/gdscript/tests/scripts/runtime/features/compare-null-equals-builtin.gd
new file mode 100644
index 0000000000..7649062fda
--- /dev/null
+++ b/modules/gdscript/tests/scripts/runtime/features/compare-null-equals-builtin.gd
@@ -0,0 +1,138 @@
+func test():
+ var value
+
+ # null
+ value = null
+ print(null == value)
+
+ # bool
+ value = false
+ print(null == value)
+
+ # int
+ value = 0
+ print(null == value)
+
+ # float
+ value = 0.0
+ print(null == value)
+
+ # String
+ value = ""
+ print(null == value)
+
+ # Vector2
+ value = Vector2()
+ print(null == value)
+
+ # Vector2i
+ value = Vector2i()
+ print(null == value)
+
+ # Rect2
+ value = Rect2()
+ print(null == value)
+
+ # Rect2i
+ value = Rect2i()
+ print(null == value)
+
+ # Vector3
+ value = Vector3()
+ print(null == value)
+
+ # Vector3i
+ value = Vector3i()
+ print(null == value)
+
+ # Transform2D
+ value = Transform2D()
+ print(null == value)
+
+ # Plane
+ value = Plane()
+ print(null == value)
+
+ # Quaternion
+ value = Quaternion()
+ print(null == value)
+
+ # AABB
+ value = AABB()
+ print(null == value)
+
+ # Basis
+ value = Basis()
+ print(null == value)
+
+ # Transform3D
+ value = Transform3D()
+ print(null == value)
+
+ # Color
+ value = Color()
+ print(null == value)
+
+ # StringName
+ value = &""
+ print(null == value)
+
+ # NodePath
+ value = ^""
+ print(null == value)
+
+ # RID
+ value = RID()
+ print(null == value)
+
+ # Callable
+ value = Callable()
+ print(null == value)
+
+ # Signal
+ value = Signal()
+ print(null == value)
+
+ # Dictionary
+ value = {}
+ print(null == value)
+
+ # Array
+ value = []
+ print(null == value)
+
+ # PackedByteArray
+ value = PackedByteArray()
+ print(null == value)
+
+ # PackedInt32Array
+ value = PackedInt32Array()
+ print(null == value)
+
+ # PackedInt64Array
+ value = PackedInt64Array()
+ print(null == value)
+
+ # PackedFloat32Array
+ value = PackedFloat32Array()
+ print(null == value)
+
+ # PackedFloat64Array
+ value = PackedFloat64Array()
+ print(null == value)
+
+ # PackedStringArray
+ value = PackedStringArray()
+ print(null == value)
+
+ # PackedVector2Array
+ value = PackedVector2Array()
+ print(null == value)
+
+ # PackedVector3Array
+ value = PackedVector3Array()
+ print(null == value)
+
+ # PackedColorArray
+ value = PackedColorArray()
+ print(null == value)
diff --git a/modules/gdscript/tests/scripts/runtime/features/compare-null-equals-builtin.out b/modules/gdscript/tests/scripts/runtime/features/compare-null-equals-builtin.out
new file mode 100644
index 0000000000..639f6027b9
--- /dev/null
+++ b/modules/gdscript/tests/scripts/runtime/features/compare-null-equals-builtin.out
@@ -0,0 +1,35 @@
+GDTEST_OK
+true
+false
+false
+false
+false
+false
+false
+false
+false
+false
+false
+false
+false
+false
+false
+false
+false
+false
+false
+false
+false
+false
+false
+false
+false
+false
+false
+false
+false
+false
+false
+false
+false
+false
diff --git a/modules/gdscript/tests/scripts/runtime/features/compare-null-not-equals-builtin.gd b/modules/gdscript/tests/scripts/runtime/features/compare-null-not-equals-builtin.gd
new file mode 100644
index 0000000000..8d5f9df1b8
--- /dev/null
+++ b/modules/gdscript/tests/scripts/runtime/features/compare-null-not-equals-builtin.gd
@@ -0,0 +1,138 @@
+func test():
+ var value
+
+ # null
+ value = null
+ print(null != value)
+
+ # bool
+ value = false
+ print(null != value)
+
+ # int
+ value = 0
+ print(null != value)
+
+ # float
+ value = 0.0
+ print(null != value)
+
+ # String
+ value = ""
+ print(null != value)
+
+ # Vector2
+ value = Vector2()
+ print(null != value)
+
+ # Vector2i
+ value = Vector2i()
+ print(null != value)
+
+ # Rect2
+ value = Rect2()
+ print(null != value)
+
+ # Rect2i
+ value = Rect2i()
+ print(null != value)
+
+ # Vector3
+ value = Vector3()
+ print(null != value)
+
+ # Vector3i
+ value = Vector3i()
+ print(null != value)
+
+ # Transform2D
+ value = Transform2D()
+ print(null != value)
+
+ # Plane
+ value = Plane()
+ print(null != value)
+
+ # Quaternion
+ value = Quaternion()
+ print(null != value)
+
+ # AABB
+ value = AABB()
+ print(null != value)
+
+ # Basis
+ value = Basis()
+ print(null != value)
+
+ # Transform3D
+ value = Transform3D()
+ print(null != value)
+
+ # Color
+ value = Color()
+ print(null != value)
+
+ # StringName
+ value = &""
+ print(null != value)
+
+ # NodePath
+ value = ^""
+ print(null != value)
+
+ # RID
+ value = RID()
+ print(null != value)
+
+ # Callable
+ value = Callable()
+ print(null != value)
+
+ # Signal
+ value = Signal()
+ print(null != value)
+
+ # Dictionary
+ value = {}
+ print(null != value)
+
+ # Array
+ value = []
+ print(null != value)
+
+ # PackedByteArray
+ value = PackedByteArray()
+ print(null != value)
+
+ # PackedInt32Array
+ value = PackedInt32Array()
+ print(null != value)
+
+ # PackedInt64Array
+ value = PackedInt64Array()
+ print(null != value)
+
+ # PackedFloat32Array
+ value = PackedFloat32Array()
+ print(null != value)
+
+ # PackedFloat64Array
+ value = PackedFloat64Array()
+ print(null != value)
+
+ # PackedStringArray
+ value = PackedStringArray()
+ print(null != value)
+
+ # PackedVector2Array
+ value = PackedVector2Array()
+ print(null != value)
+
+ # PackedVector3Array
+ value = PackedVector3Array()
+ print(null != value)
+
+ # PackedColorArray
+ value = PackedColorArray()
+ print(null != value)
diff --git a/modules/gdscript/tests/scripts/runtime/features/compare-null-not-equals-builtin.out b/modules/gdscript/tests/scripts/runtime/features/compare-null-not-equals-builtin.out
new file mode 100644
index 0000000000..d1e332afba
--- /dev/null
+++ b/modules/gdscript/tests/scripts/runtime/features/compare-null-not-equals-builtin.out
@@ -0,0 +1,35 @@
+GDTEST_OK
+false
+true
+true
+true
+true
+true
+true
+true
+true
+true
+true
+true
+true
+true
+true
+true
+true
+true
+true
+true
+true
+true
+true
+true
+true
+true
+true
+true
+true
+true
+true
+true
+true
+true
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