Skip to content

Commit 97b1bc6

Browse files
Autocompletion: Don't call const functions
1 parent 2d113cc commit 97b1bc6

File tree

8 files changed

+196
-151
lines changed

8 files changed

+196
-151
lines changed

modules/gdscript/gdscript_editor.cpp

Lines changed: 155 additions & 136 deletions
Original file line numberDiff line numberDiff line change
@@ -1764,6 +1764,113 @@ static bool _is_expression_named_identifier(const GDScriptParser::ExpressionNode
17641764
return false;
17651765
}
17661766

1767+
// Creates a map of exemplary results for some functions that return a structured dictionary.
1768+
// Setting this example as value allows autocompletion to suggest the specific keys in some cases.
1769+
static HashMap<String, Dictionary> make_structure_samples() {
1770+
HashMap<String, Dictionary> res;
1771+
const Array arr;
1772+
1773+
{
1774+
Dictionary d;
1775+
d.set("major", 0);
1776+
d.set("minor", 0);
1777+
d.set("patch", 0);
1778+
d.set("hex", 0);
1779+
d.set("status", String());
1780+
d.set("build", String());
1781+
d.set("hash", String());
1782+
d.set("timestamp", 0);
1783+
d.set("string", String());
1784+
res["Engine::get_version_info"] = d;
1785+
}
1786+
1787+
{
1788+
Dictionary d;
1789+
d.set("lead_developers", arr);
1790+
d.set("founders", arr);
1791+
d.set("project_managers", arr);
1792+
d.set("developers", arr);
1793+
res["Engine::get_author_info"] = d;
1794+
}
1795+
1796+
{
1797+
Dictionary d;
1798+
d.set("platinum_sponsors", arr);
1799+
d.set("gold_sponsors", arr);
1800+
d.set("silver_sponsors", arr);
1801+
d.set("bronze_sponsors", arr);
1802+
d.set("mini_sponsors", arr);
1803+
d.set("gold_donors", arr);
1804+
d.set("silver_donors", arr);
1805+
d.set("bronze_donors", arr);
1806+
res["Engine::get_donor_info"] = d;
1807+
}
1808+
1809+
{
1810+
Dictionary d;
1811+
d.set("physical", -1);
1812+
d.set("free", -1);
1813+
d.set("available", -1);
1814+
d.set("stack", -1);
1815+
res["OS::get_memory_info"] = d;
1816+
}
1817+
1818+
{
1819+
Dictionary d;
1820+
d.set("year", 0);
1821+
d.set("month", 0);
1822+
d.set("day", 0);
1823+
d.set("weekday", 0);
1824+
d.set("hour", 0);
1825+
d.set("minute", 0);
1826+
d.set("second", 0);
1827+
d.set("dst", 0);
1828+
res["Time::get_datetime_dict_from_system"] = d;
1829+
}
1830+
1831+
{
1832+
Dictionary d;
1833+
d.set("year", 0);
1834+
d.set("month", 0);
1835+
d.set("day", 0);
1836+
d.set("weekday", 0);
1837+
d.set("hour", 0);
1838+
d.set("minute", 0);
1839+
d.set("second", 0);
1840+
res["Time::get_datetime_dict_from_unix_time"] = d;
1841+
}
1842+
1843+
{
1844+
Dictionary d;
1845+
d.set("year", 0);
1846+
d.set("month", 0);
1847+
d.set("day", 0);
1848+
d.set("weekday", 0);
1849+
res["Time::get_date_dict_from_system"] = d;
1850+
res["Time::get_date_dict_from_unix_time"] = d;
1851+
}
1852+
1853+
{
1854+
Dictionary d;
1855+
d.set("hour", 0);
1856+
d.set("minute", 0);
1857+
d.set("second", 0);
1858+
res["Time::get_time_dict_from_system"] = d;
1859+
res["Time::get_time_dict_from_unix_time"] = d;
1860+
}
1861+
1862+
{
1863+
Dictionary d;
1864+
d.set("bias", 0);
1865+
d.set("name", String());
1866+
res["Time::get_time_zone_from_system"] = d;
1867+
}
1868+
1869+
return res;
1870+
}
1871+
1872+
static const HashMap<String, Dictionary> structure_examples = make_structure_samples();
1873+
17671874
static bool _guess_expression_type(GDScriptParser::CompletionContext &p_context, const GDScriptParser::ExpressionNode *p_expression, GDScriptCompletionIdentifier &r_type) {
17681875
bool found = false;
17691876

@@ -1884,156 +1991,68 @@ static bool _guess_expression_type(GDScriptParser::CompletionContext &p_context,
18841991
} break;
18851992
case GDScriptParser::Node::CALL: {
18861993
const GDScriptParser::CallNode *call = static_cast<const GDScriptParser::CallNode *>(p_expression);
1887-
if (GDScriptParser::get_builtin_type(call->function_name) < Variant::VARIANT_MAX) {
1888-
r_type.type.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT;
1889-
r_type.type.kind = GDScriptParser::DataType::BUILTIN;
1890-
r_type.type.builtin_type = GDScriptParser::get_builtin_type(call->function_name);
1891-
found = true;
1892-
break;
1893-
} else if (GDScriptUtilityFunctions::function_exists(call->function_name)) {
1894-
MethodInfo mi = GDScriptUtilityFunctions::get_function_info(call->function_name);
1895-
r_type = _type_from_property(mi.return_val);
1896-
found = true;
1897-
break;
1898-
} else {
1899-
GDScriptParser::CompletionContext c = p_context;
1900-
c.current_line = call->start_line;
1994+
GDScriptParser::CompletionContext c = p_context;
1995+
c.current_line = call->start_line;
19011996

1902-
GDScriptParser::Node::Type callee_type = call->get_callee_type();
1997+
GDScriptParser::Node::Type callee_type = call->get_callee_type();
19031998

1904-
GDScriptCompletionIdentifier base;
1905-
if (callee_type == GDScriptParser::Node::IDENTIFIER || call->is_super) {
1906-
// Simple call, so base is 'self'.
1907-
if (p_context.current_class) {
1908-
if (call->is_super) {
1909-
base.type = p_context.current_class->base_type;
1910-
base.value = p_context.base;
1911-
} else {
1912-
base.type.kind = GDScriptParser::DataType::CLASS;
1913-
base.type.type_source = GDScriptParser::DataType::INFERRED;
1914-
base.type.is_constant = true;
1915-
base.type.class_type = p_context.current_class;
1916-
base.value = p_context.base;
1917-
}
1999+
GDScriptCompletionIdentifier base;
2000+
if (callee_type == GDScriptParser::Node::IDENTIFIER || call->is_super) {
2001+
// Simple call, so base is 'self'.
2002+
if (p_context.current_class) {
2003+
if (call->is_super) {
2004+
base.type = p_context.current_class->base_type;
2005+
base.value = p_context.base;
19182006
} else {
1919-
break;
1920-
}
1921-
} else if (callee_type == GDScriptParser::Node::SUBSCRIPT && static_cast<const GDScriptParser::SubscriptNode *>(call->callee)->is_attribute) {
1922-
if (!_guess_expression_type(c, static_cast<const GDScriptParser::SubscriptNode *>(call->callee)->base, base)) {
1923-
found = false;
1924-
break;
2007+
base.type.kind = GDScriptParser::DataType::CLASS;
2008+
base.type.type_source = GDScriptParser::DataType::INFERRED;
2009+
base.type.is_constant = true;
2010+
base.type.class_type = p_context.current_class;
2011+
base.value = p_context.base;
19252012
}
19262013
} else {
19272014
break;
19282015
}
2016+
} else if (callee_type == GDScriptParser::Node::SUBSCRIPT && static_cast<const GDScriptParser::SubscriptNode *>(call->callee)->is_attribute) {
2017+
if (!_guess_expression_type(c, static_cast<const GDScriptParser::SubscriptNode *>(call->callee)->base, base)) {
2018+
found = false;
2019+
break;
2020+
}
2021+
} else {
2022+
break;
2023+
}
19292024

1930-
// Try call if constant methods with constant arguments
1931-
if (base.type.is_constant && base.value.get_type() == Variant::OBJECT) {
1932-
GDScriptParser::DataType native_type = base.type;
1933-
1934-
while (native_type.kind == GDScriptParser::DataType::CLASS) {
1935-
native_type = native_type.class_type->base_type;
1936-
}
1937-
1938-
while (native_type.kind == GDScriptParser::DataType::SCRIPT) {
1939-
if (native_type.script_type.is_valid()) {
1940-
Ref<Script> parent = native_type.script_type->get_base_script();
1941-
if (parent.is_valid()) {
1942-
native_type.script_type = parent;
1943-
} else {
1944-
native_type.kind = GDScriptParser::DataType::NATIVE;
1945-
native_type.builtin_type = Variant::OBJECT;
1946-
native_type.native_type = native_type.script_type->get_instance_base_type();
1947-
if (!ClassDB::class_exists(native_type.native_type)) {
1948-
native_type.kind = GDScriptParser::DataType::UNRESOLVED;
1949-
}
1950-
}
1951-
}
2025+
// Apply additional behavior aware inference that the analyzer can't do.
2026+
if (base.type.is_set()) {
2027+
// Maintain type for duplicate methods.
2028+
if (call->function_name == SNAME("duplicate")) {
2029+
if (base.type.builtin_type == Variant::OBJECT && (ClassDB::is_parent_class(base.type.native_type, SNAME("Resource")) || ClassDB::is_parent_class(base.type.native_type, SNAME("Node")))) {
2030+
r_type.type = base.type;
2031+
found = true;
2032+
break;
19522033
}
2034+
}
19532035

1954-
if (native_type.kind == GDScriptParser::DataType::NATIVE) {
1955-
MethodBind *mb = ClassDB::get_method(native_type.native_type, call->function_name);
1956-
if (mb && mb->is_const()) {
1957-
bool all_is_const = true;
1958-
Vector<Variant> args;
1959-
for (int i = 0; all_is_const && i < call->arguments.size(); i++) {
1960-
GDScriptCompletionIdentifier arg;
1961-
1962-
if (!call->arguments[i]->is_constant) {
1963-
all_is_const = false;
1964-
}
1965-
}
1966-
1967-
Object *baseptr = base.value;
1968-
1969-
if (all_is_const && call->function_name == SNAME("get_node") && ClassDB::is_parent_class(native_type.native_type, SNAME("Node")) && args.size()) {
1970-
String arg1 = args[0];
1971-
if (arg1.begins_with("/root/")) {
1972-
String which = arg1.get_slicec('/', 2);
1973-
if (!which.is_empty()) {
1974-
// Try singletons first
1975-
if (GDScriptLanguage::get_singleton()->get_named_globals_map().has(which)) {
1976-
r_type = _type_from_variant(GDScriptLanguage::get_singleton()->get_named_globals_map()[which], p_context);
1977-
found = true;
1978-
} else {
1979-
for (const KeyValue<StringName, ProjectSettings::AutoloadInfo> &E : ProjectSettings::get_singleton()->get_autoload_list()) {
1980-
String name = E.key;
1981-
if (name == which) {
1982-
String script = E.value.path;
1983-
1984-
if (!script.begins_with("res://")) {
1985-
script = "res://" + script;
1986-
}
1987-
1988-
if (!script.ends_with(".gd")) {
1989-
// not a script, try find the script anyway,
1990-
// may have some success
1991-
script = script.get_basename() + ".gd";
1992-
}
1993-
1994-
if (FileAccess::exists(script)) {
1995-
Ref<GDScriptParserRef> parser = p_context.parser->get_depended_parser_for(script);
1996-
if (parser.is_valid() && parser->raise_status(GDScriptParserRef::INTERFACE_SOLVED) == OK) {
1997-
r_type.type.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT;
1998-
r_type.type.script_path = script;
1999-
r_type.type.class_type = parser->get_parser()->get_tree();
2000-
r_type.type.is_constant = false;
2001-
r_type.type.kind = GDScriptParser::DataType::CLASS;
2002-
r_type.value = Variant();
2003-
found = true;
2004-
}
2005-
}
2006-
break;
2007-
}
2008-
}
2009-
}
2010-
}
2011-
}
2012-
}
2013-
2014-
if (!found && all_is_const && baseptr) {
2015-
Vector<const Variant *> argptr;
2016-
for (int i = 0; i < args.size(); i++) {
2017-
argptr.push_back(&args[i]);
2018-
}
2019-
2020-
Callable::CallError ce;
2021-
Variant ret = mb->call(baseptr, (const Variant **)argptr.ptr(), argptr.size(), ce);
2036+
// Simulate generics for some typed array methods.
2037+
if (base.type.builtin_type == Variant::ARRAY && base.type.has_container_element_types() && (call->function_name == SNAME("back") || call->function_name == SNAME("front") || call->function_name == SNAME("get") || call->function_name == SNAME("max") || call->function_name == SNAME("min") || call->function_name == SNAME("pick_random") || call->function_name == SNAME("pop_at") || call->function_name == SNAME("pop_back") || call->function_name == SNAME("pop_front"))) {
2038+
r_type.type = base.type.get_container_element_type(0);
2039+
found = true;
2040+
break;
2041+
}
20222042

2023-
if (ce.error == Callable::CallError::CALL_OK && ret.get_type() != Variant::NIL) {
2024-
if (ret.get_type() != Variant::OBJECT || ret.operator Object *() != nullptr) {
2025-
r_type = _type_from_variant(ret, p_context);
2026-
found = true;
2027-
}
2028-
}
2029-
}
2030-
}
2043+
// Insert example values for functions which a structured dictionary response.
2044+
if (!base.type.is_meta_type) {
2045+
const Dictionary *example = structure_examples.getptr(base.type.native_type.operator String() + "::" + call->function_name);
2046+
if (example != nullptr) {
2047+
r_type = _type_from_variant(*example, p_context);
2048+
found = true;
2049+
break;
20312050
}
20322051
}
2052+
}
20332053

2034-
if (!found) {
2035-
found = _guess_method_return_type_from_base(c, base, call->function_name, r_type);
2036-
}
2054+
if (!found) {
2055+
found = _guess_method_return_type_from_base(c, base, call->function_name, r_type);
20372056
}
20382057
} break;
20392058
case GDScriptParser::Node::SUBSCRIPT: {

modules/gdscript/tests/scripts/completion/types/local/no_type.cfg

Lines changed: 0 additions & 12 deletions
This file was deleted.
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
[output]
2+
include=[
3+
; String
4+
{"display": "begins_with(…)"},
5+
]
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
extends Node
2+
3+
var s
4+
5+
func test():
6+
var t = String(s)
7+
t.➡
8+
pass
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
[output]
2+
include=[
3+
; String
4+
{"display": "begins_with(…)"},
5+
]
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
extends Node
2+
3+
var i: int
4+
5+
func test():
6+
var t = char(i)
7+
t.➡
8+
pass
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
[output]
2+
include=[
3+
; Node
4+
{"display": "add_child(…)"},
5+
{"display": "owner"},
6+
{"display": "child_entered_tree"},
7+
8+
; GDScript: class_a.notest.gd
9+
{"display": "property_of_a"},
10+
{"display": "func_of_a()"},
11+
{"display": "signal_of_a"},
12+
]

modules/gdscript/tests/scripts/completion/types/local/no_type.gd renamed to modules/gdscript/tests/scripts/completion/types/local/no_type/script.gd

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,6 @@ extends Node
33
const A := preload("res://completion/class_a.notest.gd")
44

55
func a():
6-
var test = A.new()
7-
test.➡
8-
pass
6+
var test = A.new()
7+
test.➡
8+
pass

0 commit comments

Comments
 (0)