diff --git a/editor/code_editor.cpp b/editor/code_editor.cpp index d8648310b65..9a87d6d38cf 100644 --- a/editor/code_editor.cpp +++ b/editor/code_editor.cpp @@ -1778,6 +1778,7 @@ CodeTextEditor::CodeTextEditor() { cs.push_back("("); cs.push_back("="); cs.push_back("$"); + cs.push_back("@"); text_editor->set_completion(true, cs); idle->connect("timeout", callable_mp(this, &CodeTextEditor::_text_changed_idle_timeout)); diff --git a/modules/gdscript/gdscript.cpp b/modules/gdscript/gdscript.cpp index dffea329b49..0eade062c97 100644 --- a/modules/gdscript/gdscript.cpp +++ b/modules/gdscript/gdscript.cpp @@ -471,7 +471,7 @@ bool GDScript::_update_exports(bool *r_err, bool p_recursive_call) { members_cache.push_back(member.variable->export_info); Variant default_value; - if (member.variable->initializer->is_constant) { + if (member.variable->initializer && member.variable->initializer->is_constant) { default_value = member.variable->initializer->reduced_value; } member_default_values_cache[member.variable->identifier->name] = default_value; diff --git a/modules/gdscript/gdscript_analyzer.cpp b/modules/gdscript/gdscript_analyzer.cpp index 5b19460b0d2..7201490ceec 100644 --- a/modules/gdscript/gdscript_analyzer.cpp +++ b/modules/gdscript/gdscript_analyzer.cpp @@ -1134,6 +1134,10 @@ void GDScriptAnalyzer::resolve_return(GDScriptParser::ReturnNode *p_return) { void GDScriptAnalyzer::reduce_expression(GDScriptParser::ExpressionNode *p_expression) { // This one makes some magic happen. + if (p_expression == nullptr) { + return; + } + if (p_expression->reduced) { // Don't do this more than once. return; @@ -1248,6 +1252,10 @@ void GDScriptAnalyzer::reduce_assignment(GDScriptParser::AssignmentNode *p_assig reduce_expression(p_assignment->assignee); reduce_expression(p_assignment->assigned_value); + if (p_assignment->assigned_value == nullptr || p_assignment->assignee == nullptr) { + return; + } + if (p_assignment->assignee->get_datatype().is_constant) { push_error("Cannot assign a new value to a constant.", p_assignment->assignee); } @@ -2038,6 +2046,9 @@ void GDScriptAnalyzer::reduce_subscript(GDScriptParser::SubscriptNode *p_subscri // Reduce index first. If it's a constant StringName, use attribute instead. if (!p_subscript->is_attribute) { + if (p_subscript->index == nullptr) { + return; + } reduce_expression(p_subscript->index); if (p_subscript->index->is_constant && p_subscript->index->reduced_value.get_type() == Variant::STRING_NAME) { @@ -2053,6 +2064,9 @@ void GDScriptAnalyzer::reduce_subscript(GDScriptParser::SubscriptNode *p_subscri } if (p_subscript->is_attribute) { + if (p_subscript->attribute == nullptr) { + return; + } if (p_subscript->base->is_constant) { // Just try to get it. bool valid = false; diff --git a/modules/gdscript/gdscript_compiler.cpp b/modules/gdscript/gdscript_compiler.cpp index 80efc9519d6..da19ce5000e 100644 --- a/modules/gdscript/gdscript_compiler.cpp +++ b/modules/gdscript/gdscript_compiler.cpp @@ -165,10 +165,12 @@ GDScriptDataType GDScriptCompiler::_gdtype_from_datatype(const GDScriptParser::D } } break; - default: { + case GDScriptParser::DataType::UNRESOLVED: { ERR_PRINT("Parser bug: converting unresolved type."); return GDScriptDataType(); } + default: + break; // FIXME } return result; @@ -1325,10 +1327,9 @@ int GDScriptCompiler::_parse_expression(CodeGen &codegen, const GDScriptParser:: return -1; } - // FIXME: Actually check type. - GDScriptDataType assign_type; // = _gdtype_from_datatype(on->arguments[0]->get_datatype()); + GDScriptDataType assign_type = _gdtype_from_datatype(assignment->assignee->get_datatype()); - if (assign_type.has_type && !assignment->get_datatype().is_set()) { + if (assign_type.has_type && !assignment->assigned_value->get_datatype().is_variant()) { // Typed assignment switch (assign_type.kind) { case GDScriptDataType::BUILTIN: { diff --git a/modules/gdscript/gdscript_editor.cpp b/modules/gdscript/gdscript_editor.cpp index 86ba16aaec7..e1ca1c9f488 100644 --- a/modules/gdscript/gdscript_editor.cpp +++ b/modules/gdscript/gdscript_editor.cpp @@ -39,6 +39,7 @@ #include "gdscript_tokenizer.h" #ifdef TOOLS_ENABLED +#include "core/project_settings.h" #include "editor/editor_file_system.h" #include "editor/editor_settings.h" #endif @@ -170,6 +171,15 @@ bool GDScriptLanguage::validate(const String &p_script, int &r_line_error, int & } } + if (r_safe_lines) { + const Set &unsafe_lines = parser.get_unsafe_lines(); + for (int i = 1; i <= parser.get_last_line_number(); i++) { + if (!unsafe_lines.has(i)) { + r_safe_lines->insert(i); + } + } + } + return true; } @@ -469,11 +479,2192 @@ String GDScriptLanguage::make_function(const String &p_class, const String &p_na //////// COMPLETION ////////// -// FIXME: Readd completion +#ifdef TOOLS_ENABLED + +struct GDScriptCompletionIdentifier { + GDScriptParser::DataType type; + String enumeration; + Variant value; + const GDScriptParser::ExpressionNode *assigned_expression = nullptr; +}; + +// TODO: Move this to a central location (maybe core?). +static const char *underscore_classes[] = { + "ClassDB", + "Directory", + "Engine", + "File", + "Geometry", + "GodotSharp", + "JSON", + "Marshalls", + "Mutex", + "OS", + "ResourceLoader", + "ResourceSaver", + "Semaphore", + "Thread", + "VisualScriptEditor", + nullptr, +}; +static StringName _get_real_class_name(const StringName &p_source) { + const char **class_name = underscore_classes; + while (*class_name != nullptr) { + if (p_source == *class_name) { + return String("_") + p_source; + } + class_name++; + } + return p_source; +} + +static String _get_visual_datatype(const PropertyInfo &p_info, bool p_is_arg = true) { + if (p_info.usage & PROPERTY_USAGE_CLASS_IS_ENUM) { + String enum_name = p_info.class_name; + if (enum_name.find(".") == -1) { + return enum_name; + } + return enum_name.get_slice(".", 1); + } + + String n = p_info.name; + int idx = n.find(":"); + if (idx != -1) { + return n.substr(idx + 1, n.length()); + } + + if (p_info.type == Variant::OBJECT) { + if (p_info.hint == PROPERTY_HINT_RESOURCE_TYPE) { + return p_info.hint_string; + } else { + return p_info.class_name.operator String(); + } + } + if (p_info.type == Variant::NIL) { + if (p_is_arg || (p_info.usage & PROPERTY_USAGE_NIL_IS_VARIANT)) { + return "Variant"; + } else { + return "void"; + } + } + + return Variant::get_type_name(p_info.type); +} + +static String _make_arguments_hint(const MethodInfo &p_info, int p_arg_idx, bool p_is_annotation = false) { + String arghint; + if (!p_is_annotation) { + arghint += _get_visual_datatype(p_info.return_val, false) + " "; + } + arghint += p_info.name + "("; + + int def_args = p_info.arguments.size() - p_info.default_arguments.size(); + int i = 0; + for (const List::Element *E = p_info.arguments.front(); E; E = E->next()) { + if (i > 0) { + arghint += ", "; + } + + if (i == p_arg_idx) { + arghint += String::chr(0xFFFF); + } + arghint += E->get().name + ": " + _get_visual_datatype(E->get(), true); + + if (i - def_args >= 0) { + arghint += String(" = ") + p_info.default_arguments[i - def_args].get_construct_string(); + } + + if (i == p_arg_idx) { + arghint += String::chr(0xFFFF); + } + + i++; + } + + if (p_info.flags & METHOD_FLAG_VARARG) { + if (p_info.arguments.size() > 0) { + arghint += ", "; + } + if (p_arg_idx >= p_info.arguments.size()) { + arghint += String::chr(0xFFFF); + } + arghint += "..."; + if (p_arg_idx >= p_info.arguments.size()) { + arghint += String::chr(0xFFFF); + } + } + + arghint += ")"; + + return arghint; +} + +static String _make_arguments_hint(const GDScriptParser::FunctionNode *p_function, int p_arg_idx) { + String arghint = p_function->get_datatype().to_string() + " " + p_function->identifier->name.operator String() + "("; + + for (int i = 0; i < p_function->parameters.size(); i++) { + if (i > 0) { + arghint += ", "; + } + + if (i == p_arg_idx) { + arghint += String::chr(0xFFFF); + } + const GDScriptParser::ParameterNode *par = p_function->parameters[i]; + arghint += par->identifier->name.operator String() + ": " + par->get_datatype().to_string(); + + if (par->default_value) { + String def_val = ""; + if (par->default_value->type == GDScriptParser::Node::LITERAL) { + const GDScriptParser::LiteralNode *literal = static_cast(par->default_value); + def_val = literal->value.get_construct_string(); + } else if (par->default_value->type == GDScriptParser::Node::IDENTIFIER) { + const GDScriptParser::IdentifierNode *id = static_cast(par->default_value); + def_val = id->name.operator String(); + } + arghint += " = " + def_val; + } + if (i == p_arg_idx) { + arghint += String::chr(0xFFFF); + } + } + + arghint += ")"; + + return arghint; +} + +static void _get_directory_contents(EditorFileSystemDirectory *p_dir, Map &r_list) { + const String quote_style = EDITOR_DEF("text_editor/completion/use_single_quotes", false) ? "'" : "\""; + + for (int i = 0; i < p_dir->get_file_count(); i++) { + ScriptCodeCompletionOption option(p_dir->get_file_path(i), ScriptCodeCompletionOption::KIND_FILE_PATH); + option.insert_text = quote_style + option.display + quote_style; + r_list.insert(option.display, option); + } + + for (int i = 0; i < p_dir->get_subdir_count(); i++) { + _get_directory_contents(p_dir->get_subdir(i), r_list); + } +} + +static void _find_annotation_arguments(const GDScriptParser::AnnotationNode *p_annotation, int p_argument, const String p_quote_style, Map &r_result) { + if (p_annotation->name == "@export_range" || p_annotation->name == "@export_exp_range") { + if (p_argument == 3 || p_argument == 4) { + // Slider hint. + ScriptCodeCompletionOption slider1("or_greater", ScriptCodeCompletionOption::KIND_PLAIN_TEXT); + slider1.insert_text = p_quote_style + slider1.display + p_quote_style; + r_result.insert(slider1.display, slider1); + ScriptCodeCompletionOption slider2("or_lesser", ScriptCodeCompletionOption::KIND_PLAIN_TEXT); + slider2.insert_text = p_quote_style + slider2.display + p_quote_style; + r_result.insert(slider2.display, slider2); + } + } else if (p_annotation->name == "@export_exp_easing") { + if (p_argument == 0 || p_argument == 1) { + // Easing hint. + ScriptCodeCompletionOption hint1("attenuation", ScriptCodeCompletionOption::KIND_PLAIN_TEXT); + hint1.insert_text = p_quote_style + hint1.display + p_quote_style; + r_result.insert(hint1.display, hint1); + ScriptCodeCompletionOption hint2("inout", ScriptCodeCompletionOption::KIND_PLAIN_TEXT); + hint2.insert_text = p_quote_style + hint2.display + p_quote_style; + r_result.insert(hint2.display, hint2); + } + } else if (p_annotation->name == "@export_node_path") { + ScriptCodeCompletionOption node("Node", ScriptCodeCompletionOption::KIND_CLASS); + r_result.insert(node.display, node); + List node_types; + ClassDB::get_inheriters_from_class("Node", &node_types); + for (const List::Element *E = node_types.front(); E != nullptr; E = E->next()) { + if (!ClassDB::is_class_exposed(E->get())) { + continue; + } + ScriptCodeCompletionOption option(E->get(), ScriptCodeCompletionOption::KIND_CLASS); + r_result.insert(option.display, option); + } + } +} + +static void _list_available_types(bool p_inherit_only, GDScriptParser::CompletionContext &p_context, Map &r_result) { + List native_types; + ClassDB::get_class_list(&native_types); + for (const List::Element *E = native_types.front(); E != nullptr; E = E->next()) { + if (ClassDB::is_class_exposed(E->get()) && !Engine::get_singleton()->has_singleton(E->get())) { + ScriptCodeCompletionOption option(E->get(), ScriptCodeCompletionOption::KIND_CLASS); + r_result.insert(option.display, option); + } + } + + if (p_context.current_class) { + if (!p_inherit_only && p_context.current_class->base_type.is_set()) { + // Native enums from base class + List enums; + ClassDB::get_enum_list(p_context.current_class->base_type.native_type, &enums); + for (const List::Element *E = enums.front(); E != nullptr; E = E->next()) { + ScriptCodeCompletionOption option(E->get(), ScriptCodeCompletionOption::KIND_ENUM); + r_result.insert(option.display, option); + } + } + // Check current class for potential types + const GDScriptParser::ClassNode *current = p_context.current_class; + 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: { + ScriptCodeCompletionOption option(member.m_class->identifier->name, ScriptCodeCompletionOption::KIND_CLASS); + r_result.insert(option.display, option); + } break; + case GDScriptParser::ClassNode::Member::ENUM: { + if (!p_inherit_only) { + ScriptCodeCompletionOption option(member.m_enum->identifier->name, ScriptCodeCompletionOption::KIND_ENUM); + 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) { + ScriptCodeCompletionOption option(member.constant->identifier->name, ScriptCodeCompletionOption::KIND_CLASS); + r_result.insert(option.display, option); + } + } break; + default: + break; + } + } + current = current->outer; + } + } + + // Global scripts + List global_classes; + ScriptServer::get_global_class_list(&global_classes); + for (const List::Element *E = global_classes.front(); E != nullptr; E = E->next()) { + ScriptCodeCompletionOption option(E->get(), ScriptCodeCompletionOption::KIND_CLASS); + r_result.insert(option.display, option); + } + + // Autoload singletons + Map autoloads = ProjectSettings::get_singleton()->get_autoload_list(); + for (const Map::Element *E = autoloads.front(); E != nullptr; E = E->next()) { + const ProjectSettings::AutoloadInfo &info = E->get(); + if (!info.is_singleton || info.path.get_extension().to_lower() != "gd") { + continue; + } + ScriptCodeCompletionOption option(info.name, ScriptCodeCompletionOption::KIND_CLASS); + r_result.insert(option.display, option); + } +} + +static void _find_identifiers_in_suite(const GDScriptParser::SuiteNode *p_suite, Map &r_result) { + for (int i = 0; i < p_suite->locals.size(); i++) { + ScriptCodeCompletionOption option(p_suite->locals[i].name, ScriptCodeCompletionOption::KIND_VARIABLE); + r_result.insert(option.display, option); + } + if (p_suite->parent_block) { + _find_identifiers_in_suite(p_suite->parent_block, r_result); + } +} + +static void _find_identifiers_in_base(const GDScriptCompletionIdentifier &p_base, bool p_only_functions, Map &r_result); + +static void _find_identifiers_in_class(const GDScriptParser::ClassNode *p_class, bool p_only_functions, bool p_static, bool p_parent_only, Map &r_result) { + if (!p_parent_only) { + bool outer = false; + const GDScriptParser::ClassNode *clss = p_class; + while (clss) { + for (int i = 0; i < clss->members.size(); i++) { + const GDScriptParser::ClassNode::Member &member = clss->members[i]; + ScriptCodeCompletionOption option; + switch (member.type) { + case GDScriptParser::ClassNode::Member::VARIABLE: + if (p_only_functions || outer || (p_static)) { + continue; + } + option = ScriptCodeCompletionOption(member.variable->identifier->name, ScriptCodeCompletionOption::KIND_MEMBER); + break; + case GDScriptParser::ClassNode::Member::CONSTANT: + if (p_only_functions) { + continue; + } + option = ScriptCodeCompletionOption(member.constant->identifier->name, ScriptCodeCompletionOption::KIND_CONSTANT); + break; + case GDScriptParser::ClassNode::Member::CLASS: + if (p_only_functions) { + continue; + } + option = ScriptCodeCompletionOption(member.m_class->identifier->name, ScriptCodeCompletionOption::KIND_CLASS); + break; + case GDScriptParser::ClassNode::Member::ENUM_VALUE: + if (p_only_functions) { + continue; + } + option = ScriptCodeCompletionOption(member.enum_value.identifier->name, ScriptCodeCompletionOption::KIND_CONSTANT); + break; + case GDScriptParser::ClassNode::Member::ENUM: + if (p_only_functions) { + continue; + } + option = ScriptCodeCompletionOption(member.m_enum->identifier->name, ScriptCodeCompletionOption::KIND_ENUM); + break; + case GDScriptParser::ClassNode::Member::FUNCTION: + if (outer || (p_static && !member.function->is_static) || member.function->identifier->name.operator String().begins_with("@")) { + continue; + } + option = ScriptCodeCompletionOption(member.function->identifier->name, ScriptCodeCompletionOption::KIND_FUNCTION); + if (member.function->parameters.size() > 0) { + option.insert_text += "("; + } else { + option.insert_text += "()"; + } + break; + case GDScriptParser::ClassNode::Member::SIGNAL: + if (p_only_functions || outer) { + clss = clss->outer; + continue; + } + option = ScriptCodeCompletionOption(member.signal->identifier->name, ScriptCodeCompletionOption::KIND_SIGNAL); + break; + case GDScriptParser::ClassNode::Member::UNDEFINED: + break; + } + r_result.insert(option.display, option); + } + outer = true; + clss = clss->outer; + } + } + + // Parents. + GDScriptCompletionIdentifier base_type; + base_type.type = p_class->base_type; + base_type.type.is_meta_type = p_static; + + _find_identifiers_in_base(base_type, p_only_functions, r_result); +} + +static void _find_identifiers_in_base(const GDScriptCompletionIdentifier &p_base, bool p_only_functions, Map &r_result) { + GDScriptParser::DataType base_type = p_base.type; + bool _static = base_type.is_meta_type; + + if (_static && base_type.kind != GDScriptParser::DataType::BUILTIN) { + ScriptCodeCompletionOption option("new", ScriptCodeCompletionOption::KIND_FUNCTION); + option.insert_text += "("; + r_result.insert(option.display, option); + } + + while (!base_type.has_no_type()) { + switch (base_type.kind) { + case GDScriptParser::DataType::CLASS: { + _find_identifiers_in_class(base_type.class_type, p_only_functions, _static, false, r_result); + // This already finds all parent identifiers, so we are done. + base_type = GDScriptParser::DataType(); + } break; + case GDScriptParser::DataType::SCRIPT: { + Ref