Merge pull request #47131 from vnen/gdscript-export-fix

Fix a few issues with @export in GDScript
This commit is contained in:
Rémi Verschelde 2021-03-30 15:12:04 +02:00 committed by GitHub
commit 737f09895d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 167 additions and 143 deletions

View File

@ -39,40 +39,6 @@
#include "gdscript.h" #include "gdscript.h"
#include "gdscript_utility_functions.h" #include "gdscript_utility_functions.h"
// TODO: Move this to a central location (maybe core?).
static HashMap<StringName, StringName> underscore_map;
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) {
if (underscore_map.is_empty()) {
const char **class_name = underscore_classes;
while (*class_name != nullptr) {
underscore_map[*class_name] = String("_") + *class_name;
class_name++;
}
}
if (underscore_map.has(p_source)) {
return underscore_map[p_source];
}
return p_source;
}
static MethodInfo info_from_utility_func(const StringName &p_function) { static MethodInfo info_from_utility_func(const StringName &p_function) {
ERR_FAIL_COND_V(!Variant::has_utility_function(p_function), MethodInfo()); ERR_FAIL_COND_V(!Variant::has_utility_function(p_function), MethodInfo());
@ -106,10 +72,6 @@ static MethodInfo info_from_utility_func(const StringName &p_function) {
return info; return info;
} }
void GDScriptAnalyzer::cleanup() {
underscore_map.clear();
}
static GDScriptParser::DataType make_callable_type(const MethodInfo &p_info) { static GDScriptParser::DataType make_callable_type(const MethodInfo &p_info) {
GDScriptParser::DataType type; GDScriptParser::DataType type;
type.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT; type.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT;
@ -150,7 +112,7 @@ static GDScriptParser::DataType make_native_enum_type(const StringName &p_native
type.is_meta_type = true; type.is_meta_type = true;
List<StringName> enum_values; List<StringName> enum_values;
StringName real_native_name = get_real_class_name(p_native_class); StringName real_native_name = GDScriptParser::get_real_class_name(p_native_class);
ClassDB::get_enum_constants(real_native_name, p_enum_name, &enum_values); ClassDB::get_enum_constants(real_native_name, p_enum_name, &enum_values);
for (const List<StringName>::Element *E = enum_values.front(); E != nullptr; E = E->next()) { for (const List<StringName>::Element *E = enum_values.front(); E != nullptr; E = E->next()) {
@ -267,7 +229,7 @@ Error GDScriptAnalyzer::resolve_inheritance(GDScriptParser::ClassNode *p_class,
push_error(vformat(R"(Could not resolve super class inheritance from "%s".)", name), p_class); push_error(vformat(R"(Could not resolve super class inheritance from "%s".)", name), p_class);
return err; return err;
} }
} else if (class_exists(name) && ClassDB::can_instance(get_real_class_name(name))) { } else if (class_exists(name) && ClassDB::can_instance(GDScriptParser::get_real_class_name(name))) {
base.kind = GDScriptParser::DataType::NATIVE; base.kind = GDScriptParser::DataType::NATIVE;
base.native_type = name; base.native_type = name;
} else { } else {
@ -444,7 +406,7 @@ GDScriptParser::DataType GDScriptAnalyzer::resolve_datatype(GDScriptParser::Type
return GDScriptParser::DataType(); return GDScriptParser::DataType();
} }
result = ref->get_parser()->head->get_datatype(); result = ref->get_parser()->head->get_datatype();
} else if (ClassDB::has_enum(get_real_class_name(parser->current_class->base_type.native_type), first)) { } else if (ClassDB::has_enum(GDScriptParser::get_real_class_name(parser->current_class->base_type.native_type), first)) {
// Native enum in current class. // Native enum in current class.
result = make_native_enum_type(parser->current_class->base_type.native_type, first); result = make_native_enum_type(parser->current_class->base_type.native_type, first);
} else { } else {
@ -507,7 +469,7 @@ GDScriptParser::DataType GDScriptAnalyzer::resolve_datatype(GDScriptParser::Type
} }
} else if (result.kind == GDScriptParser::DataType::NATIVE) { } else if (result.kind == GDScriptParser::DataType::NATIVE) {
// Only enums allowed for native. // Only enums allowed for native.
if (ClassDB::has_enum(get_real_class_name(result.native_type), p_type->type_chain[1]->name)) { if (ClassDB::has_enum(GDScriptParser::get_real_class_name(result.native_type), p_type->type_chain[1]->name)) {
if (p_type->type_chain.size() > 2) { if (p_type->type_chain.size() > 2) {
push_error(R"(Enums cannot contain nested types.)", p_type->type_chain[2]); push_error(R"(Enums cannot contain nested types.)", p_type->type_chain[2]);
} else { } else {
@ -607,28 +569,10 @@ void GDScriptAnalyzer::resolve_class_interface(GDScriptParser::ClassNode *p_clas
datatype.is_constant = false; datatype.is_constant = false;
member.variable->set_datatype(datatype); member.variable->set_datatype(datatype);
if (!datatype.has_no_type()) {
// TODO: Move this out into a routine specific to validate annotations. // Apply annotations.
if (member.variable->export_info.hint == PROPERTY_HINT_TYPE_STRING) { for (List<GDScriptParser::AnnotationNode *>::Element *E = member.variable->annotations.front(); E; E = E->next()) {
// @export annotation. E->get()->apply(parser, member.variable);
switch (datatype.kind) {
case GDScriptParser::DataType::BUILTIN:
member.variable->export_info.hint_string = Variant::get_type_name(datatype.builtin_type);
break;
case GDScriptParser::DataType::NATIVE:
if (ClassDB::is_parent_class(get_real_class_name(datatype.native_type), "Resource")) {
member.variable->export_info.hint = PROPERTY_HINT_RESOURCE_TYPE;
member.variable->export_info.hint_string = get_real_class_name(datatype.native_type);
} else {
push_error(R"(Export type can only be built-in or a resource.)", member.variable);
}
break;
default:
// TODO: Allow custom user resources.
push_error(R"(Export type can only be built-in or a resource.)", member.variable);
break;
}
}
} }
} break; } break;
case GDScriptParser::ClassNode::Member::CONSTANT: { case GDScriptParser::ClassNode::Member::CONSTANT: {
@ -674,6 +618,11 @@ void GDScriptAnalyzer::resolve_class_interface(GDScriptParser::ClassNode *p_clas
datatype.is_constant = true; datatype.is_constant = true;
member.constant->set_datatype(datatype); member.constant->set_datatype(datatype);
// Apply annotations.
for (List<GDScriptParser::AnnotationNode *>::Element *E = member.constant->annotations.front(); E; E = E->next()) {
E->get()->apply(parser, member.constant);
}
} break; } break;
case GDScriptParser::ClassNode::Member::SIGNAL: { case GDScriptParser::ClassNode::Member::SIGNAL: {
for (int j = 0; j < member.signal->parameters.size(); j++) { for (int j = 0; j < member.signal->parameters.size(); j++) {
@ -688,6 +637,11 @@ void GDScriptAnalyzer::resolve_class_interface(GDScriptParser::ClassNode *p_clas
signal_type.builtin_type = Variant::SIGNAL; signal_type.builtin_type = Variant::SIGNAL;
member.signal->set_datatype(signal_type); member.signal->set_datatype(signal_type);
// Apply annotations.
for (List<GDScriptParser::AnnotationNode *>::Element *E = member.signal->annotations.front(); E; E = E->next()) {
E->get()->apply(parser, member.signal);
}
} break; } break;
case GDScriptParser::ClassNode::Member::ENUM: { case GDScriptParser::ClassNode::Member::ENUM: {
GDScriptParser::DataType enum_type; GDScriptParser::DataType enum_type;
@ -730,6 +684,11 @@ void GDScriptAnalyzer::resolve_class_interface(GDScriptParser::ClassNode *p_clas
current_enum = nullptr; current_enum = nullptr;
member.m_enum->set_datatype(enum_type); member.m_enum->set_datatype(enum_type);
// Apply annotations.
for (List<GDScriptParser::AnnotationNode *>::Element *E = member.m_enum->annotations.front(); E; E = E->next()) {
E->get()->apply(parser, member.m_enum);
}
} break; } break;
case GDScriptParser::ClassNode::Member::FUNCTION: case GDScriptParser::ClassNode::Member::FUNCTION:
resolve_function_signature(member.function); resolve_function_signature(member.function);
@ -798,6 +757,11 @@ void GDScriptAnalyzer::resolve_class_body(GDScriptParser::ClassNode *p_class) {
} }
resolve_function_body(member.function); resolve_function_body(member.function);
// Apply annotations.
for (List<GDScriptParser::AnnotationNode *>::Element *E = member.function->annotations.front(); E; E = E->next()) {
E->get()->apply(parser, member.function);
}
} }
parser->current_class = previous_class; parser->current_class = previous_class;
@ -2253,7 +2217,7 @@ void GDScriptAnalyzer::reduce_get_node(GDScriptParser::GetNodeNode *p_get_node)
result.native_type = "Node"; result.native_type = "Node";
result.builtin_type = Variant::OBJECT; result.builtin_type = Variant::OBJECT;
if (!ClassDB::is_parent_class(get_real_class_name(parser->current_class->base_type.native_type), result.native_type)) { if (!ClassDB::is_parent_class(GDScriptParser::get_real_class_name(parser->current_class->base_type.native_type), result.native_type)) {
push_error(R"*(Cannot use shorthand "get_node()" notation ("$") on a class that isn't a node.)*", p_get_node); push_error(R"*(Cannot use shorthand "get_node()" notation ("$") on a class that isn't a node.)*", p_get_node);
} }
@ -2419,7 +2383,7 @@ void GDScriptAnalyzer::reduce_identifier_from_base(GDScriptParser::IdentifierNod
} }
// Check native members. // Check native members.
const StringName &native = get_real_class_name(base.native_type); const StringName &native = GDScriptParser::get_real_class_name(base.native_type);
if (class_exists(native)) { if (class_exists(native)) {
PropertyInfo prop_info; PropertyInfo prop_info;
@ -3243,7 +3207,7 @@ bool GDScriptAnalyzer::get_function_signature(GDScriptParser::Node *p_source, GD
return true; return true;
} }
StringName real_native = get_real_class_name(base_native); StringName real_native = GDScriptParser::get_real_class_name(base_native);
MethodInfo info; MethodInfo info;
if (ClassDB::get_method_info(real_native, function_name, &info)) { if (ClassDB::get_method_info(real_native, function_name, &info)) {
@ -3338,7 +3302,7 @@ bool GDScriptAnalyzer::is_shadowing(GDScriptParser::IdentifierNode *p_local, con
StringName parent = base_native; StringName parent = base_native;
while (parent != StringName()) { while (parent != StringName()) {
StringName real_class_name = get_real_class_name(parent); StringName real_class_name = GDScriptParser::get_real_class_name(parent);
if (ClassDB::has_method(real_class_name, name, true)) { if (ClassDB::has_method(real_class_name, name, true)) {
parser->push_warning(p_local, GDScriptWarning::SHADOWED_VARIABLE_BASE_CLASS, p_context, p_local->name, "method", parent); parser->push_warning(p_local, GDScriptWarning::SHADOWED_VARIABLE_BASE_CLASS, p_context, p_local->name, "method", parent);
return true; return true;
@ -3500,14 +3464,14 @@ bool GDScriptAnalyzer::is_type_compatible(const GDScriptParser::DataType &p_targ
} }
// Get underscore-prefixed version for some classes. // Get underscore-prefixed version for some classes.
src_native = get_real_class_name(src_native); src_native = GDScriptParser::get_real_class_name(src_native);
switch (p_target.kind) { switch (p_target.kind) {
case GDScriptParser::DataType::NATIVE: { case GDScriptParser::DataType::NATIVE: {
if (p_target.is_meta_type) { if (p_target.is_meta_type) {
return ClassDB::is_parent_class(src_native, GDScriptNativeClass::get_class_static()); return ClassDB::is_parent_class(src_native, GDScriptNativeClass::get_class_static());
} }
StringName tgt_native = get_real_class_name(p_target.native_type); StringName tgt_native = GDScriptParser::get_real_class_name(p_target.native_type);
return ClassDB::is_parent_class(src_native, tgt_native); return ClassDB::is_parent_class(src_native, tgt_native);
} }
case GDScriptParser::DataType::SCRIPT: case GDScriptParser::DataType::SCRIPT:
@ -3557,7 +3521,7 @@ void GDScriptAnalyzer::mark_node_unsafe(const GDScriptParser::Node *p_node) {
} }
bool GDScriptAnalyzer::class_exists(const StringName &p_class) const { bool GDScriptAnalyzer::class_exists(const StringName &p_class) const {
StringName real_name = get_real_class_name(p_class); StringName real_name = GDScriptParser::get_real_class_name(p_class);
return ClassDB::class_exists(real_name) && ClassDB::is_class_exposed(real_name); return ClassDB::class_exists(real_name) && ClassDB::is_class_exposed(real_name);
} }

View File

@ -120,8 +120,6 @@ public:
Error analyze(); Error analyze();
GDScriptAnalyzer(GDScriptParser *p_parser); GDScriptAnalyzer(GDScriptParser *p_parser);
static void cleanup();
}; };
#endif // GDSCRIPT_ANALYZER_H #endif // GDSCRIPT_ANALYZER_H

View File

@ -137,16 +137,12 @@ GDScriptDataType GDScriptCompiler::_gdtype_from_datatype(const GDScriptParser::D
} }
} }
} break; } break;
case GDScriptParser::DataType::ENUM:
case GDScriptParser::DataType::ENUM_VALUE: case GDScriptParser::DataType::ENUM_VALUE:
result.has_type = true; result.has_type = true;
result.kind = GDScriptDataType::BUILTIN; result.kind = GDScriptDataType::BUILTIN;
result.builtin_type = Variant::INT; result.builtin_type = Variant::INT;
break; break;
case GDScriptParser::DataType::ENUM:
result.has_type = true;
result.kind = GDScriptDataType::BUILTIN;
result.builtin_type = Variant::DICTIONARY;
break;
case GDScriptParser::DataType::UNRESOLVED: { case GDScriptParser::DataType::UNRESOLVED: {
ERR_PRINT("Parser bug: converting unresolved type."); ERR_PRINT("Parser bug: converting unresolved type.");
return GDScriptDataType(); return GDScriptDataType();
@ -2218,9 +2214,8 @@ Error GDScriptCompiler::_parse_class_level(GDScript *p_script, const GDScriptPar
prop_info.hint = export_info.hint; prop_info.hint = export_info.hint;
prop_info.hint_string = export_info.hint_string; prop_info.hint_string = export_info.hint_string;
prop_info.usage = export_info.usage; prop_info.usage = export_info.usage;
} else {
prop_info.usage = PROPERTY_USAGE_SCRIPT_VARIABLE;
} }
prop_info.usage |= PROPERTY_USAGE_SCRIPT_VARIABLE;
#ifdef TOOLS_ENABLED #ifdef TOOLS_ENABLED
p_script->doc_variables[name] = variable->doc_description; p_script->doc_variables[name] = variable->doc_description;
#endif #endif

View File

@ -500,36 +500,6 @@ struct GDScriptCompletionIdentifier {
const GDScriptParser::ExpressionNode *assigned_expression = nullptr; 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) { static String _get_visual_datatype(const PropertyInfo &p_info, bool p_is_arg = true) {
if (p_info.usage & PROPERTY_USAGE_CLASS_IS_ENUM) { if (p_info.usage & PROPERTY_USAGE_CLASS_IS_ENUM) {
String enum_name = p_info.class_name; String enum_name = p_info.class_name;
@ -930,7 +900,7 @@ static void _find_identifiers_in_base(const GDScriptCompletionIdentifier &p_base
} }
} break; } break;
case GDScriptParser::DataType::NATIVE: { case GDScriptParser::DataType::NATIVE: {
StringName type = _get_real_class_name(base_type.native_type); StringName type = GDScriptParser::get_real_class_name(base_type.native_type);
if (!ClassDB::class_exists(type)) { if (!ClassDB::class_exists(type)) {
return; return;
} }
@ -1783,7 +1753,7 @@ static bool _guess_identifier_type(GDScriptParser::CompletionContext &p_context,
base_type = GDScriptParser::DataType(); base_type = GDScriptParser::DataType();
break; break;
} }
StringName real_native = _get_real_class_name(base_type.native_type); StringName real_native = GDScriptParser::get_real_class_name(base_type.native_type);
MethodInfo info; MethodInfo info;
if (ClassDB::get_method_info(real_native, p_context.current_function->identifier->name, &info)) { if (ClassDB::get_method_info(real_native, p_context.current_function->identifier->name, &info)) {
for (const List<PropertyInfo>::Element *E = info.arguments.front(); E; E = E->next()) { for (const List<PropertyInfo>::Element *E = info.arguments.front(); E; E = E->next()) {
@ -1854,7 +1824,7 @@ static bool _guess_identifier_type(GDScriptParser::CompletionContext &p_context,
} }
// Check ClassDB. // Check ClassDB.
StringName class_name = _get_real_class_name(p_identifier); StringName class_name = GDScriptParser::get_real_class_name(p_identifier);
if (ClassDB::class_exists(class_name) && ClassDB::is_class_exposed(class_name)) { if (ClassDB::class_exists(class_name) && ClassDB::is_class_exposed(class_name)) {
r_type.type.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT; r_type.type.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT;
r_type.type.kind = GDScriptParser::DataType::NATIVE; r_type.type.kind = GDScriptParser::DataType::NATIVE;
@ -1970,7 +1940,7 @@ static bool _guess_identifier_type_from_base(GDScriptParser::CompletionContext &
} }
} break; } break;
case GDScriptParser::DataType::NATIVE: { case GDScriptParser::DataType::NATIVE: {
StringName class_name = _get_real_class_name(base_type.native_type); StringName class_name = GDScriptParser::get_real_class_name(base_type.native_type);
if (!ClassDB::class_exists(class_name)) { if (!ClassDB::class_exists(class_name)) {
return false; return false;
} }
@ -2133,7 +2103,7 @@ static bool _guess_method_return_type_from_base(GDScriptParser::CompletionContex
} }
} break; } break;
case GDScriptParser::DataType::NATIVE: { case GDScriptParser::DataType::NATIVE: {
StringName native = _get_real_class_name(base_type.native_type); StringName native = GDScriptParser::get_real_class_name(base_type.native_type);
if (!ClassDB::class_exists(native)) { if (!ClassDB::class_exists(native)) {
return false; return false;
} }
@ -2230,7 +2200,7 @@ static void _find_call_arguments(GDScriptParser::CompletionContext &p_context, c
base_type = base_type.class_type->base_type; base_type = base_type.class_type->base_type;
} break; } break;
case GDScriptParser::DataType::NATIVE: { case GDScriptParser::DataType::NATIVE: {
StringName class_name = _get_real_class_name(base_type.native_type); StringName class_name = GDScriptParser::get_real_class_name(base_type.native_type);
if (!ClassDB::class_exists(class_name)) { if (!ClassDB::class_exists(class_name)) {
base_type.kind = GDScriptParser::DataType::UNRESOLVED; base_type.kind = GDScriptParser::DataType::UNRESOLVED;
break; break;
@ -2615,7 +2585,7 @@ Error GDScriptLanguage::complete_code(const String &p_code, const String &p_path
break; break;
} }
StringName class_name = _get_real_class_name(native_type.native_type); StringName class_name = GDScriptParser::get_real_class_name(native_type.native_type);
if (!ClassDB::class_exists(class_name)) { if (!ClassDB::class_exists(class_name)) {
break; break;
} }
@ -2844,7 +2814,7 @@ static Error _lookup_symbol_from_base(const GDScriptParser::DataType &p_base, co
} }
} break; } break;
case GDScriptParser::DataType::NATIVE: { case GDScriptParser::DataType::NATIVE: {
StringName class_name = _get_real_class_name(base_type.native_type); StringName class_name = GDScriptParser::get_real_class_name(base_type.native_type);
if (!ClassDB::class_exists(class_name)) { if (!ClassDB::class_exists(class_name)) {
base_type.kind = GDScriptParser::DataType::UNRESOLVED; base_type.kind = GDScriptParser::DataType::UNRESOLVED;
break; break;

View File

@ -94,8 +94,43 @@ Variant::Type GDScriptParser::get_builtin_type(const StringName &p_type) {
return Variant::VARIANT_MAX; return Variant::VARIANT_MAX;
} }
// TODO: Move this to a central location (maybe core?).
static HashMap<StringName, StringName> underscore_map;
static const char *underscore_classes[] = {
"ClassDB",
"Directory",
"Engine",
"File",
"Geometry",
"GodotSharp",
"JSON",
"Marshalls",
"Mutex",
"OS",
"ResourceLoader",
"ResourceSaver",
"Semaphore",
"Thread",
"VisualScriptEditor",
nullptr,
};
StringName GDScriptParser::get_real_class_name(const StringName &p_source) {
if (underscore_map.is_empty()) {
const char **class_name = underscore_classes;
while (*class_name != nullptr) {
underscore_map[*class_name] = String("_") + *class_name;
class_name++;
}
}
if (underscore_map.has(p_source)) {
return underscore_map[p_source];
}
return p_source;
}
void GDScriptParser::cleanup() { void GDScriptParser::cleanup() {
builtin_types.clear(); builtin_types.clear();
underscore_map.clear();
} }
void GDScriptParser::get_annotation_list(List<MethodInfo> *r_annotations) const { void GDScriptParser::get_annotation_list(List<MethodInfo> *r_annotations) const {
@ -109,12 +144,11 @@ void GDScriptParser::get_annotation_list(List<MethodInfo> *r_annotations) const
GDScriptParser::GDScriptParser() { GDScriptParser::GDScriptParser() {
// Register valid annotations. // Register valid annotations.
// TODO: Should this be static? // TODO: Should this be static?
// TODO: Validate applicable types (e.g. a VARIABLE annotation that only applies to string variables).
register_annotation(MethodInfo("@tool"), AnnotationInfo::SCRIPT, &GDScriptParser::tool_annotation); register_annotation(MethodInfo("@tool"), AnnotationInfo::SCRIPT, &GDScriptParser::tool_annotation);
register_annotation(MethodInfo("@icon", { Variant::STRING, "icon_path" }), AnnotationInfo::SCRIPT, &GDScriptParser::icon_annotation); register_annotation(MethodInfo("@icon", { Variant::STRING, "icon_path" }), AnnotationInfo::SCRIPT, &GDScriptParser::icon_annotation);
register_annotation(MethodInfo("@onready"), AnnotationInfo::VARIABLE, &GDScriptParser::onready_annotation); register_annotation(MethodInfo("@onready"), AnnotationInfo::VARIABLE, &GDScriptParser::onready_annotation);
// Export annotations. // Export annotations.
register_annotation(MethodInfo("@export"), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_TYPE_STRING, Variant::NIL>); register_annotation(MethodInfo("@export"), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_NONE, Variant::NIL>);
register_annotation(MethodInfo("@export_enum", { Variant::STRING, "names" }), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_ENUM, Variant::INT>, 0, true); register_annotation(MethodInfo("@export_enum", { Variant::STRING, "names" }), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_ENUM, Variant::INT>, 0, true);
register_annotation(MethodInfo("@export_file", { Variant::STRING, "filter" }), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_FILE, Variant::STRING>, 1, true); register_annotation(MethodInfo("@export_file", { Variant::STRING, "filter" }), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_FILE, Variant::STRING>, 1, true);
register_annotation(MethodInfo("@export_dir"), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_DIR, Variant::STRING>); register_annotation(MethodInfo("@export_dir"), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_DIR, Variant::STRING>);
@ -680,7 +714,6 @@ void GDScriptParser::parse_class_member(T *(GDScriptParser::*p_parse_function)()
while (!annotation_stack.is_empty()) { while (!annotation_stack.is_empty()) {
AnnotationNode *last_annotation = annotation_stack.back()->get(); AnnotationNode *last_annotation = annotation_stack.back()->get();
if (last_annotation->applies_to(p_target)) { if (last_annotation->applies_to(p_target)) {
last_annotation->apply(this, member);
member->annotations.push_front(last_annotation); member->annotations.push_front(last_annotation);
annotation_stack.pop_back(); annotation_stack.pop_back();
} else { } else {
@ -811,6 +844,9 @@ GDScriptParser::VariableNode *GDScriptParser::parse_variable(bool p_allow_proper
if (match(GDScriptTokenizer::Token::EQUAL)) { if (match(GDScriptTokenizer::Token::EQUAL)) {
// Initializer. // Initializer.
variable->initializer = parse_expression(false); variable->initializer = parse_expression(false);
if (variable->initializer == nullptr) {
push_error(R"(Expected expression for variable initial value after "=".)");
}
variable->assignments++; variable->assignments++;
} }
@ -3173,29 +3209,10 @@ bool GDScriptParser::export_annotations(const AnnotationNode *p_annotation, Node
} }
variable->exported = true; variable->exported = true;
// TODO: Improving setting type, especially for range hints, which can be int or float.
variable->export_info.type = t_type; variable->export_info.type = t_type;
variable->export_info.hint = t_hint; variable->export_info.hint = t_hint;
if (p_annotation->name == "@export") {
if (variable->datatype_specifier == nullptr) {
if (variable->initializer == nullptr) {
push_error(R"(Cannot use "@export" annotation with variable without type or initializer, since type can't be inferred.)", p_annotation);
return false;
}
if (variable->initializer->type == Node::LITERAL) {
variable->export_info.type = static_cast<LiteralNode *>(variable->initializer)->value.get_type();
} else if (variable->initializer->type == Node::ARRAY) {
variable->export_info.type = Variant::ARRAY;
} else if (variable->initializer->type == Node::DICTIONARY) {
variable->export_info.type = Variant::DICTIONARY;
} else {
push_error(R"(To use "@export" annotation with type-less variable, the default value must be a literal.)", p_annotation);
return false;
}
} // else: Actual type will be set by the analyzer, which can infer the proper type.
}
String hint_string; String hint_string;
for (int i = 0; i < p_annotation->resolved_arguments.size(); i++) { for (int i = 0; i < p_annotation->resolved_arguments.size(); i++) {
if (i > 0) { if (i > 0) {
@ -3206,6 +3223,86 @@ bool GDScriptParser::export_annotations(const AnnotationNode *p_annotation, Node
variable->export_info.hint_string = hint_string; variable->export_info.hint_string = hint_string;
// This is called after tne analyzer is done finding the type, so this should be set here.
DataType export_type = variable->get_datatype();
if (p_annotation->name == "@export") {
if (variable->datatype_specifier == nullptr && variable->initializer == nullptr) {
push_error(R"(Cannot use simple "@export" annotation with variable without type or initializer, since type can't be inferred.)", p_annotation);
return false;
}
bool is_array = false;
if (export_type.builtin_type == Variant::ARRAY && export_type.has_container_element_type()) {
export_type = export_type.get_container_element_type(); // Use inner type for.
is_array = true;
}
if (export_type.is_variant() || export_type.has_no_type()) {
push_error(R"(Cannot use simple "@export" annotation because the type of the initialized value can't be inferred.)", p_annotation);
return false;
}
switch (export_type.kind) {
case GDScriptParser::DataType::BUILTIN:
variable->export_info.type = export_type.builtin_type;
variable->export_info.hint = PROPERTY_HINT_NONE;
variable->export_info.hint_string = Variant::get_type_name(export_type.builtin_type);
break;
case GDScriptParser::DataType::NATIVE:
if (ClassDB::is_parent_class(get_real_class_name(export_type.native_type), "Resource")) {
variable->export_info.type = Variant::OBJECT;
variable->export_info.hint = PROPERTY_HINT_RESOURCE_TYPE;
variable->export_info.hint_string = get_real_class_name(export_type.native_type);
} else {
push_error(R"(Export type can only be built-in, a resource, or an enum.)", variable);
return false;
}
break;
case GDScriptParser::DataType::ENUM: {
variable->export_info.type = Variant::INT;
variable->export_info.hint = PROPERTY_HINT_ENUM;
String enum_hint_string;
for (const Map<StringName, int>::Element *E = export_type.enum_values.front(); E; E = E->next()) {
enum_hint_string += E->key().operator String().camelcase_to_underscore(true).capitalize().xml_escape();
enum_hint_string += ":";
enum_hint_string += String::num_int64(E->get()).xml_escape();
if (E->next()) {
enum_hint_string += ",";
}
}
variable->export_info.hint_string = enum_hint_string;
} break;
default:
// TODO: Allow custom user resources.
push_error(R"(Export type can only be built-in, a resource, or an enum.)", variable);
break;
}
if (is_array) {
String hint_prefix = itos(variable->export_info.type);
if (variable->export_info.hint) {
hint_prefix += "/" + itos(variable->export_info.hint);
}
variable->export_info.hint = PROPERTY_HINT_TYPE_STRING;
variable->export_info.hint_string = hint_prefix + ":" + variable->export_info.hint_string;
variable->export_info.type = Variant::ARRAY;
}
} else {
// Validate variable type with export.
if (!export_type.is_variant() && (export_type.kind != DataType::BUILTIN || export_type.builtin_type != t_type)) {
// Allow float/int conversion.
if ((t_type != Variant::FLOAT || export_type.builtin_type != Variant::INT) && (t_type != Variant::INT || export_type.builtin_type != Variant::FLOAT)) {
push_error(vformat(R"("%s" annotation requires a variable of type "%s" but type "%s" was given instead.)", p_annotation->name.operator String(), Variant::get_type_name(t_type), export_type.to_string()), variable);
return false;
}
}
}
return true; return true;
} }

View File

@ -132,7 +132,7 @@ public:
ClassNode *class_type = nullptr; ClassNode *class_type = nullptr;
MethodInfo method_info; // For callable/signals. MethodInfo method_info; // For callable/signals.
HashMap<StringName, int> enum_values; // For enums. Map<StringName, int> enum_values; // For enums.
_FORCE_INLINE_ bool is_set() const { return kind != UNRESOLVED; } _FORCE_INLINE_ bool is_set() const { return kind != UNRESOLVED; }
_FORCE_INLINE_ bool has_no_type() const { return type_source == UNDETECTED; } _FORCE_INLINE_ bool has_no_type() const { return type_source == UNDETECTED; }
@ -1369,6 +1369,7 @@ public:
ClassNode *get_tree() const { return head; } ClassNode *get_tree() const { return head; }
bool is_tool() const { return _is_tool; } bool is_tool() const { return _is_tool; }
static Variant::Type get_builtin_type(const StringName &p_type); static Variant::Type get_builtin_type(const StringName &p_type);
static StringName get_real_class_name(const StringName &p_source);
CompletionContext get_completion_context() const { return completion_context; } CompletionContext get_completion_context() const { return completion_context; }
CompletionCall get_completion_call() const { return completion_call; } CompletionCall get_completion_call() const { return completion_call; }

View File

@ -158,7 +158,6 @@ void unregister_gdscript_types() {
#endif // TOOLS_ENABLED #endif // TOOLS_ENABLED
GDScriptParser::cleanup(); GDScriptParser::cleanup();
GDScriptAnalyzer::cleanup();
GDScriptUtilityFunctions::unregister_functions(); GDScriptUtilityFunctions::unregister_functions();
} }