summaryrefslogtreecommitdiff
path: root/modules
diff options
context:
space:
mode:
Diffstat (limited to 'modules')
-rw-r--r--modules/bullet/bullet_physics_server.cpp4
-rw-r--r--modules/bullet/shape_bullet.cpp20
-rw-r--r--modules/bullet/shape_bullet.h6
-rw-r--r--modules/csg/csg_shape.cpp2
-rw-r--r--modules/gdscript/gdscript_analyzer.cpp100
-rw-r--r--modules/gdscript/gdscript_byte_codegen.cpp6
-rw-r--r--modules/gdscript/gdscript_byte_codegen.h1
-rw-r--r--modules/gdscript/gdscript_cache.cpp12
-rw-r--r--modules/gdscript/gdscript_cache.h1
-rw-r--r--modules/gdscript/gdscript_codegen.h1
-rw-r--r--modules/gdscript/gdscript_compiler.cpp23
-rw-r--r--modules/gdscript/gdscript_disassembler.cpp8
-rw-r--r--modules/gdscript/gdscript_editor.cpp2
-rw-r--r--modules/gdscript/gdscript_function.h1
-rw-r--r--modules/gdscript/gdscript_parser.cpp99
-rw-r--r--modules/gdscript/gdscript_parser.h4
-rw-r--r--modules/gdscript/gdscript_vm.cpp28
-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_server.cpp7
-rw-r--r--modules/gdscript/language_server/gdscript_language_server.h1
-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/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/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/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_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/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/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/features/array.gd16
-rw-r--r--modules/gdscript/tests/scripts/parser/features/array.out11
-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.gd11
-rw-r--r--modules/gdscript/tests/scripts/parser/features/export_variable.out5
-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/in.gd14
-rw-r--r--modules/gdscript/tests/scripts/parser/features/in.out11
-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_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/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/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/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/gltf/doc_classes/GLTFCamera.xml8
-rw-r--r--modules/gltf/doc_classes/GLTFLight.xml4
-rw-r--r--modules/gltf/doc_classes/GLTFNode.xml4
-rw-r--r--modules/gltf/gltf_animation.h2
-rw-r--r--modules/gltf/gltf_camera.cpp12
-rw-r--r--modules/gltf/gltf_camera.h12
-rw-r--r--modules/gltf/gltf_document.cpp172
-rw-r--r--modules/gltf/gltf_light.cpp14
-rw-r--r--modules/gltf/gltf_light.h6
-rw-r--r--modules/gltf/gltf_node.cpp14
-rw-r--r--modules/gltf/gltf_node.h6
-rw-r--r--modules/gridmap/grid_map.cpp2
-rw-r--r--modules/gridmap/grid_map_editor_plugin.cpp6
-rw-r--r--modules/lightmapper_rd/lm_compute.glsl73
-rw-r--r--modules/mono/csharp_script.cpp62
-rw-r--r--modules/mono/csharp_script.h2
-rw-r--r--modules/mono/editor/GodotTools/GodotTools/GodotSharpEditor.cs8
-rw-r--r--modules/mono/editor/GodotTools/GodotTools/Internals/Globals.cs6
-rw-r--r--modules/mono/editor/code_completion.cpp4
-rw-r--r--modules/mono/editor/editor_internal_calls.cpp7
-rw-r--r--modules/mono/glue/GodotSharp/GodotSharp/Core/Extensions/NodeExtensions.cs6
-rw-r--r--modules/mono/glue/GodotSharp/GodotSharp/Core/GD.cs2
-rw-r--r--modules/mono/glue/GodotSharp/GodotSharp/Core/Transform2D.cs94
-rw-r--r--modules/navigation/navigation_mesh_generator.cpp2
-rw-r--r--modules/raycast/SCsub3
-rw-r--r--modules/raycast/godot_update_embree.py12
-rw-r--r--modules/raycast/raycast_occlusion_cull.cpp123
-rw-r--r--modules/raycast/raycast_occlusion_cull.h17
-rw-r--r--modules/text_server_adv/SCsub1
-rw-r--r--modules/text_server_adv/script_iterator.cpp20
-rw-r--r--modules/text_server_adv/script_iterator.h2
-rw-r--r--modules/text_server_adv/text_server_adv.cpp18
-rw-r--r--modules/text_server_fb/text_server_fb.cpp7
-rw-r--r--modules/vhacd/register_types.cpp69
-rw-r--r--modules/visual_script/doc_classes/VisualScriptCustomNode.xml2
-rw-r--r--modules/visual_script/register_types.cpp2
-rw-r--r--modules/visual_script/visual_script_editor.cpp40
-rw-r--r--modules/visual_script/visual_script_editor.h6
-rw-r--r--modules/visual_script/visual_script_func_nodes.cpp2
-rw-r--r--modules/websocket/editor_debugger_server_websocket.cpp14
-rw-r--r--modules/websocket/editor_debugger_server_websocket.h2
299 files changed, 3494 insertions, 394 deletions
diff --git a/modules/bullet/bullet_physics_server.cpp b/modules/bullet/bullet_physics_server.cpp
index fc876a81cf..ed05e51e53 100644
--- a/modules/bullet/bullet_physics_server.cpp
+++ b/modules/bullet/bullet_physics_server.cpp
@@ -87,8 +87,8 @@ RID BulletPhysicsServer3D::shape_create(ShapeType p_shape) {
ShapeBullet *shape = nullptr;
switch (p_shape) {
- case SHAPE_PLANE: {
- shape = bulletnew(PlaneShapeBullet);
+ case SHAPE_WORLD_BOUNDARY: {
+ shape = bulletnew(WorldBoundaryShapeBullet);
} break;
case SHAPE_SPHERE: {
shape = bulletnew(SphereShapeBullet);
diff --git a/modules/bullet/shape_bullet.cpp b/modules/bullet/shape_bullet.cpp
index 40e785d699..88ffb9ec67 100644
--- a/modules/bullet/shape_bullet.cpp
+++ b/modules/bullet/shape_bullet.cpp
@@ -110,7 +110,7 @@ btEmptyShape *ShapeBullet::create_shape_empty() {
return bulletnew(btEmptyShape);
}
-btStaticPlaneShape *ShapeBullet::create_shape_plane(const btVector3 &planeNormal, btScalar planeConstant) {
+btStaticPlaneShape *ShapeBullet::create_shape_world_boundary(const btVector3 &planeNormal, btScalar planeConstant) {
return bulletnew(btStaticPlaneShape(planeNormal, planeConstant));
}
@@ -164,32 +164,32 @@ btRayShape *ShapeBullet::create_shape_ray(real_t p_length, bool p_slips_on_slope
return r;
}
-/* PLANE */
+/* World boundary */
-PlaneShapeBullet::PlaneShapeBullet() :
+WorldBoundaryShapeBullet::WorldBoundaryShapeBullet() :
ShapeBullet() {}
-void PlaneShapeBullet::set_data(const Variant &p_data) {
+void WorldBoundaryShapeBullet::set_data(const Variant &p_data) {
setup(p_data);
}
-Variant PlaneShapeBullet::get_data() const {
+Variant WorldBoundaryShapeBullet::get_data() const {
return plane;
}
-PhysicsServer3D::ShapeType PlaneShapeBullet::get_type() const {
- return PhysicsServer3D::SHAPE_PLANE;
+PhysicsServer3D::ShapeType WorldBoundaryShapeBullet::get_type() const {
+ return PhysicsServer3D::SHAPE_WORLD_BOUNDARY;
}
-void PlaneShapeBullet::setup(const Plane &p_plane) {
+void WorldBoundaryShapeBullet::setup(const Plane &p_plane) {
plane = p_plane;
notifyShapeChanged();
}
-btCollisionShape *PlaneShapeBullet::create_bt_shape(const btVector3 &p_implicit_scale, real_t p_extra_edge) {
+btCollisionShape *WorldBoundaryShapeBullet::create_bt_shape(const btVector3 &p_implicit_scale, real_t p_extra_edge) {
btVector3 btPlaneNormal;
G_TO_B(plane.normal, btPlaneNormal);
- return prepare(PlaneShapeBullet::create_shape_plane(btPlaneNormal, plane.d));
+ return prepare(WorldBoundaryShapeBullet::create_shape_world_boundary(btPlaneNormal, plane.d));
}
/* Sphere */
diff --git a/modules/bullet/shape_bullet.h b/modules/bullet/shape_bullet.h
index 5080d13d99..0822399b5e 100644
--- a/modules/bullet/shape_bullet.h
+++ b/modules/bullet/shape_bullet.h
@@ -81,7 +81,7 @@ public:
public:
static class btEmptyShape *create_shape_empty();
- static class btStaticPlaneShape *create_shape_plane(const btVector3 &planeNormal, btScalar planeConstant);
+ static class btStaticPlaneShape *create_shape_world_boundary(const btVector3 &planeNormal, btScalar planeConstant);
static class btSphereShape *create_shape_sphere(btScalar radius);
static class btBoxShape *create_shape_box(const btVector3 &boxHalfExtents);
static class btCapsuleShape *create_shape_capsule(btScalar radius, btScalar height);
@@ -93,11 +93,11 @@ public:
static class btRayShape *create_shape_ray(real_t p_length, bool p_slips_on_slope);
};
-class PlaneShapeBullet : public ShapeBullet {
+class WorldBoundaryShapeBullet : public ShapeBullet {
Plane plane;
public:
- PlaneShapeBullet();
+ WorldBoundaryShapeBullet();
virtual void set_data(const Variant &p_data);
virtual Variant get_data() const;
diff --git a/modules/csg/csg_shape.cpp b/modules/csg/csg_shape.cpp
index bf11cc7f68..452fb32d9d 100644
--- a/modules/csg/csg_shape.cpp
+++ b/modules/csg/csg_shape.cpp
@@ -280,7 +280,7 @@ void CSGShape3D::mikktSetTSpaceDefault(const SMikkTSpaceContext *pContext, const
}
void CSGShape3D::_update_shape() {
- if (parent) {
+ if (parent || !is_inside_tree()) {
return;
}
diff --git a/modules/gdscript/gdscript_analyzer.cpp b/modules/gdscript/gdscript_analyzer.cpp
index fc0bef3ba2..ceb6d5a5f0 100644
--- a/modules/gdscript/gdscript_analyzer.cpp
+++ b/modules/gdscript/gdscript_analyzer.cpp
@@ -171,7 +171,7 @@ Error GDScriptAnalyzer::check_native_member_name_conflict(const StringName &p_me
}
if (class_exists(p_member_name)) {
- push_error(vformat(R"(The class "%s" shadows a native class.)", p_member_name), p_member_node);
+ push_error(vformat(R"(The member "%s" shadows a native class.)", p_member_name), p_member_node);
return ERR_PARSE_ERROR;
}
@@ -218,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.
@@ -242,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);
@@ -681,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);
@@ -1172,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());
@@ -1465,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) {
@@ -1478,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);
}
@@ -1748,6 +1782,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()) {
@@ -2409,13 +2452,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: {
@@ -2436,7 +2481,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);
+ }
}
}
}
@@ -2516,14 +2563,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;
diff --git a/modules/gdscript/gdscript_byte_codegen.cpp b/modules/gdscript/gdscript_byte_codegen.cpp
index bed67b55f0..1127488db8 100644
--- a/modules/gdscript/gdscript_byte_codegen.cpp
+++ b/modules/gdscript/gdscript_byte_codegen.cpp
@@ -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 ce1a043b28..dcc11ebdce 100644
--- a/modules/gdscript/gdscript_byte_codegen.h
+++ b/modules/gdscript/gdscript_byte_codegen.h
@@ -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 7713d13bc8..e6ecc92d55 100644
--- a/modules/gdscript/gdscript_codegen.h
+++ b/modules/gdscript/gdscript_codegen.h
@@ -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 736f6eae79..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 {
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 f809a4dab8..f79e5726ce 100644
--- a/modules/gdscript/gdscript_editor.cpp
+++ b/modules/gdscript/gdscript_editor.cpp
@@ -1733,7 +1733,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()) {
diff --git a/modules/gdscript/gdscript_function.h b/modules/gdscript/gdscript_function.h
index b21cb47910..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,
diff --git a/modules/gdscript/gdscript_parser.cpp b/modules/gdscript/gdscript_parser.cpp
index 6c3d4367e4..c901d9f68f 100644
--- a/modules/gdscript/gdscript_parser.cpp
+++ b/modules/gdscript/gdscript_parser.cpp
@@ -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();
@@ -2106,22 +2123,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.
@@ -2258,6 +2287,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;
}
@@ -2369,8 +2402,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
@@ -2451,8 +2488,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;
}
@@ -2466,7 +2508,11 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_dictionary(ExpressionNode
}
if (key != nullptr) {
key->is_constant = true;
- key->reduced_value = static_cast<IdentifierNode *>(key)->name;
+ 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:
@@ -2546,6 +2592,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.)");
@@ -3541,6 +3591,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 4902f0d4a6..a641c1052d 100644
--- a/modules/gdscript/gdscript_parser.h
+++ b/modules/gdscript/gdscript_parser.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.
diff --git a/modules/gdscript/gdscript_vm.cpp b/modules/gdscript/gdscript_vm.cpp
index 882256b7e3..bf21c8510a 100644
--- a/modules/gdscript/gdscript_vm.cpp
+++ b/modules/gdscript/gdscript_vm.cpp
@@ -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->get_type() == Variant::OBJECT && !src->operator ObjectID().is_ref_counted() && ObjectDB::get_instance(src->operator ObjectID()) == nullptr) {
+ 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->get_type() == Variant::OBJECT && !src->operator ObjectID().is_ref_counted() && ObjectDB::get_instance(src->operator ObjectID()) == nullptr) {
+ 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->get_type() == Variant::OBJECT && !src->operator ObjectID().is_ref_counted() && ObjectDB::get_instance(src->operator ObjectID()) == nullptr) {
+ 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;
@@ -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_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/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..4502960105
--- /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..1879fc1adf
--- /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/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/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/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_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/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/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/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/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..51e7d4a8ed
--- /dev/null
+++ b/modules/gdscript/tests/scripts/parser/features/export_variable.gd
@@ -0,0 +1,11 @@
+@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
+
+
+func test():
+ print(example)
+ print(example_range)
+ print(example_range_step)
+ print(example_range_step_or_greater)
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..b455196359
--- /dev/null
+++ b/modules/gdscript/tests/scripts/parser/features/export_variable.out
@@ -0,0 +1,5 @@
+GDTEST_OK
+99
+100
+101
+102
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/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/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_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/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/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/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/gltf/doc_classes/GLTFCamera.xml b/modules/gltf/doc_classes/GLTFCamera.xml
index 0b95f2c802..ec25d84756 100644
--- a/modules/gltf/doc_classes/GLTFCamera.xml
+++ b/modules/gltf/doc_classes/GLTFCamera.xml
@@ -9,13 +9,13 @@
<methods>
</methods>
<members>
- <member name="fov_size" type="float" setter="set_fov_size" getter="get_fov_size" default="75.0">
+ <member name="depth_far" type="float" setter="set_depth_far" getter="get_depth_far" default="4000.0">
</member>
- <member name="perspective" type="bool" setter="set_perspective" getter="get_perspective" default="true">
+ <member name="depth_near" type="float" setter="set_depth_near" getter="get_depth_near" default="0.05">
</member>
- <member name="zfar" type="float" setter="set_zfar" getter="get_zfar" default="4000.0">
+ <member name="fov_size" type="float" setter="set_fov_size" getter="get_fov_size" default="75.0">
</member>
- <member name="znear" type="float" setter="set_znear" getter="get_znear" default="0.05">
+ <member name="perspective" type="bool" setter="set_perspective" getter="get_perspective" default="true">
</member>
</members>
<constants>
diff --git a/modules/gltf/doc_classes/GLTFLight.xml b/modules/gltf/doc_classes/GLTFLight.xml
index f51d287685..2eb5ee9070 100644
--- a/modules/gltf/doc_classes/GLTFLight.xml
+++ b/modules/gltf/doc_classes/GLTFLight.xml
@@ -15,12 +15,12 @@
</member>
<member name="intensity" type="float" setter="set_intensity" getter="get_intensity" default="0.0">
</member>
+ <member name="light_type" type="String" setter="set_light_type" getter="get_light_type" default="&quot;&quot;">
+ </member>
<member name="outer_cone_angle" type="float" setter="set_outer_cone_angle" getter="get_outer_cone_angle" default="0.0">
</member>
<member name="range" type="float" setter="set_range" getter="get_range" default="0.0">
</member>
- <member name="type" type="String" setter="set_type" getter="get_type" default="&quot;&quot;">
- </member>
</members>
<constants>
</constants>
diff --git a/modules/gltf/doc_classes/GLTFNode.xml b/modules/gltf/doc_classes/GLTFNode.xml
index bfbb12df4d..95d7283398 100644
--- a/modules/gltf/doc_classes/GLTFNode.xml
+++ b/modules/gltf/doc_classes/GLTFNode.xml
@@ -23,6 +23,8 @@
</member>
<member name="parent" type="int" setter="set_parent" getter="get_parent" default="-1">
</member>
+ <member name="position" type="Vector3" setter="set_position" getter="get_position" default="Vector3(0, 0, 0)">
+ </member>
<member name="rotation" type="Quaternion" setter="set_rotation" getter="get_rotation" default="Quaternion(0, 0, 0, 1)">
</member>
<member name="scale" type="Vector3" setter="set_scale" getter="get_scale" default="Vector3(1, 1, 1)">
@@ -31,8 +33,6 @@
</member>
<member name="skin" type="int" setter="set_skin" getter="get_skin" default="-1">
</member>
- <member name="translation" type="Vector3" setter="set_translation" getter="get_translation" default="Vector3(0, 0, 0)">
- </member>
<member name="xform" type="Transform3D" setter="set_xform" getter="get_xform" default="Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0)">
</member>
</members>
diff --git a/modules/gltf/gltf_animation.h b/modules/gltf/gltf_animation.h
index 216d2161c4..be0ed2d4c6 100644
--- a/modules/gltf/gltf_animation.h
+++ b/modules/gltf/gltf_animation.h
@@ -55,7 +55,7 @@ public:
};
struct Track {
- Channel<Vector3> translation_track;
+ Channel<Vector3> position_track;
Channel<Quaternion> rotation_track;
Channel<Vector3> scale_track;
Vector<Channel<float>> weight_tracks;
diff --git a/modules/gltf/gltf_camera.cpp b/modules/gltf/gltf_camera.cpp
index efa7c5d6d7..0f895fb989 100644
--- a/modules/gltf/gltf_camera.cpp
+++ b/modules/gltf/gltf_camera.cpp
@@ -35,13 +35,13 @@ void GLTFCamera::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_perspective", "perspective"), &GLTFCamera::set_perspective);
ClassDB::bind_method(D_METHOD("get_fov_size"), &GLTFCamera::get_fov_size);
ClassDB::bind_method(D_METHOD("set_fov_size", "fov_size"), &GLTFCamera::set_fov_size);
- ClassDB::bind_method(D_METHOD("get_zfar"), &GLTFCamera::get_zfar);
- ClassDB::bind_method(D_METHOD("set_zfar", "zfar"), &GLTFCamera::set_zfar);
- ClassDB::bind_method(D_METHOD("get_znear"), &GLTFCamera::get_znear);
- ClassDB::bind_method(D_METHOD("set_znear", "znear"), &GLTFCamera::set_znear);
+ ClassDB::bind_method(D_METHOD("get_depth_far"), &GLTFCamera::get_depth_far);
+ ClassDB::bind_method(D_METHOD("set_depth_far", "zdepth_far"), &GLTFCamera::set_depth_far);
+ ClassDB::bind_method(D_METHOD("get_depth_near"), &GLTFCamera::get_depth_near);
+ ClassDB::bind_method(D_METHOD("set_depth_near", "zdepth_near"), &GLTFCamera::set_depth_near);
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "perspective"), "set_perspective", "get_perspective"); // bool
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "fov_size"), "set_fov_size", "get_fov_size"); // float
- ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "zfar"), "set_zfar", "get_zfar"); // float
- ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "znear"), "set_znear", "get_znear"); // float
+ ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "depth_far"), "set_depth_far", "get_depth_far"); // float
+ ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "depth_near"), "set_depth_near", "get_depth_near"); // float
}
diff --git a/modules/gltf/gltf_camera.h b/modules/gltf/gltf_camera.h
index bf94b80bef..843ff417a4 100644
--- a/modules/gltf/gltf_camera.h
+++ b/modules/gltf/gltf_camera.h
@@ -39,8 +39,8 @@ class GLTFCamera : public Resource {
private:
bool perspective = true;
float fov_size = 75.0;
- float zfar = 4000.0;
- float znear = 0.05;
+ float depth_far = 4000.0;
+ float depth_near = 0.05;
protected:
static void _bind_methods();
@@ -50,9 +50,9 @@ public:
void set_perspective(bool p_val) { perspective = p_val; }
float get_fov_size() const { return fov_size; }
void set_fov_size(float p_val) { fov_size = p_val; }
- float get_zfar() const { return zfar; }
- void set_zfar(float p_val) { zfar = p_val; }
- float get_znear() const { return znear; }
- void set_znear(float p_val) { znear = p_val; }
+ float get_depth_far() const { return depth_far; }
+ void set_depth_far(float p_val) { depth_far = p_val; }
+ float get_depth_near() const { return depth_near; }
+ void set_depth_near(float p_val) { depth_near = p_val; }
};
#endif // GLTF_CAMERA_H
diff --git a/modules/gltf/gltf_document.cpp b/modules/gltf/gltf_document.cpp
index db324e23b7..df2856ec7c 100644
--- a/modules/gltf/gltf_document.cpp
+++ b/modules/gltf/gltf_document.cpp
@@ -429,8 +429,8 @@ Error GLTFDocument::_serialize_nodes(Ref<GLTFState> state) {
node["scale"] = _vec3_to_arr(n->scale);
}
- if (!n->translation.is_equal_approx(Vector3())) {
- node["translation"] = _vec3_to_arr(n->translation);
+ if (!n->position.is_equal_approx(Vector3())) {
+ node["translation"] = _vec3_to_arr(n->position);
}
if (n->children.size()) {
Array children;
@@ -584,7 +584,7 @@ Error GLTFDocument::_parse_nodes(Ref<GLTFState> state) {
node->xform = _arr_to_xform(n["matrix"]);
} else {
if (n.has("translation")) {
- node->translation = _arr_to_vec3(n["translation"]);
+ node->position = _arr_to_vec3(n["translation"]);
}
if (n.has("rotation")) {
node->rotation = _arr_to_quaternion(n["rotation"]);
@@ -594,7 +594,7 @@ Error GLTFDocument::_parse_nodes(Ref<GLTFState> state) {
}
node->xform.basis.set_quaternion_scale(node->rotation, node->scale);
- node->xform.origin = node->translation;
+ node->xform.origin = node->position;
}
if (n.has("extensions")) {
@@ -4470,8 +4470,8 @@ Error GLTFDocument::_serialize_lights(Ref<GLTFState> state) {
color[1] = light->color.g;
color[2] = light->color.b;
d["color"] = color;
- d["type"] = light->type;
- if (light->type == "spot") {
+ d["type"] = light->light_type;
+ if (light->light_type == "spot") {
Dictionary s;
float inner_cone_angle = light->inner_cone_angle;
s["innerConeAngle"] = inner_cone_angle;
@@ -4517,16 +4517,16 @@ Error GLTFDocument::_serialize_cameras(Ref<GLTFState> state) {
Dictionary og;
og["ymag"] = Math::deg2rad(camera->get_fov_size());
og["xmag"] = Math::deg2rad(camera->get_fov_size());
- og["zfar"] = camera->get_zfar();
- og["znear"] = camera->get_znear();
+ og["zfar"] = camera->get_depth_far();
+ og["znear"] = camera->get_depth_near();
d["orthographic"] = og;
d["type"] = "orthographic";
} else if (camera->get_perspective()) {
Dictionary ppt;
// GLTF spec is in radians, Godot's camera is in degrees.
ppt["yfov"] = Math::deg2rad(camera->get_fov_size());
- ppt["zfar"] = camera->get_zfar();
- ppt["znear"] = camera->get_znear();
+ ppt["zfar"] = camera->get_depth_far();
+ ppt["znear"] = camera->get_depth_near();
d["perspective"] = ppt;
d["type"] = "perspective";
}
@@ -4566,7 +4566,7 @@ Error GLTFDocument::_parse_lights(Ref<GLTFState> state) {
light.instantiate();
ERR_FAIL_COND_V(!d.has("type"), ERR_PARSE_ERROR);
const String &type = d["type"];
- light->type = type;
+ light->light_type = type;
if (d.has("color")) {
const Array &arr = d["color"];
@@ -4617,8 +4617,8 @@ Error GLTFDocument::_parse_cameras(Ref<GLTFState> state) {
const Dictionary &og = d["orthographic"];
// GLTF spec is in radians, Godot's camera is in degrees.
camera->set_fov_size(Math::rad2deg(real_t(og["ymag"])));
- camera->set_zfar(og["zfar"]);
- camera->set_znear(og["znear"]);
+ camera->set_depth_far(og["zfar"]);
+ camera->set_depth_near(og["znear"]);
} else {
camera->set_fov_size(10);
}
@@ -4628,8 +4628,8 @@ Error GLTFDocument::_parse_cameras(Ref<GLTFState> state) {
const Dictionary &ppt = d["perspective"];
// GLTF spec is in radians, Godot's camera is in degrees.
camera->set_fov_size(Math::rad2deg(real_t(ppt["yfov"])));
- camera->set_zfar(ppt["zfar"]);
- camera->set_znear(ppt["znear"]);
+ camera->set_depth_far(ppt["zfar"]);
+ camera->set_depth_near(ppt["znear"]);
} else {
camera->set_fov_size(10);
}
@@ -4690,15 +4690,15 @@ Error GLTFDocument::_serialize_animations(Ref<GLTFState> state) {
for (Map<int, GLTFAnimation::Track>::Element *track_i = gltf_animation->get_tracks().front(); track_i; track_i = track_i->next()) {
GLTFAnimation::Track track = track_i->get();
- if (track.translation_track.times.size()) {
+ if (track.position_track.times.size()) {
Dictionary t;
t["sampler"] = samplers.size();
Dictionary s;
- s["interpolation"] = interpolation_to_string(track.translation_track.interpolation);
- Vector<real_t> times = Variant(track.translation_track.times);
+ s["interpolation"] = interpolation_to_string(track.position_track.interpolation);
+ Vector<real_t> times = Variant(track.position_track.times);
s["input"] = _encode_accessor_as_floats(state, times, false);
- Vector<Vector3> values = Variant(track.translation_track.values);
+ Vector<Vector3> values = Variant(track.position_track.values);
s["output"] = _encode_accessor_as_vec3(state, values, false);
samplers.push_back(s);
@@ -4883,10 +4883,10 @@ Error GLTFDocument::_parse_animations(Ref<GLTFState> state) {
const Vector<float> times = _decode_accessor_as_floats(state, input, false);
if (path == "translation") {
- const Vector<Vector3> translations = _decode_accessor_as_vec3(state, output, false);
- track->translation_track.interpolation = interp;
- track->translation_track.times = Variant(times); //convert via variant
- track->translation_track.values = Variant(translations); //convert via variant
+ const Vector<Vector3> positions = _decode_accessor_as_vec3(state, output, false);
+ track->position_track.interpolation = interp;
+ track->position_track.times = Variant(times); //convert via variant
+ track->position_track.values = Variant(positions); //convert via variant
} else if (path == "rotation") {
const Vector<Quaternion> rotations = _decode_accessor_as_quaternion(state, output, false);
track->rotation_track.interpolation = interp;
@@ -5064,7 +5064,7 @@ Node3D *GLTFDocument::_generate_light(Ref<GLTFState> state, Node *scene_parent,
intensity /= 100;
}
- if (l->type == "directional") {
+ if (l->light_type == "directional") {
DirectionalLight3D *light = memnew(DirectionalLight3D);
light->set_param(Light3D::PARAM_ENERGY, intensity);
light->set_color(l->color);
@@ -5075,14 +5075,14 @@ Node3D *GLTFDocument::_generate_light(Ref<GLTFState> state, Node *scene_parent,
// Doubling the range will double the effective brightness, so we need double attenuation (half brightness).
// We want to have double intensity give double brightness, so we need half the attenuation.
const float attenuation = range / intensity;
- if (l->type == "point") {
+ if (l->light_type == "point") {
OmniLight3D *light = memnew(OmniLight3D);
light->set_param(OmniLight3D::PARAM_ATTENUATION, attenuation);
light->set_param(OmniLight3D::PARAM_RANGE, range);
light->set_color(l->color);
return light;
}
- if (l->type == "spot") {
+ if (l->light_type == "spot") {
SpotLight3D *light = memnew(SpotLight3D);
light->set_param(SpotLight3D::PARAM_ATTENUATION, attenuation);
light->set_param(SpotLight3D::PARAM_RANGE, range);
@@ -5109,9 +5109,9 @@ Camera3D *GLTFDocument::_generate_camera(Ref<GLTFState> state, Node *scene_paren
Ref<GLTFCamera> c = state->cameras[gltf_node->camera];
if (c->get_perspective()) {
- camera->set_perspective(c->get_fov_size(), c->get_znear(), c->get_zfar());
+ camera->set_perspective(c->get_fov_size(), c->get_depth_near(), c->get_depth_far());
} else {
- camera->set_orthogonal(c->get_fov_size(), c->get_znear(), c->get_zfar());
+ camera->set_orthogonal(c->get_fov_size(), c->get_depth_near(), c->get_depth_far());
}
return camera;
@@ -5125,14 +5125,10 @@ GLTFCameraIndex GLTFDocument::_convert_camera(Ref<GLTFState> state, Camera3D *p_
if (p_camera->get_projection() == Camera3D::Projection::PROJECTION_PERSPECTIVE) {
c->set_perspective(true);
- c->set_fov_size(p_camera->get_fov());
- c->set_zfar(p_camera->get_far());
- c->set_znear(p_camera->get_near());
- } else {
- c->set_fov_size(p_camera->get_fov());
- c->set_zfar(p_camera->get_far());
- c->set_znear(p_camera->get_near());
}
+ c->set_fov_size(p_camera->get_fov());
+ c->set_depth_far(p_camera->get_far());
+ c->set_depth_near(p_camera->get_near());
GLTFCameraIndex camera_index = state->cameras.size();
state->cameras.push_back(c);
return camera_index;
@@ -5145,18 +5141,18 @@ GLTFLightIndex GLTFDocument::_convert_light(Ref<GLTFState> state, Light3D *p_lig
l.instantiate();
l->color = p_light->get_color();
if (cast_to<DirectionalLight3D>(p_light)) {
- l->type = "directional";
+ l->light_type = "directional";
DirectionalLight3D *light = cast_to<DirectionalLight3D>(p_light);
l->intensity = light->get_param(DirectionalLight3D::PARAM_ENERGY);
l->range = FLT_MAX; // Range for directional lights is infinite in Godot.
} else if (cast_to<OmniLight3D>(p_light)) {
- l->type = "point";
+ l->light_type = "point";
OmniLight3D *light = cast_to<OmniLight3D>(p_light);
l->range = light->get_param(OmniLight3D::PARAM_RANGE);
float attenuation = p_light->get_param(OmniLight3D::PARAM_ATTENUATION);
l->intensity = l->range / attenuation;
} else if (cast_to<SpotLight3D>(p_light)) {
- l->type = "spot";
+ l->light_type = "spot";
SpotLight3D *light = cast_to<SpotLight3D>(p_light);
l->range = light->get_param(SpotLight3D::PARAM_RANGE);
float attenuation = light->get_param(SpotLight3D::PARAM_ATTENUATION);
@@ -5189,7 +5185,7 @@ void GLTFDocument::_convert_spatial(Ref<GLTFState> state, Node3D *p_spatial, Ref
Transform3D xform = p_spatial->get_transform();
p_node->scale = xform.basis.get_scale();
p_node->rotation = xform.basis.get_rotation_quaternion();
- p_node->translation = xform.origin;
+ p_node->position = xform.origin;
}
Node3D *GLTFDocument::_generate_spatial(Ref<GLTFState> state, Node *scene_parent, const GLTFNodeIndex node_index) {
@@ -5772,8 +5768,8 @@ void GLTFDocument::_import_animation(Ref<GLTFState> state, AnimationPlayer *ap,
for (int i = 0; i < track.rotation_track.times.size(); i++) {
length = MAX(length, track.rotation_track.times[i]);
}
- for (int i = 0; i < track.translation_track.times.size(); i++) {
- length = MAX(length, track.translation_track.times[i]);
+ for (int i = 0; i < track.position_track.times.size(); i++) {
+ length = MAX(length, track.position_track.times[i]);
}
for (int i = 0; i < track.scale_track.times.size(); i++) {
length = MAX(length, track.scale_track.times[i]);
@@ -5787,7 +5783,7 @@ void GLTFDocument::_import_animation(Ref<GLTFState> state, AnimationPlayer *ap,
// Animated TRS properties will not affect a skinned mesh.
const bool transform_affects_skinned_mesh_instance = gltf_node->skeleton < 0 && gltf_node->skin >= 0;
- if ((track.rotation_track.values.size() || track.translation_track.values.size() || track.scale_track.values.size()) && !transform_affects_skinned_mesh_instance) {
+ if ((track.rotation_track.values.size() || track.position_track.values.size() || track.scale_track.values.size()) && !transform_affects_skinned_mesh_instance) {
//make transform track
int track_idx = animation->get_track_count();
animation->add_track(Animation::TYPE_TRANSFORM3D);
@@ -5805,8 +5801,8 @@ void GLTFDocument::_import_animation(Ref<GLTFState> state, AnimationPlayer *ap,
base_rot = state->nodes[track_i->key()]->rotation.normalized();
}
- if (!track.translation_track.values.size()) {
- base_pos = state->nodes[track_i->key()]->translation;
+ if (!track.position_track.values.size()) {
+ base_pos = state->nodes[track_i->key()]->position;
}
if (!track.scale_track.values.size()) {
@@ -5819,8 +5815,8 @@ void GLTFDocument::_import_animation(Ref<GLTFState> state, AnimationPlayer *ap,
Quaternion rot = base_rot;
Vector3 scale = base_scale;
- if (track.translation_track.times.size()) {
- pos = _interpolate_track<Vector3>(track.translation_track.times, track.translation_track.values, time, track.translation_track.interpolation);
+ if (track.position_track.times.size()) {
+ pos = _interpolate_track<Vector3>(track.position_track.times, track.position_track.values, time, track.position_track.interpolation);
}
if (track.rotation_track.times.size()) {
@@ -5928,7 +5924,7 @@ void GLTFDocument::_convert_mesh_instances(Ref<GLTFState> state) {
Transform3D mi_xform = mi->get_transform();
node->scale = mi_xform.basis.get_scale();
node->rotation = mi_xform.basis.get_rotation_quaternion();
- node->translation = mi_xform.origin;
+ node->position = mi_xform.origin;
Dictionary json_skin;
Skeleton3D *skeleton = Object::cast_to<Skeleton3D>(mi->get_node(mi->get_skeleton_path()));
@@ -5992,7 +5988,7 @@ void GLTFDocument::_convert_mesh_instances(Ref<GLTFState> state) {
Transform3D bone_rest_xform = skeleton->get_bone_rest(bone_index);
joint_node->scale = bone_rest_xform.basis.get_scale();
joint_node->rotation = bone_rest_xform.basis.get_rotation_quaternion();
- joint_node->translation = bone_rest_xform.origin;
+ joint_node->position = bone_rest_xform.origin;
joint_node->joint = true;
int32_t joint_node_i = state->nodes.size();
@@ -6138,8 +6134,8 @@ GLTFAnimation::Track GLTFDocument::_convert_animation_track(Ref<GLTFState> state
}
const float BAKE_FPS = 30.0f;
if (track_type == Animation::TYPE_TRANSFORM3D) {
- p_track.translation_track.times = times;
- p_track.translation_track.interpolation = gltf_interpolation;
+ p_track.position_track.times = times;
+ p_track.position_track.interpolation = gltf_interpolation;
p_track.rotation_track.times = times;
p_track.rotation_track.interpolation = gltf_interpolation;
p_track.scale_track.times = times;
@@ -6147,27 +6143,27 @@ GLTFAnimation::Track GLTFDocument::_convert_animation_track(Ref<GLTFState> state
p_track.scale_track.values.resize(key_count);
p_track.scale_track.interpolation = gltf_interpolation;
- p_track.translation_track.values.resize(key_count);
- p_track.translation_track.interpolation = gltf_interpolation;
+ p_track.position_track.values.resize(key_count);
+ p_track.position_track.interpolation = gltf_interpolation;
p_track.rotation_track.values.resize(key_count);
p_track.rotation_track.interpolation = gltf_interpolation;
for (int32_t key_i = 0; key_i < key_count; key_i++) {
- Vector3 translation;
+ Vector3 position;
Quaternion rotation;
Vector3 scale;
- Error err = p_animation->transform_track_get_key(p_track_i, key_i, &translation, &rotation, &scale);
+ Error err = p_animation->transform_track_get_key(p_track_i, key_i, &position, &rotation, &scale);
ERR_CONTINUE(err != OK);
Transform3D xform;
xform.basis.set_quaternion_scale(rotation, scale);
- xform.origin = translation;
+ xform.origin = position;
xform = p_bone_rest * xform;
- p_track.translation_track.values.write[key_i] = xform.get_origin();
+ p_track.position_track.values.write[key_i] = xform.get_origin();
p_track.rotation_track.values.write[key_i] = xform.basis.get_rotation_quaternion();
p_track.scale_track.values.write[key_i] = xform.basis.get_scale();
}
} else if (path.find(":transform") != -1) {
- p_track.translation_track.times = times;
- p_track.translation_track.interpolation = gltf_interpolation;
+ p_track.position_track.times = times;
+ p_track.position_track.interpolation = gltf_interpolation;
p_track.rotation_track.times = times;
p_track.rotation_track.interpolation = gltf_interpolation;
p_track.scale_track.times = times;
@@ -6175,13 +6171,13 @@ GLTFAnimation::Track GLTFDocument::_convert_animation_track(Ref<GLTFState> state
p_track.scale_track.values.resize(key_count);
p_track.scale_track.interpolation = gltf_interpolation;
- p_track.translation_track.values.resize(key_count);
- p_track.translation_track.interpolation = gltf_interpolation;
+ p_track.position_track.values.resize(key_count);
+ p_track.position_track.interpolation = gltf_interpolation;
p_track.rotation_track.values.resize(key_count);
p_track.rotation_track.interpolation = gltf_interpolation;
for (int32_t key_i = 0; key_i < key_count; key_i++) {
Transform3D xform = p_animation->track_get_key_value(p_track_i, key_i);
- p_track.translation_track.values.write[key_i] = xform.get_origin();
+ p_track.position_track.values.write[key_i] = xform.get_origin();
p_track.rotation_track.values.write[key_i] = xform.basis.get_rotation_quaternion();
p_track.scale_track.values.write[key_i] = xform.basis.get_scale();
}
@@ -6197,16 +6193,16 @@ GLTFAnimation::Track GLTFDocument::_convert_animation_track(Ref<GLTFState> state
Quaternion rotation_track = p_animation->track_get_key_value(p_track_i, key_i);
p_track.rotation_track.values.write[key_i] = rotation_track;
}
- } else if (path.find(":translation") != -1) {
- p_track.translation_track.times = times;
- p_track.translation_track.interpolation = gltf_interpolation;
+ } else if (path.find(":position") != -1) {
+ p_track.position_track.times = times;
+ p_track.position_track.interpolation = gltf_interpolation;
- p_track.translation_track.values.resize(key_count);
- p_track.translation_track.interpolation = gltf_interpolation;
+ p_track.position_track.values.resize(key_count);
+ p_track.position_track.interpolation = gltf_interpolation;
for (int32_t key_i = 0; key_i < key_count; key_i++) {
- Vector3 translation = p_animation->track_get_key_value(p_track_i, key_i);
- p_track.translation_track.values.write[key_i] = translation;
+ Vector3 position = p_animation->track_get_key_value(p_track_i, key_i);
+ p_track.position_track.values.write[key_i] = position;
}
} else if (path.find(":rotation") != -1) {
p_track.rotation_track.times = times;
@@ -6265,34 +6261,34 @@ GLTFAnimation::Track GLTFDocument::_convert_animation_track(Ref<GLTFState> state
}
p_track.scale_track.values.write[key_i] = bezier_track;
}
- } else if (path.find("/translation") != -1) {
+ } else if (path.find("/position") != -1) {
const int32_t keys = p_animation->track_get_key_time(p_track_i, key_count - 1) * BAKE_FPS;
- if (!p_track.translation_track.times.size()) {
+ if (!p_track.position_track.times.size()) {
Vector<float> new_times;
new_times.resize(keys);
for (int32_t key_i = 0; key_i < keys; key_i++) {
new_times.write[key_i] = key_i / BAKE_FPS;
}
- p_track.translation_track.times = new_times;
- p_track.translation_track.interpolation = gltf_interpolation;
+ p_track.position_track.times = new_times;
+ p_track.position_track.interpolation = gltf_interpolation;
- p_track.translation_track.values.resize(keys);
- p_track.translation_track.interpolation = gltf_interpolation;
+ p_track.position_track.values.resize(keys);
+ p_track.position_track.interpolation = gltf_interpolation;
}
for (int32_t key_i = 0; key_i < keys; key_i++) {
- Vector3 bezier_track = p_track.translation_track.values[key_i];
- if (path.find("/translation:x") != -1) {
+ Vector3 bezier_track = p_track.position_track.values[key_i];
+ if (path.find("/position:x") != -1) {
bezier_track.x = p_animation->bezier_track_interpolate(p_track_i, key_i / BAKE_FPS);
bezier_track.x = p_bone_rest.affine_inverse().origin.x * bezier_track.x;
- } else if (path.find("/translation:y") != -1) {
+ } else if (path.find("/position:y") != -1) {
bezier_track.y = p_animation->bezier_track_interpolate(p_track_i, key_i / BAKE_FPS);
bezier_track.y = p_bone_rest.affine_inverse().origin.y * bezier_track.y;
- } else if (path.find("/translation:z") != -1) {
+ } else if (path.find("/position:z") != -1) {
bezier_track.z = p_animation->bezier_track_interpolate(p_track_i, key_i / BAKE_FPS);
bezier_track.z = p_bone_rest.affine_inverse().origin.z * bezier_track.z;
}
- p_track.translation_track.values.write[key_i] = bezier_track;
+ p_track.position_track.values.write[key_i] = bezier_track;
}
}
}
@@ -6311,17 +6307,17 @@ void GLTFDocument::_convert_animation(Ref<GLTFState> state, AnimationPlayer *ap,
continue;
}
String orig_track_path = animation->track_get_path(track_i);
- if (String(orig_track_path).find(":translation") != -1) {
- const Vector<String> node_suffix = String(orig_track_path).split(":translation");
+ if (String(orig_track_path).find(":position") != -1) {
+ const Vector<String> node_suffix = String(orig_track_path).split(":position");
const NodePath path = node_suffix[0];
const Node *node = ap->get_parent()->get_node_or_null(path);
- for (Map<GLTFNodeIndex, Node *>::Element *translation_scene_node_i = state->scene_nodes.front(); translation_scene_node_i; translation_scene_node_i = translation_scene_node_i->next()) {
- if (translation_scene_node_i->get() == node) {
- GLTFNodeIndex node_index = translation_scene_node_i->key();
- Map<int, GLTFAnimation::Track>::Element *translation_track_i = gltf_animation->get_tracks().find(node_index);
+ for (Map<GLTFNodeIndex, Node *>::Element *position_scene_node_i = state->scene_nodes.front(); position_scene_node_i; position_scene_node_i = position_scene_node_i->next()) {
+ if (position_scene_node_i->get() == node) {
+ GLTFNodeIndex node_index = position_scene_node_i->key();
+ Map<int, GLTFAnimation::Track>::Element *position_track_i = gltf_animation->get_tracks().find(node_index);
GLTFAnimation::Track track;
- if (translation_track_i) {
- track = translation_track_i->get();
+ if (position_track_i) {
+ track = position_track_i->get();
}
track = _convert_animation_track(state, track, animation, Transform3D(), track_i, node_index);
gltf_animation->get_tracks().insert(node_index, track);
@@ -6748,6 +6744,8 @@ Error GLTFDocument::_serialize_file(Ref<GLTFState> state, const String p_path) {
Error GLTFDocument::save_scene(Node *p_node, const String &p_path,
const String &p_src_path, uint32_t p_flags,
float p_bake_fps, Ref<GLTFState> r_state) {
+ ERR_FAIL_NULL_V(p_node, ERR_INVALID_PARAMETER);
+
Ref<GLTFDocument> gltf_document;
gltf_document.instantiate();
if (r_state == Ref<GLTFState>()) {
diff --git a/modules/gltf/gltf_light.cpp b/modules/gltf/gltf_light.cpp
index 95cca9cf71..c5aa8d5724 100644
--- a/modules/gltf/gltf_light.cpp
+++ b/modules/gltf/gltf_light.cpp
@@ -35,8 +35,8 @@ void GLTFLight::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_color", "color"), &GLTFLight::set_color);
ClassDB::bind_method(D_METHOD("get_intensity"), &GLTFLight::get_intensity);
ClassDB::bind_method(D_METHOD("set_intensity", "intensity"), &GLTFLight::set_intensity);
- ClassDB::bind_method(D_METHOD("get_type"), &GLTFLight::get_type);
- ClassDB::bind_method(D_METHOD("set_type", "type"), &GLTFLight::set_type);
+ ClassDB::bind_method(D_METHOD("get_light_type"), &GLTFLight::get_light_type);
+ ClassDB::bind_method(D_METHOD("set_light_type", "light_type"), &GLTFLight::set_light_type);
ClassDB::bind_method(D_METHOD("get_range"), &GLTFLight::get_range);
ClassDB::bind_method(D_METHOD("set_range", "range"), &GLTFLight::set_range);
ClassDB::bind_method(D_METHOD("get_inner_cone_angle"), &GLTFLight::get_inner_cone_angle);
@@ -46,7 +46,7 @@ void GLTFLight::_bind_methods() {
ADD_PROPERTY(PropertyInfo(Variant::COLOR, "color"), "set_color", "get_color"); // Color
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "intensity"), "set_intensity", "get_intensity"); // float
- ADD_PROPERTY(PropertyInfo(Variant::STRING, "type"), "set_type", "get_type"); // String
+ ADD_PROPERTY(PropertyInfo(Variant::STRING, "light_type"), "set_light_type", "get_light_type"); // String
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "range"), "set_range", "get_range"); // float
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "inner_cone_angle"), "set_inner_cone_angle", "get_inner_cone_angle"); // float
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "outer_cone_angle"), "set_outer_cone_angle", "get_outer_cone_angle"); // float
@@ -68,12 +68,12 @@ void GLTFLight::set_intensity(float p_intensity) {
intensity = p_intensity;
}
-String GLTFLight::get_type() {
- return type;
+String GLTFLight::get_light_type() {
+ return light_type;
}
-void GLTFLight::set_type(String p_type) {
- type = p_type;
+void GLTFLight::set_light_type(String p_light_type) {
+ light_type = p_light_type;
}
float GLTFLight::get_range() {
diff --git a/modules/gltf/gltf_light.h b/modules/gltf/gltf_light.h
index a859ca1833..079fb18151 100644
--- a/modules/gltf/gltf_light.h
+++ b/modules/gltf/gltf_light.h
@@ -44,7 +44,7 @@ protected:
private:
Color color;
float intensity = 0.0f;
- String type;
+ String light_type;
float range = 0.0f;
float inner_cone_angle = 0.0f;
float outer_cone_angle = 0.0f;
@@ -56,8 +56,8 @@ public:
float get_intensity();
void set_intensity(float p_intensity);
- String get_type();
- void set_type(String p_type);
+ String get_light_type();
+ void set_light_type(String p_light_type);
float get_range();
void set_range(float p_range);
diff --git a/modules/gltf/gltf_node.cpp b/modules/gltf/gltf_node.cpp
index 5db7ad66c3..9f925c7bbc 100644
--- a/modules/gltf/gltf_node.cpp
+++ b/modules/gltf/gltf_node.cpp
@@ -47,8 +47,8 @@ void GLTFNode::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_skeleton", "skeleton"), &GLTFNode::set_skeleton);
ClassDB::bind_method(D_METHOD("get_joint"), &GLTFNode::get_joint);
ClassDB::bind_method(D_METHOD("set_joint", "joint"), &GLTFNode::set_joint);
- ClassDB::bind_method(D_METHOD("get_translation"), &GLTFNode::get_translation);
- ClassDB::bind_method(D_METHOD("set_translation", "translation"), &GLTFNode::set_translation);
+ ClassDB::bind_method(D_METHOD("get_position"), &GLTFNode::get_position);
+ ClassDB::bind_method(D_METHOD("set_position", "position"), &GLTFNode::set_position);
ClassDB::bind_method(D_METHOD("get_rotation"), &GLTFNode::get_rotation);
ClassDB::bind_method(D_METHOD("set_rotation", "rotation"), &GLTFNode::set_rotation);
ClassDB::bind_method(D_METHOD("get_scale"), &GLTFNode::get_scale);
@@ -66,7 +66,7 @@ void GLTFNode::_bind_methods() {
ADD_PROPERTY(PropertyInfo(Variant::INT, "skin"), "set_skin", "get_skin"); // GLTFSkinIndex
ADD_PROPERTY(PropertyInfo(Variant::INT, "skeleton"), "set_skeleton", "get_skeleton"); // GLTFSkeletonIndex
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "joint"), "set_joint", "get_joint"); // bool
- ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "translation"), "set_translation", "get_translation"); // Vector3
+ ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "position"), "set_position", "get_position"); // Vector3
ADD_PROPERTY(PropertyInfo(Variant::QUATERNION, "rotation"), "set_rotation", "get_rotation"); // Quaternion
ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "scale"), "set_scale", "get_scale"); // Vector3
ADD_PROPERTY(PropertyInfo(Variant::PACKED_INT32_ARRAY, "children"), "set_children", "get_children"); // Vector<int>
@@ -137,12 +137,12 @@ void GLTFNode::set_joint(bool p_joint) {
joint = p_joint;
}
-Vector3 GLTFNode::get_translation() {
- return translation;
+Vector3 GLTFNode::get_position() {
+ return position;
}
-void GLTFNode::set_translation(Vector3 p_translation) {
- translation = p_translation;
+void GLTFNode::set_position(Vector3 p_position) {
+ position = p_position;
}
Quaternion GLTFNode::get_rotation() {
diff --git a/modules/gltf/gltf_node.h b/modules/gltf/gltf_node.h
index eca3acb239..3b6e061449 100644
--- a/modules/gltf/gltf_node.h
+++ b/modules/gltf/gltf_node.h
@@ -48,7 +48,7 @@ private:
GLTFSkinIndex skin = -1;
GLTFSkeletonIndex skeleton = -1;
bool joint = false;
- Vector3 translation;
+ Vector3 position;
Quaternion rotation;
Vector3 scale = Vector3(1, 1, 1);
Vector<int> children;
@@ -82,8 +82,8 @@ public:
bool get_joint();
void set_joint(bool p_joint);
- Vector3 get_translation();
- void set_translation(Vector3 p_translation);
+ Vector3 get_position();
+ void set_position(Vector3 p_position);
Quaternion get_rotation();
void set_rotation(Quaternion p_rotation);
diff --git a/modules/gridmap/grid_map.cpp b/modules/gridmap/grid_map.cpp
index 8e8b6f14ad..487e6deac0 100644
--- a/modules/gridmap/grid_map.cpp
+++ b/modules/gridmap/grid_map.cpp
@@ -475,7 +475,7 @@ bool GridMap::_octant_update(const OctantKey &p_key) {
}
Pair<Transform3D, IndexKey> p;
- p.first = xform;
+ p.first = xform * mesh_library->get_item_mesh_transform(c.item);
p.second = E->get();
multimesh_items[c.item].push_back(p);
}
diff --git a/modules/gridmap/grid_map_editor_plugin.cpp b/modules/gridmap/grid_map_editor_plugin.cpp
index 2331a12d0f..c170bb107e 100644
--- a/modules/gridmap/grid_map_editor_plugin.cpp
+++ b/modules/gridmap/grid_map_editor_plugin.cpp
@@ -255,6 +255,12 @@ void GridMapEditor::_update_cursor_transform() {
cursor_transform.basis *= node->get_cell_scale();
cursor_transform = node->get_global_transform() * cursor_transform;
+ if (selected_palette >= 0) {
+ if (node && !node->get_mesh_library().is_null()) {
+ cursor_transform *= node->get_mesh_library()->get_item_mesh_transform(selected_palette);
+ }
+ }
+
if (cursor_instance.is_valid()) {
RenderingServer::get_singleton()->instance_set_transform(cursor_instance, cursor_transform);
RenderingServer::get_singleton()->instance_set_visible(cursor_instance, cursor_visible);
diff --git a/modules/lightmapper_rd/lm_compute.glsl b/modules/lightmapper_rd/lm_compute.glsl
index a71652d5c4..25b334c5eb 100644
--- a/modules/lightmapper_rd/lm_compute.glsl
+++ b/modules/lightmapper_rd/lm_compute.glsl
@@ -115,7 +115,12 @@ bool ray_hits_triangle(vec3 from, vec3 dir, float max_dist, vec3 p0, vec3 p1, ve
return (r_distance > params.bias) && (r_distance < max_dist) && all(greaterThanEqual(r_barycentric, vec3(0.0)));
}
-bool trace_ray(vec3 p_from, vec3 p_to
+const uint RAY_MISS = 0;
+const uint RAY_FRONT = 1;
+const uint RAY_BACK = 2;
+const uint RAY_ANY = 3;
+
+uint trace_ray(vec3 p_from, vec3 p_to
#if defined(MODE_BOUNCE_LIGHT) || defined(MODE_LIGHT_PROBES)
,
out uint r_triangle, out vec3 r_barycentric
@@ -125,6 +130,7 @@ bool trace_ray(vec3 p_from, vec3 p_to
out float r_distance, out vec3 r_normal
#endif
) {
+
/* world coords */
vec3 rel = p_to - p_from;
@@ -150,10 +156,7 @@ bool trace_ray(vec3 p_from, vec3 p_to
while (all(greaterThanEqual(icell, ivec3(0))) && all(lessThan(icell, ivec3(params.grid_size))) && iters < 1000) {
uvec2 cell_data = texelFetch(usampler3D(grid, linear_sampler), icell, 0).xy;
if (cell_data.x > 0) { //triangles here
- bool hit = false;
-#if defined(MODE_UNOCCLUDE)
- bool hit_backface = false;
-#endif
+ uint hit = RAY_MISS;
float best_distance = 1e20;
for (uint i = 0; i < cell_data.x; i++) {
@@ -173,57 +176,46 @@ bool trace_ray(vec3 p_from, vec3 p_to
vec3 vtx0 = vertices.data[triangle.indices.x].position;
vec3 vtx1 = vertices.data[triangle.indices.y].position;
vec3 vtx2 = vertices.data[triangle.indices.z].position;
-#if defined(MODE_UNOCCLUDE)
+#if defined(MODE_UNOCCLUDE) || defined(MODE_BOUNCE_LIGHT) || defined(MODE_LIGHT_PROBES)
vec3 normal = -normalize(cross((vtx0 - vtx1), (vtx0 - vtx2)));
bool backface = dot(normal, dir) >= 0.0;
#endif
+
float distance;
vec3 barycentric;
if (ray_hits_triangle(p_from, dir, rel_len, vtx0, vtx1, vtx2, distance, barycentric)) {
#ifdef MODE_DIRECT_LIGHT
- return true; //any hit good
+ return RAY_ANY; //any hit good
#endif
-#if defined(MODE_UNOCCLUDE)
+#if defined(MODE_UNOCCLUDE) || defined(MODE_BOUNCE_LIGHT) || defined(MODE_LIGHT_PROBES)
if (!backface) {
// the case of meshes having both a front and back face in the same plane is more common than
// expected, so if this is a front-face, bias it closer to the ray origin, so it always wins over the back-face
distance = max(params.bias, distance - params.bias);
}
- hit = true;
-
if (distance < best_distance) {
- hit_backface = backface;
+ hit = backface ? RAY_BACK : RAY_FRONT;
best_distance = distance;
+#if defined(MODE_UNOCCLUDE)
r_distance = distance;
r_normal = normal;
- }
-
#endif
-
#if defined(MODE_BOUNCE_LIGHT) || defined(MODE_LIGHT_PROBES)
-
- hit = true;
- if (distance < best_distance) {
- best_distance = distance;
r_triangle = tidx;
r_barycentric = barycentric;
+#endif
}
#endif
}
}
-#if defined(MODE_UNOCCLUDE)
+#if defined(MODE_UNOCCLUDE) || defined(MODE_BOUNCE_LIGHT) || defined(MODE_LIGHT_PROBES)
- if (hit) {
- return hit_backface;
- }
-#endif
-#if defined(MODE_BOUNCE_LIGHT) || defined(MODE_LIGHT_PROBES)
- if (hit) {
- return true;
+ if (hit != RAY_MISS) {
+ return hit;
}
#endif
}
@@ -239,7 +231,7 @@ bool trace_ray(vec3 p_from, vec3 p_to
iters++;
}
- return false;
+ return RAY_MISS;
}
const float PI = 3.14159265f;
@@ -339,7 +331,7 @@ void main() {
continue; //no need to do anything
}
- if (!trace_ray(position + light_dir * params.bias, light_pos)) {
+ if (trace_ray(position + light_dir * params.bias, light_pos) == RAY_MISS) {
vec3 light = lights.data[i].color * lights.data[i].energy * attenuation;
if (lights.data[i].static_bake) {
static_light += light;
@@ -410,6 +402,7 @@ void main() {
vec4(0.0, 0.0, 0.0, 1.0));
#endif
vec3 light_average = vec3(0.0);
+ float active_rays = 0.0;
for (uint i = params.ray_from; i < params.ray_to; i++) {
vec3 ray_dir = normal_mat * vogel_hemisphere(i, params.ray_count, quick_hash(vec2(atlas_pos)));
@@ -417,7 +410,8 @@ void main() {
vec3 barycentric;
vec3 light = vec3(0.0);
- if (trace_ray(position + ray_dir * params.bias, position + ray_dir * length(params.world_size), tidx, barycentric)) {
+ uint trace_result = trace_ray(position + ray_dir * params.bias, position + ray_dir * length(params.world_size), tidx, barycentric);
+ if (trace_result == RAY_FRONT) {
//hit a triangle
vec2 uv0 = vertices.data[triangles.data[tidx].indices.x].uv;
vec2 uv1 = vertices.data[triangles.data[tidx].indices.y].uv;
@@ -425,7 +419,8 @@ void main() {
vec3 uvw = vec3(barycentric.x * uv0 + barycentric.y * uv1 + barycentric.z * uv2, float(triangles.data[tidx].slice));
light = textureLod(sampler2DArray(source_light, linear_sampler), uvw, 0.0).rgb;
- } else if (params.env_transform[0][3] == 0.0) { // Use env_transform[0][3] to indicate when we are computing the first bounce
+ active_rays += 1.0;
+ } else if (trace_result == RAY_MISS && params.env_transform[0][3] == 0.0) { // Use env_transform[0][3] to indicate when we are computing the first bounce
// Did not hit a triangle, reach out for the sky
vec3 sky_dir = normalize(mat3(params.env_transform) * ray_dir);
@@ -439,6 +434,7 @@ void main() {
st /= vec2(PI * 2.0, PI);
light = textureLod(sampler2D(environment, linear_sampler), st, 0.0).rgb;
+ active_rays += 1.0;
}
light_average += light;
@@ -462,7 +458,9 @@ void main() {
if (params.ray_from == 0) {
light_total = vec3(0.0);
} else {
- light_total = imageLoad(bounce_accum, ivec3(atlas_pos, params.atlas_slice)).rgb;
+ vec4 accum = imageLoad(bounce_accum, ivec3(atlas_pos, params.atlas_slice));
+ light_total = accum.rgb;
+ active_rays += accum.a;
}
light_total += light_average;
@@ -477,7 +475,9 @@ void main() {
#endif
if (params.ray_to == params.ray_count) {
- light_total /= float(params.ray_count);
+ if (active_rays > 0) {
+ light_total /= active_rays;
+ }
imageStore(dest_light, ivec3(atlas_pos, params.atlas_slice), vec4(light_total, 1.0));
#ifndef USE_SH_LIGHTMAPS
vec4 accum = imageLoad(accum_light, ivec3(atlas_pos, params.atlas_slice));
@@ -485,7 +485,7 @@ void main() {
imageStore(accum_light, ivec3(atlas_pos, params.atlas_slice), accum);
#endif
} else {
- imageStore(bounce_accum, ivec3(atlas_pos, params.atlas_slice), vec4(light_total, 1.0));
+ imageStore(bounce_accum, ivec3(atlas_pos, params.atlas_slice), vec4(light_total, active_rays));
}
#endif
@@ -518,7 +518,7 @@ void main() {
float d;
vec3 norm;
- if (trace_ray(base_pos, ray_to, d, norm)) {
+ if (trace_ray(base_pos, ray_to, d, norm) == RAY_BACK) {
if (d < min_d) {
vertex_pos = base_pos + rays[i] * d + norm * params.bias * 10.0; //this bias needs to be greater than the regular bias, because otherwise later, rays will go the other side when pointing back.
min_d = d;
@@ -558,7 +558,8 @@ void main() {
vec3 barycentric;
vec3 light;
- if (trace_ray(position + ray_dir * params.bias, position + ray_dir * length(params.world_size), tidx, barycentric)) {
+ uint trace_result = trace_ray(position + ray_dir * params.bias, position + ray_dir * length(params.world_size), tidx, barycentric);
+ if (trace_result == RAY_FRONT) {
vec2 uv0 = vertices.data[triangles.data[tidx].indices.x].uv;
vec2 uv1 = vertices.data[triangles.data[tidx].indices.y].uv;
vec2 uv2 = vertices.data[triangles.data[tidx].indices.z].uv;
@@ -566,7 +567,7 @@ void main() {
light = textureLod(sampler2DArray(source_light, linear_sampler), uvw, 0.0).rgb;
light += textureLod(sampler2DArray(source_direct_light, linear_sampler), uvw, 0.0).rgb;
- } else {
+ } else if (trace_result == RAY_MISS) {
//did not hit a triangle, reach out for the sky
vec3 sky_dir = normalize(mat3(params.env_transform) * ray_dir);
diff --git a/modules/mono/csharp_script.cpp b/modules/mono/csharp_script.cpp
index 3437dbd194..6cc7ddb424 100644
--- a/modules/mono/csharp_script.cpp
+++ b/modules/mono/csharp_script.cpp
@@ -43,8 +43,10 @@
#include "core/os/thread.h"
#ifdef TOOLS_ENABLED
+#include "core/os/keyboard.h"
#include "editor/bindings_generator.h"
#include "editor/editor_node.h"
+#include "editor/editor_settings.h"
#include "editor/node_dock.h"
#endif
@@ -1353,6 +1355,7 @@ void CSharpLanguage::_editor_init_callback() {
// Enable it as a plugin
EditorNode::add_editor_plugin(godotsharp_editor);
+ ED_SHORTCUT("mono/build_solution", TTR("Build Solution"), KEY_MASK_ALT | KEY_B);
godotsharp_editor->enable_plugin();
get_singleton()->godotsharp_editor = godotsharp_editor;
@@ -1861,6 +1864,28 @@ Variant::Type CSharpInstance::get_property_type(const StringName &p_name, bool *
return Variant::NIL;
}
+void CSharpInstance::get_method_list(List<MethodInfo> *p_list) const {
+ if (!script->is_valid() || !script->script_class)
+ return;
+
+ GD_MONO_SCOPE_THREAD_ATTACH;
+
+ // TODO: We're filtering out constructors but there may be other methods unsuitable for explicit calls.
+ GDMonoClass *top = script->script_class;
+
+ while (top && top != script->native) {
+ const Vector<GDMonoMethod *> &methods = top->get_all_methods();
+ for (int i = 0; i < methods.size(); ++i) {
+ MethodInfo minfo = methods[i]->get_method_info();
+ if (minfo.name != CACHED_STRING_NAME(dotctor)) {
+ p_list->push_back(minfo);
+ }
+ }
+
+ top = top->get_parent_class();
+ }
+}
+
bool CSharpInstance::has_method(const StringName &p_method) const {
if (!script.is_valid()) {
return false;
@@ -2866,12 +2891,24 @@ int CSharpScript::_try_get_member_export_hint(IMonoClassMember *p_member, Manage
ERR_FAIL_COND_V_MSG(elem_variant_type == Variant::NIL, -1, "Unknown array element type.");
- int hint_res = _try_get_member_export_hint(p_member, elem_type, elem_variant_type, /* allow_generics: */ false, elem_hint, elem_hint_string);
+ bool preset_hint = false;
+ if (elem_variant_type == Variant::STRING) {
+ MonoObject *attr = p_member->get_attribute(CACHED_CLASS(ExportAttribute));
+ if (PropertyHint(CACHED_FIELD(ExportAttribute, hint)->get_int_value(attr)) == PROPERTY_HINT_ENUM) {
+ r_hint_string = itos(elem_variant_type) + "/" + itos(PROPERTY_HINT_ENUM) + ":" + CACHED_FIELD(ExportAttribute, hintString)->get_string_value(attr);
+ preset_hint = true;
+ }
+ }
+
+ if (!preset_hint) {
+ int hint_res = _try_get_member_export_hint(p_member, elem_type, elem_variant_type, /* allow_generics: */ false, elem_hint, elem_hint_string);
- ERR_FAIL_COND_V_MSG(hint_res == -1, -1, "Error while trying to determine information about the array element type.");
+ ERR_FAIL_COND_V_MSG(hint_res == -1, -1, "Error while trying to determine information about the array element type.");
+
+ // Format: type/hint:hint_string
+ r_hint_string = itos(elem_variant_type) + "/" + itos(elem_hint) + ":" + elem_hint_string;
+ }
- // Format: type/hint:hint_string
- r_hint_string = itos(elem_variant_type) + "/" + itos(elem_hint) + ":" + elem_hint_string;
r_hint = PROPERTY_HINT_TYPE_STRING;
} else if (p_allow_generics && p_variant_type == Variant::DICTIONARY) {
@@ -3280,10 +3317,19 @@ void CSharpScript::get_script_method_list(List<MethodInfo> *p_list) const {
GD_MONO_SCOPE_THREAD_ATTACH;
- // TODO: Filter out things unsuitable for explicit calls, like constructors.
- const Vector<GDMonoMethod *> &methods = script_class->get_all_methods();
- for (int i = 0; i < methods.size(); ++i) {
- p_list->push_back(methods[i]->get_method_info());
+ // TODO: We're filtering out constructors but there may be other methods unsuitable for explicit calls.
+ GDMonoClass *top = script_class;
+
+ while (top && top != native) {
+ const Vector<GDMonoMethod *> &methods = top->get_all_methods();
+ for (int i = 0; i < methods.size(); ++i) {
+ MethodInfo minfo = methods[i]->get_method_info();
+ if (minfo.name != CACHED_STRING_NAME(dotctor)) {
+ p_list->push_back(methods[i]->get_method_info());
+ }
+ }
+
+ top = top->get_parent_class();
}
}
diff --git a/modules/mono/csharp_script.h b/modules/mono/csharp_script.h
index e3bbb20dec..afc17f694a 100644
--- a/modules/mono/csharp_script.h
+++ b/modules/mono/csharp_script.h
@@ -293,7 +293,7 @@ public:
void get_property_list(List<PropertyInfo> *p_properties) const override;
Variant::Type get_property_type(const StringName &p_name, bool *r_is_valid) const override;
- /* TODO */ void get_method_list(List<MethodInfo> *p_list) const override {}
+ void get_method_list(List<MethodInfo> *p_list) const override;
bool has_method(const StringName &p_method) const override;
Variant call(const StringName &p_method, const Variant **p_args, int p_argcount, Callable::CallError &r_error) override;
diff --git a/modules/mono/editor/GodotTools/GodotTools/GodotSharpEditor.cs b/modules/mono/editor/GodotTools/GodotTools/GodotSharpEditor.cs
index 73cabf8561..98c6881166 100644
--- a/modules/mono/editor/GodotTools/GodotTools/GodotSharpEditor.cs
+++ b/modules/mono/editor/GodotTools/GodotTools/GodotSharpEditor.cs
@@ -418,11 +418,15 @@ namespace GodotTools
AddToolSubmenuItem("C#", _menuPopup);
+ var buildSolutionShortcut = (Shortcut)EditorShortcut("mono/build_solution");
+
_toolBarBuildButton = new Button
{
Text = "Build",
- HintTooltip = "Build solution",
- FocusMode = Control.FocusModeEnum.None
+ HintTooltip = "Build Solution".TTR(),
+ FocusMode = Control.FocusModeEnum.None,
+ Shortcut = buildSolutionShortcut,
+ ShortcutInTooltip = true
};
_toolBarBuildButton.PressedSignal += BuildSolutionPressed;
AddControlToContainer(CustomControlContainer.Toolbar, _toolBarBuildButton);
diff --git a/modules/mono/editor/GodotTools/GodotTools/Internals/Globals.cs b/modules/mono/editor/GodotTools/GodotTools/Internals/Globals.cs
index 793f84fd77..5c5ced8c29 100644
--- a/modules/mono/editor/GodotTools/GodotTools/Internals/Globals.cs
+++ b/modules/mono/editor/GodotTools/GodotTools/Internals/Globals.cs
@@ -13,6 +13,9 @@ namespace GodotTools.Internals
public static object EditorDef(string setting, object defaultValue, bool restartIfChanged = false) =>
internal_EditorDef(setting, defaultValue, restartIfChanged);
+ public static object EditorShortcut(string setting) =>
+ internal_EditorShortcut(setting);
+
[SuppressMessage("ReSharper", "InconsistentNaming")]
public static string TTR(this string text) => internal_TTR(text);
@@ -28,6 +31,9 @@ namespace GodotTools.Internals
private static extern object internal_EditorDef(string setting, object defaultValue, bool restartIfChanged);
[MethodImpl(MethodImplOptions.InternalCall)]
+ private static extern object internal_EditorShortcut(string setting);
+
+ [MethodImpl(MethodImplOptions.InternalCall)]
private static extern string internal_TTR(string text);
}
}
diff --git a/modules/mono/editor/code_completion.cpp b/modules/mono/editor/code_completion.cpp
index d911f6461c..7433c865f5 100644
--- a/modules/mono/editor/code_completion.cpp
+++ b/modules/mono/editor/code_completion.cpp
@@ -123,8 +123,8 @@ PackedStringArray get_code_completion(CompletionKind p_kind, const String &p_scr
// AutoLoads
OrderedHashMap<StringName, ProjectSettings::AutoloadInfo> autoloads = ProjectSettings::get_singleton()->get_autoload_list();
- for (const KeyValue<StringName, ProjectSettings::AutoloadInfo> &E : autoloads) {
- const ProjectSettings::AutoloadInfo &info = E.value;
+ for (OrderedHashMap<StringName, ProjectSettings::AutoloadInfo>::Element E = autoloads.front(); E; E = E.next()) {
+ const ProjectSettings::AutoloadInfo &info = E.value();
suggestions.push_back(quoted("/root/" + String(info.name)));
}
}
diff --git a/modules/mono/editor/editor_internal_calls.cpp b/modules/mono/editor/editor_internal_calls.cpp
index 6692a6efec..9a61b63c12 100644
--- a/modules/mono/editor/editor_internal_calls.cpp
+++ b/modules/mono/editor/editor_internal_calls.cpp
@@ -306,6 +306,12 @@ MonoObject *godot_icall_Globals_EditorDef(MonoString *p_setting, MonoObject *p_d
return GDMonoMarshal::variant_to_mono_object(result);
}
+MonoObject *godot_icall_Globals_EditorShortcut(MonoString *p_setting) {
+ String setting = GDMonoMarshal::mono_string_to_godot(p_setting);
+ Ref<Shortcut> result = ED_GET_SHORTCUT(setting);
+ return GDMonoMarshal::variant_to_mono_object(result);
+}
+
MonoString *godot_icall_Globals_TTR(MonoString *p_text) {
String text = GDMonoMarshal::mono_string_to_godot(p_text);
return GDMonoMarshal::mono_string_from_godot(TTR(text));
@@ -380,6 +386,7 @@ void register_editor_internal_calls() {
GDMonoUtils::add_internal_call("GodotTools.Internals.Globals::internal_EditorScale", godot_icall_Globals_EditorScale);
GDMonoUtils::add_internal_call("GodotTools.Internals.Globals::internal_GlobalDef", godot_icall_Globals_GlobalDef);
GDMonoUtils::add_internal_call("GodotTools.Internals.Globals::internal_EditorDef", godot_icall_Globals_EditorDef);
+ GDMonoUtils::add_internal_call("GodotTools.Internals.Globals::internal_EditorShortcut", godot_icall_Globals_EditorShortcut);
GDMonoUtils::add_internal_call("GodotTools.Internals.Globals::internal_TTR", godot_icall_Globals_TTR);
// Utils.OS
diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Extensions/NodeExtensions.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Extensions/NodeExtensions.cs
index 658582960f..1dc21b6303 100644
--- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Extensions/NodeExtensions.cs
+++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Extensions/NodeExtensions.cs
@@ -78,7 +78,8 @@ namespace Godot
/// </example>
/// <seealso cref="GetNode{T}(NodePath)"/>
/// <param name="path">The path to the node to fetch.</param>
- /// <typeparam name="T">The type to cast to. Should be a descendant of <see cref="Node"/>.</typeparam>/// ///////// <returns>
+ /// <typeparam name="T">The type to cast to. Should be a descendant of <see cref="Node"/>.</typeparam>
+ /// <returns>
/// The <see cref="Node"/> at the given <paramref name="path"/>, or <see langword="null"/> if not found.
/// </returns>
public T GetNodeOrNull<T>(NodePath path) where T : class
@@ -97,7 +98,8 @@ namespace Godot
/// <exception cref="InvalidCastException">
/// Thrown when the given the fetched node can't be casted to the given type <typeparamref name="T"/>.
/// </exception>
- /// <typeparam name="T">The type to cast to. Should be a descendant of <see cref="Node"/>.</typeparam>/// ///////// <returns>
+ /// <typeparam name="T">The type to cast to. Should be a descendant of <see cref="Node"/>.</typeparam>
+ /// <returns>
/// The child <see cref="Node"/> at the given index <paramref name="idx"/>.
/// </returns>
public T GetChild<T>(int idx) where T : class
diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/GD.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/GD.cs
index 53c02feaa2..ef42374041 100644
--- a/modules/mono/glue/GodotSharp/GodotSharp/Core/GD.cs
+++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/GD.cs
@@ -376,7 +376,7 @@ namespace Godot
/// <example>
/// <code>
/// GD.Print(GD.RandRange(0, 1)); // Prints 0 or 1
- /// GD.Print(GD.RangeRange(-10, 1000)); // Prints any number from -10 to 1000
+ /// GD.Print(GD.RandRange(-10, 1000)); // Prints any number from -10 to 1000
/// </code>
/// </example>
/// <returns>A random <see langword="int"/> number inside the given range.</returns>
diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Transform2D.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Transform2D.cs
index daea09ba72..c82c5f4588 100644
--- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Transform2D.cs
+++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Transform2D.cs
@@ -361,6 +361,7 @@ namespace Godot
/// <seealso cref="XformInv(Vector2)"/>
/// <param name="v">A vector to transform.</param>
/// <returns>The transformed vector.</returns>
+ [Obsolete("Xform is deprecated. Use the multiplication operator (Transform2D * Vector2) instead.")]
public Vector2 Xform(Vector2 v)
{
return new Vector2(Tdotx(v), Tdoty(v)) + origin;
@@ -372,6 +373,7 @@ namespace Godot
/// <seealso cref="Xform(Vector2)"/>
/// <param name="v">A vector to inversely transform.</param>
/// <returns>The inversely transformed vector.</returns>
+ [Obsolete("XformInv is deprecated. Use the multiplication operator (Vector2 * Transform2D) instead.")]
public Vector2 XformInv(Vector2 v)
{
Vector2 vInv = v - origin;
@@ -447,7 +449,7 @@ namespace Godot
public static Transform2D operator *(Transform2D left, Transform2D right)
{
- left.origin = left.Xform(right.origin);
+ left.origin = left * right.origin;
real_t x0 = left.Tdotx(right.x);
real_t x1 = left.Tdoty(right.x);
@@ -462,6 +464,96 @@ namespace Godot
return left;
}
+ /// <summary>
+ /// Returns a Vector2 transformed (multiplied) by transformation matrix.
+ /// </summary>
+ /// <param name="transform">The transformation to apply.</param>
+ /// <param name="vector">A Vector2 to transform.</param>
+ /// <returns>The transformed Vector2.</returns>
+ public static Vector2 operator *(Transform2D transform, Vector2 vector)
+ {
+ return new Vector2(transform.Tdotx(vector), transform.Tdoty(vector)) + transform.origin;
+ }
+
+ /// <summary>
+ /// Returns a Vector2 transformed (multiplied) by the inverse transformation matrix.
+ /// </summary>
+ /// <param name="vector">A Vector2 to inversely transform.</param>
+ /// <param name="transform">The transformation to apply.</param>
+ /// <returns>The inversely transformed Vector2.</returns>
+ public static Vector2 operator *(Vector2 vector, Transform2D transform)
+ {
+ Vector2 vInv = vector - transform.origin;
+ return new Vector2(transform.x.Dot(vInv), transform.y.Dot(vInv));
+ }
+
+ /// <summary>
+ /// Returns a Rect2 transformed (multiplied) by transformation matrix.
+ /// </summary>
+ /// <param name="transform">The transformation to apply.</param>
+ /// <param name="rect">A Rect2 to transform.</param>
+ /// <returns>The transformed Rect2.</returns>
+ public static Rect2 operator *(Transform2D transform, Rect2 rect)
+ {
+ Vector2 pos = transform * rect.Position;
+ Vector2 toX = transform.x * rect.Size.x;
+ Vector2 toY = transform.y * rect.Size.y;
+
+ return new Rect2(pos, rect.Size).Expand(pos + toX).Expand(pos + toY).Expand(pos + toX + toY);
+ }
+
+ /// <summary>
+ /// Returns a Rect2 transformed (multiplied) by the inverse transformation matrix.
+ /// </summary>
+ /// <param name="rect">A Rect2 to inversely transform.</param>
+ /// <param name="transform">The transformation to apply.</param>
+ /// <returns>The inversely transformed Rect2.</returns>
+ public static Rect2 operator *(Rect2 rect, Transform2D transform)
+ {
+ Vector2 pos = rect.Position * transform;
+ Vector2 to1 = new Vector2(rect.Position.x, rect.Position.y + rect.Size.y) * transform;
+ Vector2 to2 = new Vector2(rect.Position.x + rect.Size.x, rect.Position.y + rect.Size.y) * transform;
+ Vector2 to3 = new Vector2(rect.Position.x + rect.Size.x, rect.Position.y) * transform;
+
+ return new Rect2(pos, rect.Size).Expand(to1).Expand(to2).Expand(to3);
+ }
+
+ /// <summary>
+ /// Returns a copy of the given Vector2[] transformed (multiplied) by transformation matrix.
+ /// </summary>
+ /// <param name="transform">The transformation to apply.</param>
+ /// <param name="array">A Vector2[] to transform.</param>
+ /// <returns>The transformed copy of the Vector2[].</returns>
+ public static Vector2[] operator *(Transform2D transform, Vector2[] array)
+ {
+ Vector2[] newArray = new Vector2[array.Length];
+
+ for (int i = 0; i < array.Length; i++)
+ {
+ newArray[i] = transform * array[i];
+ }
+
+ return newArray;
+ }
+
+ /// <summary>
+ /// Returns a copy of the given Vector2[] transformed (multiplied) by the inverse transformation matrix.
+ /// </summary>
+ /// <param name="array">A Vector2[] to inversely transform.</param>
+ /// <param name="transform">The transformation to apply.</param>
+ /// <returns>The inversely transformed copy of the Vector2[].</returns>
+ public static Vector2[] operator *(Vector2[] array, Transform2D transform)
+ {
+ Vector2[] newArray = new Vector2[array.Length];
+
+ for (int i = 0; i < array.Length; i++)
+ {
+ newArray[i] = array[i] * transform;
+ }
+
+ return newArray;
+ }
+
public static bool operator ==(Transform2D left, Transform2D right)
{
return left.Equals(right);
diff --git a/modules/navigation/navigation_mesh_generator.cpp b/modules/navigation/navigation_mesh_generator.cpp
index 905d10c9d4..bb6bc578a4 100644
--- a/modules/navigation/navigation_mesh_generator.cpp
+++ b/modules/navigation/navigation_mesh_generator.cpp
@@ -45,7 +45,7 @@
#include "scene/resources/primitive_meshes.h"
#include "scene/resources/shape_3d.h"
#include "scene/resources/sphere_shape_3d.h"
-#include "scene/resources/world_margin_shape_3d.h"
+#include "scene/resources/world_boundary_shape_3d.h"
#ifdef TOOLS_ENABLED
#include "editor/editor_node.h"
diff --git a/modules/raycast/SCsub b/modules/raycast/SCsub
index 6e7b3e7b8d..1fdc8fe1b3 100644
--- a/modules/raycast/SCsub
+++ b/modules/raycast/SCsub
@@ -55,6 +55,9 @@ if env["builtin_embree"]:
"kernels/bvh/bvh_builder_sah_mb.cpp",
"kernels/bvh/bvh_builder_twolevel.cpp",
"kernels/bvh/bvh_intersector1_bvh4.cpp",
+ "kernels/bvh/bvh_intersector_hybrid4_bvh4.cpp",
+ "kernels/bvh/bvh_intersector_stream_bvh4.cpp",
+ "kernels/bvh/bvh_intersector_stream_filters.cpp",
]
thirdparty_sources = [thirdparty_dir + file for file in embree_src]
diff --git a/modules/raycast/godot_update_embree.py b/modules/raycast/godot_update_embree.py
index 31a25a318f..e31d88b741 100644
--- a/modules/raycast/godot_update_embree.py
+++ b/modules/raycast/godot_update_embree.py
@@ -61,6 +61,11 @@ cpp_files = [
"kernels/bvh/bvh_builder_twolevel.cpp",
"kernels/bvh/bvh_intersector1.cpp",
"kernels/bvh/bvh_intersector1_bvh4.cpp",
+ "kernels/bvh/bvh_intersector_hybrid4_bvh4.cpp",
+ "kernels/bvh/bvh_intersector_stream_bvh4.cpp",
+ "kernels/bvh/bvh_intersector_stream_filters.cpp",
+ "kernels/bvh/bvh_intersector_hybrid.cpp",
+ "kernels/bvh/bvh_intersector_stream.cpp",
]
os.chdir("../../thirdparty")
@@ -117,7 +122,7 @@ with open(os.path.join(dest_dir, "kernels/config.h"), "w") as config_file:
/* #undef EMBREE_GEOMETRY_INSTANCE */
/* #undef EMBREE_GEOMETRY_GRID */
/* #undef EMBREE_GEOMETRY_POINT */
-/* #undef EMBREE_RAY_PACKETS */
+#define EMBREE_RAY_PACKETS
/* #undef EMBREE_COMPACT_POLYS */
#define EMBREE_CURVE_SELF_INTERSECTION_AVOIDANCE_FACTOR 2.0
@@ -249,3 +254,8 @@ with open(os.path.join(dest_dir, "include/embree3/rtcore_config.h"), "w") as con
os.chdir("..")
shutil.rmtree("embree-tmp")
+
+subprocess.run(["git", "restore", "embree/patches"])
+
+for patch in os.listdir("embree/patches"):
+ subprocess.run(["git", "apply", "embree/patches/" + patch])
diff --git a/modules/raycast/raycast_occlusion_cull.cpp b/modules/raycast/raycast_occlusion_cull.cpp
index 88c0145ebc..a55b81c05d 100644
--- a/modules/raycast/raycast_occlusion_cull.cpp
+++ b/modules/raycast/raycast_occlusion_cull.cpp
@@ -66,28 +66,45 @@ void RaycastOcclusionCull::RaycastHZBuffer::resize(const Size2i &p_size) {
void RaycastOcclusionCull::RaycastHZBuffer::update_camera_rays(const Transform3D &p_cam_transform, const CameraMatrix &p_cam_projection, bool p_cam_orthogonal, ThreadWorkPool &p_thread_work_pool) {
CameraRayThreadData td;
- td.camera_matrix = p_cam_projection;
- td.camera_transform = p_cam_transform;
- td.camera_orthogonal = p_cam_orthogonal;
td.thread_count = p_thread_work_pool.get_thread_count();
+ td.z_near = p_cam_projection.get_z_near();
+ td.z_far = p_cam_projection.get_z_far() * 1.05f;
+ td.camera_pos = p_cam_transform.origin;
+ td.camera_dir = -p_cam_transform.basis.get_axis(2);
+ td.camera_orthogonal = p_cam_orthogonal;
+
+ CameraMatrix inv_camera_matrix = p_cam_projection.inverse();
+ Vector3 camera_corner_proj = Vector3(-1.0f, -1.0f, -1.0f);
+ Vector3 camera_corner_view = inv_camera_matrix.xform(camera_corner_proj);
+ td.pixel_corner = p_cam_transform.xform(camera_corner_view);
+
+ Vector3 top_corner_proj = Vector3(-1.0f, 1.0f, -1.0f);
+ Vector3 top_corner_view = inv_camera_matrix.xform(top_corner_proj);
+ Vector3 top_corner_world = p_cam_transform.xform(top_corner_view);
+
+ Vector3 left_corner_proj = Vector3(1.0f, -1.0f, -1.0f);
+ Vector3 left_corner_view = inv_camera_matrix.xform(left_corner_proj);
+ Vector3 left_corner_world = p_cam_transform.xform(left_corner_view);
+
+ td.pixel_u_interp = left_corner_world - td.pixel_corner;
+ td.pixel_v_interp = top_corner_world - td.pixel_corner;
+
+ debug_tex_range = td.z_far;
+
p_thread_work_pool.do_work(td.thread_count, this, &RaycastHZBuffer::_camera_rays_threaded, &td);
}
-void RaycastOcclusionCull::RaycastHZBuffer::_camera_rays_threaded(uint32_t p_thread, RaycastOcclusionCull::RaycastHZBuffer::CameraRayThreadData *p_data) {
+void RaycastOcclusionCull::RaycastHZBuffer::_camera_rays_threaded(uint32_t p_thread, const CameraRayThreadData *p_data) {
uint32_t packs_total = camera_rays.size();
uint32_t total_threads = p_data->thread_count;
uint32_t from = p_thread * packs_total / total_threads;
uint32_t to = (p_thread + 1 == total_threads) ? packs_total : ((p_thread + 1) * packs_total / total_threads);
- _generate_camera_rays(p_data->camera_transform, p_data->camera_matrix, p_data->camera_orthogonal, from, to);
+ _generate_camera_rays(p_data, from, to);
}
-void RaycastOcclusionCull::RaycastHZBuffer::_generate_camera_rays(const Transform3D &p_cam_transform, const CameraMatrix &p_cam_projection, bool p_cam_orthogonal, int p_from, int p_to) {
- Size2i buffer_size = sizes[0];
-
- CameraMatrix inv_camera_matrix = p_cam_projection.inverse();
- float z_far = p_cam_projection.get_z_far() * 1.05f;
- debug_tex_range = z_far;
+void RaycastOcclusionCull::RaycastHZBuffer::_generate_camera_rays(const CameraRayThreadData *p_data, int p_from, int p_to) {
+ const Size2i &buffer_size = sizes[0];
RayPacket *ray_packets = camera_rays.ptr();
uint32_t *ray_masks = camera_ray_masks.ptr();
@@ -98,56 +115,52 @@ void RaycastOcclusionCull::RaycastHZBuffer::_generate_camera_rays(const Transfor
int tile_y = (i / packs_size.x) * TILE_SIZE;
for (int j = 0; j < TILE_RAYS; j++) {
- float x = tile_x + j % TILE_SIZE;
- float y = tile_y + j / TILE_SIZE;
-
- ray_masks[i * TILE_RAYS + j] = ~0U;
+ int x = tile_x + j % TILE_SIZE;
+ int y = tile_y + j / TILE_SIZE;
if (x >= buffer_size.x || y >= buffer_size.y) {
ray_masks[i * TILE_RAYS + j] = 0U;
- } else {
- float u = x / (buffer_size.x - 1);
- float v = y / (buffer_size.y - 1);
- u = u * 2.0f - 1.0f;
- v = v * 2.0f - 1.0f;
-
- Plane pixel_proj = Plane(u, v, -1.0, 1.0);
- Plane pixel_view = inv_camera_matrix.xform4(pixel_proj);
- Vector3 pixel_world = p_cam_transform.xform(pixel_view.normal);
-
- Vector3 dir;
- if (p_cam_orthogonal) {
- dir = -p_cam_transform.basis.get_axis(2);
- } else {
- dir = (pixel_world - p_cam_transform.origin).normalized();
- }
-
- packet.ray.org_x[j] = pixel_world.x;
- packet.ray.org_y[j] = pixel_world.y;
- packet.ray.org_z[j] = pixel_world.z;
+ continue;
+ }
- packet.ray.dir_x[j] = dir.x;
- packet.ray.dir_y[j] = dir.y;
- packet.ray.dir_z[j] = dir.z;
+ ray_masks[i * TILE_RAYS + j] = ~0U;
- packet.ray.tnear[j] = 0.0f;
+ float u = (float(x) + 0.5f) / buffer_size.x;
+ float v = (float(y) + 0.5f) / buffer_size.y;
+ Vector3 pixel_pos = p_data->pixel_corner + u * p_data->pixel_u_interp + v * p_data->pixel_v_interp;
- packet.ray.time[j] = 0.0f;
+ packet.ray.tnear[j] = p_data->z_near;
- packet.ray.flags[j] = 0;
- packet.ray.mask[j] = -1;
- packet.hit.geomID[j] = RTC_INVALID_GEOMETRY_ID;
+ Vector3 dir;
+ if (p_data->camera_orthogonal) {
+ dir = -p_data->camera_dir;
+ packet.ray.org_x[j] = pixel_pos.x - dir.x * p_data->z_near;
+ packet.ray.org_y[j] = pixel_pos.y - dir.y * p_data->z_near;
+ packet.ray.org_z[j] = pixel_pos.z - dir.z * p_data->z_near;
+ } else {
+ dir = (pixel_pos - p_data->camera_pos).normalized();
+ packet.ray.org_x[j] = p_data->camera_pos.x;
+ packet.ray.org_y[j] = p_data->camera_pos.y;
+ packet.ray.org_z[j] = p_data->camera_pos.z;
+ packet.ray.tnear[j] /= dir.dot(p_data->camera_dir);
}
- packet.ray.tfar[j] = z_far;
+ packet.ray.dir_x[j] = dir.x;
+ packet.ray.dir_y[j] = dir.y;
+ packet.ray.dir_z[j] = dir.z;
+
+ packet.ray.tfar[j] = p_data->z_far;
+ packet.ray.time[j] = 0.0f;
+
+ packet.ray.flags[j] = 0;
+ packet.ray.mask[j] = -1;
+ packet.hit.geomID[j] = RTC_INVALID_GEOMETRY_ID;
}
}
}
-void RaycastOcclusionCull::RaycastHZBuffer::sort_rays() {
- if (is_empty()) {
- return;
- }
+void RaycastOcclusionCull::RaycastHZBuffer::sort_rays(const Vector3 &p_camera_dir, bool p_orthogonal) {
+ ERR_FAIL_COND(is_empty());
Size2i buffer_size = sizes[0];
for (int i = 0; i < packs_size.y; i++) {
@@ -161,7 +174,17 @@ void RaycastOcclusionCull::RaycastHZBuffer::sort_rays() {
}
int k = tile_i * TILE_SIZE + tile_j;
int packet_index = i * packs_size.x + j;
- mips[0][y * buffer_size.x + x] = camera_rays[packet_index].ray.tfar[k];
+ float d = camera_rays[packet_index].ray.tfar[k];
+
+ if (!p_orthogonal) {
+ const float &dir_x = camera_rays[packet_index].ray.dir_x[k];
+ const float &dir_y = camera_rays[packet_index].ray.dir_y[k];
+ const float &dir_z = camera_rays[packet_index].ray.dir_z[k];
+ float cos_theta = p_camera_dir.x * dir_x + p_camera_dir.y * dir_y + p_camera_dir.z * dir_z;
+ d *= cos_theta;
+ }
+
+ mips[0][y * buffer_size.x + x] = d;
}
}
}
@@ -514,7 +537,7 @@ void RaycastOcclusionCull::buffer_update(RID p_buffer, const Transform3D &p_cam_
buffer.update_camera_rays(p_cam_transform, p_cam_projection, p_cam_orthogonal, p_thread_pool);
scenario.raycast(buffer.camera_rays, buffer.camera_ray_masks, p_thread_pool);
- buffer.sort_rays();
+ buffer.sort_rays(-p_cam_transform.basis.get_axis(2), p_cam_orthogonal);
buffer.update_mips();
}
diff --git a/modules/raycast/raycast_occlusion_cull.h b/modules/raycast/raycast_occlusion_cull.h
index 85710a790c..cc87a6342c 100644
--- a/modules/raycast/raycast_occlusion_cull.h
+++ b/modules/raycast/raycast_occlusion_cull.h
@@ -51,15 +51,20 @@ public:
Size2i packs_size;
struct CameraRayThreadData {
- CameraMatrix camera_matrix;
- Transform3D camera_transform;
- bool camera_orthogonal;
int thread_count;
+ float z_near;
+ float z_far;
+ Vector3 camera_dir;
+ Vector3 camera_pos;
+ Vector3 pixel_corner;
+ Vector3 pixel_u_interp;
+ Vector3 pixel_v_interp;
+ bool camera_orthogonal;
Size2i buffer_size;
};
- void _camera_rays_threaded(uint32_t p_thread, CameraRayThreadData *p_data);
- void _generate_camera_rays(const Transform3D &p_cam_transform, const CameraMatrix &p_cam_projection, bool p_cam_orthogonal, int p_from, int p_to);
+ void _camera_rays_threaded(uint32_t p_thread, const CameraRayThreadData *p_data);
+ void _generate_camera_rays(const CameraRayThreadData *p_data, int p_from, int p_to);
public:
LocalVector<RayPacket> camera_rays;
@@ -68,7 +73,7 @@ public:
virtual void clear() override;
virtual void resize(const Size2i &p_size) override;
- void sort_rays();
+ void sort_rays(const Vector3 &p_camera_dir, bool p_orthogonal);
void update_camera_rays(const Transform3D &p_cam_transform, const CameraMatrix &p_cam_projection, bool p_cam_orthogonal, ThreadWorkPool &p_thread_work_pool);
};
diff --git a/modules/text_server_adv/SCsub b/modules/text_server_adv/SCsub
index 6691f86e60..7cd4db6f67 100644
--- a/modules/text_server_adv/SCsub
+++ b/modules/text_server_adv/SCsub
@@ -64,6 +64,7 @@ if env["builtin_harfbuzz"]:
#'src/hb-gobject-structs.cc',
"src/hb-icu.cc",
"src/hb-map.cc",
+ "src/hb-ms-feature-ranges.cc",
"src/hb-number.cc",
"src/hb-ot-cff1-table.cc",
"src/hb-ot-cff2-table.cc",
diff --git a/modules/text_server_adv/script_iterator.cpp b/modules/text_server_adv/script_iterator.cpp
index f9bbd25a5f..d1e849def8 100644
--- a/modules/text_server_adv/script_iterator.cpp
+++ b/modules/text_server_adv/script_iterator.cpp
@@ -30,6 +30,8 @@
#include "script_iterator.h"
+// This implementation is derived from ICU: icu4c/source/extra/scrptrun/scrptrun.cpp
+
bool ScriptIterator::same_script(int32_t p_script_one, int32_t p_script_two) {
return p_script_one <= USCRIPT_INHERITED || p_script_two <= USCRIPT_INHERITED || p_script_one == p_script_two;
}
@@ -48,7 +50,8 @@ ScriptIterator::ScriptIterator(const String &p_string, int p_start, int p_length
p_start = 0;
}
- ParenStackEntry paren_stack[128];
+ int paren_size = PAREN_STACK_DEPTH;
+ ParenStackEntry *paren_stack = (ParenStackEntry *)memalloc(paren_size * sizeof(ParenStackEntry));
int script_start;
int script_end = p_start;
@@ -64,13 +67,22 @@ ScriptIterator::ScriptIterator(const String &p_string, int p_start, int p_length
UChar32 ch = str[script_end];
UScriptCode sc = uscript_getScript(ch, &err);
if (U_FAILURE(err)) {
+ memfree(paren_stack);
ERR_FAIL_MSG(u_errorName(err));
}
if (u_getIntPropertyValue(ch, UCHAR_BIDI_PAIRED_BRACKET_TYPE) != U_BPT_NONE) {
if (u_getIntPropertyValue(ch, UCHAR_BIDI_PAIRED_BRACKET_TYPE) == U_BPT_OPEN) {
- paren_stack[++paren_sp].pair_index = ch;
+ // If it's an open character, push it onto the stack.
+ paren_sp++;
+ if (unlikely(paren_sp >= paren_size)) {
+ // If the stack is full, allocate more space to handle deeply nested parentheses. This is unlikely to happen with any real text.
+ paren_size += PAREN_STACK_DEPTH;
+ paren_stack = (ParenStackEntry *)memrealloc(paren_stack, paren_size * sizeof(ParenStackEntry));
+ }
+ paren_stack[paren_sp].pair_index = ch;
paren_stack[paren_sp].script_code = script_code;
} else if (paren_sp >= 0) {
+ // If it's a close character, find the matching open on the stack, and use that script code. Any non-matching open characters above it on the stack will be poped.
UChar32 paired_ch = u_getBidiPairedBracket(ch);
while (paren_sp >= 0 && paren_stack[paren_sp].pair_index != paired_ch) {
paren_sp -= 1;
@@ -87,11 +99,13 @@ ScriptIterator::ScriptIterator(const String &p_string, int p_start, int p_length
if (same_script(script_code, sc)) {
if (script_code <= USCRIPT_INHERITED && sc > USCRIPT_INHERITED) {
script_code = sc;
+ // Now that we have a final script code, fix any open characters we pushed before we knew the script code.
while (start_sp < paren_sp) {
paren_stack[++start_sp].script_code = script_code;
}
}
if ((u_getIntPropertyValue(ch, UCHAR_BIDI_PAIRED_BRACKET_TYPE) == U_BPT_CLOSE) && paren_sp >= 0) {
+ // If this character is a close paired character pop the matching open character from the stack.
paren_sp -= 1;
if (start_sp >= 0) {
start_sp -= 1;
@@ -109,4 +123,6 @@ ScriptIterator::ScriptIterator(const String &p_string, int p_start, int p_length
script_ranges.push_back(rng);
} while (script_end < p_length);
+
+ memfree(paren_stack);
}
diff --git a/modules/text_server_adv/script_iterator.h b/modules/text_server_adv/script_iterator.h
index 896a0e5c15..5efd40f7c4 100644
--- a/modules/text_server_adv/script_iterator.h
+++ b/modules/text_server_adv/script_iterator.h
@@ -43,6 +43,8 @@
#include <hb.h>
class ScriptIterator {
+ static const int PAREN_STACK_DEPTH = 128;
+
public:
struct ScriptRange {
int start = 0;
diff --git a/modules/text_server_adv/text_server_adv.cpp b/modules/text_server_adv/text_server_adv.cpp
index 19e94adf68..22706f9b6a 100644
--- a/modules/text_server_adv/text_server_adv.cpp
+++ b/modules/text_server_adv/text_server_adv.cpp
@@ -30,6 +30,7 @@
#include "text_server_adv.h"
+#include "core/error/error_macros.h"
#include "core/string/print_string.h"
#include "core/string/translation.h"
@@ -1225,7 +1226,7 @@ _FORCE_INLINE_ bool TextServerAdvanced::_ensure_cache_for_size(FontDataAdvanced
int error = 0;
if (!library) {
error = FT_Init_FreeType(&library);
- ERR_FAIL_COND_V_MSG(error != 0, false, TTR("FreeType: Error initializing library:") + " '" + String(FT_Error_String(error)) + "'.");
+ ERR_FAIL_COND_V_MSG(error != 0, false, "FreeType: Error initializing library: '" + String(FT_Error_String(error)) + "'.");
}
memset(&fd->stream, 0, sizeof(FT_StreamRec));
@@ -1243,13 +1244,7 @@ _FORCE_INLINE_ bool TextServerAdvanced::_ensure_cache_for_size(FontDataAdvanced
if (error) {
FT_Done_Face(fd->face);
fd->face = nullptr;
- ERR_FAIL_V_MSG(false, TTR("FreeType: Error loading font:") + " '" + String(FT_Error_String(error)) + "'.");
- }
- fd->hb_handle = hb_ft_font_create(fd->face, nullptr);
- if (fd->hb_handle == nullptr) {
- FT_Done_Face(fd->face);
- fd->face = nullptr;
- ERR_FAIL_V_MSG(false, TTR("HarfBuzz: Error creating FreeType font object."));
+ ERR_FAIL_V_MSG(false, "FreeType: Error loading font: '" + String(FT_Error_String(error)) + "'.");
}
if (p_font_data->msdf) {
@@ -1278,6 +1273,8 @@ _FORCE_INLINE_ bool TextServerAdvanced::_ensure_cache_for_size(FontDataAdvanced
FT_Set_Pixel_Sizes(fd->face, 0, fd->size.x * fd->oversampling);
}
+ fd->hb_handle = hb_ft_font_create(fd->face, nullptr);
+
fd->ascent = (fd->face->size->metrics.ascender / 64.0) / fd->oversampling * fd->scale;
fd->descent = (-fd->face->size->metrics.descender / 64.0) / fd->oversampling * fd->scale;
fd->underline_position = (-FT_MulFix(fd->face->underline_position, fd->face->size->metrics.y_scale) / 64.0) / fd->oversampling * fd->scale;
@@ -1592,14 +1589,11 @@ _FORCE_INLINE_ bool TextServerAdvanced::_ensure_cache_for_size(FontDataAdvanced
FT_Done_MM_Var(library, amaster);
}
#else
- ERR_FAIL_V_MSG(false, TTR("FreeType: Can't load dynamic font, engine is compiled without FreeType support!");
+ ERR_FAIL_V_MSG(false, "FreeType: Can't load dynamic font, engine is compiled without FreeType support!");
#endif
} else {
// Init bitmap font.
fd->hb_handle = hb_bmp_font_create(fd, nullptr);
- if (!fd->hb_handle) {
- ERR_FAIL_V_MSG(false, TTR("HarfBuzz: Error creating bitmap font object."));
- }
}
p_font_data->cache[p_size] = fd;
return true;
diff --git a/modules/text_server_fb/text_server_fb.cpp b/modules/text_server_fb/text_server_fb.cpp
index e4e6797f92..8a1bd93c65 100644
--- a/modules/text_server_fb/text_server_fb.cpp
+++ b/modules/text_server_fb/text_server_fb.cpp
@@ -30,6 +30,7 @@
#include "text_server_fb.h"
+#include "core/error/error_macros.h"
#include "core/string/print_string.h"
#ifdef MODULE_MSDFGEN_ENABLED
@@ -686,7 +687,7 @@ _FORCE_INLINE_ bool TextServerFallback::_ensure_cache_for_size(FontDataFallback
int error = 0;
if (!library) {
error = FT_Init_FreeType(&library);
- ERR_FAIL_COND_V_MSG(error != 0, false, TTR("FreeType: Error initializing library:") + " '" + String(FT_Error_String(error)) + "'.");
+ ERR_FAIL_COND_V_MSG(error != 0, false, "FreeType: Error initializing library: '" + String(FT_Error_String(error)) + "'.");
}
memset(&fd->stream, 0, sizeof(FT_StreamRec));
@@ -704,7 +705,7 @@ _FORCE_INLINE_ bool TextServerFallback::_ensure_cache_for_size(FontDataFallback
if (error) {
FT_Done_Face(fd->face);
fd->face = nullptr;
- ERR_FAIL_V_MSG(false, TTR("FreeType: Error loading font:") + " '" + String(FT_Error_String(error)) + "'.");
+ ERR_FAIL_V_MSG(false, "FreeType: Error loading font: '" + String(FT_Error_String(error)) + "'.");
}
if (p_font_data->msdf) {
@@ -784,7 +785,7 @@ _FORCE_INLINE_ bool TextServerFallback::_ensure_cache_for_size(FontDataFallback
FT_Done_MM_Var(library, amaster);
}
#else
- ERR_FAIL_V_MSG(false, TTR("FreeType: Can't load dynamic font, engine is compiled without FreeType support!");
+ ERR_FAIL_V_MSG(false, "FreeType: Can't load dynamic font, engine is compiled without FreeType support!");
#endif
}
p_font_data->cache[p_size] = fd;
diff --git a/modules/vhacd/register_types.cpp b/modules/vhacd/register_types.cpp
index 2b48e94604..54240e66fc 100644
--- a/modules/vhacd/register_types.cpp
+++ b/modules/vhacd/register_types.cpp
@@ -32,48 +32,55 @@
#include "scene/resources/mesh.h"
#include "thirdparty/vhacd/public/VHACD.h"
-static Vector<Vector<Face3>> convex_decompose(const Vector<Face3> &p_faces, int p_max_convex_hulls = -1) {
- Vector<real_t> vertices;
- vertices.resize(p_faces.size() * 9);
- Vector<uint32_t> indices;
- indices.resize(p_faces.size() * 3);
-
- for (int i = 0; i < p_faces.size(); i++) {
- for (int j = 0; j < 3; j++) {
- vertices.write[i * 9 + j * 3 + 0] = p_faces[i].vertex[j].x;
- vertices.write[i * 9 + j * 3 + 1] = p_faces[i].vertex[j].y;
- vertices.write[i * 9 + j * 3 + 2] = p_faces[i].vertex[j].z;
- indices.write[i * 3 + j] = i * 3 + j;
- }
- }
-
+static Vector<Vector<Vector3>> convex_decompose(const real_t *p_vertices, int p_vertex_count, const uint32_t *p_triangles, int p_triangle_count, const Mesh::ConvexDecompositionSettings &p_settings, Vector<Vector<uint32_t>> *r_convex_indices) {
VHACD::IVHACD::Parameters params;
- if (p_max_convex_hulls > 0) {
- params.m_maxConvexHulls = p_max_convex_hulls;
- }
+ params.m_concavity = p_settings.max_concavity;
+ params.m_alpha = p_settings.symmetry_planes_clipping_bias;
+ params.m_beta = p_settings.revolution_axes_clipping_bias;
+ params.m_minVolumePerCH = p_settings.min_volume_per_convex_hull;
+ params.m_resolution = p_settings.resolution;
+ params.m_maxNumVerticesPerCH = p_settings.max_num_vertices_per_convex_hull;
+ params.m_planeDownsampling = p_settings.plane_downsampling;
+ params.m_convexhullDownsampling = p_settings.convexhull_downsampling;
+ params.m_pca = p_settings.normalize_mesh;
+ params.m_mode = p_settings.mode;
+ params.m_convexhullApproximation = p_settings.convexhull_approximation;
+ params.m_oclAcceleration = true;
+ params.m_maxConvexHulls = p_settings.max_convex_hulls;
+ params.m_projectHullVertices = p_settings.project_hull_vertices;
VHACD::IVHACD *decomposer = VHACD::CreateVHACD();
- decomposer->Compute(vertices.ptr(), vertices.size() / 3, indices.ptr(), indices.size() / 3, params);
+ decomposer->Compute(p_vertices, p_vertex_count, p_triangles, p_triangle_count, params);
int hull_count = decomposer->GetNConvexHulls();
- Vector<Vector<Face3>> ret;
+ Vector<Vector<Vector3>> ret;
+ ret.resize(hull_count);
+
+ if (r_convex_indices) {
+ r_convex_indices->resize(hull_count);
+ }
for (int i = 0; i < hull_count; i++) {
- Vector<Face3> triangles;
VHACD::IVHACD::ConvexHull hull;
decomposer->GetConvexHull(i, hull);
- triangles.resize(hull.m_nTriangles);
- for (uint32_t j = 0; j < hull.m_nTriangles; j++) {
- Face3 f;
+
+ Vector<Vector3> &points = ret.write[i];
+ points.resize(hull.m_nPoints);
+
+ Vector3 *w = points.ptrw();
+ for (uint32_t j = 0; j < hull.m_nPoints; ++j) {
for (int k = 0; k < 3; k++) {
- for (int l = 0; l < 3; l++) {
- f.vertex[k][l] = hull.m_points[hull.m_triangles[j * 3 + k] * 3 + l];
- }
+ w[j][k] = hull.m_points[j * 3 + k];
}
- triangles.write[j] = f;
}
- ret.push_back(triangles);
+
+ if (r_convex_indices) {
+ Vector<uint32_t> &indices = r_convex_indices->write[i];
+ indices.resize(hull.m_nTriangles * 3);
+
+ memcpy(indices.ptrw(), hull.m_triangles, hull.m_nTriangles * 3 * sizeof(uint32_t));
+ }
}
decomposer->Clean();
@@ -83,9 +90,9 @@ static Vector<Vector<Face3>> convex_decompose(const Vector<Face3> &p_faces, int
}
void register_vhacd_types() {
- Mesh::convex_composition_function = convex_decompose;
+ Mesh::convex_decomposition_function = convex_decompose;
}
void unregister_vhacd_types() {
- Mesh::convex_composition_function = nullptr;
+ Mesh::convex_decomposition_function = nullptr;
}
diff --git a/modules/visual_script/doc_classes/VisualScriptCustomNode.xml b/modules/visual_script/doc_classes/VisualScriptCustomNode.xml
index b574576856..2c6313c80a 100644
--- a/modules/visual_script/doc_classes/VisualScriptCustomNode.xml
+++ b/modules/visual_script/doc_classes/VisualScriptCustomNode.xml
@@ -131,7 +131,7 @@
The [code]inputs[/code] array contains the values of the input ports.
[code]outputs[/code] is an array whose indices should be set to the respective outputs.
The [code]start_mode[/code] is usually [constant START_MODE_BEGIN_SEQUENCE], unless you have used the [code]STEP_*[/code] constants.
- [code]working_mem[/code] is an array which can be used to persist information between runs of the custom node.
+ [code]working_mem[/code] is an array which can be used to persist information between runs of the custom node. The size needs to be predefined using [method _get_working_memory_size].
When returning, you can mask the returned value with one of the [code]STEP_*[/code] constants.
</description>
</method>
diff --git a/modules/visual_script/register_types.cpp b/modules/visual_script/register_types.cpp
index 7fb9707fce..890861cf82 100644
--- a/modules/visual_script/register_types.cpp
+++ b/modules/visual_script/register_types.cpp
@@ -117,7 +117,7 @@ void register_visual_script_types() {
GDREGISTER_CLASS(VisualScriptCustomNodes);
ClassDB::set_current_api(ClassDB::API_CORE);
vs_custom_nodes_singleton = memnew(VisualScriptCustomNodes);
- Engine::get_singleton()->add_singleton(Engine::Singleton("VisualScriptEditor", VisualScriptCustomNodes::get_singleton()));
+ Engine::get_singleton()->add_singleton(Engine::Singleton("VisualScriptCustomNodes", VisualScriptCustomNodes::get_singleton()));
VisualScriptEditor::register_editor();
#endif
diff --git a/modules/visual_script/visual_script_editor.cpp b/modules/visual_script/visual_script_editor.cpp
index eee9e8f32b..0a6bcedf31 100644
--- a/modules/visual_script/visual_script_editor.cpp
+++ b/modules/visual_script/visual_script_editor.cpp
@@ -2546,16 +2546,11 @@ void VisualScriptEditor::goto_line(int p_line, bool p_with_error) {
error_line = p_line;
}
- List<StringName> functions;
- script->get_function_list(&functions);
- for (const StringName &E : functions) {
- if (script->has_node(p_line)) {
- _update_graph();
- _update_members();
+ if (script->has_node(p_line)) {
+ _update_graph();
+ _update_members();
- call_deferred(SNAME("call_deferred"), "_center_on_node", E, p_line); //editor might be just created and size might not exist yet
- return;
- }
+ call_deferred(SNAME("call_deferred"), "_center_on_node", p_line); // The editor might be just created and size might not exist yet.
}
}
@@ -3592,6 +3587,11 @@ void VisualScriptEditor::_hide_timer() {
hint_text->hide();
}
+void VisualScriptEditor::_toggle_scripts_pressed() {
+ ScriptEditor::get_singleton()->toggle_scripts_panel();
+ update_toggle_scripts_button();
+}
+
void VisualScriptEditor::_notification(int p_what) {
switch (p_what) {
case NOTIFICATION_READY: {
@@ -3606,6 +3606,8 @@ void VisualScriptEditor::_notification(int p_what) {
return;
}
+ update_toggle_scripts_button();
+
edit_variable_edit->add_theme_style_override("bg", get_theme_stylebox(SNAME("bg"), SNAME("Tree")));
edit_signal_edit->add_theme_style_override("bg", get_theme_stylebox(SNAME("bg"), SNAME("Tree")));
func_input_scroll->add_theme_style_override("bg", get_theme_stylebox(SNAME("bg"), SNAME("Tree")));
@@ -3650,6 +3652,7 @@ void VisualScriptEditor::_notification(int p_what) {
}
} break;
case NOTIFICATION_VISIBILITY_CHANGED: {
+ update_toggle_scripts_button();
members_section->set_visible(is_visible_in_tree());
} break;
}
@@ -4232,6 +4235,15 @@ void VisualScriptEditor::add_syntax_highlighter(Ref<EditorSyntaxHighlighter> p_h
void VisualScriptEditor::set_syntax_highlighter(Ref<EditorSyntaxHighlighter> p_highlighter) {
}
+void VisualScriptEditor::update_toggle_scripts_button() {
+ if (is_layout_rtl()) {
+ toggle_scripts_button->set_icon(Control::get_theme_icon(ScriptEditor::get_singleton()->is_scripts_panel_toggled() ? SNAME("Forward") : SNAME("Back"), SNAME("EditorIcons")));
+ } else {
+ toggle_scripts_button->set_icon(Control::get_theme_icon(ScriptEditor::get_singleton()->is_scripts_panel_toggled() ? SNAME("Back") : SNAME("Forward"), SNAME("EditorIcons")));
+ }
+ toggle_scripts_button->set_tooltip(vformat("%s (%s)", TTR("Toggle Scripts Panel"), ED_GET_SHORTCUT("script_editor/toggle_scripts_panel")->get_as_text()));
+}
+
void VisualScriptEditor::_bind_methods() {
ClassDB::bind_method("_move_node", &VisualScriptEditor::_move_node);
ClassDB::bind_method("_update_graph", &VisualScriptEditor::_update_graph, DEFVAL(-1));
@@ -4333,6 +4345,16 @@ VisualScriptEditor::VisualScriptEditor() {
graph->hide();
graph->connect("scroll_offset_changed", callable_mp(this, &VisualScriptEditor::_graph_ofs_changed));
+ status_bar = memnew(HBoxContainer);
+ add_child(status_bar);
+ status_bar->set_h_size_flags(SIZE_EXPAND_FILL);
+ status_bar->set_custom_minimum_size(Size2(0, 24 * EDSCALE));
+
+ toggle_scripts_button = memnew(Button);
+ toggle_scripts_button->set_flat(true);
+ toggle_scripts_button->connect("pressed", callable_mp(this, &VisualScriptEditor::_toggle_scripts_pressed));
+ status_bar->add_child(toggle_scripts_button);
+
/// Add Buttons to Top Bar/Zoom bar.
HBoxContainer *graph_hbc = graph->get_zoom_hbox();
diff --git a/modules/visual_script/visual_script_editor.h b/modules/visual_script/visual_script_editor.h
index ab32aae7aa..19f5aabac9 100644
--- a/modules/visual_script/visual_script_editor.h
+++ b/modules/visual_script/visual_script_editor.h
@@ -93,6 +93,8 @@ class VisualScriptEditor : public ScriptEditorBase {
ConfirmationDialog *function_create_dialog;
GraphEdit *graph;
+ HBoxContainer *status_bar;
+ Button *toggle_scripts_button;
VisualScriptEditorSignalEdit *signal_editor;
@@ -281,6 +283,8 @@ class VisualScriptEditor : public ScriptEditorBase {
void _member_rmb_selected(const Vector2 &p_pos);
void _member_option(int p_option);
+ void _toggle_scripts_pressed();
+
protected:
void _notification(int p_what);
static void _bind_methods();
@@ -330,6 +334,8 @@ public:
static void free_clipboard();
+ void update_toggle_scripts_button() override;
+
VisualScriptEditor();
~VisualScriptEditor();
};
diff --git a/modules/visual_script/visual_script_func_nodes.cpp b/modules/visual_script/visual_script_func_nodes.cpp
index 6ba5ad4fd6..205918a5f0 100644
--- a/modules/visual_script/visual_script_func_nodes.cpp
+++ b/modules/visual_script/visual_script_func_nodes.cpp
@@ -1010,7 +1010,7 @@ PropertyInfo VisualScriptPropertySet::get_input_value_port_info(int p_idx) const
if (index != StringName()) {
detail_prop_name += "." + String(index);
}
- PropertyInfo pinfo = PropertyInfo(E.type, detail_prop_name, PROPERTY_HINT_TYPE_STRING, E.hint_string);
+ PropertyInfo pinfo = PropertyInfo(E.type, detail_prop_name, E.hint, E.hint_string);
_adjust_input_index(pinfo);
return pinfo;
}
diff --git a/modules/websocket/editor_debugger_server_websocket.cpp b/modules/websocket/editor_debugger_server_websocket.cpp
index 2e61cbfc08..d248433d82 100644
--- a/modules/websocket/editor_debugger_server_websocket.cpp
+++ b/modules/websocket/editor_debugger_server_websocket.cpp
@@ -48,11 +48,19 @@ void EditorDebuggerServerWebSocket::poll() {
server->poll();
}
-Error EditorDebuggerServerWebSocket::start() {
- int remote_port = (int)EditorSettings::get_singleton()->get("network/debug/remote_port");
+Error EditorDebuggerServerWebSocket::start(const String &p_uri) {
+ int bind_port = (int)EditorSettings::get_singleton()->get("network/debug/remote_port");
+ String bind_host = EditorSettings::get_singleton()->get("network/debug/remote_host");
+ if (!p_uri.is_empty() && p_uri != "ws://") {
+ String scheme, path;
+ Error err = p_uri.parse_url(scheme, bind_host, bind_port, path);
+ ERR_FAIL_COND_V(err != OK, ERR_INVALID_PARAMETER);
+ ERR_FAIL_COND_V(!bind_host.is_valid_ip_address() && bind_host != "*", ERR_INVALID_PARAMETER);
+ }
+ server->set_bind_ip(bind_host);
Vector<String> compatible_protocols;
compatible_protocols.push_back("binary"); // compatibility with EMSCRIPTEN TCP-to-WebSocket layer.
- return server->listen(remote_port, compatible_protocols);
+ return server->listen(bind_port, compatible_protocols);
}
void EditorDebuggerServerWebSocket::stop() {
diff --git a/modules/websocket/editor_debugger_server_websocket.h b/modules/websocket/editor_debugger_server_websocket.h
index d9543bb647..14ab0109b2 100644
--- a/modules/websocket/editor_debugger_server_websocket.h
+++ b/modules/websocket/editor_debugger_server_websocket.h
@@ -48,7 +48,7 @@ public:
void _peer_disconnected(int p_peer, bool p_was_clean);
void poll() override;
- Error start() override;
+ Error start(const String &p_uri) override;
void stop() override;
bool is_active() const override;
bool is_connection_available() const override;