Skip to content

Commit

Permalink
GDScript: Fix autocompletion issues with nested types
Browse files Browse the repository at this point in the history
  • Loading branch information
HolonProduction committed Aug 5, 2024
1 parent 3978628 commit 5e5a4aa
Show file tree
Hide file tree
Showing 19 changed files with 291 additions and 35 deletions.
67 changes: 34 additions & 33 deletions modules/gdscript/gdscript_editor.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -976,6 +976,7 @@ static void _list_available_types(bool p_inherit_only, GDScriptParser::Completio
}
}

// TODO: Unify with _find_identifiers_in_class
if (p_context.current_class) {
if (!p_inherit_only && p_context.current_class->base_type.is_set()) {
// Native enums from base class
Expand All @@ -987,31 +988,34 @@ static void _list_available_types(bool p_inherit_only, GDScriptParser::Completio
}
}
// Check current class for potential types
// TODO: Also check classes the current class inherits from.
const GDScriptParser::ClassNode *current = p_context.current_class;
int location_offset = 0;
while (current) {
for (int i = 0; i < current->members.size(); i++) {
const GDScriptParser::ClassNode::Member &member = current->members[i];
switch (member.type) {
case GDScriptParser::ClassNode::Member::CLASS: {
ScriptLanguage::CodeCompletionOption option(member.m_class->identifier->name, ScriptLanguage::CODE_COMPLETION_KIND_CLASS, ScriptLanguage::LOCATION_LOCAL);
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);
ScriptLanguage::CodeCompletionOption option(member.m_enum->identifier->name, ScriptLanguage::CODE_COMPLETION_KIND_ENUM, ScriptLanguage::LOCATION_LOCAL + location_offset);
r_result.insert(option.display, option);
}
} break;
case GDScriptParser::ClassNode::Member::CONSTANT: {
if (member.constant->get_datatype().is_meta_type && p_context.current_class->outer != nullptr) {
ScriptLanguage::CodeCompletionOption option(member.constant->identifier->name, ScriptLanguage::CODE_COMPLETION_KIND_CLASS, ScriptLanguage::LOCATION_LOCAL);
if (member.constant->get_datatype().is_meta_type) {
ScriptLanguage::CodeCompletionOption option(member.constant->identifier->name, ScriptLanguage::CODE_COMPLETION_KIND_CLASS, ScriptLanguage::LOCATION_LOCAL + location_offset);
r_result.insert(option.display, option);
}
} break;
default:
break;
}
}
location_offset += 1;
current = current->outer;
}
}
Expand Down Expand Up @@ -1076,7 +1080,7 @@ static void _find_identifiers_in_class(const GDScriptParser::ClassNode *p_class,
option = ScriptLanguage::CodeCompletionOption(member.variable->identifier->name, ScriptLanguage::CODE_COMPLETION_KIND_MEMBER, location);
break;
case GDScriptParser::ClassNode::Member::CONSTANT:
if (p_types_only || p_only_functions) {
if ((p_types_only && !member.constant->datatype.is_meta_type) || p_only_functions) {
continue;
}
if (r_result.has(member.constant->identifier->name)) {
Expand Down Expand Up @@ -1308,6 +1312,10 @@ static void _find_identifiers_in_base(const GDScriptCompletionIdentifier &p_base
return;
} break;
case GDScriptParser::DataType::ENUM: {
if (p_types_only) {
return;
}

String type_str = base_type.native_type;
StringName type = type_str.get_slicec('.', 0);
StringName type_enum = base_type.enum_type;
Expand Down Expand Up @@ -2489,6 +2497,16 @@ static bool _guess_identifier_type_from_base(GDScriptParser::CompletionContext &
return true;
}
}
if (ClassDB::has_enum(class_name, p_identifier)) {
r_type.type.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT;
r_type.type.kind = GDScriptParser::DataType::ENUM;
r_type.type.enum_type = p_identifier;
r_type.type.is_constant = true;
r_type.type.is_meta_type = true;
r_type.type.native_type = String(class_name) + "." + p_identifier;
return true;
}

return false;
} break;
case GDScriptParser::DataType::BUILTIN: {
Expand Down Expand Up @@ -3313,44 +3331,27 @@ ::Error GDScriptLanguage::complete_code(const String &p_code, const String &p_pa
if (!completion_context.current_class) {
break;
}

const GDScriptParser::TypeNode *type = static_cast<const GDScriptParser::TypeNode *>(completion_context.node);
bool found = true;
ERR_FAIL_INDEX_V_MSG(completion_context.type_chain_index - 1, type->type_chain.size(), Error::ERR_BUG, "Could not complete type argument with out of bounds type chain index.");

GDScriptCompletionIdentifier base;
base.type.kind = GDScriptParser::DataType::CLASS;
base.type.type_source = GDScriptParser::DataType::INFERRED;
base.type.is_constant = true;

if (completion_context.current_argument == 1) {
StringName type_name = type->type_chain[0]->name;

if (ClassDB::class_exists(type_name)) {
base.type.kind = GDScriptParser::DataType::NATIVE;
base.type.native_type = type_name;
} else if (ScriptServer::is_global_class(type_name)) {
base.type.kind = GDScriptParser::DataType::SCRIPT;
String scr_path = ScriptServer::get_global_class_path(type_name);
base.type.script_type = ResourceLoader::load(scr_path);
}
}

if (base.type.kind == GDScriptParser::DataType::CLASS) {
base.type.class_type = completion_context.current_class;
base.value = completion_context.base;

for (int i = 0; i < completion_context.current_argument; i++) {
if (_guess_identifier_type(completion_context, type->type_chain[0], base)) {
bool found = true;
for (int i = 1; i < completion_context.type_chain_index; i++) {
GDScriptCompletionIdentifier ci;
if (!_guess_identifier_type_from_base(completion_context, base, type->type_chain[i]->name, ci)) {
found = false;
found = _guess_identifier_type_from_base(completion_context, base, type->type_chain[i]->name, ci);
base = ci;
if (!found) {
break;
}
base = ci;
}
if (found) {
_find_identifiers_in_base(base, false, true, options, 0);
}
}

if (found) {
_find_identifiers_in_base(base, false, true, options, 0);
}
r_forced = true;
} break;
case GDScriptParser::COMPLETION_RESOURCE_PATH: {
Expand Down
5 changes: 4 additions & 1 deletion modules/gdscript/gdscript_parser.h
Original file line number Diff line number Diff line change
Expand Up @@ -1310,7 +1310,10 @@ class GDScriptParser {
FunctionNode *current_function = nullptr;
SuiteNode *current_suite = nullptr;
int current_line = -1;
int current_argument = -1;
union {
int current_argument = -1;
int type_chain_index;
};
Variant::Type builtin_type = Variant::VARIANT_MAX;
Node *node = nullptr;
Object *base = nullptr;
Expand Down
22 changes: 22 additions & 0 deletions modules/gdscript/tests/scripts/completion/class_a.notest.gd
Original file line number Diff line number Diff line change
@@ -1,5 +1,27 @@
extends Node

class InnerA:
class InnerInnerA:
enum EnumOfInnerInnerA {
ENUM_VALUE_1,
ENUM_VALUE_2,
}

enum EnumOfInnerA {
ENUM_VALUE_1,
ENUM_VALUE_2,
}

signal signal_of_inner_a
var property_of_inner_a
func func_of_inner_a():
pass

enum EnumOfA {
ENUM_VALUE_1,
ENUM_VALUE_2,
}

signal signal_of_a

var property_of_a
Expand Down
31 changes: 31 additions & 0 deletions modules/gdscript/tests/scripts/completion/class_b.notest.gd
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
extends Node
class_name B

class InnerB:
class InnerInnerB:
enum EnumOfInnerInnerB {
ENUM_VALUE_1,
ENUM_VALUE_2,
}

enum EnumOfInnerB {
ENUM_VALUE_1,
ENUM_VALUE_2,
}

signal signal_of_inner_b
var property_of_inner_b
func func_of_inner_b():
pass

enum EnumOfB {
ENUM_VALUE_1,
ENUM_VALUE_2,
}

signal signal_of_b

var property_of_b

func func_of_b():
pass
21 changes: 21 additions & 0 deletions modules/gdscript/tests/scripts/completion/types/hints/index_0.cfg
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
[output]
include=[
{"display": "A"},
{"display": "B"},
{"display": "LocalInnerClass"},
{"display": "LocalInnerEnum"},
{"display": "ConnectFlags"},
{"display": "int"},
{"display": "String"},
{"display": "float"},
{"display": "Vector2"},
{"display": "Vector3"},
{"display": "Vector4"},
{"display": "Node"},
{"display": "Node2D"},
]
exclude=[
{"display": "AInner"},
{"display": "LocalInnerInnerEnum"},
{"display": "LocalInnerInnerClass"},
]
11 changes: 11 additions & 0 deletions modules/gdscript/tests/scripts/completion/types/hints/index_0.gd
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
const A = preload("res://completion/class_a.notest.gd")

class LocalInnerClass:
const AInner = preload("res://completion/class_a.notest.gd")
enum LocalInnerInnerEnum {}
class LocalInnerInnerClass:
pass

enum LocalInnerEnum {}

var test_var: A
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
[output]
include=[
{"display": "A"},
{"display": "AInner"},
{"display": "B"},
{"display": "LocalInnerClass"},
{"display": "LocalInnerInnerClass"},
{"display": "LocalInnerEnum"},
{"display": "LocalInnerInnerEnum"},
{"display": "ConnectFlags"},
{"display": "int"},
{"display": "String"},
{"display": "float"},
{"display": "Vector2"},
{"display": "Vector3"},
{"display": "Vector4"},
{"display": "Node"},
{"display": "Node2D"},
]
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
const A = preload("res://completion/class_a.notest.gd")

class LocalInnerClass:
const AInner = preload("res://completion/class_a.notest.gd")
enum LocalInnerInnerEnum {}
class LocalInnerInnerClass:
pass
var test_var: A

enum LocalInnerEnum {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
[output]
include=[
{"display": "InnerB"},
{"display": "EnumOfB"},
{"display": "ConnectFlags"},
]
exclude=[
{"display": "int"},
{"display": "String"},
{"display": "Node2D"},
{"display": "B"},
]
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
var test_var: B.➡
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
[output]
include=[
{"display": "InnerInnerClass"},
{"display": "InnerInnerEnum"},
{"display": "ConnectFlags"},
]
exclude=[
{"display": "int"},
{"display": "String"},
{"display": "Node2D"},
{"display": "B"},
]
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
const A = preload("res://completion/class_a.notest.gd")

class LocalInnerClass:
class InnerInnerClass:
pass
enum InnerInnerEnum {}

var test_var: LocalInnerClass.➡
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
[output]
exclude=[
{"display": "TEST_LOCAL_VAL"},
{"display": "ConnectFlags"},
]
exclude=[
{"display": "int"},
{"display": "String"},
{"display": "Node2D"},
{"display": "B"},
]
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
const A = preload("res://completion/class_a.notest.gd")

enum LocalInnerEnum {
TEST_LOCAL_VAL,
}

var test_var: LocalInnerEnum.➡
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
[output]
include=[
{"display": "InnerA"},
{"display": "EnumOfA"},
{"display": "ConnectFlags"},
]
exclude=[
{"display": "int"},
{"display": "String"},
{"display": "Node2D"},
{"display": "B"},
]
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
const A = preload("res://completion/class_a.notest.gd")

var test_var: A.➡
19 changes: 19 additions & 0 deletions modules/gdscript/tests/scripts/completion/types/hints/index_2.cfg
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
[output]
include=[
{"display": "AInnerInner"},
{"display": "InnerInnerInnerEnum"},
{"display": "InnerInnerInnerClass"},
{"display": "ConnectFlags"},
]
exclude=[
{"display": "A"},
{"display": "AInner"},
{"display": "TestEnum"},
{"display": "InnerInnerEnum"},
{"display": "InnerInnerClass"},
{"display": "LocalInnerClass"},
{"display": "int"},
{"display": "String"},
{"display": "Node2D"},
{"display": "B"},
]
14 changes: 14 additions & 0 deletions modules/gdscript/tests/scripts/completion/types/hints/index_2.gd
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
const A = preload("res://completion/class_a.notest.gd")

class LocalInnerClass:
const AInner = preload("res://completion/class_a.notest.gd")
class InnerInnerClass:
const AInnerInner = preload("res://completion/class_a.notest.gd")
enum InnerInnerInnerEnum {}
class InnerInnerInnerClass:
pass
enum InnerInnerEnum {}

enum TestEnum {}

var test_var: LocalInnerClass.InnerInnerClass.➡
Loading

0 comments on commit 5e5a4aa

Please sign in to comment.