feat: updated engine

This commit is contained in:
Sara Gerretsen 2026-07-10 17:04:34 +02:00
parent cbe99774ff
commit f4cf6b3999
6607 changed files with 910135 additions and 430025 deletions

View file

@ -1,15 +1,7 @@
# Basic GDScript module architecture
This provides some basic information in how GDScript is implemented and integrates with the rest of the engine. You can learn more about GDScript in the [documentation](https://docs.godotengine.org/en/latest/tutorials/scripting/gdscript/index.html). It describes the syntax and user facing systems and concepts, and can be used as a reference for what user expectations are.
## General design
GDScript is:
1. A [gradually typed](https://en.wikipedia.org/wiki/Gradual_typing) language. Type hints are optional and help with static analysis and performance. However, typed code must easily interoperate with untyped code.
2. A tightly designed language. Features are added because they are _needed_, and not because they can be added or are interesting to develop.
3. Primarily an interpreted scripting language: it is compiled to GDScript byte code and interpreted in a GDScript virtual machine. It is meant to be easy to use and develop gameplay in. It is not meant for CPU-intensive algorithms or data processing, and is not optimized for it. For that, [C#](https://docs.godotengine.org/en/stable/tutorials/scripting/c_sharp/c_sharp_basics.html) or [GDExtension](https://docs.godotengine.org/en/stable/tutorials/scripting/gdextension/what_is_gdextension.html) may be used.
For general language design guidelines, please visit the [contributing docs](https://contributing.godotengine.org/en/latest/engine/guidelines/gdscript_language_guidelines.html).
## Integration into Godot

View file

@ -25,5 +25,6 @@ if env.editor_build:
if env["tests"]:
# TODO: Handle test creation magic without needing to pass macro.
env_gdscript.Append(CPPDEFINES=["TESTS_ENABLED"])
env_gdscript.add_source_files(env.modules_sources, "./tests/*.cpp")

View file

@ -11,7 +11,10 @@ def get_doc_classes():
return [
"@GDScript",
"GDScript",
"GDScriptLanguageProtocol",
"GDScriptSyntaxHighlighter",
"GDScriptTextDocument",
"GDScriptWorkspace",
]

View file

@ -32,7 +32,7 @@
<param index="0" name="condition" type="bool" />
<param index="1" name="message" type="String" default="&quot;&quot;" />
<description>
Asserts that the [param condition] is [code]true[/code]. If the [param condition] is [code]false[/code], an error is generated. When running from the editor, the running project will also be paused until you resume it. This can be used as a stronger form of [method @GlobalScope.push_error] for reporting errors to project developers or add-on users.
Asserts that the [param condition] is [code]true[/code]. If the [param condition] is [code]false[/code], an error is generated and the current method returns a default value. When running from the editor, failed asserts also cause a debugger break. This can be used as a stronger form of [method @GlobalScope.push_error] for reporting errors to project developers or add-on users.
An optional [param message] can be shown in addition to the generic "Assertion failed" message. You can use this to provide additional details about why the assertion failed.
[b]Warning:[/b] For performance reasons, the code inside [method assert] is only executed in debug builds or when running the project from the editor. Don't include code that has side effects in an [method assert] call. Otherwise, the project will behave differently when exported in release mode.
[codeblock]
@ -133,7 +133,7 @@
- A constant from the [enum Variant.Type] enumeration, for example [constant TYPE_INT].
- An [Object]-derived class which exists in [ClassDB], for example [Node].
- A [Script] (you can use any class, including inner one).
Unlike the right operand of the [code]is[/code] operator, [param type] can be a non-constant value. The [code]is[/code] operator supports more features (such as typed arrays). Use the operator instead of this method if you do not need to check the type dynamically.
Unlike the right operand of the [code]is[/code] operator, [param type] can be a non-constant value. The [code]is[/code] operator supports more features (such as typed arrays and dictionaries). Use the operator instead of this method if you do not need to check the type dynamically.
[b]Examples:[/b]
[codeblock]
print(is_instance_of(a, TYPE_INT))
@ -142,7 +142,7 @@
print(is_instance_of(a, MyClass.InnerClass))
[/codeblock]
[b]Note:[/b] If [param value] and/or [param type] are freed objects (see [method @GlobalScope.is_instance_valid]), or [param type] is not one of the above options, this method will raise a runtime error.
See also [method @GlobalScope.typeof], [method type_exists], [method Array.is_same_typed] (and other [Array] methods).
See also [method @GlobalScope.typeof], [method Object.is_class], [method Object.get_script], [method Array.is_same_typed] (and other [Array] methods), [method Dictionary.is_same_typed] (and other [Dictionary] methods).
</description>
</method>
<method name="len">
@ -266,7 +266,7 @@
[/codeblock]
</description>
</method>
<method name="type_exists">
<method name="type_exists" deprecated="Use [method ClassDB.class_exists] instead.">
<return type="bool" />
<param index="0" name="type" type="StringName" />
<description>

View file

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8" ?>
<class name="GDScript" inherits="Script" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../doc/class.xsd">
<class name="GDScript" inherits="Script" api_type="core" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../doc/class.xsd">
<brief_description>
A script implemented in the GDScript programming language.
</brief_description>

View file

@ -0,0 +1,69 @@
<?xml version="1.0" encoding="UTF-8" ?>
<class name="GDScriptLanguageProtocol" inherits="JSONRPC" api_type="editor" experimental="" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../doc/class.xsd">
<brief_description>
GDScript language server.
</brief_description>
<description>
Provides access to certain features that are implemented in the language server.
[b]Note:[/b] This class is not a language server client that can be used to access LSP functionality. It only provides access to a limited set of features that is implemented using the same technical foundation as the language server.
</description>
<tutorials>
</tutorials>
<methods>
<method name="get_text_document" deprecated="[GDScriptTextDocument] is deprecated.">
<return type="GDScriptTextDocument" />
<description>
Returns the language server's [GDScriptTextDocument] instance.
</description>
</method>
<method name="get_workspace">
<return type="GDScriptWorkspace" />
<description>
Returns the language server's [GDScriptWorkspace] instance.
</description>
</method>
<method name="initialize" deprecated="Accessing LSP endpoints directly might lead to unwanted side effects. Connect to the server via TCP, like a regular language server client.">
<return type="Variant" />
<param index="0" name="params" type="Dictionary" />
<description>
</description>
</method>
<method name="initialized" deprecated="Accessing LSP endpoints directly might lead to unwanted side effects. Connect to the server via TCP, like a regular language server client.">
<return type="void" />
<param index="0" name="params" type="Variant" />
<description>
</description>
</method>
<method name="is_initialized" qualifiers="const">
<return type="bool" />
<description>
Returns [code]true[/code] if the language server was initialized by a language server client, [code]false[/code] otherwise.
</description>
</method>
<method name="is_smart_resolve_enabled" qualifiers="const">
<return type="bool" />
<description>
Returns [code]true[/code] if the language server is providing the smart resolve feature, [code]false[/code] otherwise. The feature can be configured through the editor settings.
</description>
</method>
<method name="notify_client" deprecated="Might result in unwanted side effects for connected clients.">
<return type="void" />
<param index="0" name="method" type="String" />
<param index="1" name="params" type="Variant" default="null" />
<param index="2" name="client_id" type="int" default="-1" />
<description>
</description>
</method>
<method name="on_client_connected" deprecated="Might result in unwanted side effects for connected clients.">
<return type="int" enum="Error" />
<description>
</description>
</method>
<method name="on_client_disconnected" deprecated="Might result in unwanted side effects for connected clients.">
<return type="void" />
<param index="0" name="client_id" type="int" />
<description>
</description>
</method>
</methods>
</class>

View file

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8" ?>
<class name="GDScriptSyntaxHighlighter" inherits="EditorSyntaxHighlighter" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../doc/class.xsd">
<class name="GDScriptSyntaxHighlighter" inherits="EditorSyntaxHighlighter" api_type="editor" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../doc/class.xsd">
<brief_description>
A GDScript syntax highlighter that can be used with [TextEdit] and [CodeEdit] nodes.
</brief_description>

View file

@ -0,0 +1,121 @@
<?xml version="1.0" encoding="UTF-8" ?>
<class name="GDScriptTextDocument" inherits="RefCounted" api_type="editor" deprecated="" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../doc/class.xsd">
<brief_description>
Document related language server functionality.
</brief_description>
<description>
Provides language server functionality related to documents.
</description>
<tutorials>
</tutorials>
<methods>
<method name="completion" deprecated="Accessing LSP endpoints directly might lead to unwanted side effects. Connect to the server via TCP, like a regular language server client.">
<return type="Array" />
<param index="0" name="params" type="Dictionary" />
<description>
</description>
</method>
<method name="declaration" deprecated="Accessing LSP endpoints directly might lead to unwanted side effects. Connect to the server via TCP, like a regular language server client.">
<return type="Variant" />
<param index="0" name="params" type="Dictionary" />
<description>
</description>
</method>
<method name="definition" deprecated="Accessing LSP endpoints directly might lead to unwanted side effects. Connect to the server via TCP, like a regular language server client.">
<return type="Array" />
<param index="0" name="params" type="Dictionary" />
<description>
</description>
</method>
<method name="didChange" deprecated="Accessing LSP endpoints directly might lead to unwanted side effects. Connect to the server via TCP, like a regular language server client.">
<return type="void" />
<param index="0" name="params" type="Variant" />
<description>
</description>
</method>
<method name="didClose" deprecated="Accessing LSP endpoints directly might lead to unwanted side effects. Connect to the server via TCP, like a regular language server client.">
<return type="void" />
<param index="0" name="params" type="Variant" />
<description>
</description>
</method>
<method name="didOpen" deprecated="Accessing LSP endpoints directly might lead to unwanted side effects. Connect to the server via TCP, like a regular language server client.">
<return type="void" />
<param index="0" name="params" type="Variant" />
<description>
</description>
</method>
<method name="didSave" deprecated="Accessing LSP endpoints directly might lead to unwanted side effects. Connect to the server via TCP, like a regular language server client.">
<return type="void" />
<param index="0" name="params" type="Variant" />
<description>
</description>
</method>
<method name="documentLink" deprecated="Accessing LSP endpoints directly might lead to unwanted side effects. Connect to the server via TCP, like a regular language server client.">
<return type="Array" />
<param index="0" name="params" type="Dictionary" />
<description>
</description>
</method>
<method name="documentSymbol" deprecated="Accessing LSP endpoints directly might lead to unwanted side effects. Connect to the server via TCP, like a regular language server client.">
<return type="Array" />
<param index="0" name="params" type="Dictionary" />
<description>
</description>
</method>
<method name="hover" deprecated="Accessing LSP endpoints directly might lead to unwanted side effects. Connect to the server via TCP, like a regular language server client.">
<return type="Variant" />
<param index="0" name="params" type="Dictionary" />
<description>
</description>
</method>
<method name="nativeSymbol" deprecated="Accessing LSP endpoints directly might lead to unwanted side effects. Connect to the server via TCP, like a regular language server client.">
<return type="Variant" />
<param index="0" name="params" type="Dictionary" />
<description>
</description>
</method>
<method name="prepareRename" deprecated="Accessing LSP endpoints directly might lead to unwanted side effects. Connect to the server via TCP, like a regular language server client.">
<return type="Variant" />
<param index="0" name="params" type="Dictionary" />
<description>
</description>
</method>
<method name="references" deprecated="Accessing LSP endpoints directly might lead to unwanted side effects. Connect to the server via TCP, like a regular language server client.">
<return type="Array" />
<param index="0" name="params" type="Dictionary" />
<description>
</description>
</method>
<method name="rename" deprecated="Accessing LSP endpoints directly might lead to unwanted side effects. Connect to the server via TCP, like a regular language server client.">
<return type="Dictionary" />
<param index="0" name="params" type="Dictionary" />
<description>
</description>
</method>
<method name="resolve" deprecated="Accessing LSP endpoints directly might lead to unwanted side effects. Connect to the server via TCP, like a regular language server client.">
<return type="Dictionary" />
<param index="0" name="params" type="Dictionary" />
<description>
</description>
</method>
<method name="show_native_symbol_in_editor" deprecated="Use [method ScriptEditor.goto_help] instead.">
<return type="void" />
<param index="0" name="symbol_id" type="String" />
<description>
</description>
</method>
<method name="signatureHelp" deprecated="Accessing LSP endpoints directly might lead to unwanted side effects. Connect to the server via TCP, like a regular language server client.">
<return type="Variant" />
<param index="0" name="params" type="Dictionary" />
<description>
</description>
</method>
<method name="willSaveWaitUntil" deprecated="Accessing LSP endpoints directly might lead to unwanted side effects. Connect to the server via TCP, like a regular language server client.">
<return type="void" />
<param index="0" name="params" type="Variant" />
<description>
</description>
</method>
</methods>
</class>

View file

@ -0,0 +1,67 @@
<?xml version="1.0" encoding="UTF-8" ?>
<class name="GDScriptWorkspace" inherits="RefCounted" api_type="editor" experimental="" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../doc/class.xsd">
<brief_description>
Workspace related language server functionality.
</brief_description>
<description>
Provides language server functionality related to the workspace.
</description>
<tutorials>
</tutorials>
<methods>
<method name="apply_new_signal" deprecated="Might result in unwanted side effects for connected clients.">
<return type="void" />
<param index="0" name="obj" type="Object" />
<param index="1" name="function" type="String" />
<param index="2" name="args" type="PackedStringArray" />
<description>
</description>
</method>
<method name="didDeleteFiles" deprecated="Accessing LSP endpoints directly might lead to unwanted side effects. Connect to the server via TCP, like a regular language server client.">
<return type="void" />
<param index="0" name="params" type="Dictionary" />
<description>
</description>
</method>
<method name="generate_script_api">
<return type="Dictionary" />
<param index="0" name="path" type="String" />
<description>
Returns the interface of the script in a machine-readable format.
</description>
</method>
<method name="get_file_path">
<return type="String" />
<param index="0" name="uri" type="String" />
<description>
Converts a URI to a file path.
</description>
</method>
<method name="get_file_uri" qualifiers="const">
<return type="String" />
<param index="0" name="path" type="String" />
<description>
Converts a file path to a URI.
</description>
</method>
<method name="parse_local_script" deprecated="Might result in unwanted side effects for connected clients.">
<return type="int" enum="Error" />
<param index="0" name="path" type="String" />
<description>
</description>
</method>
<method name="parse_script" deprecated="Might result in unwanted side effects for connected clients.">
<return type="int" enum="Error" />
<param index="0" name="path" type="String" />
<param index="1" name="content" type="String" />
<description>
</description>
</method>
<method name="publish_diagnostics" deprecated="Might result in unwanted side effects for connected clients.">
<return type="void" />
<param index="0" name="path" type="String" />
<description>
</description>
</method>
</methods>
</class>

View file

@ -33,6 +33,7 @@
#include "../gdscript.h"
#include "core/config/project_settings.h"
#include "core/doc_data.h"
HashMap<String, String> GDScriptDocGen::singletons;
@ -61,6 +62,20 @@ String GDScriptDocGen::_get_class_name(const GDP::ClassNode &p_class) {
return full_name;
}
String GDScriptDocGen::_get_gdscript_name(const GDScript *p_script) {
if (p_script->local_name.is_empty()) {
// This is an outer unnamed class.
return _get_script_name(p_script->get_script_path());
} else {
// This is an inner or global outer class.
String name = p_script->local_name;
if (p_script->_owner) {
name = _get_gdscript_name(p_script->_owner) + "." + name;
}
return name;
}
}
void GDScriptDocGen::_doctype_from_gdtype(const GDType &p_gdtype, String &r_type, String &r_enum, bool p_is_return) {
if (!p_gdtype.is_hard_type()) {
r_type = "Variant";
@ -304,7 +319,7 @@ String GDScriptDocGen::docvalue_from_expression(const GDP::ExpressionNode *p_exp
case GDP::Node::CALL: {
const GDP::CallNode *call = static_cast<const GDP::CallNode *>(p_expression);
if (call->get_callee_type() == GDP::Node::IDENTIFIER) {
return call->function_name.operator String() + (call->arguments.is_empty() ? "()" : "(...)");
return call->function_name.string() + (call->arguments.is_empty() ? "()" : "(...)");
}
} break;
case GDP::Node::DICTIONARY: {
@ -315,6 +330,11 @@ String GDScriptDocGen::docvalue_from_expression(const GDP::ExpressionNode *p_exp
const GDP::IdentifierNode *id = static_cast<const GDP::IdentifierNode *>(p_expression);
return id->name;
} break;
case GDP::Node::LAMBDA: {
const GDP::LambdaNode *lambda = static_cast<const GDP::LambdaNode *>(p_expression);
const GDP::IdentifierNode *id = lambda->function->identifier;
return id != nullptr ? id->name : "<anonymous lambda>";
} break;
default: {
// Nothing to do.
} break;
@ -330,27 +350,16 @@ void GDScriptDocGen::_generate_docs(GDScript *p_script, const GDP::ClassNode *p_
doc.is_script_doc = true;
if (p_script->local_name == StringName()) {
// This is an outer unnamed class.
doc.name = _get_script_name(p_script->get_script_path());
} else {
// This is an inner or global outer class.
doc.name = p_script->local_name;
if (p_script->_owner) {
doc.name = p_script->_owner->doc.name + "." + doc.name;
}
}
doc.name = _get_gdscript_name(p_script);
doc.script_path = p_script->get_script_path();
if (p_script->base.is_valid() && p_script->base->is_valid()) {
if (!p_script->base->doc.name.is_empty()) {
doc.inherits = p_script->base->doc.name;
} else {
doc.inherits = p_script->base->get_instance_base_type();
}
} else if (p_script->native.is_valid()) {
doc.inherits = p_script->native->get_name();
if (p_script->base.is_valid() && p_script->base->is_script_valid()) {
// See GH-105926. Evaluate the doc name of the base class instead of using `p_script->base->doc.name`
// to avoid load/compile order issues in case of complex circular dependencies.
doc.inherits = _get_gdscript_name(p_script->base.ptr());
} else {
doc.inherits = p_script->get_instance_base_type();
}
doc.brief_description = p_class->doc_data.brief;
@ -389,7 +398,7 @@ void GDScriptDocGen::_generate_docs(GDScript *p_script, const GDP::ClassNode *p_
const_doc.name = const_name;
const_doc.value = _docvalue_from_variant(m_const->initializer->reduced_value);
const_doc.is_value_valid = true;
_doctype_from_gdtype(m_const->get_datatype(), const_doc.type, const_doc.enumeration);
_doctype_from_gdtype(m_const->type_constraint, const_doc.type, const_doc.enumeration);
const_doc.description = m_const->doc_data.description;
const_doc.is_deprecated = m_const->doc_data.is_deprecated;
const_doc.deprecated_message = m_const->doc_data.deprecated_message;
@ -418,7 +427,7 @@ void GDScriptDocGen::_generate_docs(GDScript *p_script, const GDP::ClassNode *p_
}
method_doc.qualifiers += "vararg";
method_doc.rest_argument.name = m_func->rest_parameter->identifier->name;
_doctype_from_gdtype(m_func->rest_parameter->get_datatype(), method_doc.rest_argument.type, method_doc.rest_argument.enumeration);
_doctype_from_gdtype(m_func->rest_parameter->type_constraint, method_doc.rest_argument.type, method_doc.rest_argument.enumeration);
}
if (m_func->is_abstract) {
if (!method_doc.qualifiers.is_empty()) {
@ -433,14 +442,10 @@ void GDScriptDocGen::_generate_docs(GDScript *p_script, const GDP::ClassNode *p_
method_doc.qualifiers += "static";
}
if (func_name == "_init") {
method_doc.return_type = "void";
} else if (m_func->return_type) {
// `m_func->return_type->get_datatype()` is a metatype.
_doctype_from_gdtype(m_func->get_datatype(), method_doc.return_type, method_doc.return_enum, true);
} else if (!m_func->body->has_return) {
// If no `return` statement, then return type is `void`, not `Variant`.
if (func_name == "_init" || func_name == "_static_init") {
method_doc.return_type = "void";
} else if (!m_func->return_type_constraint.is_variant()) {
_doctype_from_gdtype(m_func->return_type_constraint, method_doc.return_type, method_doc.return_enum, true);
} else {
method_doc.return_type = "Variant";
}
@ -448,7 +453,7 @@ void GDScriptDocGen::_generate_docs(GDScript *p_script, const GDP::ClassNode *p_
for (const GDP::ParameterNode *p : m_func->parameters) {
DocData::ArgumentDoc arg_doc;
arg_doc.name = p->identifier->name;
_doctype_from_gdtype(p->get_datatype(), arg_doc.type, arg_doc.enumeration);
_doctype_from_gdtype(p->type_constraint, arg_doc.type, arg_doc.enumeration);
if (p->initializer != nullptr) {
arg_doc.default_value = docvalue_from_expression(p->initializer);
}
@ -475,7 +480,7 @@ void GDScriptDocGen::_generate_docs(GDScript *p_script, const GDP::ClassNode *p_
for (const GDP::ParameterNode *p : m_signal->parameters) {
DocData::ArgumentDoc arg_doc;
arg_doc.name = p->identifier->name;
_doctype_from_gdtype(p->get_datatype(), arg_doc.type, arg_doc.enumeration);
_doctype_from_gdtype(p->type_constraint, arg_doc.type, arg_doc.enumeration);
signal_doc.arguments.push_back(arg_doc);
}
@ -495,7 +500,7 @@ void GDScriptDocGen::_generate_docs(GDScript *p_script, const GDP::ClassNode *p_
prop_doc.deprecated_message = m_var->doc_data.deprecated_message;
prop_doc.is_experimental = m_var->doc_data.is_experimental;
prop_doc.experimental_message = m_var->doc_data.experimental_message;
_doctype_from_gdtype(m_var->get_datatype(), prop_doc.type, prop_doc.enumeration);
_doctype_from_gdtype(m_var->type_constraint, prop_doc.type, prop_doc.enumeration);
switch (m_var->property) {
case GDP::VariableNode::PROP_NONE:

View file

@ -32,8 +32,6 @@
#include "../gdscript_parser.h"
#include "core/doc_data.h"
class GDScriptDocGen {
using GDP = GDScriptParser;
using GDType = GDP::DataType;
@ -42,8 +40,11 @@ class GDScriptDocGen {
static String _get_script_name(const String &p_path);
static String _get_class_name(const GDP::ClassNode &p_class);
static String _get_gdscript_name(const GDScript *p_script);
static void _doctype_from_gdtype(const GDType &p_gdtype, String &r_type, String &r_enum, bool p_is_return = false);
static String _docvalue_from_variant(const Variant &p_variant, int p_recursion_level = 1);
static void _generate_docs(GDScript *p_script, const GDP::ClassNode *p_class);
public:

View file

@ -0,0 +1,54 @@
/**************************************************************************/
/* gdscript_editor_language.h */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#pragma once
#include "core/object/editor_language.h"
class GDScriptEditorLanguage final : public EditorLanguage {
static GDScriptEditorLanguage *singleton;
public:
_FORCE_INLINE_ static GDScriptEditorLanguage *get_singleton() { return singleton; }
virtual Error complete_code(const String &p_code, const String &p_path, Object *p_owner, List<ScriptLanguage::CodeCompletionOption> *r_options, bool &r_force, String &r_call_hint) override;
virtual Error lookup_code(const String &p_code, const String &p_symbol, const String &p_path, Object *p_owner, LookupResult &r_result) override;
GDScriptEditorLanguage() {
ERR_FAIL_COND(singleton != nullptr);
singleton = this;
}
~GDScriptEditorLanguage() {
if (singleton == this) {
singleton = nullptr;
}
}
};

View file

@ -35,8 +35,8 @@
#include "core/config/project_settings.h"
#include "core/core_constants.h"
#include "core/object/class_db.h"
#include "editor/settings/editor_settings.h"
#include "editor/themes/editor_theme_manager.h"
#include "scene/gui/text_edit.h"
Dictionary GDScriptSyntaxHighlighter::_get_line_syntax_highlighting_impl(int p_line) {
@ -947,7 +947,7 @@ void GDScriptSyntaxHighlighter::_update_cache() {
HashMap<StringName, Variant> scr_constant_list;
scr_class->get_constants(&scr_constant_list);
for (const KeyValue<StringName, Variant> &E : scr_constant_list) {
member_keywords[E.key.operator String()] = member_variable_color;
member_keywords[E.key.string()] = member_variable_color;
}
scr_class = scr_class->get_base_script();
}

View file

@ -30,7 +30,7 @@
#pragma once
#include "editor/script/script_editor_plugin.h"
#include "editor/script/syntax_highlighters.h"
class GDScriptSyntaxHighlighter : public EditorSyntaxHighlighter {
GDCLASS(GDScriptSyntaxHighlighter, EditorSyntaxHighlighter)

View file

@ -450,6 +450,7 @@ GDScriptEditorTranslationParserPlugin::GDScriptEditorTranslationParserPlugin() {
first_arg_patterns.insert("add_radio_check_item");
first_arg_patterns.insert("add_separator");
first_arg_patterns.insert("add_submenu_item");
first_arg_patterns.insert("add_submenu_node_item");
second_arg_patterns.insert("set_tab_title");
second_arg_patterns.insert("add_icon_check_item");

View file

@ -38,7 +38,7 @@
#include "editor/translations/editor_translation_parser.h"
class GDScriptEditorTranslationParserPlugin : public EditorTranslationParserPlugin {
GDCLASS(GDScriptEditorTranslationParserPlugin, EditorTranslationParserPlugin);
GDSOFTCLASS(GDScriptEditorTranslationParserPlugin, EditorTranslationParserPlugin);
const HashMap<int, GDScriptTokenizer::CommentData> *comment_data = nullptr;

View file

@ -38,6 +38,11 @@
#include "gdscript_tokenizer_buffer.h"
#include "gdscript_warning.h"
#include "core/io/resource_loader.h"
#include "core/object/callable_mp.h"
#include "core/object/class_db.h"
#include "core/templates/rb_set.h"
#ifdef TOOLS_ENABLED
#include "editor/gdscript_docgen.h"
#endif
@ -50,7 +55,6 @@
#include "core/config/project_settings.h"
#include "core/core_constants.h"
#include "core/io/file_access.h"
#include "scene/resources/packed_scene.h"
#include "scene/scene_string_names.h"
@ -167,7 +171,7 @@ GDScriptInstance *GDScript::_create_instance(const Variant **p_args, int p_argco
/* STEP 2, INITIALIZE AND CONSTRUCT */
{
MutexLock lock(GDScriptLanguage::singleton->mutex);
instances.insert(instance->owner);
instances.add(&instance->script_instance_list);
}
_super_implicit_constructor(this, instance, r_error);
@ -177,7 +181,7 @@ GDScriptInstance *GDScript::_create_instance(const Variant **p_args, int p_argco
instance->owner->set_script_instance(nullptr);
{
MutexLock lock(GDScriptLanguage::singleton->mutex);
instances.erase(p_owner);
instances.remove(&instance->script_instance_list);
}
ERR_FAIL_V_MSG(nullptr, "Error constructing a GDScriptInstance: " + error_text);
}
@ -195,7 +199,7 @@ GDScriptInstance *GDScript::_create_instance(const Variant **p_args, int p_argco
instance->owner->set_script_instance(nullptr);
{
MutexLock lock(GDScriptLanguage::singleton->mutex);
instances.erase(p_owner);
instances.remove(&instance->script_instance_list);
}
ERR_FAIL_V_MSG(nullptr, "Error constructing a GDScriptInstance: " + error_text);
}
@ -224,16 +228,17 @@ Variant GDScript::_new(const Variant **p_args, int p_argcount, Callable::CallErr
ERR_FAIL_COND_V(_baseptr->native.is_null(), Variant());
if (_baseptr->native.ptr()) {
owner = _baseptr->native->instantiate();
RefCounted *r = Object::cast_to<RefCounted>(owner);
if (r) {
ref = Ref<RefCounted>(r);
}
} else {
owner = memnew(RefCounted); //by default, no base means use reference
ref = memnew(RefCounted); // By default, no base means use reference.
owner = ref.ptr();
}
ERR_FAIL_NULL_V_MSG(owner, Variant(), "Can't inherit from a virtual class.");
RefCounted *r = Object::cast_to<RefCounted>(owner);
if (r) {
ref = Ref<RefCounted>(r);
}
GDScriptInstance *instance = _create_instance(p_args, p_argcount, owner, r_error);
if (!instance) {
if (ref.is_null()) {
@ -269,7 +274,7 @@ StringName GDScript::get_instance_base_type() const {
if (native.is_valid()) {
return native->get_name();
}
if (base.is_valid() && base->is_valid()) {
if (base.is_valid() && base->is_script_valid()) {
return base->get_instance_base_type();
}
return StringName();
@ -408,7 +413,7 @@ ScriptInstance *GDScript::instance_create(Object *p_this) {
}
if (top->native.is_valid()) {
if (!ClassDB::is_parent_class(p_this->get_class_name(), top->native->get_name())) {
if (!p_this->is_class(top->native->get_name())) {
if (EngineDebugger::is_active()) {
GDScriptLanguage::get_singleton()->debug_break_parse(_get_debug_path(), 1, "Script inherits from native type '" + String(top->native->get_name()) + "', so it can't be assigned to an object of type: '" + p_this->get_class() + "'");
}
@ -431,12 +436,6 @@ PlaceHolderScriptInstance *GDScript::placeholder_instance_create(Object *p_this)
#endif
}
bool GDScript::instance_has(const Object *p_this) const {
MutexLock lock(GDScriptLanguage::singleton->mutex);
return instances.has((Object *)p_this);
}
bool GDScript::has_source_code() const {
return !source.is_empty();
}
@ -584,7 +583,7 @@ bool GDScript::_update_exports(bool *r_err, bool p_recursive_call, PlaceHolderSc
placeholder_fallback_enabled = false;
if (base_cache.is_valid() && base_cache->is_valid()) {
if (base_cache.is_valid() && base_cache->is_script_valid()) {
for (int i = 0; i < base_caches.size(); i++) {
if (base_caches[i] == base_cache.ptr()) {
if (r_err) {
@ -644,7 +643,7 @@ void GDScript::_update_exports_down(bool p_base_exports_changed) {
return;
}
HashSet<ObjectID> copy = inheriters_cache; //might get modified
HashSet<ObjectID> copy(inheriters_cache); //might get modified
for (const ObjectID &E : copy) {
Object *id = ObjectDB::get_instance(E);
@ -746,7 +745,7 @@ Error GDScript::reload(bool p_keep_state) {
{
MutexLock lock(GDScriptLanguage::singleton->mutex);
has_instances = instances.size();
has_instances = instances.first() != nullptr;
}
// Check condition but reset flag before early return
@ -805,7 +804,7 @@ Error GDScript::reload(bool p_keep_state) {
bool can_run = ScriptServer::is_scripting_enabled() || is_tool();
#ifdef TOOLS_ENABLED
if (p_keep_state && can_run && is_valid()) {
if (p_keep_state && can_run && is_script_valid()) {
_save_old_static_data();
}
#endif
@ -820,10 +819,10 @@ Error GDScript::reload(bool p_keep_state) {
}
if (err) {
if (EngineDebugger::is_active()) {
GDScriptLanguage::get_singleton()->debug_break_parse(_get_debug_path(), parser.get_errors().front()->get().line, "Parser Error: " + parser.get_errors().front()->get().message);
GDScriptLanguage::get_singleton()->debug_break_parse(_get_debug_path(), parser.get_errors().front()->get().start_line, "Parser Error: " + parser.get_errors().front()->get().message);
}
// TODO: Show all error messages.
_err_print_error("GDScript::reload", path.is_empty() ? "built-in" : (const char *)path.utf8().get_data(), parser.get_errors().front()->get().line, ("Parse Error: " + parser.get_errors().front()->get().message).utf8().get_data(), false, ERR_HANDLER_SCRIPT);
_err_print_error("GDScript::reload", path.is_empty() ? "built-in" : (const char *)path.utf8().get_data(), parser.get_errors().front()->get().start_line, ("Parse Error: " + parser.get_errors().front()->get().message).utf8().get_data(), false, ERR_HANDLER_SCRIPT);
reloading = false;
return ERR_PARSE_ERROR;
}
@ -833,12 +832,12 @@ Error GDScript::reload(bool p_keep_state) {
if (err) {
if (EngineDebugger::is_active()) {
GDScriptLanguage::get_singleton()->debug_break_parse(_get_debug_path(), parser.get_errors().front()->get().line, "Parser Error: " + parser.get_errors().front()->get().message);
GDScriptLanguage::get_singleton()->debug_break_parse(_get_debug_path(), parser.get_errors().front()->get().start_line, "Parser Error: " + parser.get_errors().front()->get().message);
}
const List<GDScriptParser::ParserError>::Element *e = parser.get_errors().front();
while (e != nullptr) {
_err_print_error("GDScript::reload", path.is_empty() ? "built-in" : (const char *)path.utf8().get_data(), e->get().line, ("Parse Error: " + e->get().message).utf8().get_data(), false, ERR_HANDLER_SCRIPT);
_err_print_error("GDScript::reload", path.is_empty() ? "built-in" : (const char *)path.utf8().get_data(), e->get().start_line, ("Parse Error: " + e->get().message).utf8().get_data(), false, ERR_HANDLER_SCRIPT);
e = e->next();
}
reloading = false;
@ -927,10 +926,6 @@ const Variant GDScript::get_rpc_config() const {
return rpc_config;
}
void GDScript::unload_static() const {
GDScriptCache::remove_script(fully_qualified_name);
}
Variant GDScript::callp(const StringName &p_method, const Variant **p_args, int p_argcount, Callable::CallError &r_error) {
GDScript *top = this;
while (top) {
@ -945,9 +940,19 @@ Variant GDScript::callp(const StringName &p_method, const Variant **p_args, int
top = top->base.ptr();
}
//none found, regular
{
Variant ret = Script::callp(p_method, p_args, p_argcount, r_error);
if (r_error.error != Callable::CallError::CALL_ERROR_INVALID_METHOD) {
return ret;
}
}
return Script::callp(p_method, p_args, p_argcount, r_error);
if (native.is_valid()) {
return native->callp(p_method, p_args, p_argcount, r_error);
}
r_error.error = Callable::CallError::CALL_ERROR_INVALID_METHOD;
return Variant();
}
bool GDScript::_get(const StringName &p_name, Variant &r_ret) const {
@ -1095,7 +1100,15 @@ void GDScript::set_path(const String &p_path, bool p_take_over) {
String old_path = path;
path = p_path;
path_valid = true;
GDScriptCache::move_script(old_path, p_path);
String old_base = GDScript::canonicalize_path(old_path);
if (!old_base.is_empty() && fully_qualified_name.begins_with(old_base)) {
fully_qualified_name = GDScript::canonicalize_path(p_path) + fully_qualified_name.substr(old_base.length());
}
if (is_root_script()) {
GDScriptCache::move_script(old_path, p_path);
}
for (KeyValue<StringName, Ref<GDScript>> &kv : subclasses) {
kv.value->set_path(p_path, p_take_over);
@ -1470,6 +1483,11 @@ void GDScript::clear() {
memdelete(E);
}
functions_to_clear.clear();
#ifdef TOOLS_ENABLED
base_cache = Ref<GDScript>();
#endif
base = Ref<GDScript>();
}
void GDScript::cancel_pending_functions(bool warn) {
@ -1541,7 +1559,7 @@ bool GDScriptInstance::set(const StringName &p_name, const Variant &p_value) {
callp(member->setter, &args, 1, err);
return err.error == Callable::CallError::CALL_OK;
} else {
members.write[member->index] = value;
members[member->index] = value;
return true;
}
}
@ -1729,9 +1747,48 @@ void GDScriptInstance::get_property_list(List<PropertyInfo> *p_properties) const
Callable::CallError err;
Variant ret = E->value->call(const_cast<GDScriptInstance *>(this), nullptr, 0, err);
if (err.error == Callable::CallError::CALL_OK) {
ERR_FAIL_COND_MSG(ret.get_type() != Variant::ARRAY, "Wrong type for _get_property_list, must be an array of dictionaries.");
// GH-118877. We decided to make an exception to maintain compatibility, since the problem can only be detected at runtime.
#ifdef DISABLE_DEPRECATED
ERR_FAIL_COND_MSG(ret.get_type() != Variant::ARRAY, R"*(Wrong type for "_get_property_list()", must be "Array[Dictionary]".)*");
#else // !DISABLE_DEPRECATED
ERR_FAIL_COND_MSG(ret.get_type() != Variant::ARRAY, R"*(Wrong type for "_get_property_list()", must be an array of dictionaries.)*");
#endif // DISABLE_DEPRECATED
Array arr = ret;
// GH-118877. We decided to make an exception to maintain compatibility, since the problem can only be detected at runtime.
#ifdef DISABLE_DEPRECATED
ERR_FAIL_COND_MSG(arr.get_typed_builtin() != Variant::DICTIONARY, R"*(Wrong type for "_get_property_list()", must be "Array[Dictionary]".)*");
#else // !DISABLE_DEPRECATED
#ifdef DEBUG_ENABLED
if (arr.get_typed_builtin() != Variant::DICTIONARY) {
static bool error_shown = false;
if (unlikely(!error_shown)) {
error_shown = true;
String elem_type;
if (arr.is_typed()) {
const Ref<Script> script_type = arr.get_typed_script();
if (script_type.is_valid() && script_type->is_script_valid()) {
elem_type = GDScript::debug_get_script_name(script_type);
} else if (!arr.get_typed_class_name().is_empty()) {
elem_type = arr.get_typed_class_name();
} else {
elem_type = Variant::get_type_name((Variant::Type)arr.get_typed_builtin());
}
elem_type = vformat("[%s]", elem_type);
}
String msg = vformat(R"*("_get_property_list()" should return "Array[Dictionary]", not "Array%s".)*", elem_type);
msg += " The old behavior is supported for compatibility and may be removed in the future.";
msg += " This message is printed once and will not be repeated for similar errors.";
ERR_PRINT(msg);
}
}
#endif // DEBUG_ENABLED
#endif // DISABLE_DEPRECATED
for (int i = 0; i < arr.size(); i++) {
Dictionary d = arr[i];
ERR_CONTINUE(!d.has("name"));
@ -1987,23 +2044,19 @@ const Variant GDScriptInstance::get_rpc_config() const {
void GDScriptInstance::reload_members() {
#ifdef DEBUG_ENABLED
Vector<Variant> new_members;
TightLocalVector<Variant> new_members;
new_members.resize(script->member_indices.size());
//pass the values to the new indices
// Transfer the old members into their new position.
for (KeyValue<StringName, GDScript::MemberInfo> &E : script->member_indices) {
if (member_indices_cache.has(E.key)) {
Variant value = members[member_indices_cache[E.key]];
new_members.write[E.value.index] = value;
new_members[E.value.index] = value;
}
}
members = std::move(new_members);
members.resize(new_members.size()); //resize
//apply
members = new_members;
//pass the values to the new indices
// Cache the new indices.
member_indices_cache.clear();
for (const KeyValue<StringName, GDScript::MemberInfo> &E : script->member_indices) {
member_indices_cache[E.key] = E.value.index;
@ -2027,8 +2080,8 @@ GDScriptInstance::~GDScriptInstance() {
}
}
if (script.is_valid() && owner) {
script->instances.erase(owner);
if (script.is_valid()) {
script->instances.remove(&script_instance_list);
}
}
@ -2094,7 +2147,7 @@ void GDScriptLanguage::remove_named_global_constant(const StringName &p_name) {
}
void GDScriptLanguage::init() {
//populate global constants
// Populate core constants.
int gcc = CoreConstants::get_global_constant_count();
for (int i = 0; i < gcc; i++) {
_add_global(StringName(CoreConstants::get_global_constant_name(i)), CoreConstants::get_global_constant_value(i));
@ -2105,19 +2158,19 @@ void GDScriptLanguage::init() {
_add_global(StringName("INF"), Math::INF);
_add_global(StringName("NAN"), Math::NaN);
//populate native classes
// Populate native classes.
LocalVector<StringName> class_list;
ClassDB::get_class_list(class_list);
for (const StringName &class_name : class_list) {
if (globals.has(class_name)) {
if (globals.has(class_name) || !GDScriptAnalyzer::class_exists(class_name)) {
continue;
}
Ref<GDScriptNativeClass> nc = memnew(GDScriptNativeClass(class_name));
_add_global(class_name, nc);
}
//populate singletons
// Populate singletons (overriding the native class registered under the same name).
List<Engine::Singleton> singletons;
Engine::get_singleton()->get_singletons(&singletons);
@ -2454,15 +2507,13 @@ void GDScriptLanguage::reload_scripts(const Array &p_scripts, bool p_soft_reload
//save state and remove script from instances
HashMap<ObjectID, List<Pair<StringName, Variant>>> &map = to_reload[scr];
while (scr->instances.front()) {
Object *obj = scr->instances.front()->get();
while (scr->instances.first()) {
GDScriptInstance *instance = scr->instances.first()->self();
//save instance info
List<Pair<StringName, Variant>> state;
if (obj->get_script_instance()) {
obj->get_script_instance()->get_property_state(state);
map[obj->get_instance_id()] = state;
obj->set_script(Variant());
}
instance->get_property_state(state);
map[instance->get_owner()->get_instance_id()] = state;
instance->get_owner()->set_script(Variant());
}
//same thing for placeholders
@ -2709,15 +2760,19 @@ String GDScriptLanguage::_get_global_class_name(const String &p_path, String *r_
while (subclass) {
if (subclass->extends_used) {
if (!subclass->extends_path.is_empty()) {
String subpath = subclass->extends_path;
if (subpath.is_relative_path()) {
subpath = path.get_base_dir().path_join(subpath).simplify_path();
}
if (subclass->extends.is_empty()) {
// We only care about the referenced class_name.
_ALLOW_DISCARD_ _get_global_class_name(subclass->extends_path, r_base_type, nullptr, nullptr, nullptr, r_visited);
_ALLOW_DISCARD_ _get_global_class_name(subpath, r_base_type, nullptr, nullptr, nullptr, r_visited);
subclass = nullptr;
break;
} else {
Vector<GDScriptParser::IdentifierNode *> extend_classes = subclass->extends;
Ref<FileAccess> subfile = FileAccess::open(subclass->extends_path, FileAccess::READ);
Ref<FileAccess> subfile = FileAccess::open(subpath, FileAccess::READ);
if (subfile.is_null()) {
break;
}
@ -2726,10 +2781,6 @@ String GDScriptLanguage::_get_global_class_name(const String &p_path, String *r_
if (subsource.is_empty()) {
break;
}
String subpath = subclass->extends_path;
if (subpath.is_relative_path()) {
subpath = path.get_base_dir().path_join(subpath).simplify_path();
}
if (OK != subparser.parse(subsource, subpath, false)) {
break;
@ -2874,170 +2925,3 @@ Ref<GDScript> GDScriptLanguage::get_orphan_subclass(const String &p_qualified_na
}
return Ref<GDScript>(Object::cast_to<GDScript>(obj));
}
Ref<GDScript> GDScriptLanguage::get_script_by_fully_qualified_name(const String &p_name) {
{
MutexLock lock(mutex);
SelfList<GDScript> *elem = script_list.first();
while (elem) {
GDScript *scr = elem->self();
if (scr->fully_qualified_name == p_name) {
return scr;
}
elem = elem->next();
}
}
Ref<GDScript> scr;
scr.instantiate();
scr->fully_qualified_name = p_name;
return scr;
}
/*************** RESOURCE ***************/
Ref<Resource> ResourceFormatLoaderGDScript::load(const String &p_path, const String &p_original_path, Error *r_error, bool p_use_sub_threads, float *r_progress, CacheMode p_cache_mode) {
Error err;
bool ignoring = p_cache_mode == CACHE_MODE_IGNORE || p_cache_mode == CACHE_MODE_IGNORE_DEEP;
Ref<GDScript> scr = GDScriptCache::get_full_script(p_original_path, err, "", ignoring);
if (err && scr.is_valid()) {
// If !scr.is_valid(), the error was likely from scr->load_source_code(), which already generates an error.
ERR_PRINT_ED(vformat(R"(Failed to load script "%s" with error "%s".)", p_original_path, error_names[err]));
}
if (r_error) {
// Don't fail loading because of parsing error.
*r_error = scr.is_valid() ? OK : err;
}
return scr;
}
void ResourceFormatLoaderGDScript::get_recognized_extensions(List<String> *p_extensions) const {
p_extensions->push_back("gd");
p_extensions->push_back("gdc");
}
bool ResourceFormatLoaderGDScript::handles_type(const String &p_type) const {
return (p_type == "Script" || p_type == "GDScript");
}
String ResourceFormatLoaderGDScript::get_resource_type(const String &p_path) const {
String el = p_path.get_extension().to_lower();
if (el == "gd" || el == "gdc") {
return "GDScript";
}
return "";
}
void ResourceFormatLoaderGDScript::get_dependencies(const String &p_path, List<String> *p_dependencies, bool p_add_types) {
Ref<FileAccess> file = FileAccess::open(p_path, FileAccess::READ);
ERR_FAIL_COND_MSG(file.is_null(), "Cannot open file '" + p_path + "'.");
String source = file->get_as_utf8_string();
if (source.is_empty()) {
return;
}
GDScriptParser parser;
if (OK != parser.parse(source, p_path, false)) {
return;
}
for (const String &E : parser.get_dependencies()) {
p_dependencies->push_back(E);
}
}
void ResourceFormatLoaderGDScript::get_classes_used(const String &p_path, HashSet<StringName> *r_classes) {
Ref<GDScript> scr = ResourceLoader::load(p_path);
if (scr.is_null()) {
return;
}
const String source = scr->get_source_code();
GDScriptTokenizerText tokenizer;
tokenizer.set_source_code(source);
GDScriptTokenizer::Token current = tokenizer.scan();
while (current.type != GDScriptTokenizer::Token::TK_EOF) {
if (!current.is_identifier()) {
current = tokenizer.scan();
continue;
}
int insert_idx = 0;
for (int i = 0; i < current.start_line - 1; i++) {
insert_idx = source.find("\n", insert_idx) + 1;
}
// Insert the "cursor" character, needed for the lookup to work.
const String source_with_cursor = source.insert(insert_idx + current.start_column, String::chr(0xFFFF));
ScriptLanguage::LookupResult result;
if (scr->get_language()->lookup_code(source_with_cursor, current.get_identifier(), p_path, nullptr, result) == OK) {
if (!result.class_name.is_empty() && ClassDB::class_exists(result.class_name)) {
r_classes->insert(result.class_name);
}
if (result.type == ScriptLanguage::LOOKUP_RESULT_CLASS_PROPERTY) {
PropertyInfo prop;
if (ClassDB::get_property_info(result.class_name, result.class_member, &prop)) {
if (!prop.class_name.is_empty() && ClassDB::class_exists(prop.class_name)) {
r_classes->insert(prop.class_name);
}
if (!prop.hint_string.is_empty() && ClassDB::class_exists(prop.hint_string)) {
r_classes->insert(prop.hint_string);
}
}
} else if (result.type == ScriptLanguage::LOOKUP_RESULT_CLASS_METHOD) {
MethodInfo met;
if (ClassDB::get_method_info(result.class_name, result.class_member, &met)) {
if (!met.return_val.class_name.is_empty() && ClassDB::class_exists(met.return_val.class_name)) {
r_classes->insert(met.return_val.class_name);
}
if (!met.return_val.hint_string.is_empty() && ClassDB::class_exists(met.return_val.hint_string)) {
r_classes->insert(met.return_val.hint_string);
}
}
}
}
current = tokenizer.scan();
}
}
Error ResourceFormatSaverGDScript::save(const Ref<Resource> &p_resource, const String &p_path, uint32_t p_flags) {
Ref<GDScript> sqscr = p_resource;
ERR_FAIL_COND_V(sqscr.is_null(), ERR_INVALID_PARAMETER);
String source = sqscr->get_source_code();
{
Error err;
Ref<FileAccess> file = FileAccess::open(p_path, FileAccess::WRITE, &err);
ERR_FAIL_COND_V_MSG(err, err, "Cannot save GDScript file '" + p_path + "'.");
file->store_string(source);
if (file->get_error() != OK && file->get_error() != ERR_FILE_EOF) {
return ERR_CANT_CREATE;
}
}
if (ScriptServer::is_reload_scripts_on_save_enabled()) {
GDScriptLanguage::get_singleton()->reload_tool_script(p_resource, true);
}
return OK;
}
void ResourceFormatSaverGDScript::get_recognized_extensions(const Ref<Resource> &p_resource, List<String> *p_extensions) const {
if (Object::cast_to<GDScript>(*p_resource)) {
p_extensions->push_back("gd");
}
}
bool ResourceFormatSaverGDScript::recognize(const Ref<Resource> &p_resource) const {
return Object::cast_to<GDScript>(*p_resource) != nullptr;
}

View file

@ -35,10 +35,7 @@
#include "core/debugger/engine_debugger.h"
#include "core/debugger/script_debugger.h"
#include "core/doc_data.h"
#include "core/io/resource_loader.h"
#include "core/io/resource_saver.h"
#include "core/object/script_language.h"
#include "core/templates/rb_set.h"
class GDScriptNativeClass : public RefCounted {
GDCLASS(GDScriptNativeClass, RefCounted);
@ -72,15 +69,6 @@ class GDScript : public Script {
PropertyInfo property_info;
};
struct ClearData {
RBSet<GDScriptFunction *> functions;
RBSet<Ref<Script>> scripts;
void clear() {
functions.clear();
scripts.clear();
}
};
friend class GDScriptInstance;
friend class GDScriptFunction;
friend class GDScriptAnalyzer;
@ -174,7 +162,7 @@ private:
Error _static_init();
void _static_default_init(); // Initialize static variables with default values based on their types.
RBSet<Object *> instances;
SelfList<GDScriptInstance>::List instances;
bool destructing = false;
bool clearing = false;
//exported members
@ -241,7 +229,7 @@ public:
// Cancels all functions of the script that are are waiting to be resumed after using await.
void cancel_pending_functions(bool warn);
virtual bool is_valid() const override { return valid; }
virtual bool is_script_valid() const override { return valid; }
bool inherits_script(const Ref<Script> &p_script) const override;
@ -287,7 +275,6 @@ public:
virtual StringName get_instance_base_type() const override; // this may not work in all scripts, will return empty if so
virtual ScriptInstance *instance_create(Object *p_this) override;
virtual PlaceHolderScriptInstance *placeholder_instance_create(Object *p_this) override;
virtual bool instance_has(const Object *p_this) const override;
virtual bool has_source_code() const override;
virtual String get_source_code() const override;
@ -339,8 +326,6 @@ public:
virtual const Variant get_rpc_config() const override;
void unload_static() const;
#ifdef TOOLS_ENABLED
virtual bool is_placeholder_fallback_enabled() const override { return placeholder_fallback_enabled; }
#endif
@ -364,10 +349,13 @@ class GDScriptInstance : public ScriptInstance {
#ifdef DEBUG_ENABLED
HashMap<StringName, int> member_indices_cache; //used only for hot script reloading
#endif
Vector<Variant> members;
TightLocalVector<Variant> members;
SelfList<GDScriptFunctionState>::List pending_func_states;
// Replacing `SelfList` with a better implementation could save 16bytes from the self and list pointer.
SelfList<GDScriptInstance> script_instance_list; // Linked list of instances with the same script.
void _call_implicit_ready_recursively(GDScript *p_script);
public:
@ -404,7 +392,8 @@ public:
virtual const Variant get_rpc_config() const;
GDScriptInstance() {}
GDScriptInstance() :
script_instance_list(this) {}
~GDScriptInstance();
};
@ -591,6 +580,10 @@ public:
virtual void finish() override;
/* EDITOR FUNCTIONS */
#ifdef TOOLS_ENABLED
virtual EditorLanguage *get_editor_language() override;
#endif // TOOLS_ENABLED
virtual Vector<String> get_reserved_words() const override;
virtual bool is_control_flow_keyword(const String &p_keywords) const override;
virtual Vector<String> get_comment_delimiters() const override;
@ -600,16 +593,11 @@ public:
virtual Ref<Script> make_template(const String &p_template, const String &p_class_name, const String &p_base_class_name) const override;
virtual Vector<ScriptTemplate> get_built_in_templates(const StringName &p_object) override;
virtual bool validate(const String &p_script, const String &p_path = "", List<String> *r_functions = nullptr, List<ScriptLanguage::ScriptError> *r_errors = nullptr, List<ScriptLanguage::Warning> *r_warnings = nullptr, HashSet<int> *r_safe_lines = nullptr) const override;
virtual Script *create_script() const override;
virtual bool supports_builtin_mode() const override;
virtual bool supports_documentation() const override;
virtual bool can_inherit_from_file() const override { return true; }
virtual int find_function(const String &p_function, const String &p_code) const override;
virtual String make_function(const String &p_class, const String &p_name, const PackedStringArray &p_args) const override;
virtual Error complete_code(const String &p_code, const String &p_path, Object *p_owner, List<ScriptLanguage::CodeCompletionOption> *r_options, bool &r_forced, String &r_call_hint) override;
#ifdef TOOLS_ENABLED
virtual Error lookup_code(const String &p_code, const String &p_symbol, const String &p_path, Object *p_owner, LookupResult &r_result) override;
#endif
virtual String _get_indentation() const;
virtual void auto_indent_code(String &p_code, int p_from_line, int p_to_line) const override;
virtual void add_global_constant(const StringName &p_variable, const Variant &p_value) override;
@ -659,29 +647,6 @@ public:
void add_orphan_subclass(const String &p_qualified_name, const ObjectID &p_subclass);
Ref<GDScript> get_orphan_subclass(const String &p_qualified_name);
Ref<GDScript> get_script_by_fully_qualified_name(const String &p_name);
GDScriptLanguage();
~GDScriptLanguage();
};
class ResourceFormatLoaderGDScript : public ResourceFormatLoader {
GDSOFTCLASS(ResourceFormatLoaderGDScript, ResourceFormatLoader);
public:
virtual Ref<Resource> load(const String &p_path, const String &p_original_path = "", Error *r_error = nullptr, bool p_use_sub_threads = false, float *r_progress = nullptr, CacheMode p_cache_mode = CACHE_MODE_REUSE) override;
virtual void get_recognized_extensions(List<String> *p_extensions) const override;
virtual bool handles_type(const String &p_type) const override;
virtual String get_resource_type(const String &p_path) const override;
virtual void get_dependencies(const String &p_path, List<String> *p_dependencies, bool p_add_types = false) override;
virtual void get_classes_used(const String &p_path, HashSet<StringName> *r_classes) override;
};
class ResourceFormatSaverGDScript : public ResourceFormatSaver {
GDSOFTCLASS(ResourceFormatSaverGDScript, ResourceFormatSaver);
public:
virtual Error save(const Ref<Resource> &p_resource, const String &p_path, uint32_t p_flags = 0) override;
virtual void get_recognized_extensions(const Ref<Resource> &p_resource, List<String> *p_extensions) const override;
virtual bool recognize(const Ref<Resource> &p_resource) const override;
};

File diff suppressed because it is too large Load diff

View file

@ -33,7 +33,6 @@
#include "gdscript_cache.h"
#include "gdscript_parser.h"
#include "core/object/object.h"
#include "core/object/ref_counted.h"
class GDScriptAnalyzer {
@ -69,7 +68,7 @@ class GDScriptAnalyzer {
Error resolve_class_inheritance(GDScriptParser::ClassNode *p_class, bool p_recursive);
GDScriptParser::DataType resolve_datatype(GDScriptParser::TypeNode *p_type);
void decide_suite_type(GDScriptParser::Node *p_suite, GDScriptParser::Node *p_statement);
void decide_pattern_type(GDScriptParser::PatternNode &p_pattern, GDScriptParser::PatternNode *p_statement);
void resolve_annotation(GDScriptParser::AnnotationNode *p_annotation);
void resolve_class_member(GDScriptParser::ClassNode *p_class, const StringName &p_name, const GDScriptParser::Node *p_source = nullptr);
@ -81,7 +80,7 @@ class GDScriptAnalyzer {
void resolve_function_signature(GDScriptParser::FunctionNode *p_function, const GDScriptParser::Node *p_source = nullptr, bool p_is_lambda = false);
void resolve_function_body(GDScriptParser::FunctionNode *p_function, bool p_is_lambda = false);
void resolve_node(GDScriptParser::Node *p_node, bool p_is_root = true);
void resolve_suite(GDScriptParser::SuiteNode *p_suite);
void resolve_suite(GDScriptParser::SuiteNode *p_suite, bool p_is_root = true);
void resolve_assignable(GDScriptParser::AssignableNode *p_assignable, const char *p_kind);
void resolve_variable(GDScriptParser::VariableNode *p_variable, bool p_is_local);
void resolve_constant(GDScriptParser::ConstantNode *p_constant, bool p_is_local);
@ -116,16 +115,26 @@ class GDScriptAnalyzer {
void reduce_type_test(GDScriptParser::TypeTestNode *p_type_test);
void reduce_unary_op(GDScriptParser::UnaryOpNode *p_unary_op);
Variant make_expression_reduced_value(GDScriptParser::ExpressionNode *p_expression, bool &is_reduced);
Variant make_array_reduced_value(GDScriptParser::ArrayNode *p_array, bool &is_reduced);
Variant make_dictionary_reduced_value(GDScriptParser::DictionaryNode *p_dictionary, bool &is_reduced);
Variant make_subscript_reduced_value(GDScriptParser::SubscriptNode *p_subscript, bool &is_reduced);
Variant make_call_reduced_value(GDScriptParser::CallNode *p_call, bool &is_reduced);
// These methods provide a fallback mechanism for constant folding in constant contexts (constant initializers,
// annotation arguments) and allow constant folding even when the normal constant folding mechanism does not consider
// an expression to be constant (usually due to the presence of `[...]` and `{...}` constructs in it).
// These methods assume that the normal expression reduction mechanism has already been performed.
Variant make_expression_reduced_value(GDScriptParser::ExpressionNode *p_expression, bool &r_is_reduced);
Variant make_array_reduced_value(GDScriptParser::ArrayNode *p_array, bool &r_is_reduced);
Variant make_dictionary_reduced_value(GDScriptParser::DictionaryNode *p_dictionary, bool &r_is_reduced);
Variant make_subscript_reduced_value(GDScriptParser::SubscriptNode *p_subscript, bool &r_is_reduced);
Variant make_call_reduced_value(GDScriptParser::CallNode *p_call, bool &r_is_reduced);
Variant make_binary_op_reduced_value(GDScriptParser::BinaryOpNode *p_binary_op, bool &r_is_reduced);
Variant make_ternary_op_reduced_value(GDScriptParser::TernaryOpNode *p_ternary_op, bool &r_is_reduced);
Variant make_cast_reduced_value(GDScriptParser::CastNode *p_cast, bool &r_is_reduced);
Variant make_type_test_reduced_value(GDScriptParser::TypeTestNode *p_type_test, bool &r_is_reduced);
// Helpers.
Array make_array_from_element_datatype(const GDScriptParser::DataType &p_element_datatype, const GDScriptParser::Node *p_source_node = nullptr);
Dictionary make_dictionary_from_element_datatype(const GDScriptParser::DataType &p_key_element_datatype, const GDScriptParser::DataType &p_value_element_datatype, const GDScriptParser::Node *p_source_node = nullptr);
GDScriptParser::DataType type_from_script(const Ref<Script> &p_script, const GDScriptParser::Node *p_source, bool p_is_meta_type);
GDScriptParser::DataType type_from_variant(const Variant &p_value, const GDScriptParser::Node *p_source);
GDScriptParser::DataType type_from_property_hint_string(const String &p_type_name) const;
GDScriptParser::DataType type_from_property(const PropertyInfo &p_property, bool p_is_arg = false, bool p_is_readonly = false) const;
GDScriptParser::DataType make_global_class_meta_type(const StringName &p_class_name, const GDScriptParser::Node *p_source);
bool get_function_signature(GDScriptParser::Node *p_source, bool p_is_constructor, GDScriptParser::DataType base_type, const StringName &p_function, GDScriptParser::DataType &r_return_type, List<GDScriptParser::DataType> &r_par_types, int &r_default_arg_count, BitField<MethodFlags> &r_method_flags, StringName *r_native_class = nullptr);
@ -138,9 +147,10 @@ class GDScriptAnalyzer {
void update_array_literal_element_type(GDScriptParser::ArrayNode *p_array, const GDScriptParser::DataType &p_element_type);
void update_dictionary_literal_element_type(GDScriptParser::DictionaryNode *p_dictionary, const GDScriptParser::DataType &p_key_element_type, const GDScriptParser::DataType &p_value_element_type);
bool is_type_compatible(const GDScriptParser::DataType &p_target, const GDScriptParser::DataType &p_source, bool p_allow_implicit_conversion = false, const GDScriptParser::Node *p_source_node = nullptr);
bool is_type_compatible_strict_collections(const GDScriptParser::DataType &p_target, const GDScriptParser::DataType &p_source);
void push_error(const String &p_message, const GDScriptParser::Node *p_origin = nullptr);
void mark_node_unsafe(const GDScriptParser::Node *p_node);
void downgrade_node_type_source(GDScriptParser::Node *p_node);
void downgrade_node_type_source(GDScriptParser::ExpressionNode *p_node);
void mark_lambda_use_self();
void resolve_pending_lambda_bodies();
void reduce_identifier_from_base_set_class(GDScriptParser::IdentifierNode *p_identifier, GDScriptParser::DataType p_identifier_datatype);
@ -148,9 +158,11 @@ class GDScriptAnalyzer {
Ref<GDScriptParserRef> find_cached_external_parser_for_class(const GDScriptParser::ClassNode *p_class, const Ref<GDScriptParserRef> &p_dependant_parser);
Ref<GDScriptParserRef> find_cached_external_parser_for_class(const GDScriptParser::ClassNode *p_class, GDScriptParser *p_dependant_parser);
Ref<GDScript> get_depended_shallow_script(const String &p_path, Error &r_error);
#ifdef DEBUG_ENABLED
void is_shadowing(GDScriptParser::IdentifierNode *p_identifier, const String &p_context, const bool p_in_local_scope);
#endif
void warn_confusable_temporary_modification(GDScriptParser::ExpressionNode *p_expression);
#endif // DEBUG_ENABLED
public:
Error resolve_inheritance();

View file

@ -30,7 +30,7 @@
#include "gdscript_byte_codegen.h"
#include "core/debugger/engine_debugger.h"
#include "core/object/class_db.h"
uint32_t GDScriptByteCodeGenerator::add_parameter(const StringName &p_name, bool p_is_optional, const GDScriptDataType &p_type) {
function->_argument_count++;
@ -189,7 +189,7 @@ GDScriptFunction *GDScriptByteCodeGenerator::write_end() {
opcodes.write[temporaries[i].bytecode_indices[j]] = stack_index | (GDScriptFunction::ADDR_TYPE_STACK << GDScriptFunction::ADDR_BITS);
}
if (temporaries[i].type != Variant::NIL) {
function->temporary_slots[stack_index] = temporaries[i].type;
function->temporary_slots.push_back(Pair(stack_index, temporaries[i].type));
}
}
@ -200,6 +200,9 @@ GDScriptFunction *GDScriptByteCodeGenerator::write_end() {
for (const KeyValue<Variant, int> &K : constant_map) {
function->constants.write[K.value] = K.key;
}
for (const KeyValue<StringName, int> &K : local_constants) {
function->constant_map.insert(K.key, function->constants[K.value]);
}
} else {
function->_constants_ptr = nullptr;
function->_constant_count = 0;
@ -1831,23 +1834,30 @@ void GDScriptByteCodeGenerator::write_newline(int p_line) {
}
}
void GDScriptByteCodeGenerator::write_return(const Address &p_return_value) {
if (!function->return_type.has_type() || p_return_value.type.has_type()) {
// Either the function is untyped or the return value is also typed.
void GDScriptByteCodeGenerator::write_return(const Address &p_return_value, bool p_use_conversion) {
if (!p_use_conversion) {
append_opcode(GDScriptFunction::OPCODE_RETURN);
append(p_return_value);
return;
}
// If this is a typed function, then we need to check for potential conversions.
if (function->return_type.has_type()) {
if (function->return_type.kind == GDScriptDataType::BUILTIN && function->return_type.builtin_type == Variant::ARRAY && function->return_type.has_container_element_type(0)) {
// Typed array.
switch (function->return_type.kind) {
case GDScriptDataType::VARIANT: {
ERR_PRINT("Compiler bug: Unresolved return.");
// Shouldn't get here, but fail-safe to a regular return.
append_opcode(GDScriptFunction::OPCODE_RETURN);
append(p_return_value);
} break;
case GDScriptDataType::BUILTIN: {
if (function->return_type.builtin_type == Variant::ARRAY && function->return_type.has_container_element_type(0)) {
const GDScriptDataType &element_type = function->return_type.get_container_element_type(0);
append_opcode(GDScriptFunction::OPCODE_RETURN_TYPED_ARRAY);
append(p_return_value);
append(get_constant_pos(element_type.script_type) | (GDScriptFunction::ADDR_TYPE_CONSTANT << GDScriptFunction::ADDR_BITS));
append(element_type.builtin_type);
append(element_type.native_type);
} else if (function->return_type.kind == GDScriptDataType::BUILTIN && function->return_type.builtin_type == Variant::DICTIONARY &&
function->return_type.has_container_element_types()) {
// Typed dictionary.
} else if (function->return_type.builtin_type == Variant::DICTIONARY && function->return_type.has_container_element_types()) {
const GDScriptDataType &key_type = function->return_type.get_container_element_type_or_variant(0);
const GDScriptDataType &value_type = function->return_type.get_container_element_type_or_variant(1);
append_opcode(GDScriptFunction::OPCODE_RETURN_TYPED_DICTIONARY);
@ -1858,72 +1868,29 @@ void GDScriptByteCodeGenerator::write_return(const Address &p_return_value) {
append(key_type.native_type);
append(value_type.builtin_type);
append(value_type.native_type);
} else if (function->return_type.kind == GDScriptDataType::BUILTIN && p_return_value.type.kind == GDScriptDataType::BUILTIN && function->return_type.builtin_type != p_return_value.type.builtin_type) {
// Add conversion.
} else {
append_opcode(GDScriptFunction::OPCODE_RETURN_TYPED_BUILTIN);
append(p_return_value);
append(function->return_type.builtin_type);
} else {
// Just assign.
append_opcode(GDScriptFunction::OPCODE_RETURN);
append(p_return_value);
}
} else {
append_opcode(GDScriptFunction::OPCODE_RETURN);
} break;
case GDScriptDataType::NATIVE: {
append_opcode(GDScriptFunction::OPCODE_RETURN_TYPED_NATIVE);
append(p_return_value);
}
} else {
switch (function->return_type.kind) {
case GDScriptDataType::BUILTIN: {
if (function->return_type.builtin_type == Variant::ARRAY && function->return_type.has_container_element_type(0)) {
const GDScriptDataType &element_type = function->return_type.get_container_element_type(0);
append_opcode(GDScriptFunction::OPCODE_RETURN_TYPED_ARRAY);
append(p_return_value);
append(get_constant_pos(element_type.script_type) | (GDScriptFunction::ADDR_TYPE_CONSTANT << GDScriptFunction::ADDR_BITS));
append(element_type.builtin_type);
append(element_type.native_type);
} else if (function->return_type.builtin_type == Variant::DICTIONARY && function->return_type.has_container_element_types()) {
const GDScriptDataType &key_type = function->return_type.get_container_element_type_or_variant(0);
const GDScriptDataType &value_type = function->return_type.get_container_element_type_or_variant(1);
append_opcode(GDScriptFunction::OPCODE_RETURN_TYPED_DICTIONARY);
append(p_return_value);
append(get_constant_pos(key_type.script_type) | (GDScriptFunction::ADDR_TYPE_CONSTANT << GDScriptFunction::ADDR_BITS));
append(get_constant_pos(value_type.script_type) | (GDScriptFunction::ADDR_TYPE_CONSTANT << GDScriptFunction::ADDR_BITS));
append(key_type.builtin_type);
append(key_type.native_type);
append(value_type.builtin_type);
append(value_type.native_type);
} else {
append_opcode(GDScriptFunction::OPCODE_RETURN_TYPED_BUILTIN);
append(p_return_value);
append(function->return_type.builtin_type);
}
} break;
case GDScriptDataType::NATIVE: {
append_opcode(GDScriptFunction::OPCODE_RETURN_TYPED_NATIVE);
append(p_return_value);
int class_idx = GDScriptLanguage::get_singleton()->get_global_map()[function->return_type.native_type];
Variant nc = GDScriptLanguage::get_singleton()->get_global_array()[class_idx];
class_idx = get_constant_pos(nc) | (GDScriptFunction::ADDR_TYPE_CONSTANT << GDScriptFunction::ADDR_BITS);
append(class_idx);
} break;
case GDScriptDataType::GDSCRIPT:
case GDScriptDataType::SCRIPT: {
Variant script = function->return_type.script_type;
int script_idx = get_constant_pos(script) | (GDScriptFunction::ADDR_TYPE_CONSTANT << GDScriptFunction::ADDR_BITS);
int class_idx = GDScriptLanguage::get_singleton()->get_global_map()[function->return_type.native_type];
Variant nc = GDScriptLanguage::get_singleton()->get_global_array()[class_idx];
class_idx = get_constant_pos(nc) | (GDScriptFunction::ADDR_TYPE_CONSTANT << GDScriptFunction::ADDR_BITS);
append(class_idx);
} break;
case GDScriptDataType::SCRIPT:
case GDScriptDataType::GDSCRIPT: {
Variant script = function->return_type.script_type;
int script_idx = get_constant_pos(script) | (GDScriptFunction::ADDR_TYPE_CONSTANT << GDScriptFunction::ADDR_BITS);
append_opcode(GDScriptFunction::OPCODE_RETURN_TYPED_SCRIPT);
append(p_return_value);
append(script_idx);
} break;
default: {
ERR_PRINT("Compiler bug: unresolved return.");
// Shouldn't get here, but fail-safe to a regular return;
append_opcode(GDScriptFunction::OPCODE_RETURN);
append(p_return_value);
} break;
}
append_opcode(GDScriptFunction::OPCODE_RETURN_TYPED_SCRIPT);
append(p_return_value);
append(script_idx);
} break;
}
}

View file

@ -100,15 +100,8 @@ class GDScriptByteCodeGenerator : public GDScriptCodeGenerator {
int current_line = 0;
int instr_args_max = 0;
#ifdef DEBUG_ENABLED
List<int> temp_stack;
#endif
HashMap<Variant, int> constant_map;
RBMap<StringName, int> name_map;
#ifdef TOOLS_ENABLED
Vector<StringName> named_globals;
#endif
RBMap<Variant::ValidatedOperatorEvaluator, int> operator_func_map;
RBMap<Variant::ValidatedSetter, int> setters_map;
RBMap<Variant::ValidatedGetter, int> getters_map;
@ -552,7 +545,7 @@ public:
virtual void write_continue() override;
virtual void write_breakpoint() override;
virtual void write_newline(int p_line) override;
virtual void write_return(const Address &p_return_value) override;
virtual void write_return(const Address &p_return_value, bool p_use_conversion) override;
virtual void write_assert(const Address &p_test, const Address &p_message) override;
virtual ~GDScriptByteCodeGenerator();

View file

@ -36,6 +36,8 @@
#include "gdscript_parser.h"
#include "core/io/file_access.h"
#include "core/io/resource_loader.h"
#include "core/templates/rb_set.h"
#include "core/templates/vector.h"
GDScriptParserRef::Status GDScriptParserRef::get_status() const {
@ -68,6 +70,10 @@ Error GDScriptParserRef::raise_status(Status p_new_status) {
ERR_FAIL_COND_V(clearing, ERR_BUG);
ERR_FAIL_COND_V(parser == nullptr && status != EMPTY, ERR_BUG);
if (p_new_status < status) {
return OK;
}
while (result == OK && p_new_status > status) {
switch (status) {
case EMPTY: {
@ -255,7 +261,7 @@ void GDScriptCache::remove_parser(const String &p_path) {
singleton->parser_map.erase(p_path);
// Have to copy while iterating, because parser_inverse_dependencies is modified.
HashSet<String> ideps = singleton->parser_inverse_dependencies[p_path];
HashSet<String> ideps(singleton->parser_inverse_dependencies[p_path]);
singleton->parser_inverse_dependencies.erase(p_path);
for (String idep_path : ideps) {
remove_parser(idep_path);
@ -423,7 +429,7 @@ Error GDScriptCache::finish_compiling(const String &p_owner) {
singleton->full_gdscript_cache[p_owner] = script;
singleton->shallow_gdscript_cache.erase(p_owner);
HashSet<String> depends = singleton->dependencies[p_owner];
HashSet<String> depends(singleton->dependencies[p_owner]);
Error err = OK;
for (const String &E : depends) {
@ -443,7 +449,7 @@ Error GDScriptCache::finish_compiling(const String &p_owner) {
void GDScriptCache::add_static_script(Ref<GDScript> p_script) {
ERR_FAIL_COND_MSG(p_script.is_null(), "Trying to cache empty script as static.");
ERR_FAIL_COND_MSG(!p_script->is_valid(), "Trying to cache non-compiled script as static.");
ERR_FAIL_COND_MSG(!p_script->is_script_valid(), "Trying to cache non-compiled script as static.");
singleton->static_gdscript_cache[p_script->get_fully_qualified_name()] = p_script;
}

View file

@ -78,11 +78,9 @@ public:
~GDScriptParserRef();
};
#ifdef TESTS_ENABLED
namespace GDScriptTests {
class TestGDScriptCacheAccessor;
}
#endif // TESTS_ENABLED
class GDScriptCache {
// String key is full path.
@ -97,9 +95,7 @@ class GDScriptCache {
friend class GDScript;
friend class GDScriptParserRef;
friend class GDScriptInstance;
#ifdef TESTS_ENABLED
friend class GDScriptTests::TestGDScriptCacheAccessor;
#endif // TESTS_ENABLED
static GDScriptCache *singleton;

View file

@ -31,7 +31,6 @@
#pragma once
#include "gdscript_function.h"
#include "gdscript_utility_functions.h"
#include "core/string/string_name.h"
#include "core/variant/variant.h"
@ -160,7 +159,7 @@ public:
virtual void write_continue() = 0;
virtual void write_breakpoint() = 0;
virtual void write_newline(int p_line) = 0;
virtual void write_return(const Address &p_return_value) = 0;
virtual void write_return(const Address &p_return_value, bool p_use_conversion) = 0;
virtual void write_assert(const Address &p_test, const Address &p_message) = 0;
virtual ~GDScriptCodeGenerator() {}

View file

@ -38,8 +38,8 @@
#include "core/config/engine.h"
#include "core/config/project_settings.h"
#include "scene/scene_string_names.h"
#include "core/io/resource_loader.h"
#include "core/object/class_db.h"
bool GDScriptCompiler::_is_class_member_property(CodeGen &codegen, const StringName &p_name) {
if (codegen.function_node && codegen.function_node->is_static) {
@ -252,7 +252,7 @@ static bool _can_use_validate_call(const MethodBind *p_method, const Vector<GDSc
}
GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &codegen, Error &r_error, const GDScriptParser::ExpressionNode *p_expression, bool p_root, bool p_initializer) {
if (p_expression->is_constant && !(p_expression->get_datatype().is_meta_type && p_expression->get_datatype().kind == GDScriptParser::DataType::CLASS)) {
if (p_expression->is_constant && !(p_expression->type_constraint.is_meta_type && p_expression->type_constraint.kind == GDScriptParser::DataType::CLASS)) {
return codegen.add_constant(p_expression->reduced_value);
}
@ -291,7 +291,7 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &code
// Try class members.
if (_is_class_member_property(codegen, identifier)) {
// Get property.
GDScriptCodeGenerator::Address temp = codegen.add_temporary(_gdtype_from_datatype(p_expression->get_datatype(), codegen.script));
GDScriptCodeGenerator::Address temp = codegen.add_temporary(_gdtype_from_datatype(p_expression->type_constraint, codegen.script));
gen->write_get_member(temp, identifier);
return temp;
}
@ -419,9 +419,9 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &code
if (GDScriptLanguage::get_singleton()->get_global_map().has(identifier)) {
// 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.
HashMap<StringName, ProjectSettings::AutoloadInfo> autoloads = ProjectSettings::get_singleton()->get_autoload_list();
HashMap<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(), codegen.script));
GDScriptCodeGenerator::Address global = codegen.add_temporary(_gdtype_from_datatype(in->type_constraint, codegen.script));
int idx = GDScriptLanguage::get_singleton()->get_global_map()[identifier];
gen->write_store_global(global, idx);
return global;
@ -503,7 +503,7 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &code
Vector<GDScriptCodeGenerator::Address> values;
// Create the result temporary first since it's the last to be killed.
GDScriptDataType array_type = _gdtype_from_datatype(an->get_datatype(), codegen.script);
GDScriptDataType array_type = _gdtype_from_datatype(an->type_constraint, codegen.script);
GDScriptCodeGenerator::Address result = codegen.add_temporary(array_type);
for (int i = 0; i < an->elements.size(); i++) {
@ -533,7 +533,7 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &code
Vector<GDScriptCodeGenerator::Address> elements;
// Create the result temporary first since it's the last to be killed.
GDScriptDataType dict_type = _gdtype_from_datatype(dn->get_datatype(), codegen.script);
GDScriptDataType dict_type = _gdtype_from_datatype(dn->type_constraint, codegen.script);
GDScriptCodeGenerator::Address result = codegen.add_temporary(dict_type);
for (int i = 0; i < dn->elements.size(); i++) {
@ -580,7 +580,7 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &code
} break;
case GDScriptParser::Node::CAST: {
const GDScriptParser::CastNode *cn = static_cast<const GDScriptParser::CastNode *>(p_expression);
GDScriptDataType cast_type = _gdtype_from_datatype(cn->get_datatype(), codegen.script, false);
GDScriptDataType cast_type = _gdtype_from_datatype(cn->type_constraint, codegen.script, false);
GDScriptCodeGenerator::Address result;
if (cast_type.has_type()) {
@ -603,12 +603,15 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &code
case GDScriptParser::Node::CALL: {
const GDScriptParser::CallNode *call = static_cast<const GDScriptParser::CallNode *>(p_expression);
bool is_awaited = p_expression == awaited_node;
GDScriptDataType type = _gdtype_from_datatype(call->get_datatype(), codegen.script);
GDScriptCodeGenerator::Address result;
if (p_root) {
result = GDScriptCodeGenerator::Address(GDScriptCodeGenerator::Address::NIL);
} else if (is_awaited) {
// When called with await, we need to use an untyped temporary regardless of whether the base
// class function is a coroutine, because an inherited class could return a function state object.
result = codegen.add_temporary();
} else {
result = codegen.add_temporary(type);
result = codegen.add_temporary(_gdtype_from_datatype(call->type_constraint, codegen.script));
}
Vector<GDScriptCodeGenerator::Address> arguments;
@ -747,7 +750,7 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &code
Vector<GDScriptCodeGenerator::Address> args;
args.push_back(codegen.add_constant(NodePath(get_node->full_path)));
GDScriptCodeGenerator::Address result = codegen.add_temporary(_gdtype_from_datatype(get_node->get_datatype(), codegen.script));
GDScriptCodeGenerator::Address result = codegen.add_temporary(_gdtype_from_datatype(get_node->type_constraint, codegen.script));
MethodBind *get_node_method = ClassDB::get_method("Node", "get_node");
gen->write_call_method_bind_validated(result, GDScriptCodeGenerator::Address(GDScriptCodeGenerator::Address::SELF), get_node_method, args);
@ -763,7 +766,7 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &code
case GDScriptParser::Node::AWAIT: {
const GDScriptParser::AwaitNode *await = static_cast<const GDScriptParser::AwaitNode *>(p_expression);
GDScriptCodeGenerator::Address result = codegen.add_temporary(_gdtype_from_datatype(p_expression->get_datatype(), codegen.script));
GDScriptCodeGenerator::Address result = codegen.add_temporary(_gdtype_from_datatype(p_expression->type_constraint, codegen.script));
GDScriptParser::ExpressionNode *previous_awaited_node = awaited_node;
awaited_node = await->to_await;
GDScriptCodeGenerator::Address argument = _parse_expression(codegen, r_error, await->to_await);
@ -783,7 +786,7 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &code
// Indexing operator.
case GDScriptParser::Node::SUBSCRIPT: {
const GDScriptParser::SubscriptNode *subscript = static_cast<const GDScriptParser::SubscriptNode *>(p_expression);
GDScriptCodeGenerator::Address result = codegen.add_temporary(_gdtype_from_datatype(subscript->get_datatype(), codegen.script));
GDScriptCodeGenerator::Address result = codegen.add_temporary(_gdtype_from_datatype(subscript->type_constraint, codegen.script));
GDScriptCodeGenerator::Address base = _parse_expression(codegen, r_error, subscript->base);
if (r_error) {
@ -811,7 +814,7 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &code
// Remove result temp as we don't need it.
gen->pop_temporary();
// Faster than indexing self (as if no self. had been used).
return GDScriptCodeGenerator::Address(GDScriptCodeGenerator::Address::MEMBER, MI->value.index, _gdtype_from_datatype(subscript->get_datatype(), codegen.script));
return GDScriptCodeGenerator::Address(GDScriptCodeGenerator::Address::MEMBER, MI->value.index, _gdtype_from_datatype(subscript->type_constraint, codegen.script));
}
}
@ -849,7 +852,7 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &code
case GDScriptParser::Node::UNARY_OPERATOR: {
const GDScriptParser::UnaryOpNode *unary = static_cast<const GDScriptParser::UnaryOpNode *>(p_expression);
GDScriptCodeGenerator::Address result = codegen.add_temporary(_gdtype_from_datatype(unary->get_datatype(), codegen.script));
GDScriptCodeGenerator::Address result = codegen.add_temporary(_gdtype_from_datatype(unary->type_constraint, codegen.script));
GDScriptCodeGenerator::Address operand = _parse_expression(codegen, r_error, unary->operand);
if (r_error) {
@ -867,7 +870,7 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &code
case GDScriptParser::Node::BINARY_OPERATOR: {
const GDScriptParser::BinaryOpNode *binary = static_cast<const GDScriptParser::BinaryOpNode *>(p_expression);
GDScriptCodeGenerator::Address result = codegen.add_temporary(_gdtype_from_datatype(binary->get_datatype(), codegen.script));
GDScriptCodeGenerator::Address result = codegen.add_temporary(_gdtype_from_datatype(binary->type_constraint, codegen.script));
switch (binary->operation) {
case GDScriptParser::BinaryOpNode::OP_LOGIC_AND: {
@ -921,7 +924,7 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &code
case GDScriptParser::Node::TERNARY_OPERATOR: {
// x IF a ELSE y operator with early out on failure.
const GDScriptParser::TernaryOpNode *ternary = static_cast<const GDScriptParser::TernaryOpNode *>(p_expression);
GDScriptCodeGenerator::Address result = codegen.add_temporary(_gdtype_from_datatype(ternary->get_datatype(), codegen.script));
GDScriptCodeGenerator::Address result = codegen.add_temporary(_gdtype_from_datatype(ternary->type_constraint, codegen.script));
gen->write_start_ternary(result);
@ -959,7 +962,7 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &code
} break;
case GDScriptParser::Node::TYPE_TEST: {
const GDScriptParser::TypeTestNode *type_test = static_cast<const GDScriptParser::TypeTestNode *>(p_expression);
GDScriptCodeGenerator::Address result = codegen.add_temporary(_gdtype_from_datatype(type_test->get_datatype(), codegen.script));
GDScriptCodeGenerator::Address result = codegen.add_temporary(_gdtype_from_datatype(type_test->type_constraint, codegen.script));
GDScriptCodeGenerator::Address operand = _parse_expression(codegen, r_error, type_test->operand);
GDScriptDataType test_type = _gdtype_from_datatype(type_test->test_datatype, codegen.script, false);
@ -1100,7 +1103,7 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &code
break;
}
const GDScriptParser::SubscriptNode *subscript_elem = E->get();
GDScriptCodeGenerator::Address value = codegen.add_temporary(_gdtype_from_datatype(subscript_elem->get_datatype(), codegen.script));
GDScriptCodeGenerator::Address value = codegen.add_temporary(_gdtype_from_datatype(subscript_elem->type_constraint, codegen.script));
GDScriptCodeGenerator::Address key;
StringName name;
@ -1139,8 +1142,8 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &code
// Perform operator if any.
if (assignment->operation != GDScriptParser::AssignmentNode::OP_NONE) {
GDScriptCodeGenerator::Address op_result = codegen.add_temporary(_gdtype_from_datatype(assignment->get_datatype(), codegen.script));
GDScriptCodeGenerator::Address value = codegen.add_temporary(_gdtype_from_datatype(subscript->get_datatype(), codegen.script));
GDScriptCodeGenerator::Address op_result = codegen.add_temporary(_gdtype_from_datatype(assignment->type_constraint, codegen.script));
GDScriptCodeGenerator::Address value = codegen.add_temporary(_gdtype_from_datatype(subscript->type_constraint, codegen.script));
if (subscript->is_attribute) {
gen->write_get_named(value, name, prev_base);
} else {
@ -1260,8 +1263,8 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &code
StringName name = static_cast<GDScriptParser::IdentifierNode *>(assignment->assignee)->name;
if (has_operation) {
GDScriptCodeGenerator::Address op_result = codegen.add_temporary(_gdtype_from_datatype(assignment->get_datatype(), codegen.script));
GDScriptCodeGenerator::Address member = codegen.add_temporary(_gdtype_from_datatype(assignment->assignee->get_datatype(), codegen.script));
GDScriptCodeGenerator::Address op_result = codegen.add_temporary(_gdtype_from_datatype(assignment->type_constraint, codegen.script));
GDScriptCodeGenerator::Address member = codegen.add_temporary(_gdtype_from_datatype(assignment->assignee->type_constraint, codegen.script));
gen->write_get_member(member, name);
gen->write_binary_operator(op_result, assignment->variant_op, member, assigned_value);
gen->pop_temporary(); // Pop member temp.
@ -1345,7 +1348,7 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &code
bool has_operation = assignment->operation != GDScriptParser::AssignmentNode::OP_NONE;
if (has_operation) {
// Perform operation.
GDScriptCodeGenerator::Address op_result = codegen.add_temporary(_gdtype_from_datatype(assignment->get_datatype(), codegen.script));
GDScriptCodeGenerator::Address op_result = codegen.add_temporary(_gdtype_from_datatype(assignment->type_constraint, codegen.script));
GDScriptCodeGenerator::Address og_value = _parse_expression(codegen, r_error, assignment->assignee);
gen->write_binary_operator(op_result, assignment->variant_op, og_value, assigned_value);
to_assign = op_result;
@ -1395,7 +1398,7 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &code
} break;
case GDScriptParser::Node::LAMBDA: {
const GDScriptParser::LambdaNode *lambda = static_cast<const GDScriptParser::LambdaNode *>(p_expression);
GDScriptCodeGenerator::Address result = codegen.add_temporary(_gdtype_from_datatype(lambda->get_datatype(), codegen.script));
GDScriptCodeGenerator::Address result = codegen.add_temporary(_gdtype_from_datatype(lambda->type_constraint, codegen.script));
Vector<GDScriptCodeGenerator::Address> captures;
captures.resize(lambda->captures.size());
@ -1909,7 +1912,7 @@ Error GDScriptCompiler::_parse_block(CodeGen &codegen, const GDScriptParser::Sui
codegen.start_block(); // Add an extra block, since @special locals belong to the match scope.
// Evaluate the match expression.
GDScriptCodeGenerator::Address value = codegen.add_local("@match_value", _gdtype_from_datatype(match->test->get_datatype(), codegen.script));
GDScriptCodeGenerator::Address value = codegen.add_local("@match_value", _gdtype_from_datatype(match->test->type_constraint, codegen.script));
GDScriptCodeGenerator::Address value_expr = _parse_expression(codegen, err, match->test);
if (err) {
return err;
@ -2039,7 +2042,7 @@ Error GDScriptCompiler::_parse_block(CodeGen &codegen, const GDScriptParser::Sui
// Also we use custom logic to clear block locals.
codegen.start_block();
GDScriptCodeGenerator::Address iterator = codegen.add_local(for_n->variable->name, _gdtype_from_datatype(for_n->variable->get_datatype(), codegen.script));
GDScriptCodeGenerator::Address iterator = codegen.add_local(for_n->variable->name, _gdtype_from_datatype(for_n->variable->type_constraint, codegen.script));
// Optimize `range()` call to not allocate an array.
GDScriptParser::CallNode *range_call = nullptr;
@ -2052,7 +2055,7 @@ Error GDScriptCompiler::_parse_block(CodeGen &codegen, const GDScriptParser::Sui
}
}
gen->start_for(iterator.type, _gdtype_from_datatype(for_n->list->get_datatype(), codegen.script), range_call != nullptr);
gen->start_for(iterator.type, _gdtype_from_datatype(for_n->list->type_constraint, codegen.script), range_call != nullptr);
if (range_call != nullptr) {
Vector<GDScriptCodeGenerator::Address> args;
@ -2169,10 +2172,10 @@ Error GDScriptCompiler::_parse_block(CodeGen &codegen, const GDScriptParser::Sui
}
if (return_n->void_return) {
// Always return "null", even if the expression is a call to a void function.
gen->write_return(codegen.add_constant(Variant()));
// Always return `null`, even if the expression is a call to a `void` function.
gen->write_return(codegen.add_constant(Variant()), false);
} else {
gen->write_return(return_value);
gen->write_return(return_value, return_n->use_conversion);
}
if (return_value.mode == GDScriptCodeGenerator::Address::TEMPORARY) {
codegen.generator->pop_temporary();
@ -2214,7 +2217,7 @@ Error GDScriptCompiler::_parse_block(CodeGen &codegen, const GDScriptParser::Sui
const GDScriptParser::VariableNode *lv = static_cast<const GDScriptParser::VariableNode *>(s);
// Should be already in stack when the block began.
GDScriptCodeGenerator::Address local = codegen.locals[lv->identifier->name];
GDScriptDataType local_type = _gdtype_from_datatype(lv->get_datatype(), codegen.script);
GDScriptDataType local_type = _gdtype_from_datatype(lv->type_constraint, codegen.script);
bool initialized = false;
if (lv->initializer != nullptr) {
@ -2310,7 +2313,7 @@ GDScriptFunction *GDScriptCompiler::_parse_function(Error &r_error, GDScript *p_
is_abstract = p_func->is_abstract;
is_static = p_func->is_static;
rpc_config = p_func->rpc_config;
return_type = _gdtype_from_datatype(p_func->get_datatype(), p_script);
return_type = _gdtype_from_datatype(p_func->return_type_constraint, p_script);
} else {
if (p_for_ready) {
func_name = "@implicit_ready";
@ -2338,11 +2341,11 @@ GDScriptFunction *GDScriptCompiler::_parse_function(Error &r_error, GDScript *p_
if (p_func) {
for (int i = 0; i < p_func->parameters.size(); i++) {
const GDScriptParser::ParameterNode *parameter = p_func->parameters[i];
GDScriptDataType par_type = _gdtype_from_datatype(parameter->get_datatype(), p_script);
GDScriptDataType par_type = _gdtype_from_datatype(parameter->type_constraint, p_script);
uint32_t par_addr = codegen.generator->add_parameter(parameter->identifier->name, parameter->initializer != nullptr, par_type);
codegen.parameters[parameter->identifier->name] = GDScriptCodeGenerator::Address(GDScriptCodeGenerator::Address::FUNCTION_PARAMETER, par_addr, par_type);
method_info.arguments.push_back(parameter->get_datatype().to_property_info(parameter->identifier->name));
method_info.arguments.push_back(parameter->type_constraint.to_property_info(parameter->identifier->name));
if (parameter->initializer != nullptr) {
optional_parameters++;
@ -2350,7 +2353,7 @@ GDScriptFunction *GDScriptCompiler::_parse_function(Error &r_error, GDScript *p_
}
if (p_func->is_vararg()) {
vararg_addr = codegen.add_local(p_func->rest_parameter->identifier->name, _gdtype_from_datatype(p_func->rest_parameter->get_datatype(), codegen.script));
vararg_addr = codegen.add_local(p_func->rest_parameter->identifier->name, _gdtype_from_datatype(p_func->rest_parameter->type_constraint, codegen.script));
method_info.flags |= METHOD_FLAG_VARARG;
}
@ -2376,7 +2379,7 @@ GDScriptFunction *GDScriptCompiler::_parse_function(Error &r_error, GDScript *p_
continue;
}
GDScriptDataType field_type = _gdtype_from_datatype(field->get_datatype(), codegen.script);
GDScriptDataType field_type = _gdtype_from_datatype(field->type_constraint, codegen.script);
if (field_type.has_type()) {
codegen.generator->write_newline(field->start_line);
@ -2420,7 +2423,7 @@ GDScriptFunction *GDScriptCompiler::_parse_function(Error &r_error, GDScript *p_
return nullptr;
}
GDScriptDataType field_type = _gdtype_from_datatype(field->get_datatype(), codegen.script);
GDScriptDataType field_type = _gdtype_from_datatype(field->type_constraint, codegen.script);
GDScriptCodeGenerator::Address dst_address(GDScriptCodeGenerator::Address::MEMBER, codegen.script->member_indices[field->identifier->name].index, field_type);
if (field->use_conversion_assign) {
@ -2510,15 +2513,8 @@ GDScriptFunction *GDScriptCompiler::_parse_function(Error &r_error, GDScript *p_
}
if (p_func) {
// If no `return` statement, then return type is `void`, not `Variant`.
if (p_func->body->has_return) {
gd_function->return_type = _gdtype_from_datatype(p_func->get_datatype(), p_script);
method_info.return_val = p_func->get_datatype().to_property_info(String());
} else {
gd_function->return_type = GDScriptDataType();
gd_function->return_type.kind = GDScriptDataType::BUILTIN;
gd_function->return_type.builtin_type = Variant::NIL;
}
gd_function->return_type = _gdtype_from_datatype(p_func->return_type_constraint, p_script);
method_info.return_val = p_func->return_type_constraint.to_property_info(String());
if (p_func->is_vararg()) {
gd_function->_vararg_index = vararg_addr.address;
@ -2572,7 +2568,7 @@ GDScriptFunction *GDScriptCompiler::_make_static_initializer(Error &r_error, GDS
continue;
}
GDScriptDataType field_type = _gdtype_from_datatype(field->get_datatype(), codegen.script);
GDScriptDataType field_type = _gdtype_from_datatype(field->type_constraint, codegen.script);
if (field_type.has_type()) {
codegen.generator->write_newline(field->start_line);
@ -2616,7 +2612,7 @@ GDScriptFunction *GDScriptCompiler::_make_static_initializer(Error &r_error, GDS
return nullptr;
}
GDScriptDataType field_type = _gdtype_from_datatype(field->get_datatype(), codegen.script);
GDScriptDataType field_type = _gdtype_from_datatype(field->type_constraint, codegen.script);
GDScriptCodeGenerator::Address temp = codegen.add_temporary(field_type);
if (field->use_conversion_assign) {
@ -2795,24 +2791,27 @@ Error GDScriptCompiler::_prepare_compilation(GDScript *p_script, const GDScriptP
if (err) {
return err;
}
} else if (!base->is_valid()) {
} else if (!base->is_script_valid()) {
String base_qualified_name = base->fully_qualified_name;
String base_path = base->path;
Error err = OK;
Ref<GDScript> base_root = GDScriptCache::get_shallow_script(base->path, err, p_script->path);
Ref<GDScript> base_root = GDScriptCache::get_shallow_script(base_path, err, p_script->path);
if (err) {
_set_error(vformat(R"(Could not parse base class "%s" from "%s": %s)", base->fully_qualified_name, base->path, error_names[err]), nullptr);
_set_error(vformat(R"(Could not parse base class "%s" from "%s": %s)", base_qualified_name, base_path, error_names[err]), nullptr);
return err;
}
if (base_root.is_valid()) {
base = Ref<GDScript>(base_root->find_class(base->fully_qualified_name));
base = Ref<GDScript>(base_root->find_class(base_qualified_name));
}
if (base.is_null()) {
_set_error(vformat(R"(Could not find class "%s" in "%s".)", base->fully_qualified_name, base->path), nullptr);
_set_error(vformat(R"(Could not find class "%s" in "%s".)", base_qualified_name, base_path), nullptr);
return ERR_COMPILATION_FAILED;
}
err = _prepare_compilation(base.ptr(), p_class->base_type.class_type, p_keep_state);
if (err) {
_set_error(vformat(R"(Could not populate class members of base class "%s" in "%s".)", base->fully_qualified_name, base->path), nullptr);
_set_error(vformat(R"(Could not populate class members of base class "%s" in "%s".)", base_qualified_name, base_path), nullptr);
return err;
}
}
@ -2860,9 +2859,11 @@ Error GDScriptCompiler::_prepare_compilation(GDScript *p_script, const GDScriptP
}
break;
}
minfo.data_type = _gdtype_from_datatype(variable->get_datatype(), p_script);
PropertyInfo prop_info = variable->get_datatype().to_property_info(name);
const GDScriptParser::DataType variable_type = variable->type_constraint;
minfo.data_type = _gdtype_from_datatype(variable_type, p_script);
PropertyInfo prop_info = variable_type.to_property_info(name);
PropertyInfo export_info = variable->export_info;
if (variable->exported) {
@ -2873,6 +2874,28 @@ Error GDScriptCompiler::_prepare_compilation(GDScript *p_script, const GDScriptP
prop_info.hint = export_info.hint;
prop_info.hint_string = export_info.hint_string;
prop_info.usage = export_info.usage;
} else {
// Enum hint doesn't really belong to the data type information, so we don't want to add it to
// `GDScriptParser::DataType::to_property_info()`. However, we still want to add this metadata
// for unexported properties so they display nicely in the Remote Tree Inspector.
if (variable_type.kind == GDScriptParser::DataType::ENUM && !variable_type.is_meta_type) {
prop_info.hint = PROPERTY_HINT_ENUM;
String enum_hint_string;
bool first = true;
for (const KeyValue<StringName, int64_t> &E : variable_type.enum_values) {
if (first) {
first = false;
} else {
enum_hint_string += ",";
}
enum_hint_string += E.key.string().capitalize().xml_escape();
enum_hint_string += ":";
enum_hint_string += String::num_int64(E.value).xml_escape();
}
prop_info.hint_string = enum_hint_string;
}
}
prop_info.usage |= PROPERTY_USAGE_SCRIPT_VARIABLE;
minfo.property_info = prop_info;
@ -3048,45 +3071,8 @@ Error GDScriptCompiler::_compile_class(GDScript *p_script, const GDScriptParser:
//validate instances if keeping state
if (p_keep_state) {
for (RBSet<Object *>::Element *E = p_script->instances.front(); E;) {
RBSet<Object *>::Element *N = E->next();
ScriptInstance *si = E->get()->get_script_instance();
if (si->is_placeholder()) {
#ifdef TOOLS_ENABLED
PlaceHolderScriptInstance *psi = static_cast<PlaceHolderScriptInstance *>(si);
if (p_script->is_tool()) {
//re-create as an instance
p_script->placeholders.erase(psi); //remove placeholder
GDScriptInstance *instance = memnew(GDScriptInstance);
instance->members.resize(p_script->member_indices.size());
instance->script = Ref<GDScript>(p_script);
instance->owner = E->get();
//needed for hot reloading
for (const KeyValue<StringName, GDScript::MemberInfo> &F : p_script->member_indices) {
instance->member_indices_cache[F.key] = F.value.index;
}
instance->owner->set_script_instance(instance);
/* STEP 2, INITIALIZE AND CONSTRUCT */
Callable::CallError ce;
p_script->initializer->call(instance, nullptr, 0, ce);
if (ce.error != Callable::CallError::CALL_OK) {
//well, tough luck, not gonna do anything here
}
}
#endif // TOOLS_ENABLED
} else {
GDScriptInstance *gi = static_cast<GDScriptInstance *>(si);
gi->reload_members();
}
E = N;
for (SelfList<GDScriptInstance> *E = p_script->instances.first(); E; E = E->next()) {
E->self()->reload_members();
}
}
#endif //DEBUG_ENABLED
@ -3117,8 +3103,8 @@ Error GDScriptCompiler::_compile_class(GDScript *p_script, const GDScriptParser:
void GDScriptCompiler::convert_to_initializer_type(Variant &p_variant, const GDScriptParser::VariableNode *p_node) {
// Set p_variant to the value of p_node's initializer, with the type of p_node's variable.
GDScriptParser::DataType member_t = p_node->datatype;
GDScriptParser::DataType init_t = p_node->initializer->datatype;
GDScriptParser::DataType member_t = p_node->type_constraint;
GDScriptParser::DataType init_t = p_node->initializer->type_constraint;
if (member_t.is_hard_type() && init_t.is_hard_type() &&
member_t.kind == GDScriptParser::DataType::BUILTIN && init_t.kind == GDScriptParser::DataType::BUILTIN) {
if (Variant::can_convert_strict(init_t.builtin_type, member_t.builtin_type)) {

View file

@ -129,7 +129,7 @@ class GDScriptCompiler {
}
void start_block() {
HashMap<StringName, GDScriptCodeGenerator::Address> old_locals = locals;
HashMap<StringName, GDScriptCodeGenerator::Address> old_locals(locals);
locals_stack.push_back(old_locals);
generator->start_block();
}

View file

@ -33,6 +33,7 @@
#include "gdscript.h"
#include "gdscript_function.h"
#include "core/object/method_bind.h"
#include "core/string/string_builder.h"
static String _get_variant_string(const Variant &p_variant) {
@ -162,7 +163,7 @@ void GDScriptFunction::disassemble(const Vector<String> &p_code_lines) const {
Variant::Type builtin_type = (Variant::Type)_code_ptr[ip + 4];
StringName native_type = get_global_name(_code_ptr[ip + 5]);
if (script_type.is_valid() && script_type->is_valid()) {
if (script_type.is_valid() && script_type->is_script_valid()) {
text += "script(";
text += GDScript::debug_get_script_name(script_type);
text += ")";
@ -187,7 +188,7 @@ void GDScriptFunction::disassemble(const Vector<String> &p_code_lines) const {
Variant::Type key_builtin_type = (Variant::Type)_code_ptr[ip + 5];
StringName key_native_type = get_global_name(_code_ptr[ip + 6]);
if (key_script_type.is_valid() && key_script_type->is_valid()) {
if (key_script_type.is_valid() && key_script_type->is_script_valid()) {
text += "script(";
text += GDScript::debug_get_script_name(key_script_type);
text += ")";
@ -203,7 +204,7 @@ void GDScriptFunction::disassemble(const Vector<String> &p_code_lines) const {
Variant::Type value_builtin_type = (Variant::Type)_code_ptr[ip + 7];
StringName value_native_type = get_global_name(_code_ptr[ip + 8]);
if (value_script_type.is_valid() && value_script_type->is_valid()) {
if (value_script_type.is_valid() && value_script_type->is_script_valid()) {
text += "script(";
text += GDScript::debug_get_script_name(value_script_type);
text += ")";
@ -577,7 +578,7 @@ void GDScriptFunction::disassemble(const Vector<String> &p_code_lines) const {
StringName native_type = get_global_name(_code_ptr[ip + argc + 5]);
String type_name;
if (script_type.is_valid() && script_type->is_valid()) {
if (script_type.is_valid() && script_type->is_script_valid()) {
type_name = "script(" + GDScript::debug_get_script_name(script_type) + ")";
} else if (native_type != StringName()) {
type_name = native_type;
@ -632,7 +633,7 @@ void GDScriptFunction::disassemble(const Vector<String> &p_code_lines) const {
StringName key_native_type = get_global_name(_code_ptr[ip + argc * 2 + 6]);
String key_type_name;
if (key_script_type.is_valid() && key_script_type->is_valid()) {
if (key_script_type.is_valid() && key_script_type->is_script_valid()) {
key_type_name = "script(" + GDScript::debug_get_script_name(key_script_type) + ")";
} else if (key_native_type != StringName()) {
key_type_name = key_native_type;
@ -645,7 +646,7 @@ void GDScriptFunction::disassemble(const Vector<String> &p_code_lines) const {
StringName value_native_type = get_global_name(_code_ptr[ip + argc * 2 + 8]);
String value_type_name;
if (value_script_type.is_valid() && value_script_type->is_valid()) {
if (value_script_type.is_valid() && value_script_type->is_script_valid()) {
value_type_name = "script(" + GDScript::debug_get_script_name(value_script_type) + ")";
} else if (value_native_type != StringName()) {
value_type_name = value_native_type;
@ -752,7 +753,7 @@ void GDScriptFunction::disassemble(const Vector<String> &p_code_lines) const {
text += " = ";
text += Variant::get_type_name(type);
text += ".";
text += _global_names_ptr[_code_ptr[ip + 2 + instr_var_args]].operator String();
text += _global_names_ptr[_code_ptr[ip + 2 + instr_var_args]].string();
text += "(";
for (int i = 0; i < argc; i++) {
@ -1003,7 +1004,7 @@ void GDScriptFunction::disassemble(const Vector<String> &p_code_lines) const {
text += DADDR(1 + captures_count);
text += "create lambda from ";
text += lambda->name.operator String();
text += lambda->name.string();
text += "function, captures (";
for (int i = 0; i < captures_count; i++) {
@ -1023,7 +1024,7 @@ void GDScriptFunction::disassemble(const Vector<String> &p_code_lines) const {
text += DADDR(1 + captures_count);
text += "create self lambda from ";
text += lambda->name.operator String();
text += lambda->name.string();
text += "function, captures (";
for (int i = 0; i < captures_count; i++) {
@ -1116,56 +1117,56 @@ void GDScriptFunction::disassemble(const Vector<String> &p_code_lines) const {
incr += 3;
} break;
#define DISASSEMBLE_ITERATE(m_type) \
case OPCODE_ITERATE_##m_type: { \
text += "for-loop (typed "; \
text += #m_type; \
text += ") "; \
text += DADDR(3); \
text += " in "; \
text += DADDR(2); \
text += " counter "; \
text += DADDR(1); \
text += " end "; \
#define DISASSEMBLE_ITERATE(m_type) \
case OPCODE_ITERATE_##m_type: { \
text += "for-loop (typed "; \
text += #m_type; \
text += ") "; \
text += DADDR(3); \
text += " in "; \
text += DADDR(2); \
text += " counter "; \
text += DADDR(1); \
text += " end "; \
text += itos(_code_ptr[ip + 4]); \
incr += 5; \
incr += 5; \
} break
#define DISASSEMBLE_ITERATE_BEGIN(m_type) \
case OPCODE_ITERATE_BEGIN_##m_type: { \
text += "for-init (typed "; \
text += #m_type; \
text += ") "; \
text += DADDR(3); \
text += " in "; \
text += DADDR(2); \
text += " counter "; \
text += DADDR(1); \
text += " end "; \
text += itos(_code_ptr[ip + 4]); \
incr += 5; \
text += "for-init (typed "; \
text += #m_type; \
text += ") "; \
text += DADDR(3); \
text += " in "; \
text += DADDR(2); \
text += " counter "; \
text += DADDR(1); \
text += " end "; \
text += itos(_code_ptr[ip + 4]); \
incr += 5; \
} break
#define DISASSEMBLE_ITERATE_TYPES(m_macro) \
m_macro(INT); \
m_macro(FLOAT); \
m_macro(VECTOR2); \
m_macro(VECTOR2I); \
m_macro(VECTOR3); \
m_macro(VECTOR3I); \
m_macro(STRING); \
m_macro(DICTIONARY); \
m_macro(ARRAY); \
m_macro(PACKED_BYTE_ARRAY); \
m_macro(PACKED_INT32_ARRAY); \
m_macro(PACKED_INT64_ARRAY); \
m_macro(PACKED_FLOAT32_ARRAY); \
m_macro(PACKED_FLOAT64_ARRAY); \
m_macro(PACKED_STRING_ARRAY); \
m_macro(PACKED_VECTOR2_ARRAY); \
m_macro(PACKED_VECTOR3_ARRAY); \
m_macro(PACKED_COLOR_ARRAY); \
m_macro(PACKED_VECTOR4_ARRAY); \
m_macro(INT); \
m_macro(FLOAT); \
m_macro(VECTOR2); \
m_macro(VECTOR2I); \
m_macro(VECTOR3); \
m_macro(VECTOR3I); \
m_macro(STRING); \
m_macro(DICTIONARY); \
m_macro(ARRAY); \
m_macro(PACKED_BYTE_ARRAY); \
m_macro(PACKED_INT32_ARRAY); \
m_macro(PACKED_INT64_ARRAY); \
m_macro(PACKED_FLOAT32_ARRAY); \
m_macro(PACKED_FLOAT64_ARRAY); \
m_macro(PACKED_STRING_ARRAY); \
m_macro(PACKED_VECTOR2_ARRAY); \
m_macro(PACKED_VECTOR3_ARRAY); \
m_macro(PACKED_COLOR_ARRAY); \
m_macro(PACKED_VECTOR4_ARRAY); \
m_macro(OBJECT)
case OPCODE_ITERATE_BEGIN: {
@ -1256,11 +1257,11 @@ void GDScriptFunction::disassemble(const Vector<String> &p_code_lines) const {
#define DISASSEMBLE_TYPE_ADJUST(m_v_type) \
case OPCODE_TYPE_ADJUST_##m_v_type: { \
text += "type adjust ("; \
text += #m_v_type; \
text += ") "; \
text += DADDR(1); \
incr += 2; \
text += "type adjust ("; \
text += #m_v_type; \
text += ") "; \
text += DADDR(1); \
incr += 2; \
} break
DISASSEMBLE_TYPE_ADJUST(BOOL);

File diff suppressed because it is too large Load diff

View file

@ -32,6 +32,125 @@
#include "gdscript.h"
#include "core/object/class_db.h"
bool GDScriptDataType::is_type(const Variant &p_variant, bool p_allow_implicit_conversion) const {
switch (kind) {
case VARIANT: {
return true;
} break;
case BUILTIN: {
Variant::Type var_type = p_variant.get_type();
bool valid = builtin_type == var_type;
if (valid && builtin_type == Variant::ARRAY && has_container_element_type(0)) {
Array array = p_variant;
if (array.is_typed()) {
const GDScriptDataType &elem_type = container_element_types[0];
Variant::Type array_builtin_type = (Variant::Type)array.get_typed_builtin();
StringName array_native_type = array.get_typed_class_name();
Ref<Script> array_script_type_ref = array.get_typed_script();
if (array_script_type_ref.is_valid()) {
valid = (elem_type.kind == SCRIPT || elem_type.kind == GDSCRIPT) && elem_type.script_type == array_script_type_ref.ptr();
} else if (array_native_type != StringName()) {
valid = elem_type.kind == NATIVE && elem_type.native_type == array_native_type;
} else {
valid = elem_type.kind == BUILTIN && elem_type.builtin_type == array_builtin_type;
}
} else {
valid = false;
}
} else if (valid && builtin_type == Variant::DICTIONARY && has_container_element_types()) {
Dictionary dictionary = p_variant;
if (dictionary.is_typed()) {
if (dictionary.is_typed_key()) {
GDScriptDataType key = get_container_element_type_or_variant(0);
Variant::Type key_builtin_type = (Variant::Type)dictionary.get_typed_key_builtin();
StringName key_native_type = dictionary.get_typed_key_class_name();
Ref<Script> key_script_type_ref = dictionary.get_typed_key_script();
if (key_script_type_ref.is_valid()) {
valid = (key.kind == SCRIPT || key.kind == GDSCRIPT) && key.script_type == key_script_type_ref.ptr();
} else if (key_native_type != StringName()) {
valid = key.kind == NATIVE && key.native_type == key_native_type;
} else {
valid = key.kind == BUILTIN && key.builtin_type == key_builtin_type;
}
}
if (valid && dictionary.is_typed_value()) {
GDScriptDataType value = get_container_element_type_or_variant(1);
Variant::Type value_builtin_type = (Variant::Type)dictionary.get_typed_value_builtin();
StringName value_native_type = dictionary.get_typed_value_class_name();
Ref<Script> value_script_type_ref = dictionary.get_typed_value_script();
if (value_script_type_ref.is_valid()) {
valid = (value.kind == SCRIPT || value.kind == GDSCRIPT) && value.script_type == value_script_type_ref.ptr();
} else if (value_native_type != StringName()) {
valid = value.kind == NATIVE && value.native_type == value_native_type;
} else {
valid = value.kind == BUILTIN && value.builtin_type == value_builtin_type;
}
}
} else {
valid = false;
}
} else if (!valid && p_allow_implicit_conversion) {
valid = Variant::can_convert_strict(var_type, builtin_type);
}
return valid;
} break;
case NATIVE: {
if (p_variant.get_type() == Variant::NIL) {
return true;
}
if (p_variant.get_type() != Variant::OBJECT) {
return false;
}
bool was_freed = false;
Object *obj = p_variant.get_validated_object_with_check(was_freed);
if (!obj) {
return !was_freed;
}
if (!obj->is_class(native_type)) {
return false;
}
return true;
} break;
case SCRIPT:
case GDSCRIPT: {
if (p_variant.get_type() == Variant::NIL) {
return true;
}
if (p_variant.get_type() != Variant::OBJECT) {
return false;
}
bool was_freed = false;
Object *obj = p_variant.get_validated_object_with_check(was_freed);
if (!obj) {
return !was_freed;
}
Ref<Script> base = obj && obj->get_script_instance() ? obj->get_script_instance()->get_script() : nullptr;
bool valid = false;
while (base.is_valid()) {
if (base == script_type) {
valid = true;
break;
}
base = base->get_base_script();
}
return valid;
} break;
}
return false;
}
/////////////////////
Variant GDScriptFunction::get_constant(int p_idx) const {
ERR_FAIL_INDEX_V(p_idx, constants.size(), "<errconst>");
return constants[p_idx];
@ -166,27 +285,6 @@ Variant GDScriptFunctionState::_signal_callback(const Variant **p_args, int p_ar
return resume(arg);
}
bool GDScriptFunctionState::is_valid(bool p_extended_check) const {
if (function == nullptr) {
return false;
}
if (p_extended_check) {
MutexLock lock(GDScriptLanguage::get_singleton()->mutex);
// Script gone?
if (!scripts_list.in_list()) {
return false;
}
// Class instance gone? (if not static function)
if (state.instance && !instances_list.in_list()) {
return false;
}
}
return true;
}
Variant GDScriptFunctionState::resume(const Variant &p_arg) {
ERR_FAIL_NULL_V(function, Variant());
{
@ -206,7 +304,7 @@ Variant GDScriptFunctionState::resume(const Variant &p_arg) {
return Variant();
#endif
}
// Do these now to avoid locking again after the call
// Do these now to avoid locking again after the call.
scripts_list.remove_from_list();
instances_list.remove_from_list();
}
@ -215,26 +313,9 @@ Variant GDScriptFunctionState::resume(const Variant &p_arg) {
Callable::CallError err;
Variant ret = function->call(nullptr, nullptr, 0, err, &state);
bool completed = true;
// If the return value is a GDScriptFunctionState reference,
// then the function did await again after resuming.
if (ret.is_ref_counted()) {
GDScriptFunctionState *gdfs = Object::cast_to<GDScriptFunctionState>(ret);
if (gdfs && gdfs->function == function) {
completed = false;
// Keep the first state alive via reference.
gdfs->first_state = first_state.is_valid() ? first_state : Ref<GDScriptFunctionState>(this);
}
}
function = nullptr; //cleaned up;
function = nullptr; // Cleaned up.
state.result = Variant();
if (completed) {
_clear_stack();
}
return ret;
}
@ -260,8 +341,6 @@ void GDScriptFunctionState::_clear_connections() {
}
void GDScriptFunctionState::_bind_methods() {
ClassDB::bind_method(D_METHOD("resume", "arg"), &GDScriptFunctionState::resume, DEFVAL(Variant()));
ClassDB::bind_method(D_METHOD("is_valid", "extended_check"), &GDScriptFunctionState::is_valid, DEFVAL(false));
ClassDB::bind_vararg_method(METHOD_FLAGS_DEFAULT, "_signal_callback", &GDScriptFunctionState::_signal_callback, MethodInfo("_signal_callback"));
ADD_SIGNAL(MethodInfo("completed", PropertyInfo(Variant::NIL, "result", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NIL_IS_VARIANT)));
@ -273,9 +352,8 @@ GDScriptFunctionState::GDScriptFunctionState() :
}
GDScriptFunctionState::~GDScriptFunctionState() {
{
MutexLock lock(GDScriptLanguage::singleton->mutex);
scripts_list.remove_from_list();
instances_list.remove_from_list();
}
MutexLock lock(GDScriptLanguage::singleton->mutex);
scripts_list.remove_from_list();
instances_list.remove_from_list();
_clear_stack();
}

View file

@ -64,120 +64,7 @@ public:
_FORCE_INLINE_ bool has_type() const { return kind != VARIANT; }
bool is_type(const Variant &p_variant, bool p_allow_implicit_conversion = false) const {
switch (kind) {
case VARIANT: {
return true;
} break;
case BUILTIN: {
Variant::Type var_type = p_variant.get_type();
bool valid = builtin_type == var_type;
if (valid && builtin_type == Variant::ARRAY && has_container_element_type(0)) {
Array array = p_variant;
if (array.is_typed()) {
const GDScriptDataType &elem_type = container_element_types[0];
Variant::Type array_builtin_type = (Variant::Type)array.get_typed_builtin();
StringName array_native_type = array.get_typed_class_name();
Ref<Script> array_script_type_ref = array.get_typed_script();
if (array_script_type_ref.is_valid()) {
valid = (elem_type.kind == SCRIPT || elem_type.kind == GDSCRIPT) && elem_type.script_type == array_script_type_ref.ptr();
} else if (array_native_type != StringName()) {
valid = elem_type.kind == NATIVE && elem_type.native_type == array_native_type;
} else {
valid = elem_type.kind == BUILTIN && elem_type.builtin_type == array_builtin_type;
}
} else {
valid = false;
}
} else if (valid && builtin_type == Variant::DICTIONARY && has_container_element_types()) {
Dictionary dictionary = p_variant;
if (dictionary.is_typed()) {
if (dictionary.is_typed_key()) {
GDScriptDataType key = get_container_element_type_or_variant(0);
Variant::Type key_builtin_type = (Variant::Type)dictionary.get_typed_key_builtin();
StringName key_native_type = dictionary.get_typed_key_class_name();
Ref<Script> key_script_type_ref = dictionary.get_typed_key_script();
if (key_script_type_ref.is_valid()) {
valid = (key.kind == SCRIPT || key.kind == GDSCRIPT) && key.script_type == key_script_type_ref.ptr();
} else if (key_native_type != StringName()) {
valid = key.kind == NATIVE && key.native_type == key_native_type;
} else {
valid = key.kind == BUILTIN && key.builtin_type == key_builtin_type;
}
}
if (valid && dictionary.is_typed_value()) {
GDScriptDataType value = get_container_element_type_or_variant(1);
Variant::Type value_builtin_type = (Variant::Type)dictionary.get_typed_value_builtin();
StringName value_native_type = dictionary.get_typed_value_class_name();
Ref<Script> value_script_type_ref = dictionary.get_typed_value_script();
if (value_script_type_ref.is_valid()) {
valid = (value.kind == SCRIPT || value.kind == GDSCRIPT) && value.script_type == value_script_type_ref.ptr();
} else if (value_native_type != StringName()) {
valid = value.kind == NATIVE && value.native_type == value_native_type;
} else {
valid = value.kind == BUILTIN && value.builtin_type == value_builtin_type;
}
}
} else {
valid = false;
}
} else if (!valid && p_allow_implicit_conversion) {
valid = Variant::can_convert_strict(var_type, builtin_type);
}
return valid;
} break;
case NATIVE: {
if (p_variant.get_type() == Variant::NIL) {
return true;
}
if (p_variant.get_type() != Variant::OBJECT) {
return false;
}
bool was_freed = false;
Object *obj = p_variant.get_validated_object_with_check(was_freed);
if (!obj) {
return !was_freed;
}
if (!ClassDB::is_parent_class(obj->get_class_name(), native_type)) {
return false;
}
return true;
} break;
case SCRIPT:
case GDSCRIPT: {
if (p_variant.get_type() == Variant::NIL) {
return true;
}
if (p_variant.get_type() != Variant::OBJECT) {
return false;
}
bool was_freed = false;
Object *obj = p_variant.get_validated_object_with_check(was_freed);
if (!obj) {
return !was_freed;
}
Ref<Script> base = obj && obj->get_script_instance() ? obj->get_script_instance()->get_script() : nullptr;
bool valid = false;
while (base.is_valid()) {
if (base == script_type) {
valid = true;
break;
}
base = base->get_base_script();
}
return valid;
} break;
}
return false;
}
bool is_type(const Variant &p_variant, bool p_allow_implicit_conversion = false) const;
bool can_contain_object() const {
if (kind == BUILTIN) {
@ -473,12 +360,13 @@ private:
SelfList<GDScriptFunction> function_list{ this };
mutable Variant nil;
HashMap<int, Variant::Type> temporary_slots;
TightLocalVector<Pair<int, Variant::Type>> temporary_slots;
List<StackDebug> stack_debug;
Vector<int> code;
Vector<int> default_arguments;
Vector<Variant> constants;
HashMap<StringName, Variant> constant_map;
Vector<StringName> global_names;
Vector<Variant::ValidatedOperatorEvaluator> operator_funcs;
Vector<Variant::ValidatedSetter> setters;
@ -613,22 +501,22 @@ public:
class GDScriptFunctionState : public RefCounted {
GDCLASS(GDScriptFunctionState, RefCounted);
friend class GDScriptFunction;
GDScriptFunction *function = nullptr;
GDScriptFunction::CallState state;
Variant _signal_callback(const Variant **p_args, int p_argcount, Callable::CallError &r_error);
Ref<GDScriptFunctionState> first_state;
SelfList<GDScriptFunctionState> scripts_list;
SelfList<GDScriptFunctionState> instances_list;
Variant _signal_callback(const Variant **p_args, int p_argcount, Callable::CallError &r_error);
Variant resume(const Variant &p_arg);
protected:
static void _bind_methods();
public:
bool is_valid(bool p_extended_check = false) const;
Variant resume(const Variant &p_arg = Variant());
#ifdef DEBUG_ENABLED
// Returns a human-readable representation of the function.
String get_readable_function() {

View file

@ -59,7 +59,7 @@ String GDScriptLambdaCallable::get_as_text() const {
return "<invalid lambda>";
}
if (function->get_name() != StringName()) {
return function->get_name().operator String() + "(lambda)";
return function->get_name().string() + "(lambda)";
}
return "(anonymous lambda)";
}
@ -183,7 +183,7 @@ String GDScriptLambdaSelfCallable::get_as_text() const {
return "<invalid self lambda>";
}
if (function->get_name() != StringName()) {
return function->get_name().operator String() + "(self lambda)";
return function->get_name().string() + "(self lambda)";
}
return "(anonymous self lambda)";
}

View file

@ -36,6 +36,7 @@
#include "core/config/project_settings.h"
#include "core/io/resource_loader.h"
#include "core/math/math_defs.h"
#include "core/object/class_db.h"
#include "scene/main/multiplayer_api.h"
#ifdef DEBUG_ENABLED
@ -43,10 +44,6 @@
#include "servers/text/text_server.h"
#endif
#ifdef TOOLS_ENABLED
#include "editor/settings/editor_settings.h"
#endif
// This function is used to determine that a type is "built-in" as opposed to native
// and custom classes. So `Variant::NIL` and `Variant::OBJECT` are excluded:
// `Variant::NIL` - `null` is literal, not a type.
@ -235,17 +232,48 @@ void GDScriptParser::push_error(const String &p_message, const Node *p_origin) {
// TODO: Improve error reporting by pointing at source code.
// TODO: Errors might point at more than one place at once (e.g. show previous declaration).
panic_mode = true;
// TODO: Improve positional information.
ParserError err;
err.message = p_message;
if (p_origin == nullptr) {
errors.push_back({ p_message, previous.start_line, previous.start_column });
err.start_line = previous.start_line;
err.start_column = previous.start_column;
err.end_line = previous.end_line;
err.end_column = previous.end_column;
} else {
errors.push_back({ p_message, p_origin->start_line, p_origin->start_column });
err.start_line = p_origin->start_line;
err.start_column = p_origin->start_column;
err.end_line = p_origin->end_line;
err.end_column = p_origin->end_column;
}
errors.push_back(err);
}
void GDScriptParser::push_error(const String &p_message, const GDScriptTokenizer::Token &p_origin) {
push_error(p_message, p_origin.start_line, p_origin.start_column, p_origin.end_line, p_origin.end_column);
}
void GDScriptParser::push_error(const String &p_message, int p_start_line, int p_start_column, int p_end_line, int p_end_column) {
panic_mode = true;
ParserError err;
err.message = p_message;
err.start_line = p_start_line;
err.start_column = p_start_column;
err.end_line = p_end_line;
err.end_column = p_end_column;
errors.push_back(err);
}
#ifdef DEBUG_ENABLED
void GDScriptParser::push_warning(const Node *p_source, GDScriptWarning::Code p_code, const Vector<String> &p_symbols) {
ERR_FAIL_NULL(p_source);
push_warning(p_source->start_line, p_source->start_column, p_source->end_line, p_source->end_column, p_code, p_symbols);
}
void GDScriptParser::push_warning(int p_start_line, int p_start_column, int p_end_line, int p_end_column, GDScriptWarning::Code p_code, const Vector<String> &p_symbols) {
ERR_FAIL_INDEX(p_code, GDScriptWarning::WARNING_MAX);
if (is_project_ignoring_warnings || is_script_ignoring_warnings) {
@ -258,7 +286,10 @@ void GDScriptParser::push_warning(const Node *p_source, GDScriptWarning::Code p_
}
PendingWarning pw;
pw.source = p_source;
pw.start_line = p_start_line;
pw.start_column = p_start_column;
pw.end_line = p_end_line;
pw.end_column = p_end_column;
pw.code = p_code;
pw.treated_as_error = warn_level == GDScriptWarning::ERROR;
pw.symbols = p_symbols;
@ -268,21 +299,23 @@ void GDScriptParser::push_warning(const Node *p_source, GDScriptWarning::Code p_
void GDScriptParser::apply_pending_warnings() {
for (const PendingWarning &pw : pending_warnings) {
if (warning_ignored_lines[pw.code].has(pw.source->start_line)) {
if (warning_ignored_lines[pw.code].has(pw.start_line)) {
continue;
}
if (warning_ignore_start_lines[pw.code] <= pw.source->start_line) {
if (warning_ignore_start_lines[pw.code] <= pw.start_line) {
continue;
}
GDScriptWarning warning;
warning.code = pw.code;
warning.symbols = pw.symbols;
warning.start_line = pw.source->start_line;
warning.end_line = pw.source->end_line;
warning.start_line = pw.start_line;
warning.start_column = pw.start_column;
warning.end_line = pw.end_line;
warning.end_column = pw.end_column;
if (pw.treated_as_error) {
push_error(warning.get_message() + String(" (Warning treated as error.)"), pw.source);
push_error(warning.get_message() + String(" (Warning treated as error.)"), pw.start_line, pw.start_column, pw.end_line, pw.end_column);
continue;
}
@ -422,13 +455,6 @@ Error GDScriptParser::parse(const String &p_source_code, const String &p_script_
for_completion = p_for_completion;
parse_body = p_parse_body;
int tab_size = 4;
#ifdef TOOLS_ENABLED
if (EditorSettings::get_singleton()) {
tab_size = EditorSettings::get_singleton()->get_setting("text_editor/behavior/indent/size");
}
#endif // TOOLS_ENABLED
if (p_for_completion) {
// Remove cursor sentinel char.
const Vector<String> lines = p_source_code.split("\n");
@ -441,8 +467,6 @@ Error GDScriptParser::parse(const String &p_source_code, const String &p_script_
if (line[j] == char32_t(0xFFFF)) {
found = true;
break;
} else if (line[j] == '\t') {
cursor_column += tab_size - 1;
}
cursor_column++;
}
@ -473,7 +497,7 @@ Error GDScriptParser::parse(const String &p_source_code, const String &p_script_
// 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);
push_error(current.literal, current);
}
current = tokenizer->scan();
}
@ -484,7 +508,7 @@ Error GDScriptParser::parse(const String &p_source_code, const String &p_script_
// 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->start_column = 1;
nd->end_line = 1;
push_warning(nd, GDScriptWarning::EMPTY_FILE);
}
@ -536,7 +560,7 @@ Error GDScriptParser::parse_binary(const Vector<uint8_t> &p_binary, const String
// 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);
push_error(current.literal, current);
}
current = tokenizer->scan();
}
@ -564,7 +588,7 @@ GDScriptTokenizer::Token GDScriptParser::advance() {
previous = current;
current = tokenizer->scan();
while (current.type == GDScriptTokenizer::Token::ERROR) {
push_error(current.literal);
push_error(current.literal, current);
current = tokenizer->scan();
}
if (previous.type != GDScriptTokenizer::Token::DEDENT) { // `DEDENT` belongs to the next non-empty line.
@ -691,12 +715,12 @@ void GDScriptParser::parse_program() {
current_class = head;
bool can_have_class_or_extends = true;
#define PUSH_PENDING_ANNOTATIONS_TO_HEAD \
if (!annotation_stack.is_empty()) { \
#define PUSH_PENDING_ANNOTATIONS_TO_HEAD \
if (!annotation_stack.is_empty()) { \
for (AnnotationNode *annot : annotation_stack) { \
head->annotations.push_back(annot); \
} \
annotation_stack.clear(); \
head->annotations.push_back(annot); \
} \
annotation_stack.clear(); \
}
while (!check(GDScriptTokenizer::Token::TK_EOF)) {
@ -713,9 +737,8 @@ void GDScriptParser::parse_program() {
// Some annotations need to be resolved and applied in the parser.
// The root class is not in any class, so `head->outer == nullptr`.
annotation->apply(this, head, nullptr);
} else {
head->annotations.push_back(annotation);
}
head->annotations.push_back(annotation);
} else if (annotation->applies_to(AnnotationInfo::STANDALONE)) {
if (previous.type != GDScriptTokenizer::Token::NEWLINE) {
push_error(R"(Expected newline after a standalone annotation.)");
@ -925,6 +948,8 @@ bool GDScriptParser::has_class(const GDScriptParser::ClassNode *p_class) const {
GDScriptParser::ClassNode *GDScriptParser::parse_class(bool p_is_static) {
ClassNode *n_class = alloc_node<ClassNode>();
make_completion_context(COMPLETION_DECLARATION, n_class);
ClassNode *previous_class = current_class;
current_class = n_class;
n_class->outer = previous_class;
@ -981,6 +1006,12 @@ void GDScriptParser::parse_class_name() {
current_class->fqcn = String(current_class->identifier->name);
}
if (script_path.begins_with("res://") && script_path.contains("::")) {
push_error(R"("class_name" isn't allowed in built-in scripts.)");
}
make_completion_context(COMPLETION_DECLARATION, current_class);
if (match(GDScriptTokenizer::Token::EXTENDS)) {
// Allow extends on the same line.
parse_extends();
@ -992,6 +1023,8 @@ void GDScriptParser::parse_class_name() {
void GDScriptParser::parse_extends() {
current_class->extends_used = true;
current_class->extends_start_line = previous.start_line;
current_class->extends_start_column = previous.start_column;
int chain_index = 0;
@ -1002,6 +1035,8 @@ void GDScriptParser::parse_extends() {
current_class->extends_path = previous.literal;
if (!match(GDScriptTokenizer::Token::PERIOD)) {
current_class->extends_end_line = previous.end_line;
current_class->extends_end_column = previous.end_column;
return;
}
}
@ -1009,6 +1044,8 @@ void GDScriptParser::parse_extends() {
make_completion_context(COMPLETION_INHERIT_TYPE, current_class, chain_index++);
if (!consume(GDScriptTokenizer::Token::IDENTIFIER, R"(Expected superclass name after "extends".)")) {
current_class->extends_end_line = previous.end_line;
current_class->extends_end_column = previous.end_column;
return;
}
current_class->extends.push_back(parse_identifier());
@ -1016,10 +1053,15 @@ void GDScriptParser::parse_extends() {
while (match(GDScriptTokenizer::Token::PERIOD)) {
make_completion_context(COMPLETION_INHERIT_TYPE, current_class, chain_index++);
if (!consume(GDScriptTokenizer::Token::IDENTIFIER, R"(Expected superclass name after ".".)")) {
current_class->extends_end_line = previous.end_line;
current_class->extends_end_column = previous.end_column;
return;
}
current_class->extends.push_back(parse_identifier());
}
current_class->extends_end_line = previous.end_line;
current_class->extends_end_column = previous.end_column;
}
template <typename T>
@ -1214,6 +1256,8 @@ GDScriptParser::VariableNode *GDScriptParser::parse_variable(bool p_is_static) {
GDScriptParser::VariableNode *GDScriptParser::parse_variable(bool p_is_static, bool p_allow_property) {
VariableNode *variable = alloc_node<VariableNode>();
make_completion_context(COMPLETION_DECLARATION, variable);
if (!consume(GDScriptTokenizer::Token::IDENTIFIER, R"(Expected variable name after "var".)")) {
complete_extents(variable);
return nullptr;
@ -1382,6 +1426,9 @@ void GDScriptParser::parse_property_setter(VariableNode *p_variable) {
consume(GDScriptTokenizer::Token::PARENTHESIS_CLOSE, R"*(Expected ")" after parameter name.)*");
consume(GDScriptTokenizer::Token::COLON, R"*(Expected ":" after ")".)*");
function->header_end_line = previous.start_line;
function->header_end_column = previous.start_column;
FunctionNode *previous_function = current_function;
current_function = function;
if (p_variable->setter_parameter != nullptr) {
@ -1418,6 +1465,9 @@ void GDScriptParser::parse_property_getter(VariableNode *p_variable) {
consume(GDScriptTokenizer::Token::COLON, R"(Expected ":" or "(" after "get".)");
}
function->header_end_line = previous.start_line;
function->header_end_column = previous.start_column;
IdentifierNode *identifier = alloc_node<IdentifierNode>();
complete_extents(identifier);
identifier->name = "@" + p_variable->identifier->name + "_getter";
@ -1450,6 +1500,8 @@ void GDScriptParser::parse_property_getter(VariableNode *p_variable) {
GDScriptParser::ConstantNode *GDScriptParser::parse_constant(bool p_is_static) {
ConstantNode *constant = alloc_node<ConstantNode>();
make_completion_context(COMPLETION_DECLARATION, constant);
if (!consume(GDScriptTokenizer::Token::IDENTIFIER, R"(Expected constant name after "const".)")) {
complete_extents(constant);
return nullptr;
@ -1518,6 +1570,8 @@ GDScriptParser::ParameterNode *GDScriptParser::parse_parameter() {
GDScriptParser::SignalNode *GDScriptParser::parse_signal(bool p_is_static) {
SignalNode *signal = alloc_node<SignalNode>();
make_completion_context(COMPLETION_DECLARATION, signal);
if (!consume(GDScriptTokenizer::Token::IDENTIFIER, R"(Expected signal name after "signal".)")) {
complete_extents(signal);
return nullptr;
@ -1564,6 +1618,8 @@ GDScriptParser::EnumNode *GDScriptParser::parse_enum(bool p_is_static) {
EnumNode *enum_node = alloc_node<EnumNode>();
bool named = false;
make_completion_context(COMPLETION_DECLARATION, enum_node);
if (match(GDScriptTokenizer::Token::IDENTIFIER)) {
enum_node->identifier = parse_identifier();
named = true;
@ -1737,6 +1793,9 @@ bool GDScriptParser::parse_function_signature(FunctionNode *p_function, SuiteNod
}
#endif // TOOLS_ENABLED
p_function->header_end_line = previous.end_line;
p_function->header_end_column = previous.end_column;
// TODO: Improve token consumption so it synchronizes to a statement boundary. This way we can get into the function body with unrecognized tokens.
if (p_type == "lambda") {
return consume(GDScriptTokenizer::Token::COLON, R"(Expected ":" after lambda declaration.)");
@ -2792,7 +2851,7 @@ GDScriptParser::IdentifierNode *GDScriptParser::parse_identifier() {
#ifdef DEBUG_ENABLED
// Check for spoofing here (if available in TextServer) since this isn't called inside expressions. This is only relevant for declarations.
if (identifier && TS->has_feature(TextServer::FEATURE_UNICODE_SECURITY) && TS->spoof_check(identifier->name)) {
push_warning(identifier, GDScriptWarning::CONFUSABLE_IDENTIFIER, identifier->name.operator String());
push_warning(identifier, GDScriptWarning::CONFUSABLE_IDENTIFIER, identifier->name.string());
}
#endif
return identifier;
@ -2805,7 +2864,7 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_identifier(ExpressionNode
IdentifierNode *identifier = alloc_node<IdentifierNode>();
complete_extents(identifier);
identifier->name = previous.get_identifier();
if (identifier->name.operator String().is_empty()) {
if (identifier->name.string().is_empty()) {
print_line("Empty identifier found.");
}
identifier->suite = current_suite;
@ -3454,8 +3513,9 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_call(ExpressionNode *p_pre
if (!check(GDScriptTokenizer::Token::PERIOD)) {
make_completion_context(COMPLETION_SUPER, call);
}
push_multiline(true);
if (match(GDScriptTokenizer::Token::PARENTHESIS_OPEN)) {
if (check(GDScriptTokenizer::Token::PARENTHESIS_OPEN)) {
push_multiline(true);
advance();
// Implicit call to the parent method of the same name.
if (current_function == nullptr) {
push_error(R"(Cannot use implicit "super" call outside of a function.)");
@ -3472,15 +3532,18 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_call(ExpressionNode *p_pre
consume(GDScriptTokenizer::Token::PERIOD, R"(Expected "." or "(" after "super".)");
make_completion_context(COMPLETION_SUPER_METHOD, call);
if (!consume(GDScriptTokenizer::Token::IDENTIFIER, R"(Expected function name after ".".)")) {
pop_multiline();
complete_extents(call);
return nullptr;
}
IdentifierNode *identifier = parse_identifier();
call->callee = identifier;
call->function_name = identifier->name;
if (!consume(GDScriptTokenizer::Token::PARENTHESIS_OPEN, R"(Expected "(" after function name.)")) {
pop_multiline();
if (check(GDScriptTokenizer::Token::PARENTHESIS_OPEN)) {
push_multiline(true);
advance();
} else {
push_error(R"(Expected "(" after function name.)");
complete_extents(call);
return nullptr;
}
@ -3902,20 +3965,24 @@ enum DocLineState {
DOC_LINE_IN_KBD,
};
static String _process_doc_line(const String &p_line, const String &p_text, const String &p_space_prefix, DocLineState &r_state) {
static void _process_doc_line(const String &p_line, String &r_text, const String &p_space_prefix, DocLineState &r_state) {
String line = p_line;
if (r_state == DOC_LINE_NORMAL) {
line = line.strip_edges(true, false);
line = line.lstrip(" \t");
} else {
line = line.trim_prefix(p_space_prefix);
}
String line_join;
if (!p_text.is_empty()) {
if (!r_text.is_empty()) {
if (r_state == DOC_LINE_NORMAL) {
if (p_text.ends_with("[/codeblock]")) {
if (r_text.ends_with("[/codeblock]")) {
line_join = "\n";
} else if (!p_text.ends_with("[br]")) {
} else if (r_text.ends_with("[br]")) {
// We want to replace `[br][br]` with `\n` (paragraph), so we move the trailing `[br]` here.
r_text = r_text.left(-4); // `-len("[br]")`.
line = "[br]" + line;
} else if (!r_text.ends_with("\n")) {
line_join = " ";
}
} else {
@ -3945,7 +4012,14 @@ static String _process_doc_line(const String &p_line, const String &p_text, cons
from = rb_pos + 1;
String tag = line.substr(lb_pos + 1, rb_pos - lb_pos - 1);
if (tag == "code" || tag.begins_with("code ")) {
if (tag == "br") {
if (line.substr(from, 4) == "[br]") { // `len("[br]")`.
// Replace `[br][br]` with `\n` (paragraph).
result += line.substr(buffer_start, lb_pos - buffer_start) + '\n';
from += 4; // `len("[br]")`.
buffer_start = from;
}
} else if (tag == "code" || tag.begins_with("code ")) {
r_state = DOC_LINE_IN_CODE;
} else if (tag == "codeblock" || tag.begins_with("codeblock ")) {
if (lb_pos == 0) {
@ -4013,10 +4087,10 @@ static String _process_doc_line(const String &p_line, const String &p_text, cons
result += line.substr(buffer_start);
if (r_state == DOC_LINE_NORMAL) {
result = result.strip_edges(false, true);
result = result.rstrip(" \t");
}
return line_join + result;
r_text += line_join + result;
}
bool GDScriptParser::has_comment(int p_line, bool p_must_be_doc) {
@ -4079,7 +4153,7 @@ GDScriptParser::MemberDocData GDScriptParser::parse_doc_comment(int p_line, bool
}
}
result.description += _process_doc_line(doc_line, result.description, space_prefix, state);
_process_doc_line(doc_line, result.description, space_prefix, state);
}
return result;
@ -4189,9 +4263,9 @@ GDScriptParser::ClassDocData GDScriptParser::parse_class_doc_comment(int p_line,
}
if (is_in_brief) {
result.brief += _process_doc_line(doc_line, result.brief, space_prefix, state);
_process_doc_line(doc_line, result.brief, space_prefix, state);
} else {
result.description += _process_doc_line(doc_line, result.description, space_prefix, state);
_process_doc_line(doc_line, result.description, space_prefix, state);
}
}
@ -4483,7 +4557,7 @@ bool GDScriptParser::abstract_annotation(AnnotationNode *p_annotation, Node *p_t
bool GDScriptParser::onready_annotation(AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class) {
ERR_FAIL_COND_V_MSG(p_target->type != Node::VARIABLE, false, R"("@onready" annotation can only be applied to class variables.)");
if (current_class && !ClassDB::is_parent_class(current_class->get_datatype().native_type, SNAME("Node"))) {
if (current_class && !ClassDB::is_parent_class(current_class->self_type.native_type, SNAME("Node"))) {
push_error(R"("@onready" can only be used in classes that inherit "Node".)", p_annotation);
return false;
}
@ -4695,11 +4769,11 @@ bool GDScriptParser::export_annotations(AnnotationNode *p_annotation, Node *p_ta
variable->export_info.hint_string = hint_string;
// This is called after the analyzer is done finding the type, so this should be set here.
DataType export_type = variable->get_datatype();
DataType export_type = variable->type_constraint;
// Use initializer type if specified type is `Variant`.
if (export_type.is_variant() && variable->initializer != nullptr && variable->initializer->datatype.is_set()) {
export_type = variable->initializer->get_datatype();
if (export_type.is_variant() && variable->initializer != nullptr && variable->initializer->type_constraint.is_set()) {
export_type = variable->initializer->type_constraint;
export_type.type_source = DataType::INFERRED;
}
@ -4713,7 +4787,7 @@ bool GDScriptParser::export_annotations(AnnotationNode *p_annotation, Node *p_ta
} else if (export_type.is_typed_container_type()) {
is_array = true;
export_type = export_type.get_typed_container_type();
export_type.type_source = variable->datatype.type_source;
export_type.type_source = variable->type_constraint.type_source;
}
bool is_dict = false;
@ -4735,7 +4809,7 @@ bool GDScriptParser::export_annotations(AnnotationNode *p_annotation, Node *p_ta
if (export_type.builtin_type != Variant::STRING && export_type.builtin_type != Variant::DICTIONARY) {
Vector<Variant::Type> expected_types = { Variant::STRING, Variant::DICTIONARY };
push_error(_get_annotation_error_string(p_annotation->name, expected_types, variable->get_datatype()), p_annotation);
push_error(_get_annotation_error_string(p_annotation->name, expected_types, variable->type_constraint), p_annotation);
return false;
}
@ -4788,12 +4862,12 @@ bool GDScriptParser::export_annotations(AnnotationNode *p_annotation, Node *p_ta
String enum_hint_string;
bool first = true;
for (const KeyValue<StringName, int64_t> &E : export_type.enum_values) {
if (!first) {
enum_hint_string += ",";
} else {
if (first) {
first = false;
} else {
enum_hint_string += ",";
}
enum_hint_string += E.key.operator String().capitalize().xml_escape();
enum_hint_string += E.key.string().capitalize().xml_escape();
enum_hint_string += ":";
enum_hint_string += String::num_int64(E.value).xml_escape();
}
@ -4865,12 +4939,12 @@ bool GDScriptParser::export_annotations(AnnotationNode *p_annotation, Node *p_ta
String enum_hint_string;
bool first = true;
for (const KeyValue<StringName, int64_t> &E : export_type.enum_values) {
if (!first) {
enum_hint_string += ",";
} else {
if (first) {
first = false;
} else {
enum_hint_string += ",";
}
enum_hint_string += E.key.operator String().capitalize().xml_escape();
enum_hint_string += E.key.string().capitalize().xml_escape();
enum_hint_string += ":";
enum_hint_string += String::num_int64(E.value).xml_escape();
}
@ -4915,7 +4989,7 @@ bool GDScriptParser::export_annotations(AnnotationNode *p_annotation, Node *p_ta
if (!export_type.is_variant() && (export_type.kind != DataType::BUILTIN || export_type.builtin_type != enum_type)) {
Vector<Variant::Type> expected_types = { Variant::INT, Variant::STRING };
push_error(_get_annotation_error_string(p_annotation->name, expected_types, variable->get_datatype()), p_annotation);
push_error(_get_annotation_error_string(p_annotation->name, expected_types, variable->type_constraint), p_annotation);
return false;
}
}
@ -4926,7 +5000,7 @@ bool GDScriptParser::export_annotations(AnnotationNode *p_annotation, Node *p_ta
// Allow float/int conversion.
if ((t_type != Variant::FLOAT || export_type.builtin_type != Variant::INT) && (t_type != Variant::INT || export_type.builtin_type != Variant::FLOAT)) {
Vector<Variant::Type> expected_types = { t_type };
push_error(_get_annotation_error_string(p_annotation->name, expected_types, variable->get_datatype()), p_annotation);
push_error(_get_annotation_error_string(p_annotation->name, expected_types, variable->type_constraint), p_annotation);
return false;
}
}
@ -4966,7 +5040,7 @@ bool GDScriptParser::export_storage_annotation(AnnotationNode *p_annotation, Nod
variable->exported = true;
// Save the info because the compiler uses export info for overwriting member info.
variable->export_info = variable->get_datatype().to_property_info(variable->identifier->name);
variable->export_info = variable->type_constraint.to_property_info(variable->identifier->name);
variable->export_info.usage |= PROPERTY_USAGE_STORAGE;
return true;
@ -4988,7 +5062,7 @@ bool GDScriptParser::export_custom_annotation(AnnotationNode *p_annotation, Node
variable->exported = true;
DataType export_type = variable->get_datatype();
DataType export_type = variable->type_constraint;
variable->export_info.type = export_type.builtin_type;
variable->export_info.hint = static_cast<PropertyHint>(p_annotation->resolved_arguments[0].operator int64_t());
@ -5021,7 +5095,7 @@ bool GDScriptParser::export_tool_button_annotation(AnnotationNode *p_annotation,
return false;
}
const DataType variable_type = variable->get_datatype();
const DataType variable_type = variable->type_constraint;
if (!variable_type.is_variant() && variable_type.is_hard_type()) {
if (variable_type.kind != DataType::BUILTIN || variable_type.builtin_type != Variant::CALLABLE) {
push_error(vformat(R"("@export_tool_button" annotation requires a variable of type "Callable", but type "%s" was given instead.)", variable_type.to_string()), p_annotation);
@ -5088,14 +5162,14 @@ bool GDScriptParser::warning_ignore_annotation(AnnotationNode *p_annotation, Nod
int end_line = p_target->end_line;
switch (p_target->type) {
#define SIMPLE_CASE(m_type, m_class, m_property) \
case m_type: { \
#define SIMPLE_CASE(m_type, m_class, m_property) \
case m_type: { \
m_class *node = static_cast<m_class *>(p_target); \
if (node->m_property == nullptr) { \
end_line = node->start_line; \
} else { \
end_line = node->m_property->end_line; \
} \
if (node->m_property == nullptr) { \
end_line = node->start_line; \
} else { \
end_line = node->m_property->end_line; \
} \
} break;
// Can contain properties (set/get).
@ -5256,14 +5330,14 @@ bool GDScriptParser::rpc_annotation(AnnotationNode *p_annotation, Node *p_target
GDScriptParser::DataType GDScriptParser::SuiteNode::Local::get_datatype() const {
switch (type) {
case CONSTANT:
return constant->get_datatype();
return constant->type_constraint;
case VARIABLE:
return variable->get_datatype();
return variable->type_constraint;
case PARAMETER:
return parameter->get_datatype();
return parameter->type_constraint;
case FOR_VARIABLE:
case PATTERN_BIND:
return bind->get_datatype();
return bind->type_constraint;
case UNDEFINED:
return DataType();
}
@ -5308,15 +5382,15 @@ String GDScriptParser::DataType::to_string() const {
if (is_meta_type) {
return GDScriptNativeClass::get_class_static();
}
return native_type.operator String();
return native_type.string();
case CLASS:
if (class_type->identifier != nullptr) {
return class_type->identifier->name.operator String();
return class_type->identifier->name.string();
}
return class_type->fqcn;
case SCRIPT: {
if (is_meta_type) {
return script_type.is_valid() ? script_type->get_class_name().operator String() : "";
return script_type.is_valid() ? script_type->get_class_name().string() : "";
}
String name = script_type.is_valid() ? script_type->get_name() : "";
if (!name.is_empty()) {
@ -5326,7 +5400,7 @@ String GDScriptParser::DataType::to_string() const {
if (!name.is_empty()) {
return name;
}
return native_type.operator String();
return native_type.string();
}
case ENUM: {
// native_type contains either the native class defining the enum
@ -5341,6 +5415,35 @@ String GDScriptParser::DataType::to_string() const {
ERR_FAIL_V_MSG("<unresolved type>", "Kind set outside the enum range.");
}
String GDScriptParser::DataType::to_property_info_hint_string() const {
switch (kind) {
case BUILTIN:
return Variant::get_type_name(builtin_type);
case NATIVE:
return native_type;
case SCRIPT:
if (script_type.is_valid() && script_type->get_global_name() != StringName()) {
return script_type->get_global_name();
} else {
return native_type;
}
case CLASS:
if (class_type != nullptr && class_type->get_global_name() != StringName()) {
return class_type->get_global_name();
} else {
return native_type;
}
case ENUM:
return String(native_type).replace("::", ".");
case VARIANT:
return "Variant";
case RESOLVING:
case UNRESOLVED:
break;
}
ERR_FAIL_V_MSG("Variant", "GDScript bug: Unexpected type kind.");
}
PropertyInfo GDScriptParser::DataType::to_property_info(const String &p_name) const {
PropertyInfo result;
result.name = p_name;
@ -5356,106 +5459,21 @@ PropertyInfo GDScriptParser::DataType::to_property_info(const String &p_name) co
result.type = builtin_type;
if (builtin_type == Variant::ARRAY && has_container_element_type(0)) {
const DataType elem_type = get_container_element_type(0);
switch (elem_type.kind) {
case BUILTIN:
result.hint = PROPERTY_HINT_ARRAY_TYPE;
result.hint_string = Variant::get_type_name(elem_type.builtin_type);
break;
case NATIVE:
result.hint = PROPERTY_HINT_ARRAY_TYPE;
result.hint_string = elem_type.native_type;
break;
case SCRIPT:
result.hint = PROPERTY_HINT_ARRAY_TYPE;
if (elem_type.script_type.is_valid() && elem_type.script_type->get_global_name() != StringName()) {
result.hint_string = elem_type.script_type->get_global_name();
} else {
result.hint_string = elem_type.native_type;
}
break;
case CLASS:
result.hint = PROPERTY_HINT_ARRAY_TYPE;
if (elem_type.class_type != nullptr && elem_type.class_type->get_global_name() != StringName()) {
result.hint_string = elem_type.class_type->get_global_name();
} else {
result.hint_string = elem_type.native_type;
}
break;
case ENUM:
result.hint = PROPERTY_HINT_ARRAY_TYPE;
result.hint_string = String(elem_type.native_type).replace("::", ".");
break;
case VARIANT:
case RESOLVING:
case UNRESOLVED:
break;
if (elem_type.is_variant()) {
break;
}
result.hint = PROPERTY_HINT_ARRAY_TYPE;
result.hint_string = elem_type.to_property_info_hint_string();
} else if (builtin_type == Variant::DICTIONARY && has_container_element_types()) {
const DataType key_type = get_container_element_type_or_variant(0);
const DataType value_type = get_container_element_type_or_variant(1);
if ((key_type.kind == VARIANT && value_type.kind == VARIANT) || key_type.kind == RESOLVING ||
key_type.kind == UNRESOLVED || value_type.kind == RESOLVING || value_type.kind == UNRESOLVED) {
if (key_type.is_variant() && value_type.is_variant()) {
break;
}
String key_hint, value_hint;
switch (key_type.kind) {
case BUILTIN:
key_hint = Variant::get_type_name(key_type.builtin_type);
break;
case NATIVE:
key_hint = key_type.native_type;
break;
case SCRIPT:
if (key_type.script_type.is_valid() && key_type.script_type->get_global_name() != StringName()) {
key_hint = key_type.script_type->get_global_name();
} else {
key_hint = key_type.native_type;
}
break;
case CLASS:
if (key_type.class_type != nullptr && key_type.class_type->get_global_name() != StringName()) {
key_hint = key_type.class_type->get_global_name();
} else {
key_hint = key_type.native_type;
}
break;
case ENUM:
key_hint = String(key_type.native_type).replace("::", ".");
break;
default:
key_hint = "Variant";
break;
}
switch (value_type.kind) {
case BUILTIN:
value_hint = Variant::get_type_name(value_type.builtin_type);
break;
case NATIVE:
value_hint = value_type.native_type;
break;
case SCRIPT:
if (value_type.script_type.is_valid() && value_type.script_type->get_global_name() != StringName()) {
value_hint = value_type.script_type->get_global_name();
} else {
value_hint = value_type.native_type;
}
break;
case CLASS:
if (value_type.class_type != nullptr && value_type.class_type->get_global_name() != StringName()) {
value_hint = value_type.class_type->get_global_name();
} else {
value_hint = value_type.native_type;
}
break;
case ENUM:
value_hint = String(value_type.native_type).replace("::", ".");
break;
default:
value_hint = "Variant";
break;
}
result.hint = PROPERTY_HINT_DICTIONARY_TYPE;
result.hint_string = key_hint + ";" + value_hint;
result.hint_string = key_type.to_property_info_hint_string() + ";" + value_type.to_property_info_hint_string();
}
break;
case NATIVE:
@ -6118,7 +6136,7 @@ void GDScriptParser::TreePrinter::print_lambda(LambdaNode *p_lambda) {
if (i > 0) {
push_text(" , ");
}
push_text(p_lambda->captures[i]->name.operator String());
push_text(p_lambda->captures[i]->name.string());
}
push_line(" ]");
}
@ -6215,9 +6233,12 @@ void GDScriptParser::TreePrinter::print_match_pattern(PatternNode *p_match_patte
if (p_match_pattern->dictionary[i].key != nullptr) {
// Key can be null for rest pattern.
print_expression(p_match_pattern->dictionary[i].key);
push_text(" : ");
if (p_match_pattern->dictionary[i].value_pattern != nullptr) {
// Value can be null when only matching key.
push_text(" : ");
print_match_pattern(p_match_pattern->dictionary[i].value_pattern);
}
}
print_match_pattern(p_match_pattern->dictionary[i].value_pattern);
}
push_text(" }");
break;

View file

@ -146,6 +146,8 @@ public:
String to_string() const;
_FORCE_INLINE_ String to_string_strict() const { return is_hard_type() ? to_string() : "Variant"; }
String to_property_info_hint_string() const;
PropertyInfo to_property_info(const String &p_name) const;
_FORCE_INLINE_ static DataType get_variant_type() { // Default DataType for container elements.
@ -269,7 +271,10 @@ public:
// };
// Type type = NO_ERROR;
String message;
int line = 0, column = 0;
int start_line;
int start_column;
int end_line;
int end_column;
};
#ifdef TOOLS_ENABLED
@ -337,16 +342,14 @@ public:
};
Type type = NONE;
int start_line = 0, end_line = 0;
int start_column = 0, end_column = 0;
// Negative values indicate a node which was used for sentence level recovery.
int start_line = -1;
int start_column = -1;
int end_line = -1;
int end_column = -1;
Node *next = nullptr;
List<AnnotationNode *> annotations;
DataType datatype;
virtual DataType get_datatype() const { return datatype; }
virtual void set_datatype(const DataType &p_datatype) { datatype = p_datatype; }
virtual bool is_expression() const { return false; }
virtual ~Node() {}
@ -358,6 +361,8 @@ public:
bool is_constant = false;
Variant reduced_value;
DataType type_constraint;
virtual bool is_expression() const override { return true; }
virtual ~ExpressionNode() {}
@ -409,6 +414,8 @@ public:
bool use_conversion_assign = false;
int usages = 0;
DataType type_constraint;
virtual ~AssignableNode() {}
protected:
@ -527,6 +534,8 @@ public:
};
struct EnumNode : public Node {
DataType enum_type;
struct Value {
IdentifierNode *identifier = nullptr;
ExpressionNode *custom_value = nullptr;
@ -658,19 +667,19 @@ public:
DataType get_datatype() const {
switch (type) {
case CLASS:
return m_class->get_datatype();
return m_class->self_type;
case CONSTANT:
return constant->get_datatype();
return constant->type_constraint;
case FUNCTION:
return function->get_datatype();
return function->return_type_constraint;
case VARIABLE:
return variable->get_datatype();
return variable->type_constraint;
case ENUM:
return m_enum->get_datatype();
return m_enum->enum_type;
case ENUM_VALUE:
return enum_value.identifier->get_datatype();
return enum_value.identifier->type_constraint;
case SIGNAL:
return signal->get_datatype();
return signal->signal_type;
case GROUP:
return DataType();
case UNDEFINED:
@ -753,7 +762,16 @@ public:
String extends_path;
Vector<IdentifierNode *> extends; // List for indexing: extends A.B.C
DataType base_type;
// Metatype that represents this class.
DataType self_type;
String fqcn; // Fully-qualified class name. Identifies uniquely any class in the project.
// Range for a class's "extends <CLASS_NAME>" line.
// Used as range for some warnings/errors.
int extends_start_line = -1;
int extends_start_column = -1;
int extends_end_line = -1;
int extends_end_column = -1;
#ifdef TOOLS_ENABLED
ClassDocData doc_data;
@ -852,7 +870,10 @@ public:
Vector<ParameterNode *> parameters;
HashMap<StringName, int> parameters_indices;
ParameterNode *rest_parameter = nullptr;
TypeNode *return_type = nullptr;
DataType return_type_constraint;
SuiteNode *body = nullptr;
bool is_abstract = false;
bool is_static = false; // For lambdas it's determined in the analyzer.
@ -861,6 +882,9 @@ public:
MethodInfo info;
LambdaNode *source_lambda = nullptr;
Vector<Variant> default_arg_values;
int header_end_line = 0;
int header_end_column = 0;
#ifdef TOOLS_ENABLED
MemberDocData doc_data;
int min_local_doc_line = 0;
@ -995,6 +1019,8 @@ public:
};
struct PatternNode : public Node {
DataType type_constraint;
enum Type {
PT_LITERAL,
PT_EXPRESSION,
@ -1040,8 +1066,11 @@ public:
};
struct ReturnNode : public Node {
DataType return_type;
ExpressionNode *return_value = nullptr;
bool void_return = false;
bool use_conversion = false;
ReturnNode() {
type = RETURN;
@ -1065,6 +1094,8 @@ public:
MemberDocData doc_data;
#endif // TOOLS_ENABLED
DataType signal_type;
int usages = 0;
SignalNode() {
@ -1087,6 +1118,8 @@ public:
};
struct SuiteNode : public Node {
DataType suite_type;
SuiteNode *parent_block = nullptr;
Vector<Node *> statements;
struct Local {
@ -1108,8 +1141,10 @@ public:
StringName name;
FunctionNode *source_function = nullptr;
int start_line = 0, end_line = 0;
int start_column = 0, end_column = 0;
int start_line = 0;
int start_column = 0;
int end_line = 0;
int end_column = 0;
DataType get_datatype() const;
String get_name() const;
@ -1204,6 +1239,8 @@ public:
Vector<IdentifierNode *> type_chain;
Vector<TypeNode *> container_types;
DataType resolved_type;
TypeNode *get_container_type_or_null(int p_index) const {
return p_index >= 0 && p_index < container_types.size() ? container_types[p_index] : nullptr;
}
@ -1290,7 +1327,7 @@ public:
COMPLETION_ATTRIBUTE_METHOD, // After id.| to look for methods.
COMPLETION_BUILT_IN_TYPE_CONSTANT_OR_STATIC_METHOD, // Constants inside a built-in type (e.g. Color.BLUE) or static methods (e.g. Color.html).
COMPLETION_CALL_ARGUMENTS, // Complete with nodes, input actions, enum values (or usual expressions).
// TODO: COMPLETION_DECLARATION, // Potential declaration (var, const, func).
COMPLETION_DECLARATION, // Potential declaration (var, const, class, etc.).
COMPLETION_GET_NODE, // Get node with $ notation.
COMPLETION_IDENTIFIER, // List available identifiers in scope.
COMPLETION_INHERIT_TYPE, // Type after extends. Exclude non-viable types (built-ins, enums, void). Includes subtypes using the argument index.
@ -1363,7 +1400,10 @@ public:
private:
struct PendingWarning {
const Node *source = nullptr;
int start_line = 0;
int start_column = 0;
int end_line = 0;
int end_column = 0;
GDScriptWarning::Code code = GDScriptWarning::WARNING_MAX;
bool treated_as_error = false;
Vector<String> symbols;
@ -1492,12 +1532,20 @@ private:
void clear();
void push_error(const String &p_message, const Node *p_origin = nullptr);
void push_error(const String &p_message, int p_start_line, int p_start_column, int p_end_line, int p_end_column);
void push_error(const String &p_message, const GDScriptTokenizer::Token &p_origin);
#ifdef DEBUG_ENABLED
void push_warning(const Node *p_source, GDScriptWarning::Code p_code, const Vector<String> &p_symbols);
template <typename... Symbols>
void push_warning(const Node *p_source, GDScriptWarning::Code p_code, const Symbols &...p_symbols) {
push_warning(p_source, p_code, Vector<String>{ p_symbols... });
}
void push_warning(int p_start_line, int p_start_column, int p_end_line, int p_end_column, GDScriptWarning::Code p_code, const Vector<String> &p_symbols);
template <typename... Symbols>
void push_warning(int p_start_line, int p_start_column, int p_end_line, int p_end_column, GDScriptWarning::Code p_code, const Symbols &...p_symbols) {
push_warning(p_start_line, p_start_column, p_end_line, p_end_column, p_code, Vector<String>{ p_symbols... });
}
void apply_pending_warnings();
void evaluate_warning_directory_rules_for_script_path();
#endif // DEBUG_ENABLED

View file

@ -0,0 +1,188 @@
/**************************************************************************/
/* gdscript_resource_format.cpp */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#include "gdscript_resource_format.h"
#include "gdscript_cache.h"
#include "gdscript_parser.h"
#include "core/io/file_access.h"
#include "core/object/class_db.h"
#ifdef TOOLS_ENABLED
#include "modules/gdscript/editor/gdscript_editor_language.h"
#endif // TOOLS_ENABLED
Ref<Resource> ResourceFormatLoaderGDScript::load(const String &p_path, const String &p_original_path, Error *r_error, bool p_use_sub_threads, float *r_progress, CacheMode p_cache_mode) {
Error err;
bool ignoring = p_cache_mode == CACHE_MODE_IGNORE || p_cache_mode == CACHE_MODE_IGNORE_DEEP;
Ref<GDScript> scr = GDScriptCache::get_full_script(p_original_path, err, "", ignoring);
if (err && scr.is_valid()) {
// If !scr.is_valid(), the error was likely from scr->load_source_code(), which already generates an error.
ERR_PRINT_ED(vformat(R"(Failed to load script "%s" with error "%s".)", p_original_path, error_names[err]));
}
if (r_error) {
// Don't fail loading because of parsing error.
*r_error = scr.is_valid() ? OK : err;
}
return scr;
}
void ResourceFormatLoaderGDScript::get_recognized_extensions(List<String> *p_extensions) const {
p_extensions->push_back("gd");
p_extensions->push_back("gdc");
}
bool ResourceFormatLoaderGDScript::handles_type(const String &p_type) const {
return (p_type == "Script" || p_type == "GDScript");
}
String ResourceFormatLoaderGDScript::get_resource_type(const String &p_path) const {
String el = p_path.get_extension().to_lower();
if (el == "gd" || el == "gdc") {
return "GDScript";
}
return "";
}
void ResourceFormatLoaderGDScript::get_dependencies(const String &p_path, List<String> *p_dependencies, bool p_add_types) {
Ref<FileAccess> file = FileAccess::open(p_path, FileAccess::READ);
ERR_FAIL_COND_MSG(file.is_null(), "Cannot open file '" + p_path + "'.");
String source = file->get_as_utf8_string();
if (source.is_empty()) {
return;
}
GDScriptParser parser;
if (OK != parser.parse(source, p_path, false)) {
return;
}
for (const String &E : parser.get_dependencies()) {
p_dependencies->push_back(E);
}
}
void ResourceFormatLoaderGDScript::get_classes_used(const String &p_path, HashSet<StringName> *r_classes) {
#ifdef TOOLS_ENABLED
Ref<GDScript> scr = ResourceLoader::load(p_path);
if (scr.is_null()) {
return;
}
const String source = scr->get_source_code();
GDScriptTokenizerText tokenizer;
tokenizer.set_source_code(source);
GDScriptTokenizer::Token current = tokenizer.scan();
while (current.type != GDScriptTokenizer::Token::TK_EOF) {
if (!current.is_identifier()) {
current = tokenizer.scan();
continue;
}
int insert_idx = 0;
for (int i = 0; i < current.start_line - 1; i++) {
insert_idx = source.find("\n", insert_idx) + 1;
}
// Insert the "cursor" character, needed for the lookup to work.
const String source_with_cursor = source.insert(insert_idx + current.start_column, String::chr(0xFFFF));
EditorLanguage::LookupResult result;
if (GDScriptEditorLanguage::get_singleton()->lookup_code(source_with_cursor, current.get_identifier(), p_path, nullptr, result) == OK) {
if (!result.class_name.is_empty() && ClassDB::class_exists(result.class_name)) {
r_classes->insert(result.class_name);
}
if (result.type == EditorLanguage::LookupResult::Type::CLASS_PROPERTY) {
PropertyInfo prop;
if (ClassDB::get_property_info(result.class_name, result.class_member, &prop)) {
if (!prop.class_name.is_empty() && ClassDB::class_exists(prop.class_name)) {
r_classes->insert(prop.class_name);
}
if (!prop.hint_string.is_empty() && ClassDB::class_exists(prop.hint_string)) {
r_classes->insert(prop.hint_string);
}
}
} else if (result.type == EditorLanguage::LookupResult::Type::CLASS_METHOD) {
MethodInfo met;
if (ClassDB::get_method_info(result.class_name, result.class_member, &met)) {
if (!met.return_val.class_name.is_empty() && ClassDB::class_exists(met.return_val.class_name)) {
r_classes->insert(met.return_val.class_name);
}
if (!met.return_val.hint_string.is_empty() && ClassDB::class_exists(met.return_val.hint_string)) {
r_classes->insert(met.return_val.hint_string);
}
}
}
}
current = tokenizer.scan();
}
#endif // TOOLS_ENABLED
}
Error ResourceFormatSaverGDScript::save(const Ref<Resource> &p_resource, const String &p_path, uint32_t p_flags) {
Ref<GDScript> sqscr = p_resource;
ERR_FAIL_COND_V(sqscr.is_null(), ERR_INVALID_PARAMETER);
String source = sqscr->get_source_code();
{
Error err;
Ref<FileAccess> file = FileAccess::open(p_path, FileAccess::WRITE, &err);
ERR_FAIL_COND_V_MSG(err, err, "Cannot save GDScript file '" + p_path + "'.");
file->store_string(source);
if (file->get_error() != OK && file->get_error() != ERR_FILE_EOF) {
return ERR_CANT_CREATE;
}
}
if (ScriptServer::is_reload_scripts_on_save_enabled()) {
GDScriptLanguage::get_singleton()->reload_tool_script(p_resource, true);
}
return OK;
}
void ResourceFormatSaverGDScript::get_recognized_extensions(const Ref<Resource> &p_resource, List<String> *p_extensions) const {
if (Object::cast_to<GDScript>(*p_resource)) {
p_extensions->push_back("gd");
}
}
bool ResourceFormatSaverGDScript::recognize(const Ref<Resource> &p_resource) const {
return Object::cast_to<GDScript>(*p_resource) != nullptr;
}

View file

@ -0,0 +1,55 @@
/**************************************************************************/
/* gdscript_resource_format.h */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#pragma once
#include "core/io/resource_loader.h"
#include "core/io/resource_saver.h"
class ResourceFormatLoaderGDScript : public ResourceFormatLoader {
GDSOFTCLASS(ResourceFormatLoaderGDScript, ResourceFormatLoader);
public:
virtual Ref<Resource> load(const String &p_path, const String &p_original_path = "", Error *r_error = nullptr, bool p_use_sub_threads = false, float *r_progress = nullptr, CacheMode p_cache_mode = CACHE_MODE_REUSE) override;
virtual void get_recognized_extensions(List<String> *p_extensions) const override;
virtual bool handles_type(const String &p_type) const override;
virtual String get_resource_type(const String &p_path) const override;
virtual void get_dependencies(const String &p_path, List<String> *p_dependencies, bool p_add_types = false) override;
virtual void get_classes_used(const String &p_path, HashSet<StringName> *r_classes) override;
};
class ResourceFormatSaverGDScript : public ResourceFormatSaver {
GDSOFTCLASS(ResourceFormatSaverGDScript, ResourceFormatSaver);
public:
virtual Error save(const Ref<Resource> &p_resource, const String &p_path, uint32_t p_flags = 0) override;
virtual void get_recognized_extensions(const Ref<Resource> &p_resource, List<String> *p_extensions) const override;
virtual bool recognize(const Ref<Resource> &p_resource) const override;
};

View file

@ -372,7 +372,6 @@ GDScriptTokenizer::Token GDScriptTokenizerText::make_token(Token::Type p_type) {
if (start_line == line) {
// Single line token.
if (cursor_line == start_line && cursor_column >= start_column && cursor_column <= last_column) {
token.cursor_position = cursor_column - start_column;
if (cursor_column == start_column) {
token.cursor_place = CURSOR_BEGINNING;
} else if (cursor_column < column) {
@ -385,7 +384,6 @@ GDScriptTokenizer::Token GDScriptTokenizerText::make_token(Token::Type p_type) {
// Multi line token.
if (cursor_line == start_line && cursor_column >= start_column) {
// Is in first line.
token.cursor_position = cursor_column - start_column;
if (cursor_column == start_column) {
token.cursor_place = CURSOR_BEGINNING;
} else {
@ -393,7 +391,6 @@ GDScriptTokenizer::Token GDScriptTokenizerText::make_token(Token::Type p_type) {
}
} else if (cursor_line == line && cursor_column <= last_column) {
// Is in last line.
token.cursor_position = cursor_column - start_column;
if (cursor_column < column) {
token.cursor_place = CURSOR_MIDDLE;
} else {
@ -401,7 +398,7 @@ GDScriptTokenizer::Token GDScriptTokenizerText::make_token(Token::Type p_type) {
}
} else if (cursor_line > start_line && cursor_line < line) {
// Is in middle line.
token.cursor_position = CURSOR_MIDDLE;
token.cursor_place = CURSOR_MIDDLE;
}
}
}
@ -486,66 +483,66 @@ GDScriptTokenizer::Token GDScriptTokenizerText::annotation() {
return annotation;
}
#define KEYWORDS(KEYWORD_GROUP, KEYWORD) \
KEYWORD_GROUP('a') \
KEYWORD("as", Token::AS) \
KEYWORD("and", Token::AND) \
KEYWORD("assert", Token::ASSERT) \
KEYWORD("await", Token::AWAIT) \
KEYWORD_GROUP('b') \
KEYWORD("break", Token::BREAK) \
#define KEYWORDS(KEYWORD_GROUP, KEYWORD) \
KEYWORD_GROUP('a') \
KEYWORD("as", Token::AS) \
KEYWORD("and", Token::AND) \
KEYWORD("assert", Token::ASSERT) \
KEYWORD("await", Token::AWAIT) \
KEYWORD_GROUP('b') \
KEYWORD("break", Token::BREAK) \
KEYWORD("breakpoint", Token::BREAKPOINT) \
KEYWORD_GROUP('c') \
KEYWORD("class", Token::CLASS) \
KEYWORD_GROUP('c') \
KEYWORD("class", Token::CLASS) \
KEYWORD("class_name", Token::CLASS_NAME) \
KEYWORD("const", Token::TK_CONST) \
KEYWORD("continue", Token::CONTINUE) \
KEYWORD_GROUP('e') \
KEYWORD("elif", Token::ELIF) \
KEYWORD("else", Token::ELSE) \
KEYWORD("enum", Token::ENUM) \
KEYWORD("extends", Token::EXTENDS) \
KEYWORD_GROUP('f') \
KEYWORD("for", Token::FOR) \
KEYWORD("func", Token::FUNC) \
KEYWORD_GROUP('i') \
KEYWORD("if", Token::IF) \
KEYWORD("in", Token::TK_IN) \
KEYWORD("is", Token::IS) \
KEYWORD_GROUP('m') \
KEYWORD("match", Token::MATCH) \
KEYWORD_GROUP('n') \
KEYWORD("namespace", Token::NAMESPACE) \
KEYWORD("not", Token::NOT) \
KEYWORD_GROUP('o') \
KEYWORD("or", Token::OR) \
KEYWORD_GROUP('p') \
KEYWORD("pass", Token::PASS) \
KEYWORD("preload", Token::PRELOAD) \
KEYWORD_GROUP('r') \
KEYWORD("return", Token::RETURN) \
KEYWORD_GROUP('s') \
KEYWORD("self", Token::SELF) \
KEYWORD("signal", Token::SIGNAL) \
KEYWORD("static", Token::STATIC) \
KEYWORD("super", Token::SUPER) \
KEYWORD_GROUP('t') \
KEYWORD("trait", Token::TRAIT) \
KEYWORD_GROUP('v') \
KEYWORD("var", Token::VAR) \
KEYWORD("void", Token::TK_VOID) \
KEYWORD_GROUP('w') \
KEYWORD("while", Token::WHILE) \
KEYWORD("when", Token::WHEN) \
KEYWORD_GROUP('y') \
KEYWORD("yield", Token::YIELD) \
KEYWORD_GROUP('I') \
KEYWORD("INF", Token::CONST_INF) \
KEYWORD_GROUP('N') \
KEYWORD("NAN", Token::CONST_NAN) \
KEYWORD_GROUP('P') \
KEYWORD("PI", Token::CONST_PI) \
KEYWORD_GROUP('T') \
KEYWORD("const", Token::TK_CONST) \
KEYWORD("continue", Token::CONTINUE) \
KEYWORD_GROUP('e') \
KEYWORD("elif", Token::ELIF) \
KEYWORD("else", Token::ELSE) \
KEYWORD("enum", Token::ENUM) \
KEYWORD("extends", Token::EXTENDS) \
KEYWORD_GROUP('f') \
KEYWORD("for", Token::FOR) \
KEYWORD("func", Token::FUNC) \
KEYWORD_GROUP('i') \
KEYWORD("if", Token::IF) \
KEYWORD("in", Token::TK_IN) \
KEYWORD("is", Token::IS) \
KEYWORD_GROUP('m') \
KEYWORD("match", Token::MATCH) \
KEYWORD_GROUP('n') \
KEYWORD("namespace", Token::NAMESPACE) \
KEYWORD("not", Token::NOT) \
KEYWORD_GROUP('o') \
KEYWORD("or", Token::OR) \
KEYWORD_GROUP('p') \
KEYWORD("pass", Token::PASS) \
KEYWORD("preload", Token::PRELOAD) \
KEYWORD_GROUP('r') \
KEYWORD("return", Token::RETURN) \
KEYWORD_GROUP('s') \
KEYWORD("self", Token::SELF) \
KEYWORD("signal", Token::SIGNAL) \
KEYWORD("static", Token::STATIC) \
KEYWORD("super", Token::SUPER) \
KEYWORD_GROUP('t') \
KEYWORD("trait", Token::TRAIT) \
KEYWORD_GROUP('v') \
KEYWORD("var", Token::VAR) \
KEYWORD("void", Token::TK_VOID) \
KEYWORD_GROUP('w') \
KEYWORD("while", Token::WHILE) \
KEYWORD("when", Token::WHEN) \
KEYWORD_GROUP('y') \
KEYWORD("yield", Token::YIELD) \
KEYWORD_GROUP('I') \
KEYWORD("INF", Token::CONST_INF) \
KEYWORD_GROUP('N') \
KEYWORD("NAN", Token::CONST_NAN) \
KEYWORD_GROUP('P') \
KEYWORD("PI", Token::CONST_PI) \
KEYWORD_GROUP('T') \
KEYWORD("TAU", Token::CONST_TAU)
#define MIN_KEYWORD_LENGTH 2
@ -607,18 +604,18 @@ GDScriptTokenizer::Token GDScriptTokenizerText::potential_identifier() {
// Define some helper macros for the switch case.
#define KEYWORD_GROUP_CASE(char) \
break; \
break; \
case char:
#define KEYWORD(keyword, token_type) \
{ \
const int keyword_length = sizeof(keyword) - 1; \
static_assert(keyword_length <= MAX_KEYWORD_LENGTH, "There's a keyword longer than the defined maximum length"); \
#define KEYWORD(keyword, token_type) \
{ \
const int keyword_length = sizeof(keyword) - 1; \
static_assert(keyword_length <= MAX_KEYWORD_LENGTH, "There's a keyword longer than the defined maximum length"); \
static_assert(keyword_length >= MIN_KEYWORD_LENGTH, "There's a keyword shorter than the defined minimum length"); \
if (keyword_length == len && name == keyword) { \
Token kw = make_token(token_type); \
kw.literal = name; \
return kw; \
} \
if (keyword_length == len && name == keyword) { \
Token kw = make_token(token_type); \
kw.literal = name; \
return kw; \
} \
}
// Find if it's a keyword.
@ -1167,8 +1164,8 @@ void GDScriptTokenizerText::check_indent() {
while (!_is_at_end()) {
char32_t space = _peek();
if (space == '\t') {
// Consider individual tab columns.
column += tab_size - 1;
// Counting tab indent with the configured tab size, allows error tolerant parsing in the editor,
// so that autocompletion still is functional when a file contains mixed indentation.
indent_count += tab_size;
} else if (space == ' ') {
indent_count += 1;
@ -1310,12 +1307,8 @@ void GDScriptTokenizerText::_skip_whitespace() {
char32_t c = _peek();
switch (c) {
case ' ':
_advance();
break;
case '\t':
_advance();
// Consider individual tab columns.
column += tab_size - 1;
break;
case '\r':
_advance(); // Consume either way.

View file

@ -165,8 +165,13 @@ public:
Type type = EMPTY;
Variant literal;
int start_line = 0, end_line = 0, start_column = 0, end_column = 0;
int cursor_position = -1;
// The parser positions errors based on the previous token. This needs to
// be default initialized so that errors on the first token don't access
// uninitialized memory.
int start_line = 1;
int start_column = 1;
int end_line = 1;
int end_column = 1;
CursorPlace cursor_place = CURSOR_NONE;
String source;
@ -225,13 +230,16 @@ class GDScriptTokenizerText : public GDScriptTokenizer {
String source;
const char32_t *_source = nullptr;
const char32_t *_current = nullptr;
int line = -1, column = -1;
int cursor_line = -1, cursor_column = -1;
int line = 1;
int column = 1;
int cursor_line = -1;
int cursor_column = -1;
int tab_size = 4;
// Keep track of multichar tokens.
const char32_t *_start = nullptr;
int start_line = 0, start_column = 0;
int start_line = 1;
int start_column = 1;
// Info cache.
bool line_continuation = false; // Whether this line is a continuation of the previous, like when using '\'.

View file

@ -300,7 +300,7 @@ Vector<uint8_t> GDScriptTokenizerBuffer::parse_code_string(const String &p_code,
// Save identifiers.
for (const StringName &id : rev_identifier_map) {
String s = id.operator String();
String s = id.string();
int len = s.length();
contents.resize(buf_pos + (len + 1) * 4);
@ -447,6 +447,8 @@ GDScriptTokenizer::Token GDScriptTokenizerBuffer::scan() {
}
Token eof;
eof.type = Token::TK_EOF;
eof.start_line = current_line;
eof.end_line = current_line;
return eof;
};

View file

@ -41,36 +41,36 @@
#ifdef DEBUG_ENABLED
#define DEBUG_VALIDATE_ARG_COUNT(m_min_count, m_max_count) \
if (unlikely(p_arg_count < m_min_count)) { \
*r_ret = Variant(); \
r_error.error = Callable::CallError::CALL_ERROR_TOO_FEW_ARGUMENTS; \
r_error.expected = m_min_count; \
return; \
} \
if (unlikely(p_arg_count > m_max_count)) { \
*r_ret = Variant(); \
#define DEBUG_VALIDATE_ARG_COUNT(m_min_count, m_max_count) \
if (unlikely(p_arg_count < m_min_count)) { \
*r_ret = Variant(); \
r_error.error = Callable::CallError::CALL_ERROR_TOO_FEW_ARGUMENTS; \
r_error.expected = m_min_count; \
return; \
} \
if (unlikely(p_arg_count > m_max_count)) { \
*r_ret = Variant(); \
r_error.error = Callable::CallError::CALL_ERROR_TOO_MANY_ARGUMENTS; \
r_error.expected = m_max_count; \
return; \
r_error.expected = m_max_count; \
return; \
}
#define DEBUG_VALIDATE_ARG_TYPE(m_arg, m_type) \
#define DEBUG_VALIDATE_ARG_TYPE(m_arg, m_type) \
if (unlikely(!Variant::can_convert_strict(p_args[m_arg]->get_type(), m_type))) { \
*r_ret = Variant(); \
r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT; \
r_error.argument = m_arg; \
r_error.expected = m_type; \
return; \
*r_ret = Variant(); \
r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT; \
r_error.argument = m_arg; \
r_error.expected = m_type; \
return; \
}
#define DEBUG_VALIDATE_ARG_CUSTOM(m_arg, m_type, m_cond, m_msg) \
if (unlikely(m_cond)) { \
*r_ret = m_msg; \
#define DEBUG_VALIDATE_ARG_CUSTOM(m_arg, m_type, m_cond, m_msg) \
if (unlikely(m_cond)) { \
*r_ret = m_msg; \
r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT; \
r_error.argument = m_arg; \
r_error.expected = m_type; \
return; \
r_error.argument = m_arg; \
r_error.expected = m_type; \
return; \
}
#else // !DEBUG_ENABLED
@ -81,20 +81,20 @@
#endif // DEBUG_ENABLED
#define VALIDATE_ARG_CUSTOM(m_arg, m_type, m_cond, m_msg) \
if (unlikely(m_cond)) { \
*r_ret = m_msg; \
#define VALIDATE_ARG_CUSTOM(m_arg, m_type, m_cond, m_msg) \
if (unlikely(m_cond)) { \
*r_ret = m_msg; \
r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT; \
r_error.argument = m_arg; \
r_error.expected = m_type; \
return; \
r_error.argument = m_arg; \
r_error.expected = m_type; \
return; \
}
#define GDFUNC_FAIL_COND_MSG(m_cond, m_msg) \
if (unlikely(m_cond)) { \
*r_ret = m_msg; \
#define GDFUNC_FAIL_COND_MSG(m_cond, m_msg) \
if (unlikely(m_cond)) { \
*r_ret = m_msg; \
r_error.error = Callable::CallError::CALL_ERROR_INVALID_METHOD; \
return; \
return; \
}
struct GDScriptUtilityFunctionsDefinitions {
@ -109,19 +109,20 @@ struct GDScriptUtilityFunctionsDefinitions {
Variant::construct(Variant::Type(type), *r_ret, p_args, 1, r_error);
}
#endif // DISABLE_DEPRECATED
static inline void type_exists(Variant *r_ret, const Variant **p_args, int p_arg_count, Callable::CallError &r_error) {
DEBUG_VALIDATE_ARG_COUNT(1, 1);
DEBUG_VALIDATE_ARG_TYPE(0, Variant::STRING_NAME);
*r_ret = ClassDB::class_exists(*p_args[0]);
}
#endif // DISABLE_DEPRECATED
static inline void _char(Variant *r_ret, const Variant **p_args, int p_arg_count, Callable::CallError &r_error) {
DEBUG_VALIDATE_ARG_COUNT(1, 1);
DEBUG_VALIDATE_ARG_TYPE(0, Variant::INT);
const int64_t code = *p_args[0];
VALIDATE_ARG_CUSTOM(0, Variant::INT, code < 0 || code > UINT32_MAX, RTR("Expected an integer between 0 and 2^32 - 1."));
VALIDATE_ARG_CUSTOM(0, Variant::INT, code <= 0 || (code & 0xFFFFF800) == 0xD800 || code > 0x10FFFF,
vformat(RTR("%d is not a valid character code."), code));
*r_ret = String::chr(code);
}
@ -324,7 +325,7 @@ struct GDScriptUtilityFunctionsDefinitions {
for (KeyValue<StringName, GDScript::MemberInfo> &E : gd_ref->member_indices) {
if (d.has(E.key)) {
inst->members.write[E.value.index] = d[E.key];
inst->members[E.value.index] = d[E.key];
}
}
}
@ -478,7 +479,7 @@ struct GDScriptUtilityFunctionsDefinitions {
GDScriptNativeClass *native_type = Object::cast_to<GDScriptNativeClass>(type_object);
if (native_type) {
*r_ret = ClassDB::is_parent_class(value_object->get_class_name(), native_type->get_name());
*r_ret = value_object->is_class(native_type->get_name());
return;
}
@ -527,19 +528,19 @@ static void _register_function(const StringName &p_name, const MethodInfo &p_met
utility_function_name_table.push_back(p_name);
}
#define REGISTER_FUNC(m_func, m_is_const, m_return, m_args, m_is_vararg, m_default_args) \
{ \
String name(#m_func); \
if (name.begins_with("_")) { \
name = name.substr(1); \
} \
MethodInfo info = m_args; \
info.name = name; \
info.return_val = m_return; \
info.default_arguments = m_default_args; \
if (m_is_vararg) { \
info.flags |= METHOD_FLAG_VARARG; \
} \
#define REGISTER_FUNC(m_func, m_is_const, m_return, m_args, m_is_vararg, m_default_args) \
{ \
String name(#m_func); \
if (name.begins_with("_")) { \
name = name.substr(1); \
} \
MethodInfo info = m_args; \
info.name = name; \
info.return_val = m_return; \
info.default_arguments = m_default_args; \
if (m_is_vararg) { \
info.flags |= METHOD_FLAG_VARARG; \
} \
_register_function(name, info, GDScriptUtilityFunctionsDefinitions::m_func, m_is_const); \
}
@ -571,8 +572,8 @@ void GDScriptUtilityFunctions::register_functions() {
/* clang-format off */
#ifndef DISABLE_DEPRECATED
REGISTER_FUNC( convert, true, RETVAR, ARGS( ARGVAR("what"), ARGTYPE("type") ), false, varray( ));
#endif // DISABLE_DEPRECATED
REGISTER_FUNC( type_exists, true, RET(BOOL), ARGS( ARG("type", STRING_NAME) ), false, varray( ));
#endif // DISABLE_DEPRECATED
REGISTER_FUNC( _char, true, RET(STRING), ARGS( ARG("code", INT) ), false, varray( ));
REGISTER_FUNC( ord, true, RET(INT), ARGS( ARG("char", STRING) ), false, varray( ));
REGISTER_FUNC( range, false, RET(ARRAY), NOARGS, true, varray( ));

File diff suppressed because it is too large Load diff

View file

@ -30,6 +30,7 @@
#include "gdscript_warning.h"
#include "core/object/property_info.h"
#include "core/variant/variant.h"
#ifdef DEBUG_ENABLED
@ -153,6 +154,12 @@ String GDScriptWarning::get_message() const {
case CONFUSABLE_CAPTURE_REASSIGNMENT:
CHECK_SYMBOLS(1);
return vformat(R"(Reassigning lambda capture does not modify the outer local variable "%s".)", symbols[0]);
case CONFUSABLE_TEMPORARY_MODIFICATION:
CHECK_SYMBOLS(2);
if (symbols.size() > 2) {
return vformat(R"*(The built-in property "%s" will not be modified as a result of calling the method "%s()". Consider assigning the desired value to the property instead, or check the "%s" class for a specialized API.)*", symbols[1], symbols[2], symbols[0]);
}
return vformat(R"(The built-in property "%s" will not be modified in the assignment chain. Consider using a temporary variable and assigning it back instead, or check the "%s" class for a specialized API.)", symbols[1], symbols[0]);
case INFERENCE_ON_VARIANT:
CHECK_SYMBOLS(1);
return vformat("The %s type is being inferred from a Variant value, so it will be typed as Variant.", symbols[0]);
@ -237,6 +244,7 @@ String GDScriptWarning::get_name_from_code(Code p_code) {
PNAME("CONFUSABLE_LOCAL_DECLARATION"),
PNAME("CONFUSABLE_LOCAL_USAGE"),
PNAME("CONFUSABLE_CAPTURE_REASSIGNMENT"),
PNAME("CONFUSABLE_TEMPORARY_MODIFICATION"),
PNAME("INFERENCE_ON_VARIANT"),
PNAME("NATIVE_METHOD_OVERRIDE"),
PNAME("GET_NODE_DEFAULT_WITHOUT_ONREADY"),

View file

@ -32,10 +32,11 @@
#ifdef DEBUG_ENABLED
#include "core/object/object.h"
#include "core/string/ustring.h"
#include "core/templates/vector.h"
class String;
struct PropertyInfo;
class GDScriptWarning {
public:
enum WarnLevel {
@ -86,6 +87,7 @@ public:
CONFUSABLE_LOCAL_DECLARATION, // The parent block declares an identifier with the same name below.
CONFUSABLE_LOCAL_USAGE, // The identifier will be shadowed below in the block.
CONFUSABLE_CAPTURE_REASSIGNMENT, // Reassigning lambda capture does not modify the outer local variable.
CONFUSABLE_TEMPORARY_MODIFICATION, // Modifying a temporary value in a complex assignment chain or calling a non-`const` method.
INFERENCE_ON_VARIANT, // The declaration uses type inference but the value is typed as Variant.
NATIVE_METHOD_OVERRIDE, // The script method overrides a native one, this may not work as intended.
GET_NODE_DEFAULT_WITHOUT_ONREADY, // A class variable uses `get_node()` (or the `$` notation) as its default value, but does not use the @onready annotation.
@ -144,6 +146,7 @@ public:
WARN, // CONFUSABLE_LOCAL_DECLARATION
WARN, // CONFUSABLE_LOCAL_USAGE
WARN, // CONFUSABLE_CAPTURE_REASSIGNMENT
WARN, // CONFUSABLE_TEMPORARY_MODIFICATION
ERROR, // INFERENCE_ON_VARIANT // Most likely done by accident, usually inference is trying for a particular type.
ERROR, // NATIVE_METHOD_OVERRIDE // May not work as expected.
ERROR, // GET_NODE_DEFAULT_WITHOUT_ONREADY // May not work as expected.
@ -158,7 +161,10 @@ public:
static_assert(std_size(default_warning_levels) == WARNING_MAX, "Amount of default levels does not match the amount of warnings.");
Code code = WARNING_MAX;
int start_line = -1, end_line = -1;
int start_line;
int start_column;
int end_line;
int end_column;
Vector<String> symbols;
String get_name() const;

View file

@ -32,97 +32,30 @@
#include "../gdscript.h"
#include "../gdscript_analyzer.h"
#include "editor/settings/editor_settings.h"
#include "gdscript_language_protocol.h"
#include "gdscript_workspace.h"
int get_indent_size() {
if (EditorSettings::get_singleton()) {
return EditorSettings::get_singleton()->get_setting("text_editor/behavior/indent/size");
} else {
return 4;
}
}
LSP::Position GodotPosition::to_lsp(const Vector<String> &p_lines) const {
LSP::Position GodotPosition::to_lsp() const {
LSP::Position res;
// Special case: `line = 0` -> root class (range covers everything).
if (line <= 0) {
return res;
}
// Special case: `line = p_lines.size() + 1` -> root class (range covers everything).
if (line >= p_lines.size() + 1) {
res.line = p_lines.size();
return res;
}
res.line = line - 1;
// Special case: `column = 0` -> Starts at beginning of line.
if (column <= 0) {
return res;
}
// Note: character outside of `pos_line.length()-1` is valid.
res.character = column - 1;
String pos_line = p_lines[res.line];
if (pos_line.contains_char('\t')) {
int tab_size = get_indent_size();
int in_col = 1;
int res_char = 0;
while (res_char < pos_line.size() && in_col < column) {
if (pos_line[res_char] == '\t') {
in_col += tab_size;
res_char++;
} else {
in_col++;
res_char++;
}
}
res.character = res_char;
}
return res;
}
GodotPosition GodotPosition::from_lsp(const LSP::Position p_pos, const Vector<String> &p_lines) {
GodotPosition res(p_pos.line + 1, p_pos.character + 1);
// Line outside of actual text is valid (-> pos/cursor at end of text).
if (res.line > p_lines.size()) {
return res;
}
String line = p_lines[p_pos.line];
int tabs_before_char = 0;
for (int i = 0; i < p_pos.character && i < line.length(); i++) {
if (line[i] == '\t') {
tabs_before_char++;
}
}
if (tabs_before_char > 0) {
int tab_size = get_indent_size();
res.column += tabs_before_char * (tab_size - 1);
}
return res;
GodotPosition GodotPosition::from_lsp(const LSP::Position p_pos) {
return GodotPosition(p_pos.line + 1, p_pos.character + 1);
}
LSP::Range GodotRange::to_lsp(const Vector<String> &p_lines) const {
LSP::Range GodotRange::to_lsp() const {
LSP::Range res;
res.start = start.to_lsp(p_lines);
res.end = end.to_lsp(p_lines);
res.start = start.to_lsp();
res.end = end.to_lsp();
return res;
}
GodotRange GodotRange::from_lsp(const LSP::Range &p_range, const Vector<String> &p_lines) {
GodotPosition start = GodotPosition::from_lsp(p_range.start, p_lines);
GodotPosition end = GodotPosition::from_lsp(p_range.end, p_lines);
GodotRange GodotRange::from_lsp(const LSP::Range &p_range) {
GodotPosition start = GodotPosition::from_lsp(p_range.start);
GodotPosition end = GodotPosition::from_lsp(p_range.end);
return GodotRange(start, end);
}
@ -135,18 +68,12 @@ void ExtendGDScriptParser::update_diagnostics() {
diagnostic.severity = LSP::DiagnosticSeverity::Error;
diagnostic.message = error.message;
diagnostic.source = "gdscript";
diagnostic.code = -1;
LSP::Range range;
LSP::Position pos;
const PackedStringArray line_array = get_lines();
int line = CLAMP(LINE_NUMBER_TO_INDEX(error.line), 0, line_array.size() - 1);
const String &line_text = line_array[line];
pos.line = line;
pos.character = line_text.length() - line_text.strip_edges(true, false).length();
range.start = pos;
range.end = range.start;
range.end.character = line_text.strip_edges(false).length();
diagnostic.range = range;
GodotRange godot_range(
GodotPosition(error.start_line, error.start_column),
GodotPosition(error.end_line, error.end_column));
diagnostic.range = godot_range.to_lsp();
diagnostics.push_back(diagnostic);
}
@ -156,17 +83,12 @@ void ExtendGDScriptParser::update_diagnostics() {
diagnostic.severity = LSP::DiagnosticSeverity::Warning;
diagnostic.message = "(" + warning.get_name() + "): " + warning.get_message();
diagnostic.source = "gdscript";
diagnostic.code = warning.code;
LSP::Range range;
LSP::Position pos;
int line = LINE_NUMBER_TO_INDEX(warning.start_line);
const String &line_text = get_lines()[line];
pos.line = line;
pos.character = line_text.length() - line_text.strip_edges(true, false).length();
range.start = pos;
range.end = pos;
range.end.character = line_text.strip_edges(false).length();
diagnostic.range = range;
GodotRange godot_range(
GodotPosition(warning.start_line, warning.start_column),
GodotPosition(warning.end_line, warning.end_column));
diagnostic.range = godot_range.to_lsp();
diagnostics.push_back(diagnostic);
}
}
@ -217,7 +139,7 @@ void ExtendGDScriptParser::update_document_links(const String &p_code) {
String value = const_val;
LSP::DocumentLink link;
link.target = GDScriptLanguageProtocol::get_singleton()->get_workspace()->get_file_uri(scr_path);
link.range = GodotRange(GodotPosition(token.start_line, token.start_column), GodotPosition(token.end_line, token.end_column)).to_lsp(lines);
link.range = GodotRange(GodotPosition(token.start_line, token.start_column), GodotPosition(token.end_line, token.end_column)).to_lsp();
document_links.push_back(link);
}
}
@ -228,7 +150,7 @@ void ExtendGDScriptParser::update_document_links(const String &p_code) {
LSP::Range ExtendGDScriptParser::range_of_node(const GDScriptParser::Node *p_node) const {
GodotPosition start(p_node->start_line, p_node->start_column);
GodotPosition end(p_node->end_line, p_node->end_column);
return GodotRange(start, end).to_lsp(lines);
return GodotRange(start, end).to_lsp();
}
void ExtendGDScriptParser::parse_class_symbol(const GDScriptParser::ClassNode *p_class, LSP::DocumentSymbol &r_symbol) {
@ -333,8 +255,8 @@ void ExtendGDScriptParser::parse_class_symbol(const GDScriptParser::ClassNode *p
symbol.script_path = path;
symbol.detail = "const " + symbol.name;
if (m.constant->get_datatype().is_hard_type()) {
symbol.detail += ": " + m.constant->get_datatype().to_string();
if (m.constant->type_constraint.is_hard_type()) {
symbol.detail += ": " + m.constant->type_constraint.to_string();
}
const Variant &default_value = m.constant->initializer->reduced_value;
@ -391,8 +313,8 @@ void ExtendGDScriptParser::parse_class_symbol(const GDScriptParser::ClassNode *p
param_symbol.uri = uri;
param_symbol.script_path = path;
param_symbol.detail = "var " + param_symbol.name;
if (param->get_datatype().is_hard_type()) {
param_symbol.detail += ": " + param->get_datatype().to_string();
if (param->type_constraint.is_hard_type()) {
param_symbol.detail += ": " + param->type_constraint.to_string();
}
symbol.children.push_back(param_symbol);
}
@ -404,8 +326,8 @@ void ExtendGDScriptParser::parse_class_symbol(const GDScriptParser::ClassNode *p
symbol.name = m.enum_value.identifier->name;
symbol.kind = LSP::SymbolKind::EnumMember;
symbol.deprecated = false;
symbol.range.start = GodotPosition(m.enum_value.line, m.enum_value.start_column).to_lsp(lines);
symbol.range.end = GodotPosition(m.enum_value.line, m.enum_value.end_column).to_lsp(lines);
symbol.range.start = GodotPosition(m.enum_value.line, m.enum_value.start_column).to_lsp();
symbol.range.end = GodotPosition(m.enum_value.line, m.enum_value.end_column).to_lsp();
symbol.selectionRange = range_of_node(m.enum_value.identifier);
symbol.documentation = m.enum_value.doc_data.description;
symbol.uri = uri;
@ -440,8 +362,8 @@ void ExtendGDScriptParser::parse_class_symbol(const GDScriptParser::ClassNode *p
child.name = value.identifier->name;
child.kind = LSP::SymbolKind::EnumMember;
child.deprecated = false;
child.range.start = GodotPosition(value.line, value.start_column).to_lsp(lines);
child.range.end = GodotPosition(value.line, value.end_column).to_lsp(lines);
child.range.start = GodotPosition(value.line, value.start_column).to_lsp();
child.range.end = GodotPosition(value.line, value.end_column).to_lsp();
child.selectionRange = range_of_node(value.identifier);
child.documentation = value.doc_data.description;
child.uri = uri;
@ -502,8 +424,8 @@ void ExtendGDScriptParser::parse_function_symbol(const GDScriptParser::FunctionN
parameters += ", ";
}
parameters += String(parameter->identifier->name);
if (parameter->get_datatype().is_hard_type()) {
parameters += ": " + parameter->get_datatype().to_string();
if (parameter->type_constraint.is_hard_type()) {
parameters += ": " + parameter->type_constraint.to_string();
}
if (parameter->initializer != nullptr) {
parameters += " = " + parameter->initializer->reduced_value.to_json_string();
@ -514,11 +436,11 @@ void ExtendGDScriptParser::parse_function_symbol(const GDScriptParser::FunctionN
parameters += ", ";
}
const ParameterNode *rest_param = p_func->rest_parameter;
parameters += "..." + rest_param->identifier->name + ": " + rest_param->get_datatype().to_string();
parameters += "..." + rest_param->identifier->name + ": " + rest_param->type_constraint.to_string();
}
r_symbol.detail += parameters + ")";
const DataType return_type = p_func->get_datatype();
const DataType return_type = p_func->return_type_constraint;
if (return_type.is_hard_type()) {
if (return_type.kind == DataType::BUILTIN && return_type.builtin_type == Variant::NIL) {
r_symbol.detail += " -> void";
@ -615,8 +537,8 @@ void ExtendGDScriptParser::parse_function_symbol(const GDScriptParser::FunctionN
break;
default:
// Fallback.
symbol.range.start = GodotPosition(local.start_line, local.start_column).to_lsp(get_lines());
symbol.range.end = GodotPosition(local.end_line, local.end_column).to_lsp(get_lines());
symbol.range.start = GodotPosition(local.start_line, local.start_column).to_lsp();
symbol.range.end = GodotPosition(local.end_line, local.end_column).to_lsp();
symbol.selectionRange = symbol.range;
break;
}
@ -710,14 +632,29 @@ String ExtendGDScriptParser::get_text_for_lookup_symbol(const LSP::Position &p_c
return longthing;
}
String ExtendGDScriptParser::get_identifier_under_position(const LSP::Position &p_position, LSP::Range &r_range) const {
String ExtendGDScriptParser::get_symbol_name_under_position(const LSP::Position &p_position, LSP::Range &r_range) const {
r_range = LSP::Range(p_position, p_position); // Default for error macros.
ERR_FAIL_INDEX_V(p_position.line, lines.size(), "");
String line = lines[p_position.line];
if (line.is_empty()) {
return "";
}
// Checks against line.size(), which includes a terminating NUL. This is to allow a cursor after the last character.
ERR_FAIL_INDEX_V(p_position.character, line.size(), "");
LSP::Position pos = p_position;
// Cursor after last character.
if (pos.character >= line.length()) {
pos.character--;
}
// If on the start of an annotation move the position into the identifier part. We account for "@" at the end.
if (line[pos.character] == '@' && pos.character + 1 < line.size() && is_unicode_identifier_start(line[pos.character + 1])) {
pos.character++;
}
// `p_position` cursor is BETWEEN chars, not ON chars.
// ->
// ```gdscript
@ -729,51 +666,54 @@ String ExtendGDScriptParser::get_identifier_under_position(const LSP::Position &
// |
// | cursor on `member`, pos on ` ` (space)
// ```
// -> Move position to previous character if:
// * Position not on valid identifier char.
// * Prev position is valid identifier char.
LSP::Position pos = p_position;
if (
pos.character >= line.length() // Cursor at end of line.
|| (!is_unicode_identifier_continue(line[pos.character]) // Not on valid identifier char.
&& (pos.character > 0 // Not line start -> there is a prev char.
&& is_unicode_identifier_continue(line[pos.character - 1]) // Prev is valid identifier char.
))) {
if (!is_unicode_identifier_continue(line[pos.character])) {
if (pos.character == 0 || !is_unicode_identifier_continue(line[pos.character - 1])) {
// In between two non-identifier chars.
return "";
}
// Move position to previous character if not on valid char and the previous char is valid.
pos.character--;
}
int start_pos = pos.character;
// Iterate forward till we have a start. Symbol starts require lookahead, so we save the latest valid start and track back to it once we can stop looking.
// E.g. ?0nly
// ^
// | could be an identifier start (since 0 can't start identifiers), but if there was another symbol in front of 0 the identifier would be longer.
int last_valid_start_pos = -1;
for (int c = pos.character; c >= 0; c--) {
start_pos = c;
char32_t ch = line[c];
bool valid_char = is_unicode_identifier_continue(ch);
if (!valid_char) {
if (is_unicode_identifier_start(ch)) {
last_valid_start_pos = c;
}
if (!is_unicode_identifier_continue(ch)) {
break;
}
}
int end_pos = pos.character;
// Iterate backwards till we have an end. No lookahead required. Uses +1 since the end of a range is exclusive.
int end_pos = pos.character + 1;
for (int c = pos.character; c < line.length(); c++) {
char32_t ch = line[c];
bool valid_char = is_unicode_identifier_continue(ch);
if (!valid_char) {
if (!is_unicode_identifier_continue(ch)) {
break;
}
end_pos = c;
end_pos = c + 1;
}
if (!is_unicode_identifier_start(line[start_pos + 1])) {
if (last_valid_start_pos == -1) {
return "";
}
if (start_pos < end_pos) {
r_range.start.line = r_range.end.line = pos.line;
r_range.start.character = start_pos + 1;
r_range.end.character = end_pos + 1;
return line.substr(start_pos + 1, end_pos - start_pos);
// For annotations we include the @, since it is included in the symbol name used by other parts of the LSP code.
int start_pos = last_valid_start_pos;
if (start_pos > 0 && line[start_pos - 1] == '@') {
start_pos -= 1;
}
return "";
r_range.start.character = start_pos;
r_range.end.character = end_pos;
return line.substr(start_pos, end_pos - start_pos);
}
String ExtendGDScriptParser::get_uri() const {
@ -870,41 +810,17 @@ const List<LSP::DocumentLink> &ExtendGDScriptParser::get_document_links() const
return document_links;
}
const Array &ExtendGDScriptParser::get_member_completions() {
if (member_completions.is_empty()) {
for (const KeyValue<String, const LSP::DocumentSymbol *> &E : members) {
const LSP::DocumentSymbol *symbol = E.value;
LSP::CompletionItem item = symbol->make_completion_item();
item.data = JOIN_SYMBOLS(path, E.key);
member_completions.push_back(item.to_json());
}
for (const KeyValue<String, ClassMembers> &E : inner_classes) {
const ClassMembers *inner_class = &E.value;
for (const KeyValue<String, const LSP::DocumentSymbol *> &F : *inner_class) {
const LSP::DocumentSymbol *symbol = F.value;
LSP::CompletionItem item = symbol->make_completion_item();
item.data = JOIN_SYMBOLS(path, JOIN_SYMBOLS(E.key, F.key));
member_completions.push_back(item.to_json());
}
}
}
return member_completions;
}
Dictionary ExtendGDScriptParser::dump_function_api(const GDScriptParser::FunctionNode *p_func) const {
ERR_FAIL_NULL_V(p_func, Dictionary());
Dictionary func;
func["name"] = p_func->identifier->name;
func["return_type"] = p_func->get_datatype().to_string();
func["return_type"] = p_func->return_type_constraint.to_string();
func["rpc_config"] = p_func->rpc_config;
Array parameters;
for (int i = 0; i < p_func->parameters.size(); i++) {
Dictionary arg;
arg["name"] = p_func->parameters[i]->identifier->name;
arg["type"] = p_func->parameters[i]->get_datatype().to_string();
arg["type"] = p_func->parameters[i]->type_constraint.to_string();
if (p_func->parameters[i]->initializer != nullptr) {
arg["default_value"] = p_func->parameters[i]->initializer->reduced_value;
}
@ -954,7 +870,7 @@ Dictionary ExtendGDScriptParser::dump_class_api(const GDScriptParser::ClassNode
Dictionary api;
api["name"] = m.constant->identifier->name;
api["value"] = m.constant->initializer->reduced_value;
api["data_type"] = m.constant->get_datatype().to_string();
api["data_type"] = m.constant->type_constraint.to_string();
if (const LSP::DocumentSymbol *symbol = get_symbol_defined_at_line(LINE_NUMBER_TO_INDEX(m.constant->start_line))) {
api["signature"] = symbol->detail;
api["description"] = symbol->documentation;
@ -991,7 +907,7 @@ Dictionary ExtendGDScriptParser::dump_class_api(const GDScriptParser::ClassNode
case ClassNode::Member::VARIABLE: {
Dictionary api;
api["name"] = m.variable->identifier->name;
api["data_type"] = m.variable->get_datatype().to_string();
api["data_type"] = m.variable->type_constraint.to_string();
api["default_value"] = m.variable->initializer != nullptr ? m.variable->initializer->reduced_value : Variant();
api["setter"] = m.variable->setter ? ("@" + String(m.variable->identifier->name) + "_setter") : (m.variable->setter_pointer != nullptr ? String(m.variable->setter_pointer->name) : String());
api["getter"] = m.variable->getter ? ("@" + String(m.variable->identifier->name) + "_getter") : (m.variable->getter_pointer != nullptr ? String(m.variable->getter_pointer->name) : String());

View file

@ -42,14 +42,6 @@
#define COLUMN_NUMBER_TO_INDEX(p_column) ((p_column) - 1)
#endif
#ifndef SYMBOL_SEPARATOR
#define SYMBOL_SEPARATOR "::"
#endif
#ifndef JOIN_SYMBOLS
#define JOIN_SYMBOLS(p_path, name) ((p_path) + SYMBOL_SEPARATOR + (name))
#endif
typedef HashMap<String, const LSP::DocumentSymbol *> ClassMembers;
/**
@ -79,8 +71,8 @@ struct GodotPosition {
GodotPosition(int p_line, int p_column) :
line(p_line), column(p_column) {}
LSP::Position to_lsp(const Vector<String> &p_lines) const;
static GodotPosition from_lsp(const LSP::Position p_pos, const Vector<String> &p_lines);
LSP::Position to_lsp() const;
static GodotPosition from_lsp(const LSP::Position p_pos);
bool operator==(const GodotPosition &p_other) const {
return line == p_other.line && column == p_other.column;
@ -98,8 +90,8 @@ struct GodotRange {
GodotRange(GodotPosition p_start, GodotPosition p_end) :
start(p_start), end(p_end) {}
LSP::Range to_lsp(const Vector<String> &p_lines) const;
static GodotRange from_lsp(const LSP::Range &p_range, const Vector<String> &p_lines);
LSP::Range to_lsp() const;
static GodotRange from_lsp(const LSP::Range &p_range);
bool operator==(const GodotRange &p_other) const {
return start == p_other.start && end == p_other.end;
@ -134,8 +126,6 @@ class ExtendGDScriptParser : public GDScriptParser {
const LSP::DocumentSymbol *search_symbol_defined_at_line(int p_line, const LSP::DocumentSymbol &p_parent, const String &p_symbol_name = "") const;
Array member_completions;
public:
_FORCE_INLINE_ const String &get_path() const { return path; }
_FORCE_INLINE_ const Vector<String> &get_lines() const { return lines; }
@ -149,7 +139,13 @@ public:
String get_text_for_completion(const LSP::Position &p_cursor) const;
String get_text_for_lookup_symbol(const LSP::Position &p_cursor, const String &p_symbol = "", bool p_func_required = false) const;
String get_identifier_under_position(const LSP::Position &p_position, LSP::Range &r_range) const;
/**
* Parses the symbol name at the given position. Returns that name and its full range.
*
* The returned name might be a false positive and not translate to an actual symbol e.g. it might be a language keyword.
* The results of this method are not equivalent to identifier AST nodes. Instead it returns results that are compatible with `LSP::DocumentSymbol::name` i.e. includes `@` for annotations.
*/
String get_symbol_name_under_position(const LSP::Position &p_position, LSP::Range &r_range) const;
String get_uri() const;
/**

View file

@ -30,23 +30,27 @@
#include "gdscript_language_protocol.h"
#include "godot_lsp.h"
#include "core/config/project_settings.h"
#include "core/object/callable_mp.h"
#include "core/object/class_db.h"
#include "core/os/os.h"
#include "editor/doc/doc_tools.h"
#include "editor/doc/editor_help.h"
#include "editor/editor_log.h"
#include "editor/editor_node.h"
#include "editor/settings/editor_settings.h"
#include "modules/gdscript/language_server/godot_lsp.h"
#define LSP_CLIENT_V(m_ret_val) \
#define LSP_CLIENT_V(m_ret_val) \
ERR_FAIL_COND_V(latest_client_id == LSP_NO_CLIENT, m_ret_val); \
ERR_FAIL_COND_V(!clients.has(latest_client_id), m_ret_val); \
Ref<LSPeer> client = clients.get(latest_client_id); \
ERR_FAIL_COND_V(!clients.has(latest_client_id), m_ret_val); \
Ref<LSPeer> client = clients.get(latest_client_id); \
ERR_FAIL_COND_V(!client.is_valid(), m_ret_val);
#define LSP_CLIENT \
ERR_FAIL_COND(latest_client_id == LSP_NO_CLIENT); \
ERR_FAIL_COND(!clients.has(latest_client_id)); \
#define LSP_CLIENT \
ERR_FAIL_COND(latest_client_id == LSP_NO_CLIENT); \
ERR_FAIL_COND(!clients.has(latest_client_id)); \
Ref<LSPeer> client = clients.get(latest_client_id); \
ERR_FAIL_COND(!client.is_valid());
@ -148,6 +152,9 @@ Error GDScriptLanguageProtocol::on_client_connected() {
void GDScriptLanguageProtocol::on_client_disconnected(const int &p_client_id) {
clients.erase(p_client_id);
if (clients.is_empty()) {
scene_cache.clear();
}
EditorNode::get_log()->add_message("[LSP] Disconnected", EditorLog::MSG_TYPE_EDITOR);
}
@ -171,18 +178,40 @@ String GDScriptLanguageProtocol::format_output(const String &p_text) {
}
void GDScriptLanguageProtocol::_bind_methods() {
ClassDB::bind_method(D_METHOD("get_text_document"), &GDScriptLanguageProtocol::get_text_document);
ClassDB::bind_method(D_METHOD("get_workspace"), &GDScriptLanguageProtocol::get_workspace);
ClassDB::bind_method(D_METHOD("is_smart_resolve_enabled"), &GDScriptLanguageProtocol::is_smart_resolve_enabled);
ClassDB::bind_method(D_METHOD("is_initialized"), &GDScriptLanguageProtocol::is_initialized);
#ifndef DISABLE_DEPRECATED
ClassDB::bind_method(D_METHOD("initialize", "params"), &GDScriptLanguageProtocol::initialize);
ClassDB::bind_method(D_METHOD("initialized", "params"), &GDScriptLanguageProtocol::initialized);
ClassDB::bind_method(D_METHOD("on_client_connected"), &GDScriptLanguageProtocol::on_client_connected);
ClassDB::bind_method(D_METHOD("on_client_disconnected"), &GDScriptLanguageProtocol::on_client_disconnected);
ClassDB::bind_method(D_METHOD("on_client_disconnected", "client_id"), &GDScriptLanguageProtocol::on_client_disconnected);
ClassDB::bind_method(D_METHOD("notify_client", "method", "params", "client_id"), &GDScriptLanguageProtocol::notify_client, DEFVAL(Variant()), DEFVAL(-1));
ClassDB::bind_method(D_METHOD("is_smart_resolve_enabled"), &GDScriptLanguageProtocol::is_smart_resolve_enabled);
ClassDB::bind_method(D_METHOD("get_text_document"), &GDScriptLanguageProtocol::get_text_document);
ClassDB::bind_method(D_METHOD("get_workspace"), &GDScriptLanguageProtocol::get_workspace);
ClassDB::bind_method(D_METHOD("is_initialized"), &GDScriptLanguageProtocol::is_initialized);
#endif // !DISABLE_DEPRECATED
}
Dictionary GDScriptLanguageProtocol::initialize(const Dictionary &p_params) {
template <typename T>
Variant get_deep(Variant p_dict, Variant p_default, T p_key) {
if (p_dict.get_type() != Variant::DICTIONARY) {
return p_default;
}
return p_dict.operator Dictionary().get(p_key, p_default);
}
template <typename T1, typename... T2>
Variant get_deep(Variant p_dict, Variant p_default, T1 p_key1, T2... p_key2) {
if (p_dict.get_type() != Variant::DICTIONARY || !p_dict.operator Dictionary().has(p_key1)) {
return p_default;
}
return get_deep(p_dict.operator Dictionary()[p_key1], p_default, p_key2...);
}
Variant GDScriptLanguageProtocol::initialize(const Dictionary &p_params) {
LSP_CLIENT_V(Variant());
LSP::InitializeResult ret;
{
@ -199,50 +228,35 @@ Dictionary GDScriptLanguageProtocol::initialize(const Dictionary &p_params) {
}
if (ProjectSettings::get_singleton()->localize_path(root) != "res://") {
// Show a general warning, which works for all clients.
LSP::ShowMessageParams params{
LSP::MessageType::Warning,
"The GDScript Language Server might not work correctly with other projects than the one opened in Godot."
};
notify_client("window/showMessage", params.to_json());
}
}
String root_uri = p_params["rootUri"];
String root = p_params.get("rootPath", "");
bool is_same_workspace;
#ifndef WINDOWS_ENABLED
is_same_workspace = root.to_lower() == workspace->root.to_lower();
#else
is_same_workspace = root.replace_char('\\', '/').to_lower() == workspace->root.to_lower();
#endif
if (root_uri.length() && is_same_workspace) {
workspace->root_uri = root_uri;
} else {
String r_root = workspace->root;
r_root = r_root.lstrip("/");
workspace->root_uri = "file:///" + r_root;
Dictionary params;
params["path"] = workspace->root;
Dictionary request = make_notification("gdscript_client/changeWorkspace", params);
ERR_FAIL_COND_V_MSG(!clients.has(latest_client_id), ret.to_json(),
vformat("GDScriptLanguageProtocol: Can't initialize invalid peer '%d'.", latest_client_id));
Ref<LSPeer> peer = clients.get(latest_client_id);
if (peer.is_valid()) {
String msg = Variant(request).to_json_string();
msg = format_output(msg);
(*peer)->res_queue.push_back(msg.utf8());
// Send gdscript_client/changeWorkspace to prompt client side handling, where supported. (Currently known users: VSCode extension, Rider extension).
notify_client("gdscript_client/changeWorkspace", Dictionary({ { "path", ProjectSettings::get_singleton()->get_resource_path() } }));
}
}
if (!_initialized) {
workspace->initialize();
text_document->initialize();
_initialized = true;
}
// Handle client capabilities.
Dictionary capabilities = p_params["capabilities"];
client->behavior.use_snippets_for_brace_completion = get_deep(capabilities, false,
"textDocument", "completion", "completionItem", "snippetSupport");
Array allowed_tags = get_deep(capabilities, Array(), "general", "markdown", "allowedTags");
for (const Variant &tag : allowed_tags) {
if (tag.is_string()) {
client->behavior.markdown_allowed_html_tags.insert(tag);
}
}
return ret.to_json();
}
@ -270,6 +284,8 @@ void GDScriptLanguageProtocol::poll(int p_limit_usec) {
on_client_connected();
}
scene_cache.poll();
HashMap<int, Ref<LSPeer>>::Iterator E = clients.begin();
while (E != clients.end()) {
Ref<LSPeer> peer = E->value;
@ -316,6 +332,7 @@ void GDScriptLanguageProtocol::stop() {
peer->connection->disconnect_from_host();
}
scene_cache.clear();
server->stop();
}
@ -339,7 +356,7 @@ void GDScriptLanguageProtocol::notify_client(const String &p_method, const Varia
peer->res_queue.push_back(msg.utf8());
}
void GDScriptLanguageProtocol::request_client(const String &p_method, const Variant &p_params, int p_client_id) {
void GDScriptLanguageProtocol::request_client(const String &p_method, const Variant &p_params, int p_client_id, const Callable &p_response_handler) {
#ifdef TESTS_ENABLED
if (clients.is_empty()) {
return;
@ -354,6 +371,7 @@ void GDScriptLanguageProtocol::request_client(const String &p_method, const Vari
ERR_FAIL_COND(peer.is_null());
Dictionary message = make_request(p_method, p_params, next_server_id);
set_response_handler(next_client_id, p_response_handler);
next_server_id++;
String msg = Variant(message).to_json_string();
msg = format_output(msg);
@ -430,6 +448,12 @@ ExtendGDScriptParser *GDScriptLanguageProtocol::get_parse_result(const String &p
return *cached_parser;
}
const HashSet<String> &GDScriptLanguageProtocol::get_client_markdown_allowed_html_tags() const {
static const HashSet<String> default_tags = {};
LSP_CLIENT_V(default_tags);
return client->behavior.markdown_allowed_html_tags;
}
void GDScriptLanguageProtocol::lsp_did_open(const Dictionary &p_params) {
LSP_CLIENT;
@ -448,6 +472,8 @@ void GDScriptLanguageProtocol::lsp_did_open(const Dictionary &p_params) {
client->managed_files[path] = document;
client->parse_script(path);
scene_cache.request_load(path);
}
void GDScriptLanguageProtocol::lsp_did_change(const Dictionary &p_params) {
@ -493,6 +519,92 @@ void GDScriptLanguageProtocol::lsp_did_close(const Dictionary &p_params) {
/// A close notification requires a previous open notification to be sent.
ERR_FAIL_COND_MSG(!was_opened, "LSP: Client is closing file without opening it.");
scene_cache.unload(path);
}
Array GDScriptLanguageProtocol::lsp_completion(const Dictionary &p_params) {
Array arr;
LSP_CLIENT_V(arr);
LSP::CompletionParams params;
params.load(p_params);
Dictionary request_data = params.to_json();
List<ScriptLanguage::CodeCompletionOption> options;
get_workspace()->completion(params, &options);
if (!options.is_empty()) {
int i = 0;
arr.resize(options.size());
for (const ScriptLanguage::CodeCompletionOption &option : options) {
LSP::CompletionItem item;
item.label = option.display;
item.data = request_data;
item.insertText = option.insert_text;
// LSP clients won't autoclose brackets.
if (client->behavior.use_snippets_for_brace_completion) {
// Use snippet insert mode to insert closing brace as well.
if (item.insertText.ends_with("(")) {
item.insertText += "$1)";
item.insertTextFormat = LSP::InsertTextFormat::Snippet;
}
} else {
// Trim braces.
item.insertText = item.insertText.trim_suffix("(");
}
if (option.text_edit.is_set()) {
GodotRange range(GodotPosition(option.text_edit.start_line, option.text_edit.start_column), GodotPosition(option.text_edit.end_line, option.text_edit.end_column));
item.textEdit.newText = option.text_edit.new_text;
item.textEdit.range = range.to_lsp();
}
switch (option.kind) {
case ScriptLanguage::CODE_COMPLETION_KIND_ENUM:
item.kind = LSP::CompletionItemKind::Enum;
break;
case ScriptLanguage::CODE_COMPLETION_KIND_CLASS:
item.kind = LSP::CompletionItemKind::Class;
break;
case ScriptLanguage::CODE_COMPLETION_KIND_MEMBER:
item.kind = LSP::CompletionItemKind::Property;
break;
case ScriptLanguage::CODE_COMPLETION_KIND_FUNCTION:
item.kind = LSP::CompletionItemKind::Method;
break;
case ScriptLanguage::CODE_COMPLETION_KIND_SIGNAL:
item.kind = LSP::CompletionItemKind::Event;
break;
case ScriptLanguage::CODE_COMPLETION_KIND_CONSTANT:
item.kind = LSP::CompletionItemKind::Constant;
break;
case ScriptLanguage::CODE_COMPLETION_KIND_VARIABLE:
item.kind = LSP::CompletionItemKind::Variable;
break;
case ScriptLanguage::CODE_COMPLETION_KIND_FILE_PATH:
item.kind = LSP::CompletionItemKind::File;
break;
case ScriptLanguage::CODE_COMPLETION_KIND_NODE_PATH:
item.kind = LSP::CompletionItemKind::Snippet;
break;
case ScriptLanguage::CODE_COMPLETION_KIND_PLAIN_TEXT:
item.kind = LSP::CompletionItemKind::Text;
break;
case ScriptLanguage::CODE_COMPLETION_KIND_KEYWORD:
item.kind = LSP::CompletionItemKind::Keyword;
break;
case ScriptLanguage::CODE_COMPLETION_KIND_MAX: {
}
}
arr[i] = item.to_json();
i++;
}
}
return arr;
}
void GDScriptLanguageProtocol::resolve_related_symbols(const LSP::TextDocumentPositionParams &p_doc_pos, List<const LSP::DocumentSymbol *> &r_list) {
@ -505,12 +617,12 @@ void GDScriptLanguageProtocol::resolve_related_symbols(const LSP::TextDocumentPo
return;
}
String symbol_identifier;
String symbol_name;
LSP::Range range;
symbol_identifier = parser->get_identifier_under_position(p_doc_pos.position, range);
symbol_name = parser->get_symbol_name_under_position(p_doc_pos.position, range);
for (const KeyValue<StringName, ClassMembers> &E : workspace->native_members) {
if (const LSP::DocumentSymbol *const *symbol = E.value.getptr(symbol_identifier)) {
if (const LSP::DocumentSymbol *const *symbol = E.value.getptr(symbol_name)) {
r_list.push_back(*symbol);
}
}
@ -518,13 +630,13 @@ void GDScriptLanguageProtocol::resolve_related_symbols(const LSP::TextDocumentPo
for (const KeyValue<String, ExtendGDScriptParser *> &E : client->parse_results) {
const ExtendGDScriptParser *scr = E.value;
const ClassMembers &members = scr->get_members();
if (const LSP::DocumentSymbol *const *symbol = members.getptr(symbol_identifier)) {
if (const LSP::DocumentSymbol *const *symbol = members.getptr(symbol_name)) {
r_list.push_back(*symbol);
}
for (const KeyValue<String, ClassMembers> &F : scr->get_inner_classes()) {
const ClassMembers *inner_class = &F.value;
if (const LSP::DocumentSymbol *const *symbol = inner_class->getptr(symbol_identifier)) {
if (const LSP::DocumentSymbol *const *symbol = inner_class->getptr(symbol_name)) {
r_list.push_back(*symbol);
}
}
@ -558,14 +670,12 @@ GDScriptLanguageProtocol::GDScriptLanguageProtocol() {
SET_DOCUMENT_METHOD(didSave);
SET_DOCUMENT_METHOD(documentSymbol);
SET_DOCUMENT_METHOD(documentHighlight);
SET_DOCUMENT_METHOD(completion);
SET_DOCUMENT_METHOD(rename);
SET_DOCUMENT_METHOD(prepareRename);
SET_DOCUMENT_METHOD(references);
SET_DOCUMENT_METHOD(foldingRange);
SET_DOCUMENT_METHOD(codeLens);
SET_DOCUMENT_METHOD(documentLink);
SET_DOCUMENT_METHOD(colorPresentation);
SET_DOCUMENT_METHOD(hover);
SET_DOCUMENT_METHOD(definition);
SET_DOCUMENT_METHOD(declaration);
@ -577,8 +687,6 @@ GDScriptLanguageProtocol::GDScriptLanguageProtocol() {
set_method("initialize", callable_mp(this, &GDScriptLanguageProtocol::initialize));
set_method("initialized", callable_mp(this, &GDScriptLanguageProtocol::initialized));
workspace->root = ProjectSettings::get_singleton()->get_resource_path();
}
#undef SET_DOCUMENT_METHOD

View file

@ -32,6 +32,7 @@
#include "gdscript_text_document.h"
#include "gdscript_workspace.h"
#include "scene_cache.h"
#include "core/io/stream_peer_tcp.h"
#include "core/io/tcp_server.h"
@ -46,9 +47,15 @@
class GDScriptLanguageProtocol : public JSONRPC {
GDCLASS(GDScriptLanguageProtocol, JSONRPC)
#ifdef TESTS_ENABLED
friend class TestGDScriptLanguageProtocolInitializer;
#endif
public:
struct ClientBehavior {
/** If `true` use snippet insert mode to position the cursor between braces of completion options. If `false` strip braces from completion options since we can't provide good UX for them. */
bool use_snippets_for_brace_completion = false;
/** HTML tags, e.g. `span`, which the client is capable of rendering inside Markdown content. Assumes full support for attributes e.g. `style`. */
HashSet<String> markdown_allowed_html_tags;
};
private:
struct LSPeer : RefCounted {
@ -65,6 +72,12 @@ private:
Error handle_data();
Error send_data();
/**
* Represents how the server should behave towards this client in certain situations.
* This gets derived from client capabilities so the configured behavior is guaranteed to be supported by the client.
*/
ClientBehavior behavior;
/**
* Tracks all files that the client claimed, however for files deemed not relevant
* to the server the `text` might not be persisted.
@ -92,6 +105,7 @@ private:
static GDScriptLanguageProtocol *singleton;
HashMap<int, Ref<LSPeer>> clients;
SceneCache scene_cache;
Ref<TCPServer> server;
int latest_client_id = LSP_NO_CLIENT;
int next_client_id = 0;
@ -112,13 +126,15 @@ private:
protected:
static void _bind_methods();
Dictionary initialize(const Dictionary &p_params);
Variant initialize(const Dictionary &p_params);
void initialized(const Variant &p_params);
public:
_FORCE_INLINE_ static GDScriptLanguageProtocol *get_singleton() { return singleton; }
_FORCE_INLINE_ Ref<GDScriptWorkspace> get_workspace() { return workspace; }
_FORCE_INLINE_ Ref<GDScriptTextDocument> get_text_document() { return text_document; }
_FORCE_INLINE_ SceneCache *get_scene_cache() { return &scene_cache; }
_FORCE_INLINE_ bool is_initialized() const { return _initialized; }
void poll(int p_limit_usec);
@ -126,7 +142,7 @@ public:
void stop();
void notify_client(const String &p_method, const Variant &p_params = Variant(), int p_client_id = -1);
void request_client(const String &p_method, const Variant &p_params = Variant(), int p_client_id = -1);
void request_client(const String &p_method, const Variant &p_params = Variant(), int p_client_id = -1, const Callable &p_response_handler = Callable());
bool is_smart_resolve_enabled() const;
bool is_goto_native_symbols_enabled() const;
@ -136,6 +152,9 @@ public:
void lsp_did_change(const Dictionary &p_params);
void lsp_did_close(const Dictionary &p_params);
// Completion
Array lsp_completion(const Dictionary &p_params);
/**
* Returns a list of symbols that might be related to the document position.
*
@ -150,6 +169,14 @@ public:
*/
ExtendGDScriptParser *get_parse_result(const String &p_path);
/**
* Returns the HTML tags the client can render inside Markdown content, e.g. `span`.
* Defaults to an empty set if no client is connected.
*
* TODO: Remove after moving endpoints into unified class. Access non-null client directly.
*/
const HashSet<String> &get_client_markdown_allowed_html_tags() const;
GDScriptLanguageProtocol();
~GDScriptLanguageProtocol() {
clients.clear();

View file

@ -30,6 +30,8 @@
#include "gdscript_language_server.h"
#include "gdscript_language_protocol.h"
#include "core/os/os.h"
#include "editor/editor_log.h"
#include "editor/editor_node.h"
@ -38,14 +40,6 @@
int GDScriptLanguageServer::port_override = -1;
GDScriptLanguageServer::GDScriptLanguageServer() {
// TODO: Move to editor_settings.cpp
_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);
_EDITOR_DEF("network/language_server/use_thread", use_thread);
_EDITOR_DEF("network/language_server/poll_limit_usec", poll_limit_usec);
set_process_internal(true);
}
@ -62,7 +56,7 @@ void GDScriptLanguageServer::_notification(int p_what) {
}
if (started && !use_thread) {
protocol.poll(poll_limit_usec);
GDScriptLanguageProtocol::get_singleton()->poll(poll_limit_usec);
}
} break;
@ -88,7 +82,7 @@ void GDScriptLanguageServer::thread_main(void *p_userdata) {
GDScriptLanguageServer *self = static_cast<GDScriptLanguageServer *>(p_userdata);
while (self->thread_running) {
// Poll 20 times per second
self->protocol.poll(self->poll_limit_usec);
GDScriptLanguageProtocol::get_singleton()->poll(self->poll_limit_usec);
OS::get_singleton()->delay_usec(50000);
}
}
@ -98,15 +92,18 @@ void GDScriptLanguageServer::start() {
port = (GDScriptLanguageServer::port_override > -1) ? GDScriptLanguageServer::port_override : (int)_EDITOR_GET("network/language_server/remote_port");
use_thread = (bool)_EDITOR_GET("network/language_server/use_thread");
poll_limit_usec = (int)_EDITOR_GET("network/language_server/poll_limit_usec");
if (protocol.start(port, IPAddress(host)) == OK) {
EditorNode::get_log()->add_message("--- GDScript language server started on port " + itos(port) + " ---", EditorLog::MSG_TYPE_EDITOR);
if (use_thread) {
thread_running = true;
thread.start(GDScriptLanguageServer::thread_main, this);
}
set_process_internal(!use_thread);
started = true;
const Error status = GDScriptLanguageProtocol::get_singleton()->start(port, IPAddress(host));
if (status != OK) {
EditorNode::get_log()->add_message("--- Failed to start GDScript language server on port " + itos(port) + ": " + error_names[status] + " ---", EditorLog::MSG_TYPE_EDITOR);
return;
}
EditorNode::get_log()->add_message("--- GDScript language server started on port " + itos(port) + " ---", EditorLog::MSG_TYPE_EDITOR);
if (use_thread) {
thread_running = true;
thread.start(GDScriptLanguageServer::thread_main, this);
}
set_process_internal(!use_thread);
started = true;
}
void GDScriptLanguageServer::stop() {
@ -115,7 +112,7 @@ void GDScriptLanguageServer::stop() {
thread_running = false;
thread.wait_to_finish();
}
protocol.stop();
GDScriptLanguageProtocol::get_singleton()->stop();
started = false;
EditorNode::get_log()->add_message("--- GDScript language server stopped ---", EditorLog::MSG_TYPE_EDITOR);
}

View file

@ -30,24 +30,23 @@
#pragma once
#include "gdscript_language_protocol.h"
#include "editor/plugins/editor_plugin.h"
class GDScriptLanguageServer : public EditorPlugin {
GDCLASS(GDScriptLanguageServer, EditorPlugin);
GDScriptLanguageProtocol protocol;
GDSOFTCLASS(GDScriptLanguageServer, EditorPlugin);
Thread thread;
bool thread_running = false;
// There is no notification when the editor is initialized. We need to poll till we attempted to start the server.
bool start_attempted = false;
bool started = false;
// Defaults located in editor_settings.cpp
bool use_thread = false;
String host = "127.0.0.1";
int port = 6005;
int poll_limit_usec = 100000;
String host;
int port = 0;
int poll_limit_usec = 0;
static void thread_main(void *p_userdata);
private:

View file

@ -34,32 +34,35 @@
#include "gdscript_extend_parser.h"
#include "gdscript_language_protocol.h"
#include "editor/script/script_text_editor.h"
#include "core/io/resource_loader.h"
#include "core/object/callable_mp.h"
#include "core/object/class_db.h"
#include "editor/script/script_editor_plugin.h"
#include "editor/settings/editor_settings.h"
#include "servers/display/display_server.h"
void GDScriptTextDocument::_bind_methods() {
ClassDB::bind_method(D_METHOD("didOpen"), &GDScriptTextDocument::didOpen);
ClassDB::bind_method(D_METHOD("didClose"), &GDScriptTextDocument::didClose);
ClassDB::bind_method(D_METHOD("didChange"), &GDScriptTextDocument::didChange);
ClassDB::bind_method(D_METHOD("willSaveWaitUntil"), &GDScriptTextDocument::willSaveWaitUntil);
ClassDB::bind_method(D_METHOD("didSave"), &GDScriptTextDocument::didSave);
ClassDB::bind_method(D_METHOD("nativeSymbol"), &GDScriptTextDocument::nativeSymbol);
ClassDB::bind_method(D_METHOD("documentSymbol"), &GDScriptTextDocument::documentSymbol);
ClassDB::bind_method(D_METHOD("completion"), &GDScriptTextDocument::completion);
ClassDB::bind_method(D_METHOD("resolve"), &GDScriptTextDocument::resolve);
ClassDB::bind_method(D_METHOD("rename"), &GDScriptTextDocument::rename);
ClassDB::bind_method(D_METHOD("prepareRename"), &GDScriptTextDocument::prepareRename);
ClassDB::bind_method(D_METHOD("references"), &GDScriptTextDocument::references);
ClassDB::bind_method(D_METHOD("foldingRange"), &GDScriptTextDocument::foldingRange);
ClassDB::bind_method(D_METHOD("codeLens"), &GDScriptTextDocument::codeLens);
ClassDB::bind_method(D_METHOD("documentLink"), &GDScriptTextDocument::documentLink);
ClassDB::bind_method(D_METHOD("colorPresentation"), &GDScriptTextDocument::colorPresentation);
ClassDB::bind_method(D_METHOD("hover"), &GDScriptTextDocument::hover);
ClassDB::bind_method(D_METHOD("definition"), &GDScriptTextDocument::definition);
ClassDB::bind_method(D_METHOD("declaration"), &GDScriptTextDocument::declaration);
ClassDB::bind_method(D_METHOD("signatureHelp"), &GDScriptTextDocument::signatureHelp);
ClassDB::bind_method(D_METHOD("show_native_symbol_in_editor"), &GDScriptTextDocument::show_native_symbol_in_editor);
ClassDB::bind_method(D_METHOD("show_native_symbol_in_editor", "symbol_id"), &GDScriptTextDocument::show_native_symbol_in_editor);
#ifndef DISABLE_DEPRECATED
ClassDB::bind_method(D_METHOD("didOpen", "params"), &GDScriptTextDocument::didOpen);
ClassDB::bind_method(D_METHOD("didClose", "params"), &GDScriptTextDocument::didClose);
ClassDB::bind_method(D_METHOD("didChange", "params"), &GDScriptTextDocument::didChange);
ClassDB::bind_method(D_METHOD("willSaveWaitUntil", "params"), &GDScriptTextDocument::willSaveWaitUntil);
ClassDB::bind_method(D_METHOD("didSave", "params"), &GDScriptTextDocument::didSave);
ClassDB::bind_method(D_METHOD("nativeSymbol", "params"), &GDScriptTextDocument::nativeSymbol);
ClassDB::bind_method(D_METHOD("documentSymbol", "params"), &GDScriptTextDocument::documentSymbol);
ClassDB::bind_method(D_METHOD("completion", "params"), &GDScriptTextDocument::completion);
ClassDB::bind_method(D_METHOD("resolve", "params"), &GDScriptTextDocument::resolve);
ClassDB::bind_method(D_METHOD("rename", "params"), &GDScriptTextDocument::rename);
ClassDB::bind_method(D_METHOD("prepareRename", "params"), &GDScriptTextDocument::prepareRename);
ClassDB::bind_method(D_METHOD("references", "params"), &GDScriptTextDocument::references);
ClassDB::bind_method(D_METHOD("documentLink", "params"), &GDScriptTextDocument::documentLink);
ClassDB::bind_method(D_METHOD("hover", "params"), &GDScriptTextDocument::hover);
ClassDB::bind_method(D_METHOD("definition", "params"), &GDScriptTextDocument::definition);
ClassDB::bind_method(D_METHOD("declaration", "params"), &GDScriptTextDocument::declaration);
ClassDB::bind_method(D_METHOD("signatureHelp", "params"), &GDScriptTextDocument::signatureHelp);
#endif // !DISABLE_DEPRECATED
}
void GDScriptTextDocument::didOpen(const Variant &p_param) {
@ -122,21 +125,6 @@ void GDScriptTextDocument::notify_client_show_symbol(const LSP::DocumentSymbol *
GDScriptLanguageProtocol::get_singleton()->notify_client("gdscript/show_native_symbol", symbol->to_json(true));
}
void GDScriptTextDocument::initialize() {
if (GDScriptLanguageProtocol::get_singleton()->is_smart_resolve_enabled()) {
for (const KeyValue<StringName, ClassMembers> &E : GDScriptLanguageProtocol::get_singleton()->get_workspace()->native_members) {
const ClassMembers &members = E.value;
for (const KeyValue<String, const LSP::DocumentSymbol *> &F : members) {
const LSP::DocumentSymbol *symbol = members.get(F.key);
LSP::CompletionItem item = symbol->make_completion_item();
item.data = JOIN_SYMBOLS(String(E.key), F.key);
native_member_completions.push_back(item.to_json());
}
}
}
}
Variant GDScriptTextDocument::nativeSymbol(const Dictionary &p_params) {
Variant ret;
@ -165,68 +153,29 @@ Array GDScriptTextDocument::documentSymbol(const Dictionary &p_params) {
return arr;
}
Array GDScriptTextDocument::completion(const Dictionary &p_params) {
Array GDScriptTextDocument::documentHighlight(const Dictionary &p_params) {
Array arr;
LSP::CompletionParams params;
LSP::TextDocumentPositionParams params;
params.load(p_params);
Dictionary request_data = params.to_json();
List<ScriptLanguage::CodeCompletionOption> options;
GDScriptLanguageProtocol::get_singleton()->get_workspace()->completion(params, &options);
const LSP::DocumentSymbol *symbol = GDScriptLanguageProtocol::get_singleton()->get_workspace()->resolve_symbol(params);
if (symbol) {
String path = GDScriptLanguageProtocol::get_singleton()->get_workspace()->get_file_path(params.textDocument.uri);
Vector<LSP::Location> usages = GDScriptLanguageProtocol::get_singleton()->get_workspace()->find_usages_in_file(*symbol, path);
if (!options.is_empty()) {
int i = 0;
arr.resize(options.size());
for (const ScriptLanguage::CodeCompletionOption &option : options) {
LSP::CompletionItem item;
item.label = option.display;
item.data = request_data;
item.insertText = option.insert_text;
switch (option.kind) {
case ScriptLanguage::CODE_COMPLETION_KIND_ENUM:
item.kind = LSP::CompletionItemKind::Enum;
break;
case ScriptLanguage::CODE_COMPLETION_KIND_CLASS:
item.kind = LSP::CompletionItemKind::Class;
break;
case ScriptLanguage::CODE_COMPLETION_KIND_MEMBER:
item.kind = LSP::CompletionItemKind::Property;
break;
case ScriptLanguage::CODE_COMPLETION_KIND_FUNCTION:
item.kind = LSP::CompletionItemKind::Method;
break;
case ScriptLanguage::CODE_COMPLETION_KIND_SIGNAL:
item.kind = LSP::CompletionItemKind::Event;
break;
case ScriptLanguage::CODE_COMPLETION_KIND_CONSTANT:
item.kind = LSP::CompletionItemKind::Constant;
break;
case ScriptLanguage::CODE_COMPLETION_KIND_VARIABLE:
item.kind = LSP::CompletionItemKind::Variable;
break;
case ScriptLanguage::CODE_COMPLETION_KIND_FILE_PATH:
item.kind = LSP::CompletionItemKind::File;
break;
case ScriptLanguage::CODE_COMPLETION_KIND_NODE_PATH:
item.kind = LSP::CompletionItemKind::Snippet;
break;
case ScriptLanguage::CODE_COMPLETION_KIND_PLAIN_TEXT:
item.kind = LSP::CompletionItemKind::Text;
break;
default: {
}
}
arr[i] = item.to_json();
i++;
for (const LSP::Location &usage : usages) {
LSP::DocumentHighlight highlight;
highlight.range = usage.range;
arr.push_back(highlight.to_json());
}
}
return arr;
}
Array GDScriptTextDocument::completion(const Dictionary &p_params) {
return GDScriptLanguageProtocol::get_singleton()->lsp_completion(p_params);
}
Dictionary GDScriptTextDocument::rename(const Dictionary &p_params) {
LSP::TextDocumentPositionParams params;
params.load(p_params);
@ -289,37 +238,11 @@ Dictionary GDScriptTextDocument::resolve(const Dictionary &p_params) {
if (data.get_type() == Variant::DICTIONARY) {
params.load(p_params["data"]);
symbol = GDScriptLanguageProtocol::get_singleton()->get_workspace()->resolve_symbol(params, item.label, item.kind == LSP::CompletionItemKind::Method || item.kind == LSP::CompletionItemKind::Function);
} else if (data.is_string()) {
String query = data;
Vector<String> param_symbols = query.split(SYMBOL_SEPARATOR, false);
if (param_symbols.size() >= 2) {
StringName class_name = param_symbols[0];
const String &member_name = param_symbols[param_symbols.size() - 1];
String inner_class_name;
if (param_symbols.size() >= 3) {
inner_class_name = param_symbols[1];
}
if (const ClassMembers *members = GDScriptLanguageProtocol::get_singleton()->get_workspace()->native_members.getptr(class_name)) {
if (const LSP::DocumentSymbol *const *member = members->getptr(member_name)) {
symbol = *member;
}
}
if (!symbol) {
ExtendGDScriptParser *parser = GDScriptLanguageProtocol::get_singleton()->get_parse_result(class_name);
if (parser) {
symbol = parser->get_member_symbol(member_name, inner_class_name);
}
}
}
}
if (symbol) {
item.documentation = symbol->render();
const HashSet<String> &allowed_tags = GDScriptLanguageProtocol::get_singleton()->get_client_markdown_allowed_html_tags();
item.documentation = symbol->render(allowed_tags);
}
if (item.kind == LSP::CompletionItemKind::Event) {
@ -341,14 +264,6 @@ Dictionary GDScriptTextDocument::resolve(const Dictionary &p_params) {
return item.to_json(true);
}
Array GDScriptTextDocument::foldingRange(const Dictionary &p_params) {
return Array();
}
Array GDScriptTextDocument::codeLens(const Dictionary &p_params) {
return Array();
}
Array GDScriptTextDocument::documentLink(const Dictionary &p_params) {
Array ret;
@ -363,10 +278,6 @@ Array GDScriptTextDocument::documentLink(const Dictionary &p_params) {
return ret;
}
Array GDScriptTextDocument::colorPresentation(const Dictionary &p_params) {
return Array();
}
Variant GDScriptTextDocument::hover(const Dictionary &p_params) {
LSP::TextDocumentPositionParams params;
params.load(p_params);
@ -374,7 +285,8 @@ Variant GDScriptTextDocument::hover(const Dictionary &p_params) {
const LSP::DocumentSymbol *symbol = GDScriptLanguageProtocol::get_singleton()->get_workspace()->resolve_symbol(params);
if (symbol) {
LSP::Hover hover;
hover.contents = symbol->render();
const HashSet<String> &allowed_tags = GDScriptLanguageProtocol::get_singleton()->get_client_markdown_allowed_html_tags();
hover.contents = symbol->render(allowed_tags);
hover.range.start = params.position;
hover.range.end = params.position;
return hover.to_json();
@ -384,9 +296,10 @@ Variant GDScriptTextDocument::hover(const Dictionary &p_params) {
Array contents;
List<const LSP::DocumentSymbol *> list;
GDScriptLanguageProtocol::get_singleton()->resolve_related_symbols(params, list);
const HashSet<String> &allowed_tags = GDScriptLanguageProtocol::get_singleton()->get_client_markdown_allowed_html_tags();
for (const LSP::DocumentSymbol *&E : list) {
if (const LSP::DocumentSymbol *s = E) {
contents.push_back(s->render().value);
contents.push_back(s->render(allowed_tags).value);
}
}
ret["contents"] = contents;
@ -430,7 +343,7 @@ Variant GDScriptTextDocument::declaration(const Dictionary &p_params) {
case LSP::SymbolKind::Function:
id = "class_method:" + symbol->native_class + ":" + symbol->name;
break;
default:
default: // Deprecated.
id = "class_global:" + symbol->native_class + ":" + symbol->name;
break;
}

View file

@ -44,8 +44,6 @@ protected:
Ref<FileAccess> file_checker;
Array native_member_completions;
private:
Array find_symbols(const LSP::TextDocumentPositionParams &p_location, List<const LSP::DocumentSymbol *> &r_list);
void notify_client_show_symbol(const LSP::DocumentSymbol *symbol);
@ -62,21 +60,17 @@ public:
Variant nativeSymbol(const Dictionary &p_params);
Array documentSymbol(const Dictionary &p_params);
Array documentHighlight(const Dictionary &p_params);
Array completion(const Dictionary &p_params);
Dictionary resolve(const Dictionary &p_params);
Dictionary rename(const Dictionary &p_params);
Variant prepareRename(const Dictionary &p_params);
Array references(const Dictionary &p_params);
Array foldingRange(const Dictionary &p_params);
Array codeLens(const Dictionary &p_params);
Array documentLink(const Dictionary &p_params);
Array colorPresentation(const Dictionary &p_params);
Variant hover(const Dictionary &p_params);
Array definition(const Dictionary &p_params);
Variant declaration(const Dictionary &p_params);
Variant signatureHelp(const Dictionary &p_params);
void initialize();
GDScriptTextDocument();
};

View file

@ -30,30 +30,33 @@
#include "gdscript_workspace.h"
#include "../editor/gdscript_editor_language.h"
#include "../gdscript.h"
#include "../gdscript_parser.h"
#include "gdscript_language_protocol.h"
#include "core/config/project_settings.h"
#include "core/object/callable_mp.h"
#include "core/object/class_db.h"
#include "core/object/editor_language.h"
#include "core/object/script_language.h"
#include "editor/doc/doc_tools.h"
#include "editor/doc/editor_help.h"
#include "editor/editor_node.h"
#include "editor/file_system/editor_file_system.h"
#include "editor/settings/editor_settings.h"
#include "scene/resources/packed_scene.h"
void GDScriptWorkspace::_bind_methods() {
ClassDB::bind_method(D_METHOD("apply_new_signal"), &GDScriptWorkspace::apply_new_signal);
ClassDB::bind_method(D_METHOD("apply_new_signal", "obj", "function", "args"), &GDScriptWorkspace::apply_new_signal);
ClassDB::bind_method(D_METHOD("get_file_path", "uri"), &GDScriptWorkspace::get_file_path);
ClassDB::bind_method(D_METHOD("get_file_uri", "path"), &GDScriptWorkspace::get_file_uri);
ClassDB::bind_method(D_METHOD("publish_diagnostics", "path"), &GDScriptWorkspace::publish_diagnostics);
ClassDB::bind_method(D_METHOD("generate_script_api", "path"), &GDScriptWorkspace::generate_script_api);
#ifndef DISABLE_DEPRECATED
ClassDB::bind_method(D_METHOD("didDeleteFiles"), &GDScriptWorkspace::didDeleteFiles);
ClassDB::bind_method(D_METHOD("didDeleteFiles", "params"), &GDScriptWorkspace::didDeleteFiles);
ClassDB::bind_method(D_METHOD("parse_script", "path", "content"), &GDScriptWorkspace::parse_script);
ClassDB::bind_method(D_METHOD("parse_local_script", "path"), &GDScriptWorkspace::parse_script);
ClassDB::bind_method(D_METHOD("parse_local_script", "path"), &GDScriptWorkspace::parse_local_script);
ClassDB::bind_method(D_METHOD("publish_diagnostics", "path"), &GDScriptWorkspace::publish_diagnostics);
#endif
}
@ -422,7 +425,7 @@ bool GDScriptWorkspace::can_rename(const LSP::TextDocumentPositionParams &p_doc_
String path = get_file_path(p_doc_pos.textDocument.uri);
const ExtendGDScriptParser *parser = GDScriptLanguageProtocol::get_singleton()->get_parse_result(path);
if (parser) {
_ALLOW_DISCARD_ parser->get_identifier_under_position(p_doc_pos.position, r_range);
_ALLOW_DISCARD_ parser->get_symbol_name_under_position(p_doc_pos.position, r_range);
r_symbol = *reference_symbol;
return true;
}
@ -433,7 +436,7 @@ bool GDScriptWorkspace::can_rename(const LSP::TextDocumentPositionParams &p_doc_
Vector<LSP::Location> GDScriptWorkspace::find_usages_in_file(const LSP::DocumentSymbol &p_symbol, const String &p_file_path) {
Vector<LSP::Location> usages;
String identifier = p_symbol.name;
const String &identifier = p_symbol.name;
const ExtendGDScriptParser *parser = GDScriptLanguageProtocol::get_singleton()->get_parse_result(p_file_path);
if (parser) {
const PackedStringArray &content = parser->get_lines();
@ -451,18 +454,30 @@ Vector<LSP::Location> GDScriptWorkspace::find_usages_in_file(const LSP::Document
params.position.line = i;
params.position.character = character;
const LSP::DocumentSymbol *other_symbol = resolve_symbol(params);
LSP::Range range;
String identifier_under_cursor = parser->get_symbol_name_under_position(params.position, range);
if (other_symbol == &p_symbol) {
LSP::Location loc;
loc.uri = text_doc.uri;
loc.range.start = params.position;
loc.range.end.line = params.position.line;
loc.range.end.character = params.position.character + identifier.length();
usages.append(loc);
if (identifier_under_cursor == identifier) {
const LSP::DocumentSymbol *other_symbol = resolve_symbol(params);
if (other_symbol == &p_symbol) {
LSP::Location loc;
loc.uri = text_doc.uri;
loc.range.start = params.position;
loc.range.end.line = params.position.line;
loc.range.end.character = params.position.character + identifier.length();
usages.append(loc);
}
}
character = line.find(identifier, character + 1);
if (identifier_under_cursor.length() < identifier.length()) {
// `get_symbol_name_under_position` is supposed to recognize all possible symbol names. Since a simple string search already confirmed
// the presence of `p_symbol.name` in the text, this case has to be a bug.
ERR_PRINT(vformat("LSP Bug, please report. \"get_symbol_name_under_position\" did not correctly resolve \"%s\"", identifier));
character = line.find(identifier, character + 1);
} else {
character = line.find(identifier, range.end.character);
}
}
}
}
@ -592,51 +607,6 @@ void GDScriptWorkspace::publish_diagnostics(const String &p_path) {
GDScriptLanguageProtocol::get_singleton()->notify_client("textDocument/publishDiagnostics", params);
}
void GDScriptWorkspace::_get_owners(EditorFileSystemDirectory *efsd, String p_path, List<String> &owners) {
if (!efsd) {
return;
}
for (int i = 0; i < efsd->get_subdir_count(); i++) {
_get_owners(efsd->get_subdir(i), p_path, owners);
}
for (int i = 0; i < efsd->get_file_count(); i++) {
Vector<String> deps = efsd->get_file_deps(i);
bool found = false;
for (int j = 0; j < deps.size(); j++) {
if (deps[j] == p_path) {
found = true;
break;
}
}
if (!found) {
continue;
}
owners.push_back(efsd->get_file_path(i));
}
}
Node *GDScriptWorkspace::_get_owner_scene_node(String p_path) {
Node *owner_scene_node = nullptr;
List<String> owners;
_get_owners(EditorFileSystem::get_singleton()->get_filesystem(), p_path, owners);
for (const String &owner : owners) {
NodePath owner_path = owner;
Ref<Resource> owner_res = ResourceLoader::load(String(owner_path));
if (Object::cast_to<PackedScene>(owner_res.ptr())) {
Ref<PackedScene> owner_packed_scene = Ref<PackedScene>(Object::cast_to<PackedScene>(*owner_res));
owner_scene_node = owner_packed_scene->instantiate();
break;
}
}
return owner_scene_node;
}
void GDScriptWorkspace::completion(const LSP::CompletionParams &p_params, List<ScriptLanguage::CodeCompletionOption> *r_options) {
String path = get_file_path(p_params.textDocument.uri);
String call_hint;
@ -644,7 +614,7 @@ void GDScriptWorkspace::completion(const LSP::CompletionParams &p_params, List<S
const ExtendGDScriptParser *parser = GDScriptLanguageProtocol::get_singleton()->get_parse_result(path);
if (parser) {
Node *owner_scene_node = _get_owner_scene_node(path);
Node *owner_scene_node = GDScriptLanguageProtocol::get_singleton()->get_scene_cache()->get(path);
Array stack;
Node *current = nullptr;
@ -669,10 +639,7 @@ void GDScriptWorkspace::completion(const LSP::CompletionParams &p_params, List<S
}
String code = parser->get_text_for_completion(p_params.position);
GDScriptLanguage::get_singleton()->complete_code(code, path, current, r_options, forced, call_hint);
if (owner_scene_node) {
memdelete(owner_scene_node);
}
GDScriptEditorLanguage::get_singleton()->complete_code(code, path, current, r_options, forced, call_hint);
}
}
@ -683,46 +650,45 @@ const LSP::DocumentSymbol *GDScriptWorkspace::resolve_symbol(const LSP::TextDocu
const ExtendGDScriptParser *parser = GDScriptLanguageProtocol::get_singleton()->get_parse_result(path);
if (parser) {
String symbol_identifier = p_symbol_name;
if (symbol_identifier.get_slice_count("(") > 0) {
symbol_identifier = symbol_identifier.get_slicec('(', 0);
String symbol_name = p_symbol_name;
if (symbol_name.get_slice_count("(") > 0) {
symbol_name = symbol_name.get_slicec('(', 0);
}
LSP::Position pos = p_doc_pos.position;
if (symbol_identifier.is_empty()) {
if (symbol_name.is_empty()) {
LSP::Range range;
symbol_identifier = parser->get_identifier_under_position(p_doc_pos.position, range);
symbol_name = parser->get_symbol_name_under_position(p_doc_pos.position, range);
pos.character = range.end.character;
}
if (!symbol_identifier.is_empty()) {
if (ScriptServer::is_global_class(symbol_identifier)) {
String class_path = ScriptServer::get_global_class_path(symbol_identifier);
if (!symbol_name.is_empty()) {
if (ScriptServer::is_global_class(symbol_name)) {
String class_path = ScriptServer::get_global_class_path(symbol_name);
symbol = get_script_symbol(class_path);
} else {
ScriptLanguage::LookupResult ret;
if (symbol_identifier == "new" && parser->get_lines()[p_doc_pos.position.line].remove_chars(" \t").contains("new(")) {
symbol_identifier = "_init";
EditorLanguage::LookupResult ret;
// TODO: `lookup_code` should already account for this. We might be able to simplify code here.
if (symbol_name == "new" && parser->get_lines()[p_doc_pos.position.line].remove_chars(" \t").contains("new(")) {
symbol_name = "_init";
}
if (OK == GDScriptLanguage::get_singleton()->lookup_code(parser->get_text_for_lookup_symbol(pos, symbol_identifier, p_func_required), symbol_identifier, path, nullptr, ret)) {
if (OK == GDScriptEditorLanguage::get_singleton()->lookup_code(parser->get_text_for_lookup_symbol(pos, symbol_name, p_func_required), symbol_name, path, nullptr, ret)) {
if (ret.location >= 0) {
String target_script_path = path;
if (ret.script.is_valid()) {
target_script_path = ret.script->get_path();
} else if (!ret.script_path.is_empty()) {
if (!ret.script_path.is_empty()) {
target_script_path = ret.script_path;
}
const ExtendGDScriptParser *target_parser = GDScriptLanguageProtocol::get_singleton()->get_parse_result(target_script_path);
if (target_parser) {
symbol = target_parser->get_symbol_defined_at_line(LINE_NUMBER_TO_INDEX(ret.location), symbol_identifier);
symbol = target_parser->get_symbol_defined_at_line(LINE_NUMBER_TO_INDEX(ret.location), symbol_name);
if (symbol) {
switch (symbol->kind) {
case LSP::SymbolKind::Function: {
if (symbol->name != symbol_identifier) {
symbol = get_parameter_symbol(symbol, symbol_identifier);
if (symbol->name != symbol_name) {
symbol = get_parameter_symbol(symbol, symbol_name);
}
} break;
}
@ -730,15 +696,15 @@ const LSP::DocumentSymbol *GDScriptWorkspace::resolve_symbol(const LSP::TextDocu
}
} else {
String member = ret.class_member;
if (member.is_empty() && symbol_identifier != ret.class_name) {
member = symbol_identifier;
if (member.is_empty() && symbol_name != ret.class_name) {
member = symbol_name;
}
symbol = get_native_symbol(ret.class_name, member);
}
} else {
symbol = get_local_symbol_at(parser, symbol_identifier, p_doc_pos.position);
symbol = get_local_symbol_at(parser, symbol_name, p_doc_pos.position);
if (!symbol) {
symbol = parser->get_member_symbol(symbol_identifier);
symbol = parser->get_member_symbol(symbol_name);
}
}
}
@ -804,7 +770,8 @@ Error GDScriptWorkspace::resolve_signature(const LSP::TextDocumentPositionParams
if (symbol->kind == LSP::SymbolKind::Method || symbol->kind == LSP::SymbolKind::Function) {
LSP::SignatureInformation signature_info;
signature_info.label = symbol->detail;
signature_info.documentation = symbol->render();
const HashSet<String> &allowed_tags = GDScriptLanguageProtocol::get_singleton()->get_client_markdown_allowed_html_tags();
signature_info.documentation = symbol->render(allowed_tags);
for (int i = 0; i < symbol->children.size(); i++) {
const LSP::DocumentSymbol &arg = symbol->children[i];

View file

@ -30,22 +30,18 @@
#pragma once
#include "core/error/error_macros.h"
#include "gdscript_extend_parser.h"
#include "godot_lsp.h"
#include "core/error/error_macros.h"
#include "core/variant/variant.h"
#include "editor/file_system/editor_file_system.h"
class GDScriptWorkspace : public RefCounted {
GDCLASS(GDScriptWorkspace, RefCounted);
private:
void _get_owners(EditorFileSystemDirectory *efsd, String p_path, List<String> &owners);
Node *_get_owner_scene_node(String p_path);
#ifndef DISABLE_DEPRECATED
void didDeleteFiles() {}
void didDeleteFiles(const Dictionary &p_params) {}
Error parse_script(const String &p_path, const String &p_content) {
WARN_DEPRECATED;
return Error::FAILED;
@ -76,9 +72,6 @@ protected:
void apply_new_signal(Object *obj, String function, PackedStringArray args);
public:
String root;
String root_uri;
HashMap<StringName, ClassMembers> native_members;
public:

View file

@ -34,12 +34,21 @@
#include "core/object/class_db.h"
#include "core/templates/list.h"
// Enable additional LSP related logging.
//#define DEBUG_LSP
#ifdef DEBUG_LSP
#define LOG_LSP(...) print_line("[ LSP -", __FILE__, ":", __LINE__, "-", __func__, "] -", ##__VA_ARGS__)
#else
#define LOG_LSP(...)
#endif
namespace LSP {
typedef String DocumentUri;
/** Format BBCode documentation from DocData to markdown */
static String marked_documentation(const String &p_bbcode);
static String marked_documentation(const String &p_bbcode, const HashSet<String> &p_allowed_html_tags);
/**
* Text documents are identified using a URI. On the protocol level, URIs are passed as strings.
@ -101,6 +110,9 @@ struct Position {
dict["character"] = character;
return dict;
}
Position() = default;
Position(int p_line, int p_character) : line(p_line), character(p_character) {}
};
/**
@ -151,6 +163,10 @@ struct Range {
dict["end"] = end.to_json();
return dict;
}
Range() = default;
Range(Position p_start, Position p_end) : start(p_start), end(p_end) {}
Range(int p_start_line, int p_start_column, int p_end_line, int p_end_column) : start(p_start_line, p_start_column), end(p_end_line, p_end_column) {}
};
/**
@ -310,6 +326,13 @@ struct TextEdit {
* empty string.
*/
String newText;
_FORCE_INLINE_ Dictionary to_json() const {
Dictionary dict;
dict["newText"] = newText;
dict["range"] = range.to_json();
return dict;
}
};
/**
@ -490,22 +513,6 @@ struct SignatureHelpOptions {
}
};
/**
* Code Lens options.
*/
struct CodeLensOptions {
/**
* Code lens has a resolve provider as well.
*/
bool resolveProvider = false;
Dictionary to_json() {
Dictionary dict;
dict["resolveProvider"] = resolveProvider;
return dict;
}
};
/**
* Rename options
*/
@ -570,24 +577,6 @@ struct SaveOptions {
}
};
/**
* Color provider options.
*/
struct ColorProviderOptions {
Dictionary to_json() {
return Dictionary();
}
};
/**
* Folding range provider options.
*/
struct FoldingRangeProviderOptions {
Dictionary to_json() {
return Dictionary();
}
};
struct TextDocumentSyncOptions {
/**
* Open and close notifications are sent to the server. If omitted open close notification should not
@ -1067,9 +1056,19 @@ struct CompletionItem {
if (!insertText.is_empty()) {
dict["insertText"] = insertText;
}
if (insertTextFormat) {
dict["insertTextFormat"] = insertTextFormat;
}
if (!textEdit.newText.is_empty()) {
dict["textEdit"] = textEdit.to_json();
}
if (resolved) {
dict["detail"] = detail;
dict["documentation"] = documentation.to_json();
if (!detail.is_empty()) {
dict["detail"] = detail;
}
if (!documentation.value.is_empty()) {
dict["documentation"] = documentation.to_json();
}
dict["deprecated"] = deprecated;
dict["preselect"] = preselect;
if (!sortText.is_empty()) {
@ -1122,6 +1121,7 @@ struct CompletionItem {
if (p_dict.has("insertText")) {
insertText = p_dict["insertText"];
}
insertTextFormat = p_dict.get("insertTextFormat", 0);
if (p_dict.has("data")) {
data = p_dict["data"];
}
@ -1272,61 +1272,19 @@ struct DocumentSymbol {
return dict;
}
_FORCE_INLINE_ MarkupContent render() const {
_FORCE_INLINE_ MarkupContent render(const HashSet<String> &p_allowed_html_tags) const {
MarkupContent markdown;
if (detail.length()) {
markdown.value = "\t" + detail + "\n\n";
}
if (documentation.length()) {
markdown.value += marked_documentation(documentation) + "\n\n";
markdown.value += marked_documentation(documentation, p_allowed_html_tags) + "\n\n";
}
if (script_path.length()) {
markdown.value += "Defined in [" + script_path + "](" + uri + ")";
}
return markdown;
}
_FORCE_INLINE_ CompletionItem make_completion_item(bool resolved = false) const {
LSP::CompletionItem item;
item.label = name;
if (resolved) {
item.documentation = render();
}
switch (kind) {
case LSP::SymbolKind::Enum:
item.kind = LSP::CompletionItemKind::Enum;
break;
case LSP::SymbolKind::Class:
item.kind = LSP::CompletionItemKind::Class;
break;
case LSP::SymbolKind::Property:
item.kind = LSP::CompletionItemKind::Property;
break;
case LSP::SymbolKind::Method:
case LSP::SymbolKind::Function:
item.kind = LSP::CompletionItemKind::Method;
break;
case LSP::SymbolKind::Event:
item.kind = LSP::CompletionItemKind::Event;
break;
case LSP::SymbolKind::Constant:
item.kind = LSP::CompletionItemKind::Constant;
break;
case LSP::SymbolKind::Variable:
item.kind = LSP::CompletionItemKind::Variable;
break;
case LSP::SymbolKind::File:
item.kind = LSP::CompletionItemKind::File;
break;
default:
item.kind = LSP::CompletionItemKind::Text;
break;
}
return item;
}
};
struct ApplyWorkspaceEditParams {
@ -1766,7 +1724,7 @@ struct ServerCapabilities {
/**
* The server provides document highlight support.
*/
bool documentHighlightProvider = false;
bool documentHighlightProvider = true;
/**
* The server provides document symbol support.
@ -1790,11 +1748,6 @@ struct ServerCapabilities {
*/
bool codeActionProvider = false;
/**
* The server provides code lens.
*/
CodeLensOptions codeLensProvider;
/**
* The server provides document formatting.
*/
@ -1822,20 +1775,6 @@ struct ServerCapabilities {
*/
DocumentLinkOptions documentLinkProvider;
/**
* The server provides color provider support.
*
* Since 3.6.0
*/
ColorProviderOptions colorProvider;
/**
* The server provides folding provider support.
*
* Since 3.10.0
*/
FoldingRangeProviderOptions foldingRangeProvider;
/**
* The server provides go to declaration support.
*
@ -1855,12 +1794,9 @@ struct ServerCapabilities {
signatureHelpProvider.triggerCharacters.push_back(",");
signatureHelpProvider.triggerCharacters.push_back("(");
dict["signatureHelpProvider"] = signatureHelpProvider.to_json();
//dict["codeLensProvider"] = codeLensProvider.to_json();
dict["documentOnTypeFormattingProvider"] = documentOnTypeFormattingProvider.to_json();
dict["renameProvider"] = renameProvider.to_json();
dict["documentLinkProvider"] = documentLinkProvider.to_json();
dict["colorProvider"] = false; // colorProvider.to_json();
dict["foldingRangeProvider"] = false; //foldingRangeProvider.to_json();
dict["executeCommandProvider"] = executeCommandProvider.to_json();
dict["hoverProvider"] = hoverProvider;
dict["definitionProvider"] = definitionProvider;
@ -1924,7 +1860,8 @@ struct GodotCapabilities {
};
/** Format BBCode documentation from DocData to markdown */
static String marked_documentation(const String &p_bbcode) {
static String marked_documentation(const String &p_bbcode, const HashSet<String> &p_allowed_html_tags) {
bool span_allowed = p_allowed_html_tags.has("span");
String markdown = p_bbcode.strip_edges();
Vector<String> lines = markdown.split("\n");
@ -2004,7 +1941,6 @@ static String marked_documentation(const String &p_bbcode) {
line = line.replace("[center]", "");
line = line.replace("[/center]", "");
line = line.replace("[/font]", "");
line = line.replace("[/color]", "");
line = line.replace("[/img]", "");
// Convert remaining simple bracketed class names to backticks and literal brackets.
@ -2077,6 +2013,35 @@ static String marked_documentation(const String &p_bbcode) {
pos += replacement.length();
}
// Convert [color] BBCode to <span>, if the client is capable of rendering it, strip it otherwise.
pos = 0;
while ((pos = line.find("[color=", pos)) != -1) {
constexpr int COLOR_OPEN_TAG_LENGTH = 7; // Length of "[color=".
constexpr int COLOR_CLOSE_TAG_LENGTH = 8; // Length of "[/color]".
int color_end = line.find_char(']', pos);
int close_start = line.find("[/color]", color_end);
if (color_end == -1 || close_start == -1) {
break;
}
String text = line.substr(color_end + 1, close_start - color_end - 1);
String replacement;
if (span_allowed) {
const String color = line.substr(pos + COLOR_OPEN_TAG_LENGTH, color_end - pos - COLOR_OPEN_TAG_LENGTH).strip_edges();
if (Color::html_is_valid(color)) {
replacement = "<span style=\"color:" + color + "\">" + text + "</span>";
} else if (int named_color_index = Color::find_named_color(color); named_color_index != -1) {
replacement = "<span style=\"color:#" + Color::get_named_color(named_color_index).to_html(false) + "\">" + text + "</span>";
}
}
// If no span replacement was produced (client can't render spans, or invalid color), just keep the inner text.
if (replacement.is_empty()) {
replacement = text;
}
line = line.substr(0, pos) + replacement + line.substr(close_start + COLOR_CLOSE_TAG_LENGTH);
pos += replacement.length();
}
// Replace the various link types with inline code ([class MyNode] to `MyNode`).
// Uses a while loop because there can occasionally be multiple links of the same type in a single line.
const Vector<String> link_start_patterns = {
@ -2098,10 +2063,10 @@ static String marked_documentation(const String &p_bbcode) {
}
}
// Remove tags with attributes like [color=red], as they don't have a direct Markdown
// Remove tags with attributes like [font=Arial], as they don't have a direct Markdown
// equivalent supported by external tools.
const String attribute_tags[] = {
"color", "font", "img"
"font", "img"
};
for (const String &tag_name : attribute_tags) {
int tag_pos = 0;
@ -2123,4 +2088,26 @@ static String marked_documentation(const String &p_bbcode) {
}
return markdown;
}
/**
* A document highlight is a range inside a text document which deserves
* special attention. Usually a document highlight is visualized by changing
* the background color of its range.
*/
struct DocumentHighlight {
/**
* The range this highlight applies to.
*/
Range range;
_FORCE_INLINE_ Dictionary to_json() const {
Dictionary dict;
dict["range"] = range.to_json();
return dict;
}
_FORCE_INLINE_ void load(const Dictionary &p_params) {
range.load(p_params["range"]);
}
};
} // namespace LSP

View file

@ -0,0 +1,186 @@
/**************************************************************************/
/* scene_cache.cpp */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#include "scene_cache.h"
#include "godot_lsp.h"
#include "core/io/resource_loader.h"
#include "editor/file_system/editor_file_system.h"
#include "scene/resources/packed_scene.h"
void SceneCache::_get_owner_paths(EditorFileSystemDirectory *p_dir, const String &p_script_path, LocalVector<String> &r_owner_paths) {
if (!p_dir) {
return;
}
for (int i = 0; i < p_dir->get_subdir_count(); i++) {
_get_owner_paths(p_dir->get_subdir(i), p_script_path, r_owner_paths);
}
for (int i = 0; i < p_dir->get_file_count(); i++) {
if (p_dir->get_file_deps(i).has(p_script_path)) {
r_owner_paths.push_back(p_dir->get_file_path(i));
}
}
}
void SceneCache::_finalize_scene_load() {
ERR_FAIL_COND(current_loaded_owner.is_empty() || script_path_queue.is_empty());
Ref<PackedScene> scene_res = ResourceLoader::load_threaded_get(current_loaded_owner);
if (scene_res.is_valid()) {
cache[script_path_queue[0]] = scene_res->instantiate();
} else {
cache[script_path_queue[0]] = nullptr;
}
LOG_LSP("Scene cached for script:", script_path_queue[0]);
LOG_LSP("pending_script_queue length:", script_path_queue.size() - 1);
script_path_queue.remove_at(0);
current_loaded_owner = String();
}
void SceneCache::poll() {
if (current_loaded_owner.is_empty()) {
// No load ongoing, start the next one.
if (EditorFileSystem::get_singleton()->is_scanning() || script_path_queue.is_empty()) {
return;
}
LocalVector<String> owners;
_get_owner_paths(EditorFileSystem::get_singleton()->get_filesystem(), script_path_queue[0], owners);
for (const String &owner : owners) {
if (ResourceLoader::load_threaded_request(owner) == Error::OK) {
current_loaded_owner = owner;
LOG_LSP("Scene load started for:", current_loaded_owner);
break;
}
}
if (current_loaded_owner.is_empty()) {
cache[script_path_queue[0]] = nullptr;
LOG_LSP("No scene found for script:", script_path_queue[0]);
script_path_queue.remove_at(0);
LOG_LSP("pending_script_queue length:", script_path_queue.size());
}
} else {
ERR_FAIL_COND(script_path_queue.is_empty());
// There is an ongoing load. Check the status.
ResourceLoader::ThreadLoadStatus status = ResourceLoader::load_threaded_get_status(current_loaded_owner);
if (status == ResourceLoader::THREAD_LOAD_IN_PROGRESS) {
return;
}
if (status == ResourceLoader::THREAD_LOAD_LOADED) {
_finalize_scene_load();
} else {
LOG_LSP("Scene load failure for:", current_loaded_owner);
cache[script_path_queue[0]] = nullptr;
script_path_queue.remove_at(0);
current_loaded_owner = String();
}
}
}
Node *SceneCache::get(const String &p_script_path) {
if (!script_path_queue.is_empty() && script_path_queue[0] == p_script_path && !current_loaded_owner.is_empty()) {
_finalize_scene_load();
} else {
script_path_queue.erase(p_script_path);
}
if (Node **entry = cache.getptr(p_script_path)) {
return *entry;
}
// Fallback to blocking load. This could happen if the open request was only recently sent.
// TODO: This could also happen when multiple clients are connected.
LocalVector<String> owners;
_get_owner_paths(EditorFileSystem::get_singleton()->get_filesystem(), p_script_path, owners);
for (const String &owner : owners) {
Ref<PackedScene> scene = ResourceLoader::load(owner);
if (scene.is_valid()) {
Node *instance = scene->instantiate();
cache[p_script_path] = instance;
return instance;
}
}
cache[p_script_path] = nullptr;
return nullptr;
}
void SceneCache::request_load(const String &p_script_path) {
if (!cache.has(p_script_path) && !script_path_queue.has(p_script_path)) {
script_path_queue.push_back(p_script_path);
LOG_LSP("Scene load requested for:", p_script_path);
LOG_LSP("pending_script_queue length:", script_path_queue.size());
}
}
void SceneCache::unload(const String &p_script_path) {
if (!script_path_queue.is_empty() && script_path_queue[0] == p_script_path && !current_loaded_owner.is_empty()) {
_ALLOW_DISCARD_ ResourceLoader::load_threaded_get(current_loaded_owner);
script_path_queue.remove_at(0);
current_loaded_owner = String();
} else {
script_path_queue.erase(p_script_path);
}
if (!cache.has(p_script_path)) {
return;
}
memdelete_notnull(cache[p_script_path]);
cache.erase(p_script_path);
LOG_LSP("Cache cleared for path:", p_script_path);
}
void SceneCache::clear() {
if (!current_loaded_owner.is_empty()) {
_ALLOW_DISCARD_ ResourceLoader::load_threaded_get(current_loaded_owner);
current_loaded_owner = String();
}
script_path_queue.clear();
for (const KeyValue<String, Node *> &E : cache) {
memdelete_notnull(E.value);
}
cache.clear();
LOG_LSP("Cache cleared.");
}

View file

@ -0,0 +1,65 @@
/**************************************************************************/
/* scene_cache.h */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#pragma once
#include "core/string/ustring.h"
#include "core/templates/hash_map.h"
#include "core/templates/local_vector.h"
class Node;
class EditorFileSystemDirectory;
class PackedScene;
/**
* Used to load and cache scene instances for LSP autocompletion.
*
* This implementation is not thread safe.
*/
class SceneCache {
// Always contains the path to the scene which is currently loaded via the `ResourceLoader`.
// If this is not empty, `script_path_queue` must have at least one element.
String current_loaded_owner;
LocalVector<String> script_path_queue;
HashMap<String, Node *> cache;
void _get_owner_paths(EditorFileSystemDirectory *p_dir, const String &p_script_path, LocalVector<String> &r_owner_paths);
void _finalize_scene_load();
public:
void poll();
void clear();
void request_load(const String &p_script_path);
void unload(const String &p_script_path);
Node *get(const String &p_script_path);
};

View file

@ -33,14 +33,18 @@
#include "gdscript.h"
#include "gdscript_cache.h"
#include "gdscript_parser.h"
#include "gdscript_resource_format.h"
#include "gdscript_tokenizer_buffer.h"
#include "gdscript_utility_functions.h"
#ifdef TOOLS_ENABLED
#include "editor/gdscript_editor_language.h"
#include "editor/gdscript_highlighter.h"
#include "editor/gdscript_translation_parser_plugin.h"
#include "editor/script/script_editor_plugin.h"
#ifndef GDSCRIPT_NO_LSP
#include "language_server/gdscript_language_protocol.h"
#include "language_server/gdscript_language_server.h"
#endif
#endif // TOOLS_ENABLED
@ -51,6 +55,8 @@
#include "core/io/file_access.h"
#include "core/io/resource_loader.h"
#include "core/io/resource_saver.h"
#include "core/object/class_db.h"
#ifdef TOOLS_ENABLED
#include "editor/editor_node.h"
@ -75,8 +81,8 @@ GDScriptCache *gdscript_cache = nullptr;
Ref<GDScriptEditorTranslationParserPlugin> gdscript_translation_parser_plugin;
class EditorExportGDScript : public EditorExportPlugin {
GDCLASS(EditorExportGDScript, EditorExportPlugin);
class GDScriptExportPlugin : public EditorExportPlugin {
GDSOFTCLASS(GDScriptExportPlugin, EditorExportPlugin);
static constexpr EditorExportPreset::ScriptExportMode DEFAULT_SCRIPT_MODE = EditorExportPreset::MODE_SCRIPT_BINARY_TOKENS_COMPRESSED;
EditorExportPreset::ScriptExportMode script_mode = DEFAULT_SCRIPT_MODE;
@ -116,7 +122,7 @@ public:
};
static void _editor_init() {
Ref<EditorExportGDScript> gd_export;
Ref<GDScriptExportPlugin> gd_export;
gd_export.instantiate();
EditorExport::get_singleton()->add_export_plugin(gd_export);
@ -125,13 +131,6 @@ static void _editor_init() {
gdscript_syntax_highlighter.instantiate();
ScriptEditor::get_singleton()->register_syntax_highlighter(gdscript_syntax_highlighter);
#endif
#ifndef GDSCRIPT_NO_LSP
register_lsp_types();
GDScriptLanguageServer *lsp_plugin = memnew(GDScriptLanguageServer);
EditorNode::get_singleton()->add_editor_plugin(lsp_plugin);
Engine::get_singleton()->add_singleton(Engine::Singleton("GDScriptLanguageProtocol", GDScriptLanguageProtocol::get_singleton()));
#endif // !GDSCRIPT_NO_LSP
}
#endif // TOOLS_ENABLED
@ -139,6 +138,7 @@ static void _editor_init() {
void initialize_gdscript_module(ModuleInitializationLevel p_level) {
if (p_level == MODULE_INITIALIZATION_LEVEL_SERVERS) {
GDREGISTER_CLASS(GDScript);
GDREGISTER_INTERNAL_CLASS(GDScriptFunctionState);
script_language_gd = memnew(GDScriptLanguage);
ScriptServer::register_language(script_language_gd);
@ -161,7 +161,18 @@ void initialize_gdscript_module(ModuleInitializationLevel p_level) {
gdscript_translation_parser_plugin.instantiate();
EditorTranslationParser::get_singleton()->add_parser(gdscript_translation_parser_plugin, EditorTranslationParser::STANDARD);
} else if (p_level == MODULE_INITIALIZATION_LEVEL_EDITOR) {
memnew(GDScriptEditorLanguage);
GDREGISTER_CLASS(GDScriptSyntaxHighlighter);
#ifndef GDSCRIPT_NO_LSP
register_lsp_types();
memnew(GDScriptLanguageProtocol);
EditorPlugins::add_by_type<GDScriptLanguageServer>();
Engine::Singleton singleton("GDScriptLanguageProtocol", GDScriptLanguageProtocol::get_singleton());
singleton.editor_only = true;
Engine::get_singleton()->add_singleton(singleton);
#endif // !GDSCRIPT_NO_LSP
}
#endif // TOOLS_ENABLED
}
@ -192,6 +203,10 @@ void uninitialize_gdscript_module(ModuleInitializationLevel p_level) {
if (p_level == MODULE_INITIALIZATION_LEVEL_EDITOR) {
EditorTranslationParser::get_singleton()->remove_parser(gdscript_translation_parser_plugin, EditorTranslationParser::STANDARD);
gdscript_translation_parser_plugin.unref();
#ifndef GDSCRIPT_NO_LSP
memdelete(GDScriptLanguageProtocol::get_singleton());
#endif // GDSCRIPT_NO_LSP
memdelete(GDScriptEditorLanguage::get_singleton());
}
#endif // TOOLS_ENABLED
}

View file

@ -39,18 +39,18 @@
#include "core/config/project_settings.h"
#include "core/core_globals.h"
#include "core/io/dir_access.h"
#include "core/io/file_access_pack.h"
#include "core/io/file_access.h"
#include "core/io/resource_loader.h"
#include "core/io/resource_uid.h"
#include "core/object/class_db.h"
#include "core/os/os.h"
#include "core/string/string_builder.h"
#include "scene/resources/packed_scene.h"
#include "tests/test_macros.h"
namespace GDScriptTests {
void init_autoloads() {
HashMap<StringName, ProjectSettings::AutoloadInfo> autoloads = ProjectSettings::get_singleton()->get_autoload_list();
// First pass, add the constants so they exist before any script is loaded.
for (const KeyValue<StringName, ProjectSettings::AutoloadInfo> &E : ProjectSettings::get_singleton()->get_autoload_list()) {
const ProjectSettings::AutoloadInfo &info = E.value;
@ -573,9 +573,21 @@ GDScriptTest::TestResult GDScriptTest::execute_test_code(bool p_is_generating) {
result.status = GDTEST_ANALYZER_ERROR;
result.output = get_text_for_status(result.status) + "\n";
// Errors are stored in the order they were added, which may not match the source code.
// Here we sort only by lines, preserving the original order for columns.
// So, within a single line, the primary error is printed first, not cascading ones.
struct SortErrors {
_FORCE_INLINE_ bool operator()(const GDScriptParser::ParserError &p_a, const GDScriptParser::ParserError &p_b) const {
return p_a.start_line < p_b.start_line;
}
};
List<GDScriptParser::ParserError> errors = List<GDScriptParser::ParserError>(parser.get_errors());
errors.sort_custom<SortErrors>();
StringBuilder error_string;
for (const GDScriptParser::ParserError &error : parser.get_errors()) {
error_string.append(vformat(">> ERROR at line %d: %s\n", error.line, error.message));
for (const GDScriptParser::ParserError &error : errors) {
error_string.append(vformat(">> ERROR at line %d: %s\n", error.start_line, error.message));
}
result.output += error_string.as_string();
if (!p_is_generating) {

View file

@ -30,10 +30,9 @@
#pragma once
#include "../gdscript.h"
#include "core/error/error_macros.h"
#include "core/string/print_string.h"
#include "core/string/string_name.h"
#include "core/string/ustring.h"
#include "core/templates/vector.h"

View file

@ -30,12 +30,18 @@
#pragma once
#include "../gdscript_cache.h"
#include "gdscript_test_runner.h"
#include "modules/gdscript/gdscript_cache.h"
#include "core/io/file_access.h"
#include "core/io/resource_loader.h"
#include "tests/test_macros.h"
#include "tests/test_utils.h"
#ifdef TOOLS_ENABLED
#include "core/os/os.h"
#endif
namespace GDScriptTests {
class TestGDScriptCacheAccessor {
@ -86,6 +92,7 @@ func _init():
}
TEST_CASE("[Modules][GDScript] Loading keeps ResourceCache and GDScriptCache in sync") {
GDScriptLanguage::get_singleton()->init();
const String path = TestUtils::get_temp_path("gdscript_load_test.gd");
{

View file

@ -1,5 +0,0 @@
GDTEST_ANALYZER_ERROR
>> ERROR at line 9: Native class "InstancePlaceholder" cannot be constructed as it is abstract.
>> ERROR at line 9: Name "new" is a Callable. You can call it with "new.call()" instead.
>> ERROR at line 10: Class "abstract_class_instantiate.gd::B" cannot be constructed as it is based on abstract native class "InstancePlaceholder".
>> ERROR at line 10: Name "new" is a Callable. You can call it with "new.call()" instead.

View file

@ -1,13 +1,13 @@
GDTEST_ANALYZER_ERROR
>> ERROR at line 37: "@abstract" annotation can only be used once per class.
>> ERROR at line 28: "@abstract" annotation can only be used once per function.
>> ERROR at line 35: "@abstract" annotation cannot be applied to static functions.
>> ERROR at line 40: A lambda function must have a ":" followed by a body.
>> ERROR at line 41: A lambda function must have a ":" followed by a body.
>> ERROR at line 11: Class "Test1" is not abstract but contains abstract methods. Mark the class as "@abstract" or remove "@abstract" from all methods in this class.
>> ERROR at line 14: Class "Test2" must implement "AbstractClass.some_func()" and other inherited abstract methods or be marked as "@abstract".
>> ERROR at line 17: Class "Test3" must implement "AbstractClassAgain.some_func()" and other inherited abstract methods or be marked as "@abstract".
>> ERROR at line 22: Cannot call the parent class' abstract function "some_func()" because it hasn't been defined.
>> ERROR at line 25: Cannot call the parent class' abstract function "some_func()" because it hasn't been defined.
>> ERROR at line 28: "@abstract" annotation can only be used once per function.
>> ERROR at line 32: An abstract function cannot have a body.
>> ERROR at line 35: "@abstract" annotation cannot be applied to static functions.
>> ERROR at line 35: A function must either have a ":" followed by a body, or be marked as "@abstract".
>> ERROR at line 37: "@abstract" annotation can only be used once per class.
>> ERROR at line 40: A lambda function must have a ":" followed by a body.
>> ERROR at line 41: A lambda function must have a ":" followed by a body.

View file

@ -0,0 +1,14 @@
enum { VALUE }
enum NamedEnum { VALUE }
const CONST = 0
signal signal_1
signal signal_2
func test():
VALUE = 1
NamedEnum = {}
NamedEnum.VALUE = 1
CONST = 1
signal_1 = signal_2

View file

@ -0,0 +1,6 @@
GDTEST_ANALYZER_ERROR
>> ERROR at line 10: Cannot assign a new value to a constant.
>> ERROR at line 11: Cannot assign a new value to a constant.
>> ERROR at line 12: Cannot assign a new value to a constant.
>> ERROR at line 13: Cannot assign a new value to a constant.
>> ERROR at line 14: Cannot assign a new value to a constant.

View file

@ -1,3 +0,0 @@
enum { V }
func test():
V = 1

View file

@ -1,2 +0,0 @@
GDTEST_ANALYZER_ERROR
>> ERROR at line 3: Cannot assign a new value to a constant.

View file

@ -1,3 +0,0 @@
enum NamedEnum { V }
func test():
NamedEnum.V = 1

View file

@ -1,2 +0,0 @@
GDTEST_ANALYZER_ERROR
>> ERROR at line 3: Cannot assign a new value to a constant.

View file

@ -1,4 +0,0 @@
signal your_base
signal my_base
func test():
your_base = my_base

View file

@ -1,2 +0,0 @@
GDTEST_ANALYZER_ERROR
>> ERROR at line 4: Cannot assign a new value to a constant.

View file

@ -1,4 +1,10 @@
func test():
# Directly.
var tree := SceneTree.new()
tree.root = Window.new()
tree.free()
# Indirectly.
var state := PhysicsDirectBodyState3DExtension.new()
state.center_of_mass.x += 1.0
state.free()

View file

@ -1,2 +1,3 @@
GDTEST_ANALYZER_ERROR
>> ERROR at line 3: Cannot assign a new value to a read-only property.
>> ERROR at line 4: Cannot assign a new value to a read-only property.
>> ERROR at line 9: Cannot assign a new value to a read-only property.

View file

@ -1,4 +0,0 @@
func test():
var state := PhysicsDirectBodyState3DExtension.new()
state.center_of_mass.x += 1.0
state.free()

View file

@ -1,2 +0,0 @@
GDTEST_ANALYZER_ERROR
>> ERROR at line 3: Cannot assign a new value to a read-only property.

View file

@ -1,2 +1,3 @@
GDTEST_ANALYZER_ERROR
>> ERROR at line 2: Invalid operands to operator <<, float and int.
>> ERROR at line 3: Invalid operands to operator >>, int and float.

View file

@ -1,3 +0,0 @@
func test():
# Error here.
print(2.2 << 4)

View file

@ -1,2 +0,0 @@
GDTEST_ANALYZER_ERROR
>> ERROR at line 3: Invalid operands to operator <<, float and int.

View file

@ -1,3 +0,0 @@
func test():
var integer := 1
print(integer as Array)

View file

@ -1,2 +0,0 @@
GDTEST_ANALYZER_ERROR
>> ERROR at line 3: Invalid cast. Cannot convert from "int" to "Array".

View file

@ -1,3 +0,0 @@
func test():
var integer := 1
print(integer as Node)

View file

@ -1,2 +0,0 @@
GDTEST_ANALYZER_ERROR
>> ERROR at line 3: Invalid cast. Cannot convert from "int" to "Node".

View file

@ -1,2 +0,0 @@
GDTEST_ANALYZER_ERROR
>> ERROR at line 3: Invalid cast. Cannot convert from "RefCounted" to "int".

View file

@ -1,5 +0,0 @@
class Vector2:
pass
func test():
pass

View file

@ -1,2 +0,0 @@
GDTEST_ANALYZER_ERROR
>> ERROR at line 1: Class "Vector2" hides a built-in type.

View file

@ -0,0 +1,20 @@
# GH-118877
class Test1:
func _get_property_list() -> Array[int]:
return []
class Test2:
func _get_property_list() -> int:
return 0
class Test3:
func _get_property_list() -> void:
pass
class Test4:
func _get_property_list():
return 0
func test():
pass

View file

@ -0,0 +1,6 @@
GDTEST_ANALYZER_ERROR
>> ERROR at line 4: The function signature doesn't match the parent. Parent signature is "_get_property_list() -> Array[Dictionary]".
>> ERROR at line 8: The function signature doesn't match the parent. Parent signature is "_get_property_list() -> Array[Dictionary]".
>> ERROR at line 12: The function signature doesn't match the parent. Parent signature is "_get_property_list() -> Array[Dictionary]".
>> ERROR at line 17: Cannot return a value of type "int" as "Array".
>> ERROR at line 17: Cannot return value of type "int" because the function return type is "Array".

View file

@ -0,0 +1,10 @@
func test():
# GH-89357
print(Color(""))
print(Color("invalid"))
# GH-120049
print(char(-1))
print(char(0)) # The NUL character is not currently supported.
print(char(0xD800))
print(char(0x110000))

View file

@ -0,0 +1,7 @@
GDTEST_ANALYZER_ERROR
>> ERROR at line 3: Invalid color code "".
>> ERROR at line 4: Invalid color code "invalid".
>> ERROR at line 7: Invalid argument for "char()" function: -1 is not a valid character code.
>> ERROR at line 8: Invalid argument for "char()" function: 0 is not a valid character code.
>> ERROR at line 9: Invalid argument for "char()" function: 55296 is not a valid character code.
>> ERROR at line 10: Invalid argument for "char()" function: 1114112 is not a valid character code.

View file

@ -1,5 +0,0 @@
const array: Array = [0]
func test():
var key: int = 0
array[key] = 0

View file

@ -1,2 +0,0 @@
GDTEST_ANALYZER_ERROR
>> ERROR at line 5: Cannot assign a new value to a constant.

View file

@ -0,0 +1,7 @@
const ARRAY: Array = [0]
const DICTIONARY: Dictionary = { 0: 0 }
func test():
var key: int = 0
ARRAY[key] = 0
DICTIONARY[key] = 0

View file

@ -1,2 +1,3 @@
GDTEST_ANALYZER_ERROR
>> ERROR at line 6: Cannot assign a new value to a constant.
>> ERROR at line 7: Cannot assign a new value to a constant.

View file

@ -1,5 +0,0 @@
const dictionary := {}
func test():
var key: int = 0
dictionary[key] = 0

View file

@ -1,2 +0,0 @@
GDTEST_ANALYZER_ERROR
>> ERROR at line 5: Cannot assign a new value to a constant.

View file

@ -1,4 +0,0 @@
const Vector2 = 0
func test():
pass

View file

@ -1,2 +0,0 @@
GDTEST_ANALYZER_ERROR
>> ERROR at line 1: The member "Vector2" cannot have the same name as a builtin type.

View file

@ -1,5 +0,0 @@
const CONSTANT = 25
func test():
CONSTANT(123)

Some files were not shown because too many files have changed in this diff Show more