Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions doc/classes/ProjectSettings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -673,6 +673,9 @@
<member name="debug/gdscript/warnings/unused_signal" type="int" setter="" getter="" default="1">
When set to [code]warn[/code] or [code]error[/code], produces a warning or an error respectively when a signal is declared but never explicitly used in the class.
</member>
<member name="debug/gdscript/warnings/unused_static_overriding_trait" type="int" setter="" getter="" default="1">
When set to [code]warn[/code] or [code]error[/code], produces a warning or an error respectively when an overridden static member from a trait is not declared with the [code]static[/code] keyword.
</member>
<member name="debug/gdscript/warnings/unused_variable" type="int" setter="" getter="" default="1">
When set to [code]warn[/code] or [code]error[/code], produces a warning or an error respectively when a local variable is unused.
</member>
Expand Down
5 changes: 5 additions & 0 deletions modules/gdscript/editor/gdscript_docgen.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,7 @@ void GDScriptDocGen::_doctype_from_gdtype(const GDType &p_gdtype, String &r_type
}
r_type = "Object";
return;
case GDType::TRAIT:
case GDType::CLASS:
if (p_gdtype.is_meta_type) {
r_type = GDScript::get_class_static();
Expand Down Expand Up @@ -369,6 +370,10 @@ void GDScriptDocGen::_generate_docs(GDScript *p_script, const GDP::ClassNode *p_
for (const GDP::ClassNode::Member &member : p_class->members) {
switch (member.type) {
case GDP::ClassNode::Member::CLASS: {
if (p_class->type == GDP::Node::TRAIT) {
// Trait do not have scripts for their inner classes.
continue;
}
const GDP::ClassNode *inner_class = member.m_class;
const StringName &class_name = inner_class->identifier->name;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,7 @@ void GDScriptEditorTranslationParserPlugin::_traverse_class(const GDScriptParser
const GDScriptParser::ClassNode::Member &m = p_class->members[i];
// Other member types can't contain translatable strings.
switch (m.type) {
case GDScriptParser::ClassNode::Member::TRAIT:
case GDScriptParser::ClassNode::Member::CLASS:
_traverse_class(m.m_class);
break;
Expand Down
3 changes: 2 additions & 1 deletion modules/gdscript/gdscript.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2752,7 +2752,8 @@ Vector<String> GDScriptLanguage::get_reserved_words() const {
"namespace", // Reserved for potential future use.
"signal",
"static",
"trait", // Reserved for potential future use.
"trait",
"uses",
"var",
// Other keywords.
"await",
Expand Down
872 changes: 821 additions & 51 deletions modules/gdscript/gdscript_analyzer.cpp

Large diffs are not rendered by default.

10 changes: 10 additions & 0 deletions modules/gdscript/gdscript_analyzer.h
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,8 @@ class GDScriptAnalyzer {
void resolve_class_interface(GDScriptParser::ClassNode *p_class, bool p_recursive);
void resolve_class_body(GDScriptParser::ClassNode *p_class, const GDScriptParser::Node *p_source = nullptr);
void resolve_class_body(GDScriptParser::ClassNode *p_class, bool p_recursive);
void resolve_class_uses(GDScriptParser::ClassNode *p_class, const GDScriptParser::Node *p_source = nullptr);
void resolve_class_uses(GDScriptParser::ClassNode *p_class, bool p_recursive);
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);
Expand Down Expand Up @@ -153,9 +155,17 @@ class GDScriptAnalyzer {
void is_shadowing(GDScriptParser::IdentifierNode *p_identifier, const String &p_context, const bool p_in_local_scope);
#endif

// Resolving Traits Helpers.
void override_member_function(GDScriptParser::FunctionNode *p_target_function, const GDScriptParser::FunctionNode *p_source_function, const String &p_trait_name);
void extend_class(GDScriptParser::ClassNode *p_class, const GDScriptParser::ClassNode *p_trait, const GDScriptParser::Node *p_trait_name_node, const String &p_trait_name);
#ifdef TOOLS_ENABLED
void copy_over_member_doc_data(GDScriptParser::MemberDocData &p_target_doc_data, const GDScriptParser::MemberDocData &p_source_doc_data);
#endif

public:
Error resolve_inheritance();
Error resolve_interface();
Error resolve_uses();
Error resolve_body();
Error resolve_dependencies();
Error analyze();
Expand Down
10 changes: 9 additions & 1 deletion modules/gdscript/gdscript_cache.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -91,13 +91,21 @@ Error GDScriptParserRef::raise_status(Status p_new_status) {
result = get_analyzer()->resolve_inheritance();
} break;
case INHERITANCE_SOLVED: {
status = USES_SOLVED;
result = get_analyzer()->resolve_uses();
} break;
case USES_SOLVED: {
status = INTERFACE_SOLVED;
result = get_analyzer()->resolve_interface();
} break;
case INTERFACE_SOLVED: {
status = FULLY_SOLVED;
status = BODY_SOLVED;
result = get_analyzer()->resolve_body();
} break;
case BODY_SOLVED: {
status = FULLY_SOLVED;
result = get_analyzer()->resolve_dependencies();
} break;
case FULLY_SOLVED: {
return result;
}
Expand Down
2 changes: 2 additions & 0 deletions modules/gdscript/gdscript_cache.h
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,9 @@ class GDScriptParserRef : public RefCounted {
EMPTY,
PARSED,
INHERITANCE_SOLVED,
USES_SOLVED,
INTERFACE_SOLVED,
BODY_SOLVED,
FULLY_SOLVED,
};

Expand Down
10 changes: 10 additions & 0 deletions modules/gdscript/gdscript_compiler.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,7 @@ GDScriptDataType GDScriptCompiler::_gdtype_from_datatype(const GDScriptParser::D
result.script_type = result.script_type_ref.ptr();
result.native_type = p_datatype.native_type;
} break;
case GDScriptParser::DataType::TRAIT:
case GDScriptParser::DataType::CLASS: {
if (p_handle_metatype && p_datatype.is_meta_type) {
result.kind = GDScriptDataType::NATIVE;
Expand All @@ -149,6 +150,9 @@ GDScriptDataType GDScriptCompiler::_gdtype_from_datatype(const GDScriptParser::D
result.kind = GDScriptDataType::GDSCRIPT;
result.builtin_type = p_datatype.builtin_type;
result.native_type = p_datatype.native_type;
if (p_datatype.kind == GDScriptParser::DataType::TRAIT) {
result.kind = GDScriptDataType::GDTRAIT;
}

bool is_local_class = parser->has_class(p_datatype.class_type);

Expand Down Expand Up @@ -357,6 +361,7 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &code
}
} break;
case GDScriptParser::IdentifierNode::MEMBER_CONSTANT:
case GDScriptParser::IdentifierNode::MEMBER_TRAIT:
case GDScriptParser::IdentifierNode::MEMBER_CLASS: {
// Try class constants.
GDScript *owner = codegen.script;
Expand Down Expand Up @@ -2986,6 +2991,11 @@ Error GDScriptCompiler::_prepare_compilation(GDScript *p_script, const GDScriptP
}

Error GDScriptCompiler::_compile_class(GDScript *p_script, const GDScriptParser::ClassNode *p_class, bool p_keep_state) {
if (p_class->type == GDScriptParser::Node::TRAIT) {
// No need to compile traits.
return OK;
}

// Compile member functions, getters, and setters.
for (int i = 0; i < p_class->members.size(); i++) {
const GDScriptParser::ClassNode::Member &member = p_class->members[i];
Expand Down
60 changes: 53 additions & 7 deletions modules/gdscript/gdscript_editor.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1048,7 +1048,7 @@ static void _find_global_enums(HashMap<String, ScriptLanguage::CodeCompletionOpt
}
}

static void _list_available_types(bool p_inherit_only, GDScriptParser::CompletionContext &p_context, HashMap<String, ScriptLanguage::CodeCompletionOption> &r_result) {
static void _list_available_types(bool p_inherit_only, bool p_include_trait, GDScriptParser::CompletionContext &p_context, HashMap<String, ScriptLanguage::CodeCompletionOption> &r_result) {
// Built-in Variant Types
_find_built_in_variants(r_result, true);

Expand Down Expand Up @@ -1084,6 +1084,12 @@ static void _list_available_types(bool p_inherit_only, GDScriptParser::Completio
ScriptLanguage::CodeCompletionOption option(member.m_class->identifier->name, ScriptLanguage::CODE_COMPLETION_KIND_CLASS, ScriptLanguage::LOCATION_LOCAL + location_offset);
r_result.insert(option.display, option);
} break;
case GDScriptParser::ClassNode::Member::TRAIT: {
if (p_include_trait) {
ScriptLanguage::CodeCompletionOption option(member.m_class->identifier->name, ScriptLanguage::CODE_COMPLETION_KIND_CLASS, ScriptLanguage::LOCATION_LOCAL + location_offset);
r_result.insert(option.display, option);
}
} break;
case GDScriptParser::ClassNode::Member::ENUM: {
if (!p_inherit_only) {
ScriptLanguage::CodeCompletionOption option(member.m_enum->identifier->name, ScriptLanguage::CODE_COMPLETION_KIND_ENUM, ScriptLanguage::LOCATION_LOCAL + location_offset);
Expand Down Expand Up @@ -1181,6 +1187,7 @@ static void _find_identifiers_in_class(const GDScriptParser::ClassNode *p_class,
option.default_value = member.constant->initializer->reduced_value;
}
break;
case GDScriptParser::ClassNode::Member::TRAIT:
case GDScriptParser::ClassNode::Member::CLASS:
if (p_only_functions) {
continue;
Expand Down Expand Up @@ -1261,6 +1268,7 @@ static void _find_identifiers_in_base(const GDScriptCompletionIdentifier &p_base

while (!base_type.has_no_type()) {
switch (base_type.kind) {
case GDScriptParser::DataType::TRAIT:
case GDScriptParser::DataType::CLASS: {
_find_identifiers_in_class(base_type.class_type, p_only_functions, p_types_only, base_type.is_meta_type, false, p_add_braces, r_result, p_recursion_depth);
// This already finds all parent identifiers, so we are done.
Expand Down Expand Up @@ -1565,7 +1573,7 @@ static void _find_identifiers(const GDScriptParser::CompletionContext &p_context
}

static const char *_keywords_with_space[] = {
"and", "not", "or", "in", "as", "class", "class_name", "extends", "is", "func", "signal", "await",
"and", "not", "or", "in", "as", "class", "class_name", "trait", "extends", "uses", "is", "func", "signal", "await",
"const", "enum", "static", "var", "if", "elif", "else", "for", "match", "when", "while",
nullptr
};
Expand Down Expand Up @@ -1892,6 +1900,7 @@ static bool _guess_expression_type(GDScriptParser::CompletionContext &p_context,
r_type = _type_from_variant(p_expression->reduced_value, p_context);
switch (p_expression->get_datatype().kind) {
case GDScriptParser::DataType::ENUM:
case GDScriptParser::DataType::TRAIT:
case GDScriptParser::DataType::CLASS:
r_type.type = p_expression->get_datatype();
break;
Expand Down Expand Up @@ -2256,7 +2265,7 @@ static bool _guess_expression_type(GDScriptParser::CompletionContext &p_context,
}

// If the found type was not fully analyzed we analyze it now.
if (found && r_type.type.kind == GDScriptParser::DataType::CLASS && !r_type.type.class_type->resolved_body) {
if (found && (r_type.type.kind == GDScriptParser::DataType::CLASS || r_type.type.kind == GDScriptParser::DataType::TRAIT) && !r_type.type.class_type->resolved_body) {
Error err;
Ref<GDScriptParserRef> r = GDScriptCache::get_parser(r_type.type.script_path, GDScriptParserRef::FULLY_SOLVED, err);
}
Expand Down Expand Up @@ -2294,6 +2303,7 @@ static bool _guess_identifier_type(GDScriptParser::CompletionContext &p_context,
case GDScriptParser::IdentifierNode::MEMBER_CONSTANT:
case GDScriptParser::IdentifierNode::MEMBER_FUNCTION:
case GDScriptParser::IdentifierNode::MEMBER_SIGNAL:
case GDScriptParser::IdentifierNode::MEMBER_TRAIT:
case GDScriptParser::IdentifierNode::MEMBER_CLASS:
case GDScriptParser::IdentifierNode::INHERITED_VARIABLE:
case GDScriptParser::IdentifierNode::STATIC_VARIABLE:
Expand Down Expand Up @@ -2419,6 +2429,7 @@ static bool _guess_identifier_type(GDScriptParser::CompletionContext &p_context,
GDScriptParser::DataType base_type = p_context.current_class->base_type;
while (base_type.is_set()) {
switch (base_type.kind) {
case GDScriptParser::DataType::TRAIT:
case GDScriptParser::DataType::CLASS:
if (base_type.class_type->has_function(p_context.current_function->identifier->name)) {
GDScriptParser::FunctionNode *parent_function = base_type.class_type->get_member(p_context.current_function->identifier->name).function;
Expand Down Expand Up @@ -2530,6 +2541,7 @@ static bool _guess_identifier_type_from_base(GDScriptParser::CompletionContext &
bool is_static = base_type.is_meta_type;
while (base_type.is_set()) {
switch (base_type.kind) {
case GDScriptParser::DataType::TRAIT:
case GDScriptParser::DataType::CLASS:
if (base_type.class_type->has_member(p_identifier)) {
const GDScriptParser::ClassNode::Member &member = base_type.class_type->get_member(p_identifier);
Expand Down Expand Up @@ -2590,6 +2602,12 @@ static bool _guess_identifier_type_from_base(GDScriptParser::CompletionContext &
}
r_type = _callable_type_from_method_info(member.function->info);
return true;
case GDScriptParser::ClassNode::Member::TRAIT:
r_type.type.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT;
r_type.type.kind = GDScriptParser::DataType::TRAIT;
r_type.type.class_type = member.m_class;
r_type.type.is_meta_type = true;
return true;
case GDScriptParser::ClassNode::Member::CLASS:
r_type.type.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT;
r_type.type.kind = GDScriptParser::DataType::CLASS;
Expand Down Expand Up @@ -2788,6 +2806,7 @@ static bool _guess_method_return_type_from_base(GDScriptParser::CompletionContex

while (base_type.is_set() && !base_type.is_variant()) {
switch (base_type.kind) {
case GDScriptParser::DataType::TRAIT:
case GDScriptParser::DataType::CLASS:
if (base_type.class_type->has_function(p_method)) {
GDScriptParser::FunctionNode *method = base_type.class_type->get_member(p_method).function;
Expand Down Expand Up @@ -2941,6 +2960,7 @@ static void _list_call_arguments(GDScriptParser::CompletionContext &p_context, c

while (base_type.is_set() && !base_type.is_variant()) {
switch (base_type.kind) {
case GDScriptParser::DataType::TRAIT:
case GDScriptParser::DataType::CLASS: {
if (base_type.is_meta_type && method == SNAME("new")) {
const GDScriptParser::ClassNode *current = base_type.class_type;
Expand Down Expand Up @@ -3085,6 +3105,7 @@ static void _list_call_arguments(GDScriptParser::CompletionContext &p_context, c
n++;
}
} break;
case GDScriptParser::DataType::TRAIT:
case GDScriptParser::DataType::CLASS: {
GDScriptParser::ClassNode *clss = tweened_object->datatype.class_type;
native_type = clss->base_type.native_type;
Expand Down Expand Up @@ -3510,7 +3531,30 @@ ::Error GDScriptLanguage::complete_code(const String &p_code, const String &p_pa
}
} break;
case GDScriptParser::COMPLETION_INHERIT_TYPE: {
_list_available_types(true, completion_context, options);
_list_available_types(true, false, completion_context, options);
r_forced = true;
} break;
case GDScriptParser::COMPLETION_USES_TYPE: {
const GDScriptParser::ClassNode *current = completion_context.current_class;
for (const GDScriptParser::ClassNode::Member &member : current->members) {
switch (member.type) {
case GDScriptParser::ClassNode::Member::TRAIT:
case GDScriptParser::ClassNode::Member::CLASS:
if (member.m_class && member.m_class->identifier) {
ScriptLanguage::CodeCompletionOption option(member.m_class->identifier->name, ScriptLanguage::CODE_COMPLETION_KIND_CLASS, ScriptLanguage::LOCATION_LOCAL);
options.insert(option.display, option);
}
break;
default:
break;
}
}
LocalVector<StringName> global_classes;
ScriptServer::get_global_class_list(global_classes);
for (const StringName &E : global_classes) {
ScriptLanguage::CodeCompletionOption option(E, ScriptLanguage::CODE_COMPLETION_KIND_CLASS, ScriptLanguage::LOCATION_OTHER_USER_CODE);
options.insert(option.display, option);
}
r_forced = true;
} break;
case GDScriptParser::COMPLETION_TYPE_NAME_OR_VOID: {
Expand All @@ -3519,11 +3563,11 @@ ::Error GDScriptLanguage::complete_code(const String &p_code, const String &p_pa
}
[[fallthrough]];
case GDScriptParser::COMPLETION_TYPE_NAME: {
_list_available_types(false, completion_context, options);
_list_available_types(false, true, completion_context, options);
r_forced = true;
} break;
case GDScriptParser::COMPLETION_PROPERTY_DECLARATION_OR_TYPE: {
_list_available_types(false, completion_context, options);
_list_available_types(false, false, completion_context, options);
ScriptLanguage::CodeCompletionOption get("get", ScriptLanguage::CODE_COMPLETION_KIND_PLAIN_TEXT);
options.insert(get.display, get);
ScriptLanguage::CodeCompletionOption set("set", ScriptLanguage::CODE_COMPLETION_KIND_PLAIN_TEXT);
Expand Down Expand Up @@ -3933,6 +3977,7 @@ static Error _lookup_symbol_from_base(const GDScriptParser::DataType &p_base, co

while (true) {
switch (base_type.kind) {
case GDScriptParser::DataType::TRAIT:
case GDScriptParser::DataType::CLASS: {
ERR_FAIL_NULL_V(base_type.class_type, ERR_BUG);

Expand All @@ -3952,6 +3997,7 @@ static Error _lookup_symbol_from_base(const GDScriptParser::DataType &p_base, co
case GDScriptParser::ClassNode::Member::UNDEFINED:
case GDScriptParser::ClassNode::Member::GROUP:
return ERR_BUG;
case GDScriptParser::ClassNode::Member::TRAIT:
case GDScriptParser::ClassNode::Member::CLASS: {
String doc_type_name;
String doc_enum_name;
Expand Down Expand Up @@ -4580,7 +4626,7 @@ ::Error GDScriptLanguage::lookup_code(const String &p_code, const String &p_symb
}
prev = E;
}
if (base_type.kind != GDScriptParser::DataType::CLASS) {
if (base_type.kind != GDScriptParser::DataType::CLASS && base_type.kind != GDScriptParser::DataType::TRAIT) {
GDScriptCompletionIdentifier base;
if (!_guess_expression_type(context, prev, base)) {
break;
Expand Down
3 changes: 3 additions & 0 deletions modules/gdscript/gdscript_function.h
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ class GDScriptDataType {
NATIVE,
SCRIPT,
GDSCRIPT,
GDTRAIT,
};

Kind kind = VARIANT;
Expand All @@ -66,6 +67,8 @@ class GDScriptDataType {

bool is_type(const Variant &p_variant, bool p_allow_implicit_conversion = false) const {
switch (kind) {
case GDTRAIT:
break;
case VARIANT: {
return true;
} break;
Expand Down
Loading