Browse Source

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

Fix a few issues with @export in GDScript
Rémi Verschelde 4 years ago
parent
commit
737f09895d

+ 35 - 71
modules/gdscript/gdscript_analyzer.cpp

@@ -39,40 +39,6 @@
 #include "gdscript.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) {
 	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;
 }
 
-void GDScriptAnalyzer::cleanup() {
-	underscore_map.clear();
-}
-
 static GDScriptParser::DataType make_callable_type(const MethodInfo &p_info) {
 	GDScriptParser::DataType type;
 	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;
 
 	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);
 
 	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);
 					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.native_type = name;
 			} else {
@@ -444,7 +406,7 @@ GDScriptParser::DataType GDScriptAnalyzer::resolve_datatype(GDScriptParser::Type
 			return GDScriptParser::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.
 		result = make_native_enum_type(parser->current_class->base_type.native_type, first);
 	} else {
@@ -507,7 +469,7 @@ GDScriptParser::DataType GDScriptAnalyzer::resolve_datatype(GDScriptParser::Type
 			}
 		} else if (result.kind == GDScriptParser::DataType::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) {
 					push_error(R"(Enums cannot contain nested types.)", p_type->type_chain[2]);
 				} else {
@@ -607,28 +569,10 @@ void GDScriptAnalyzer::resolve_class_interface(GDScriptParser::ClassNode *p_clas
 
 				datatype.is_constant = false;
 				member.variable->set_datatype(datatype);
-				if (!datatype.has_no_type()) {
-					// TODO: Move this out into a routine specific to validate annotations.
-					if (member.variable->export_info.hint == PROPERTY_HINT_TYPE_STRING) {
-						// @export annotation.
-						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;
-						}
-					}
+
+				// Apply annotations.
+				for (List<GDScriptParser::AnnotationNode *>::Element *E = member.variable->annotations.front(); E; E = E->next()) {
+					E->get()->apply(parser, member.variable);
 				}
 			} break;
 			case GDScriptParser::ClassNode::Member::CONSTANT: {
@@ -674,6 +618,11 @@ void GDScriptAnalyzer::resolve_class_interface(GDScriptParser::ClassNode *p_clas
 				datatype.is_constant = true;
 
 				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;
 			case GDScriptParser::ClassNode::Member::SIGNAL: {
 				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;
 
 				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;
 			case GDScriptParser::ClassNode::Member::ENUM: {
 				GDScriptParser::DataType enum_type;
@@ -730,6 +684,11 @@ void GDScriptAnalyzer::resolve_class_interface(GDScriptParser::ClassNode *p_clas
 				current_enum = nullptr;
 
 				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;
 			case GDScriptParser::ClassNode::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);
+
+		// 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;
@@ -2253,7 +2217,7 @@ void GDScriptAnalyzer::reduce_get_node(GDScriptParser::GetNodeNode *p_get_node)
 	result.native_type = "Node";
 	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);
 	}
 
@@ -2419,7 +2383,7 @@ void GDScriptAnalyzer::reduce_identifier_from_base(GDScriptParser::IdentifierNod
 	}
 
 	// 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)) {
 		PropertyInfo prop_info;
@@ -3243,7 +3207,7 @@ bool GDScriptAnalyzer::get_function_signature(GDScriptParser::Node *p_source, GD
 		return true;
 	}
 
-	StringName real_native = get_real_class_name(base_native);
+	StringName real_native = GDScriptParser::get_real_class_name(base_native);
 
 	MethodInfo 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;
 	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)) {
 			parser->push_warning(p_local, GDScriptWarning::SHADOWED_VARIABLE_BASE_CLASS, p_context, p_local->name, "method", parent);
 			return true;
@@ -3500,14 +3464,14 @@ bool GDScriptAnalyzer::is_type_compatible(const GDScriptParser::DataType &p_targ
 	}
 
 	// 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) {
 		case GDScriptParser::DataType::NATIVE: {
 			if (p_target.is_meta_type) {
 				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);
 		}
 		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 {
-	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);
 }
 

+ 0 - 2
modules/gdscript/gdscript_analyzer.h

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

+ 2 - 7
modules/gdscript/gdscript_compiler.cpp

@@ -137,16 +137,12 @@ GDScriptDataType GDScriptCompiler::_gdtype_from_datatype(const GDScriptParser::D
 				}
 			}
 		} break;
+		case GDScriptParser::DataType::ENUM:
 		case GDScriptParser::DataType::ENUM_VALUE:
 			result.has_type = true;
 			result.kind = GDScriptDataType::BUILTIN;
 			result.builtin_type = Variant::INT;
 			break;
-		case GDScriptParser::DataType::ENUM:
-			result.has_type = true;
-			result.kind = GDScriptDataType::BUILTIN;
-			result.builtin_type = Variant::DICTIONARY;
-			break;
 		case GDScriptParser::DataType::UNRESOLVED: {
 			ERR_PRINT("Parser bug: converting unresolved type.");
 			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_string = export_info.hint_string;
 					prop_info.usage = export_info.usage;
-				} else {
-					prop_info.usage = PROPERTY_USAGE_SCRIPT_VARIABLE;
 				}
+				prop_info.usage |= PROPERTY_USAGE_SCRIPT_VARIABLE;
 #ifdef TOOLS_ENABLED
 				p_script->doc_variables[name] = variable->doc_description;
 #endif

+ 8 - 38
modules/gdscript/gdscript_editor.cpp

@@ -500,36 +500,6 @@ struct GDScriptCompletionIdentifier {
 	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;
@@ -930,7 +900,7 @@ static void _find_identifiers_in_base(const GDScriptCompletionIdentifier &p_base
 				}
 			} break;
 			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)) {
 					return;
 				}
@@ -1783,7 +1753,7 @@ static bool _guess_identifier_type(GDScriptParser::CompletionContext &p_context,
 						base_type = GDScriptParser::DataType();
 						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;
 					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()) {
@@ -1854,7 +1824,7 @@ static bool _guess_identifier_type(GDScriptParser::CompletionContext &p_context,
 	}
 
 	// 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)) {
 		r_type.type.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT;
 		r_type.type.kind = GDScriptParser::DataType::NATIVE;
@@ -1970,7 +1940,7 @@ static bool _guess_identifier_type_from_base(GDScriptParser::CompletionContext &
 				}
 			} break;
 			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)) {
 					return false;
 				}
@@ -2133,7 +2103,7 @@ static bool _guess_method_return_type_from_base(GDScriptParser::CompletionContex
 				}
 			} break;
 			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)) {
 					return false;
 				}
@@ -2230,7 +2200,7 @@ static void _find_call_arguments(GDScriptParser::CompletionContext &p_context, c
 				base_type = base_type.class_type->base_type;
 			} break;
 			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)) {
 					base_type.kind = GDScriptParser::DataType::UNRESOLVED;
 					break;
@@ -2615,7 +2585,7 @@ Error GDScriptLanguage::complete_code(const String &p_code, const String &p_path
 				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)) {
 				break;
 			}
@@ -2844,7 +2814,7 @@ static Error _lookup_symbol_from_base(const GDScriptParser::DataType &p_base, co
 				}
 			} break;
 			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)) {
 					base_type.kind = GDScriptParser::DataType::UNRESOLVED;
 					break;

+ 120 - 23
modules/gdscript/gdscript_parser.cpp

@@ -94,8 +94,43 @@ Variant::Type GDScriptParser::get_builtin_type(const StringName &p_type) {
 	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() {
 	builtin_types.clear();
+	underscore_map.clear();
 }
 
 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() {
 	// Register valid annotations.
 	// 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("@icon", { Variant::STRING, "icon_path" }), AnnotationInfo::SCRIPT, &GDScriptParser::icon_annotation);
 	register_annotation(MethodInfo("@onready"), AnnotationInfo::VARIABLE, &GDScriptParser::onready_annotation);
 	// 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_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>);
@@ -680,7 +714,6 @@ void GDScriptParser::parse_class_member(T *(GDScriptParser::*p_parse_function)()
 	while (!annotation_stack.is_empty()) {
 		AnnotationNode *last_annotation = annotation_stack.back()->get();
 		if (last_annotation->applies_to(p_target)) {
-			last_annotation->apply(this, member);
 			member->annotations.push_front(last_annotation);
 			annotation_stack.pop_back();
 		} else {
@@ -811,6 +844,9 @@ GDScriptParser::VariableNode *GDScriptParser::parse_variable(bool p_allow_proper
 	if (match(GDScriptTokenizer::Token::EQUAL)) {
 		// Initializer.
 		variable->initializer = parse_expression(false);
+		if (variable->initializer == nullptr) {
+			push_error(R"(Expected expression for variable initial value after "=".)");
+		}
 		variable->assignments++;
 	}
 
@@ -3173,29 +3209,10 @@ bool GDScriptParser::export_annotations(const AnnotationNode *p_annotation, Node
 	}
 
 	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.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;
 	for (int i = 0; i < p_annotation->resolved_arguments.size(); i++) {
 		if (i > 0) {
@@ -3206,6 +3223,86 @@ bool GDScriptParser::export_annotations(const AnnotationNode *p_annotation, Node
 
 	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;
 }
 

+ 2 - 1
modules/gdscript/gdscript_parser.h

@@ -132,7 +132,7 @@ public:
 		ClassNode *class_type = nullptr;
 
 		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 has_no_type() const { return type_source == UNDETECTED; }
@@ -1369,6 +1369,7 @@ public:
 	ClassNode *get_tree() const { return head; }
 	bool is_tool() const { return _is_tool; }
 	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; }
 	CompletionCall get_completion_call() const { return completion_call; }

+ 0 - 1
modules/gdscript/register_types.cpp

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