|
@@ -1764,6 +1764,113 @@ static bool _is_expression_named_identifier(const GDScriptParser::ExpressionNode
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
+// Creates a map of exemplary results for some functions that return a structured dictionary.
|
|
|
+// Setting this example as value allows autocompletion to suggest the specific keys in some cases.
|
|
|
+static HashMap<String, Dictionary> make_structure_samples() {
|
|
|
+ HashMap<String, Dictionary> res;
|
|
|
+ const Array arr;
|
|
|
+
|
|
|
+ {
|
|
|
+ Dictionary d;
|
|
|
+ d.set("major", 0);
|
|
|
+ d.set("minor", 0);
|
|
|
+ d.set("patch", 0);
|
|
|
+ d.set("hex", 0);
|
|
|
+ d.set("status", String());
|
|
|
+ d.set("build", String());
|
|
|
+ d.set("hash", String());
|
|
|
+ d.set("timestamp", 0);
|
|
|
+ d.set("string", String());
|
|
|
+ res["Engine::get_version_info"] = d;
|
|
|
+ }
|
|
|
+
|
|
|
+ {
|
|
|
+ Dictionary d;
|
|
|
+ d.set("lead_developers", arr);
|
|
|
+ d.set("founders", arr);
|
|
|
+ d.set("project_managers", arr);
|
|
|
+ d.set("developers", arr);
|
|
|
+ res["Engine::get_author_info"] = d;
|
|
|
+ }
|
|
|
+
|
|
|
+ {
|
|
|
+ Dictionary d;
|
|
|
+ d.set("platinum_sponsors", arr);
|
|
|
+ d.set("gold_sponsors", arr);
|
|
|
+ d.set("silver_sponsors", arr);
|
|
|
+ d.set("bronze_sponsors", arr);
|
|
|
+ d.set("mini_sponsors", arr);
|
|
|
+ d.set("gold_donors", arr);
|
|
|
+ d.set("silver_donors", arr);
|
|
|
+ d.set("bronze_donors", arr);
|
|
|
+ res["Engine::get_donor_info"] = d;
|
|
|
+ }
|
|
|
+
|
|
|
+ {
|
|
|
+ Dictionary d;
|
|
|
+ d.set("physical", -1);
|
|
|
+ d.set("free", -1);
|
|
|
+ d.set("available", -1);
|
|
|
+ d.set("stack", -1);
|
|
|
+ res["OS::get_memory_info"] = d;
|
|
|
+ }
|
|
|
+
|
|
|
+ {
|
|
|
+ Dictionary d;
|
|
|
+ d.set("year", 0);
|
|
|
+ d.set("month", 0);
|
|
|
+ d.set("day", 0);
|
|
|
+ d.set("weekday", 0);
|
|
|
+ d.set("hour", 0);
|
|
|
+ d.set("minute", 0);
|
|
|
+ d.set("second", 0);
|
|
|
+ d.set("dst", 0);
|
|
|
+ res["Time::get_datetime_dict_from_system"] = d;
|
|
|
+ }
|
|
|
+
|
|
|
+ {
|
|
|
+ Dictionary d;
|
|
|
+ d.set("year", 0);
|
|
|
+ d.set("month", 0);
|
|
|
+ d.set("day", 0);
|
|
|
+ d.set("weekday", 0);
|
|
|
+ d.set("hour", 0);
|
|
|
+ d.set("minute", 0);
|
|
|
+ d.set("second", 0);
|
|
|
+ res["Time::get_datetime_dict_from_unix_time"] = d;
|
|
|
+ }
|
|
|
+
|
|
|
+ {
|
|
|
+ Dictionary d;
|
|
|
+ d.set("year", 0);
|
|
|
+ d.set("month", 0);
|
|
|
+ d.set("day", 0);
|
|
|
+ d.set("weekday", 0);
|
|
|
+ res["Time::get_date_dict_from_system"] = d;
|
|
|
+ res["Time::get_date_dict_from_unix_time"] = d;
|
|
|
+ }
|
|
|
+
|
|
|
+ {
|
|
|
+ Dictionary d;
|
|
|
+ d.set("hour", 0);
|
|
|
+ d.set("minute", 0);
|
|
|
+ d.set("second", 0);
|
|
|
+ res["Time::get_time_dict_from_system"] = d;
|
|
|
+ res["Time::get_time_dict_from_unix_time"] = d;
|
|
|
+ }
|
|
|
+
|
|
|
+ {
|
|
|
+ Dictionary d;
|
|
|
+ d.set("bias", 0);
|
|
|
+ d.set("name", String());
|
|
|
+ res["Time::get_time_zone_from_system"] = d;
|
|
|
+ }
|
|
|
+
|
|
|
+ return res;
|
|
|
+}
|
|
|
+
|
|
|
+static const HashMap<String, Dictionary> structure_examples = make_structure_samples();
|
|
|
+
|
|
|
static bool _guess_expression_type(GDScriptParser::CompletionContext &p_context, const GDScriptParser::ExpressionNode *p_expression, GDScriptCompletionIdentifier &r_type) {
|
|
|
bool found = false;
|
|
|
|
|
@@ -1884,156 +1991,68 @@ static bool _guess_expression_type(GDScriptParser::CompletionContext &p_context,
|
|
|
} break;
|
|
|
case GDScriptParser::Node::CALL: {
|
|
|
const GDScriptParser::CallNode *call = static_cast<const GDScriptParser::CallNode *>(p_expression);
|
|
|
- if (GDScriptParser::get_builtin_type(call->function_name) < Variant::VARIANT_MAX) {
|
|
|
- r_type.type.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT;
|
|
|
- r_type.type.kind = GDScriptParser::DataType::BUILTIN;
|
|
|
- r_type.type.builtin_type = GDScriptParser::get_builtin_type(call->function_name);
|
|
|
- found = true;
|
|
|
- break;
|
|
|
- } else if (GDScriptUtilityFunctions::function_exists(call->function_name)) {
|
|
|
- MethodInfo mi = GDScriptUtilityFunctions::get_function_info(call->function_name);
|
|
|
- r_type = _type_from_property(mi.return_val);
|
|
|
- found = true;
|
|
|
- break;
|
|
|
- } else {
|
|
|
- GDScriptParser::CompletionContext c = p_context;
|
|
|
- c.current_line = call->start_line;
|
|
|
+ GDScriptParser::CompletionContext c = p_context;
|
|
|
+ c.current_line = call->start_line;
|
|
|
|
|
|
- GDScriptParser::Node::Type callee_type = call->get_callee_type();
|
|
|
+ GDScriptParser::Node::Type callee_type = call->get_callee_type();
|
|
|
|
|
|
- GDScriptCompletionIdentifier base;
|
|
|
- if (callee_type == GDScriptParser::Node::IDENTIFIER || call->is_super) {
|
|
|
- // Simple call, so base is 'self'.
|
|
|
- if (p_context.current_class) {
|
|
|
- if (call->is_super) {
|
|
|
- base.type = p_context.current_class->base_type;
|
|
|
- base.value = p_context.base;
|
|
|
- } else {
|
|
|
- base.type.kind = GDScriptParser::DataType::CLASS;
|
|
|
- base.type.type_source = GDScriptParser::DataType::INFERRED;
|
|
|
- base.type.is_constant = true;
|
|
|
- base.type.class_type = p_context.current_class;
|
|
|
- base.value = p_context.base;
|
|
|
- }
|
|
|
+ GDScriptCompletionIdentifier base;
|
|
|
+ if (callee_type == GDScriptParser::Node::IDENTIFIER || call->is_super) {
|
|
|
+ // Simple call, so base is 'self'.
|
|
|
+ if (p_context.current_class) {
|
|
|
+ if (call->is_super) {
|
|
|
+ base.type = p_context.current_class->base_type;
|
|
|
+ base.value = p_context.base;
|
|
|
} else {
|
|
|
- break;
|
|
|
- }
|
|
|
- } else if (callee_type == GDScriptParser::Node::SUBSCRIPT && static_cast<const GDScriptParser::SubscriptNode *>(call->callee)->is_attribute) {
|
|
|
- if (!_guess_expression_type(c, static_cast<const GDScriptParser::SubscriptNode *>(call->callee)->base, base)) {
|
|
|
- found = false;
|
|
|
- break;
|
|
|
+ base.type.kind = GDScriptParser::DataType::CLASS;
|
|
|
+ base.type.type_source = GDScriptParser::DataType::INFERRED;
|
|
|
+ base.type.is_constant = true;
|
|
|
+ base.type.class_type = p_context.current_class;
|
|
|
+ base.value = p_context.base;
|
|
|
}
|
|
|
} else {
|
|
|
break;
|
|
|
}
|
|
|
+ } else if (callee_type == GDScriptParser::Node::SUBSCRIPT && static_cast<const GDScriptParser::SubscriptNode *>(call->callee)->is_attribute) {
|
|
|
+ if (!_guess_expression_type(c, static_cast<const GDScriptParser::SubscriptNode *>(call->callee)->base, base)) {
|
|
|
+ found = false;
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ break;
|
|
|
+ }
|
|
|
|
|
|
- // Try call if constant methods with constant arguments
|
|
|
- if (base.type.is_constant && base.value.get_type() == Variant::OBJECT) {
|
|
|
- GDScriptParser::DataType native_type = base.type;
|
|
|
-
|
|
|
- while (native_type.kind == GDScriptParser::DataType::CLASS) {
|
|
|
- native_type = native_type.class_type->base_type;
|
|
|
- }
|
|
|
-
|
|
|
- while (native_type.kind == GDScriptParser::DataType::SCRIPT) {
|
|
|
- if (native_type.script_type.is_valid()) {
|
|
|
- Ref<Script> parent = native_type.script_type->get_base_script();
|
|
|
- if (parent.is_valid()) {
|
|
|
- native_type.script_type = parent;
|
|
|
- } else {
|
|
|
- native_type.kind = GDScriptParser::DataType::NATIVE;
|
|
|
- native_type.builtin_type = Variant::OBJECT;
|
|
|
- native_type.native_type = native_type.script_type->get_instance_base_type();
|
|
|
- if (!ClassDB::class_exists(native_type.native_type)) {
|
|
|
- native_type.kind = GDScriptParser::DataType::UNRESOLVED;
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
+ // Apply additional behavior aware inference that the analyzer can't do.
|
|
|
+ if (base.type.is_set()) {
|
|
|
+ // Maintain type for duplicate methods.
|
|
|
+ if (call->function_name == SNAME("duplicate")) {
|
|
|
+ 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")))) {
|
|
|
+ r_type.type = base.type;
|
|
|
+ found = true;
|
|
|
+ break;
|
|
|
}
|
|
|
+ }
|
|
|
|
|
|
- if (native_type.kind == GDScriptParser::DataType::NATIVE) {
|
|
|
- MethodBind *mb = ClassDB::get_method(native_type.native_type, call->function_name);
|
|
|
- if (mb && mb->is_const()) {
|
|
|
- bool all_is_const = true;
|
|
|
- Vector<Variant> args;
|
|
|
- for (int i = 0; all_is_const && i < call->arguments.size(); i++) {
|
|
|
- GDScriptCompletionIdentifier arg;
|
|
|
-
|
|
|
- if (!call->arguments[i]->is_constant) {
|
|
|
- all_is_const = false;
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- Object *baseptr = base.value;
|
|
|
-
|
|
|
- if (all_is_const && call->function_name == SNAME("get_node") && ClassDB::is_parent_class(native_type.native_type, SNAME("Node")) && args.size()) {
|
|
|
- String arg1 = args[0];
|
|
|
- if (arg1.begins_with("/root/")) {
|
|
|
- String which = arg1.get_slicec('/', 2);
|
|
|
- if (!which.is_empty()) {
|
|
|
- // Try singletons first
|
|
|
- if (GDScriptLanguage::get_singleton()->get_named_globals_map().has(which)) {
|
|
|
- r_type = _type_from_variant(GDScriptLanguage::get_singleton()->get_named_globals_map()[which], p_context);
|
|
|
- found = true;
|
|
|
- } else {
|
|
|
- for (const KeyValue<StringName, ProjectSettings::AutoloadInfo> &E : ProjectSettings::get_singleton()->get_autoload_list()) {
|
|
|
- String name = E.key;
|
|
|
- if (name == which) {
|
|
|
- String script = E.value.path;
|
|
|
-
|
|
|
- if (!script.begins_with("res://")) {
|
|
|
- script = "res://" + script;
|
|
|
- }
|
|
|
-
|
|
|
- if (!script.ends_with(".gd")) {
|
|
|
- // not a script, try find the script anyway,
|
|
|
- // may have some success
|
|
|
- script = script.get_basename() + ".gd";
|
|
|
- }
|
|
|
-
|
|
|
- if (FileAccess::exists(script)) {
|
|
|
- Ref<GDScriptParserRef> parser = p_context.parser->get_depended_parser_for(script);
|
|
|
- if (parser.is_valid() && parser->raise_status(GDScriptParserRef::INTERFACE_SOLVED) == OK) {
|
|
|
- r_type.type.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT;
|
|
|
- r_type.type.script_path = script;
|
|
|
- r_type.type.class_type = parser->get_parser()->get_tree();
|
|
|
- r_type.type.is_constant = false;
|
|
|
- r_type.type.kind = GDScriptParser::DataType::CLASS;
|
|
|
- r_type.value = Variant();
|
|
|
- found = true;
|
|
|
- }
|
|
|
- }
|
|
|
- break;
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- if (!found && all_is_const && baseptr) {
|
|
|
- Vector<const Variant *> argptr;
|
|
|
- for (int i = 0; i < args.size(); i++) {
|
|
|
- argptr.push_back(&args[i]);
|
|
|
- }
|
|
|
-
|
|
|
- Callable::CallError ce;
|
|
|
- Variant ret = mb->call(baseptr, (const Variant **)argptr.ptr(), argptr.size(), ce);
|
|
|
+ // Simulate generics for some typed array methods.
|
|
|
+ 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"))) {
|
|
|
+ r_type.type = base.type.get_container_element_type(0);
|
|
|
+ found = true;
|
|
|
+ break;
|
|
|
+ }
|
|
|
|
|
|
- if (ce.error == Callable::CallError::CALL_OK && ret.get_type() != Variant::NIL) {
|
|
|
- if (ret.get_type() != Variant::OBJECT || ret.operator Object *() != nullptr) {
|
|
|
- r_type = _type_from_variant(ret, p_context);
|
|
|
- found = true;
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
+ // Insert example values for functions which a structured dictionary response.
|
|
|
+ if (!base.type.is_meta_type) {
|
|
|
+ const Dictionary *example = structure_examples.getptr(base.type.native_type.operator String() + "::" + call->function_name);
|
|
|
+ if (example != nullptr) {
|
|
|
+ r_type = _type_from_variant(*example, p_context);
|
|
|
+ found = true;
|
|
|
+ break;
|
|
|
}
|
|
|
}
|
|
|
+ }
|
|
|
|
|
|
- if (!found) {
|
|
|
- found = _guess_method_return_type_from_base(c, base, call->function_name, r_type);
|
|
|
- }
|
|
|
+ if (!found) {
|
|
|
+ found = _guess_method_return_type_from_base(c, base, call->function_name, r_type);
|
|
|
}
|
|
|
} break;
|
|
|
case GDScriptParser::Node::SUBSCRIPT: {
|