diff options
Diffstat (limited to 'modules/gdscript/gdscript_analyzer.cpp')
-rw-r--r-- | modules/gdscript/gdscript_analyzer.cpp | 414 |
1 files changed, 241 insertions, 173 deletions
diff --git a/modules/gdscript/gdscript_analyzer.cpp b/modules/gdscript/gdscript_analyzer.cpp index 11daf739d2..7bde4e7c4b 100644 --- a/modules/gdscript/gdscript_analyzer.cpp +++ b/modules/gdscript/gdscript_analyzer.cpp @@ -42,6 +42,9 @@ #include "gdscript_utility_functions.h" #include "scene/resources/packed_scene.h" +#define UNNAMED_ENUM "<anonymous enum>" +#define ENUM_SEPARATOR "::" + static MethodInfo info_from_utility_func(const StringName &p_function) { ERR_FAIL_COND_V(!Variant::has_utility_function(p_function), MethodInfo()); @@ -106,13 +109,26 @@ static GDScriptParser::DataType make_native_meta_type(const StringName &p_class_ return type; } -static GDScriptParser::DataType make_native_enum_type(const StringName &p_native_class, const StringName &p_enum_name) { +// In enum types, native_type is used to store the class (native or otherwise) that the enum belongs to. +// This disambiguates between similarly named enums in base classes or outer classes +static GDScriptParser::DataType make_enum_type(const StringName &p_enum_name, const String &p_base_name, const bool p_meta = false) { GDScriptParser::DataType type; type.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT; type.kind = GDScriptParser::DataType::ENUM; - type.builtin_type = Variant::INT; + type.builtin_type = p_meta ? Variant::DICTIONARY : Variant::INT; + type.enum_type = p_enum_name; type.is_constant = true; - type.is_meta_type = true; + type.is_meta_type = p_meta; + + // For enums, native_type is only used to check compatibility in is_type_compatible() + // We can set anything readable here for error messages, as long as it uniquely identifies the type of the enum + type.native_type = p_base_name + ENUM_SEPARATOR + p_enum_name; + + return type; +} + +static GDScriptParser::DataType make_native_enum_type(const StringName &p_enum_name, const StringName &p_native_class, const bool p_meta = true) { + GDScriptParser::DataType type = make_enum_type(p_enum_name, p_native_class, p_meta); List<StringName> enum_values; ClassDB::get_enum_constants(p_native_class, p_enum_name, &enum_values); @@ -134,6 +150,19 @@ static GDScriptParser::DataType make_builtin_meta_type(Variant::Type p_type) { return type; } +static StringName enum_get_value_name(const GDScriptParser::DataType p_type, int64_t p_val) { + // Check that an enum has a given value, not key. + // Make sure that implicit conversion to int64_t is sensible before calling! + HashMap<StringName, int64_t>::ConstIterator i = p_type.enum_values.begin(); + while (i) { + if (i->value == p_val) { + return i->key; + } + ++i; + } + return StringName(); +} + bool GDScriptAnalyzer::has_member_name_conflict_in_script_class(const StringName &p_member_name, const GDScriptParser::ClassNode *p_class, const GDScriptParser::Node *p_member) { if (p_class->members_indices.has(p_member_name)) { int index = p_class->members_indices[p_member_name]; @@ -192,6 +221,7 @@ Error GDScriptAnalyzer::check_native_member_name_conflict(const StringName &p_me } Error GDScriptAnalyzer::check_class_member_name_conflict(const GDScriptParser::ClassNode *p_class_node, const StringName &p_member_name, const GDScriptParser::Node *p_member_node) { + // TODO check outer classes for static members only const GDScriptParser::DataType *current_data_type = &p_class_node->base_type; while (current_data_type && current_data_type->kind == GDScriptParser::DataType::Kind::CLASS) { GDScriptParser::ClassNode *current_class_node = current_data_type->class_type; @@ -220,9 +250,13 @@ Error GDScriptAnalyzer::check_class_member_name_conflict(const GDScriptParser::C } void GDScriptAnalyzer::get_class_node_current_scope_classes(GDScriptParser::ClassNode *p_node, List<GDScriptParser::ClassNode *> *p_list) { + ERR_FAIL_NULL(p_node); + ERR_FAIL_NULL(p_list); + if (p_list->find(p_node) != nullptr) { return; } + p_list->push_back(p_node); // TODO: Try to solve class inheritance if not yet resolving. @@ -591,12 +625,17 @@ GDScriptParser::DataType GDScriptAnalyzer::resolve_datatype(GDScriptParser::Type result = ref->get_parser()->head->get_datatype(); } else if (ClassDB::has_enum(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); + result = make_native_enum_type(first, parser->current_class->base_type.native_type); } else { // Classes in current scope. List<GDScriptParser::ClassNode *> script_classes; + bool found = false; get_class_node_current_scope_classes(parser->current_class, &script_classes); for (GDScriptParser::ClassNode *script_class : script_classes) { + if (found) { + break; + } + if (script_class->identifier && script_class->identifier->name == first) { result = script_class->get_datatype(); break; @@ -608,14 +647,17 @@ GDScriptParser::DataType GDScriptAnalyzer::resolve_datatype(GDScriptParser::Type switch (member.type) { case GDScriptParser::ClassNode::Member::CLASS: result = member.get_datatype(); + found = true; break; case GDScriptParser::ClassNode::Member::ENUM: result = member.get_datatype(); + found = true; break; case GDScriptParser::ClassNode::Member::CONSTANT: if (member.get_datatype().is_meta_type) { result = member.get_datatype(); result.is_meta_type = false; + found = true; break; } else if (Ref<Script>(member.constant->initializer->reduced_value).is_valid()) { Ref<GDScript> gdscript = member.constant->initializer->reduced_value; @@ -636,6 +678,7 @@ GDScriptParser::DataType GDScriptAnalyzer::resolve_datatype(GDScriptParser::Type result.native_type = script->get_instance_base_type(); result.is_meta_type = false; } + found = true; break; } [[fallthrough]]; @@ -667,15 +710,17 @@ GDScriptParser::DataType GDScriptAnalyzer::resolve_datatype(GDScriptParser::Type } } else if (result.kind == GDScriptParser::DataType::NATIVE) { // Only enums allowed for native. - if (!ClassDB::has_enum(result.native_type, p_type->type_chain[1]->name)) { - push_error(vformat(R"(Could not find nested type "%s" under base "%s".)", p_type->type_chain[1]->name, result.to_string()), p_type->type_chain[1]); - return bad_type; - } - if (p_type->type_chain.size() > 2) { - push_error(R"(Enums cannot contain nested types.)", p_type->type_chain[2]); + if (ClassDB::has_enum(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]); + return bad_type; + } else { + result = make_native_enum_type(p_type->type_chain[1]->name, result.native_type); + } + } else { + push_error(vformat(R"(Could not find type "%s" in "%s".)", p_type->type_chain[1]->name, first), p_type->type_chain[1]); return bad_type; } - result = make_native_enum_type(result.native_type, p_type->type_chain[1]->name); } else { push_error(vformat(R"(Could not find nested type "%s" under base "%s".)", p_type->type_chain[1]->name, result.to_string()), p_type->type_chain[1]); return bad_type; @@ -804,15 +849,7 @@ void GDScriptAnalyzer::resolve_class_member(GDScriptParser::ClassNode *p_class, check_class_member_name_conflict(p_class, member.m_enum->identifier->name, member.m_enum); member.m_enum->set_datatype(resolving_datatype); - - GDScriptParser::DataType enum_type; - enum_type.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT; - enum_type.kind = GDScriptParser::DataType::ENUM; - enum_type.builtin_type = Variant::DICTIONARY; - enum_type.enum_type = member.m_enum->identifier->name; - enum_type.native_type = p_class->fqcn + "." + member.m_enum->identifier->name; - enum_type.is_meta_type = true; - enum_type.is_constant = true; + GDScriptParser::DataType enum_type = make_enum_type(member.m_enum->identifier->name, p_class->fqcn, true); const GDScriptParser::EnumNode *prev_enum = current_enum; current_enum = member.m_enum; @@ -846,6 +883,7 @@ void GDScriptAnalyzer::resolve_class_member(GDScriptParser::ClassNode *p_class, current_enum = prev_enum; + dictionary.set_read_only(true); member.m_enum->set_datatype(enum_type); member.m_enum->dictionary = dictionary; @@ -892,11 +930,7 @@ void GDScriptAnalyzer::resolve_class_member(GDScriptParser::ClassNode *p_class, // Also update the original references. member.enum_value.parent_enum->values.set(member.enum_value.index, member.enum_value); - GDScriptParser::DataType datatype; - datatype.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT; - datatype.kind = GDScriptParser::DataType::BUILTIN; - datatype.builtin_type = Variant::INT; - member.enum_value.identifier->set_datatype(datatype); + member.enum_value.identifier->set_datatype(make_enum_type(UNNAMED_ENUM, p_class->fqcn, false)); } break; case GDScriptParser::ClassNode::Member::CLASS: check_class_member_name_conflict(p_class, member.m_class->identifier->name, member.m_class); @@ -1330,7 +1364,7 @@ void GDScriptAnalyzer::resolve_function_signature(GDScriptParser::FunctionNode * } } else { if (p_function->return_type != nullptr) { - p_function->set_datatype(resolve_datatype(p_function->return_type)); + p_function->set_datatype(type_from_metatype(resolve_datatype(p_function->return_type))); } else { // In case the function is not typed, we can safely assume it's a Variant, so it's okay to mark as "inferred" here. // It's not "undetected" to not mix up with unknown functions. @@ -1364,7 +1398,7 @@ void GDScriptAnalyzer::resolve_function_signature(GDScriptParser::FunctionNode * if (!valid) { // Compute parent signature as a string to show in the error message. - String parent_signature = function_name.operator String() + "("; + String parent_signature = String(function_name) + "("; int j = 0; for (const GDScriptParser::DataType &par_type : parameters_types) { if (j > 0) { @@ -1507,9 +1541,9 @@ void GDScriptAnalyzer::resolve_assignable(GDScriptParser::AssignableNode *p_assi if (is_constant) { if (p_assignable->initializer->type == GDScriptParser::Node::ARRAY) { - const_fold_array(static_cast<GDScriptParser::ArrayNode *>(p_assignable->initializer)); + const_fold_array(static_cast<GDScriptParser::ArrayNode *>(p_assignable->initializer), true); } else if (p_assignable->initializer->type == GDScriptParser::Node::DICTIONARY) { - const_fold_dictionary(static_cast<GDScriptParser::DictionaryNode *>(p_assignable->initializer)); + const_fold_dictionary(static_cast<GDScriptParser::DictionaryNode *>(p_assignable->initializer), true); } if (!p_assignable->initializer->is_constant) { push_error(vformat(R"(Assigned value for %s "%s" isn't a constant expression.)", p_kind, p_assignable->identifier->name), p_assignable->initializer); @@ -2063,6 +2097,10 @@ void GDScriptAnalyzer::reduce_assignment(GDScriptParser::AssignmentNode *p_assig GDScriptParser::DataType assignee_type = p_assignment->assignee->get_datatype(); + if (assignee_type.is_constant || (p_assignment->assignee->type == GDScriptParser::Node::SUBSCRIPT && static_cast<GDScriptParser::SubscriptNode *>(p_assignment->assignee)->base->is_constant)) { + push_error("Cannot assign a new value to a constant.", p_assignment->assignee); + } + // Check if assigned value is an array literal, so we can make it a typed array too if appropriate. if (assignee_type.has_container_element_type() && p_assignment->assigned_value->type == GDScriptParser::Node::ARRAY) { update_array_literal_element_type(assignee_type, static_cast<GDScriptParser::ArrayNode *>(p_assignment->assigned_value)); @@ -2070,24 +2108,22 @@ void GDScriptAnalyzer::reduce_assignment(GDScriptParser::AssignmentNode *p_assig GDScriptParser::DataType assigned_value_type = p_assignment->assigned_value->get_datatype(); - if (assignee_type.is_constant) { - push_error("Cannot assign a new value to a constant.", p_assignment->assignee); - } - bool compatible = true; GDScriptParser::DataType op_type = assigned_value_type; - if (p_assignment->operation != GDScriptParser::AssignmentNode::OP_NONE) { + if (p_assignment->operation != GDScriptParser::AssignmentNode::OP_NONE && !op_type.is_variant()) { op_type = get_operation_type(p_assignment->variant_op, assignee_type, assigned_value_type, compatible, p_assignment->assigned_value); } p_assignment->set_datatype(op_type); - if (assignee_type.is_hard_type() && !assignee_type.is_variant() && op_type.is_hard_type()) { + // If Assignee is a variant, then you can assign anything + // When the assigned value has a known type, further checks are possible. + if (assignee_type.is_hard_type() && !assignee_type.is_variant() && op_type.is_hard_type() && !op_type.is_variant()) { if (compatible) { compatible = is_type_compatible(assignee_type, op_type, true, p_assignment->assigned_value); if (!compatible) { // Try reverse test since it can be a masked subtype. if (!is_type_compatible(op_type, assignee_type, true)) { - push_error(vformat(R"(Cannot assign a value of type "%s" to a target of type "%s".)", assigned_value_type.to_string(), assignee_type.to_string()), p_assignment->assigned_value); + push_error(vformat(R"(Value of type "%s" cannot be assigned to a variable of type "%s".)", assigned_value_type.to_string(), assignee_type.to_string()), p_assignment->assigned_value); } else { // TODO: Add warning. mark_node_unsafe(p_assignment); @@ -2141,7 +2177,7 @@ void GDScriptAnalyzer::reduce_assignment(GDScriptParser::AssignmentNode *p_assig if (!id_type.is_hard_type()) { id_type.kind = GDScriptParser::DataType::VARIANT; id_type.type_source = GDScriptParser::DataType::UNDETECTED; - identifier->variable_source->set_datatype(id_type); + identifier->bind_source->set_datatype(id_type); } } break; default: @@ -2368,7 +2404,7 @@ void GDScriptAnalyzer::reduce_call(GDScriptParser::CallNode *p_call, bool p_is_a switch (err.error) { case Callable::CallError::CALL_ERROR_INVALID_ARGUMENT: - push_error(vformat(R"(Invalid argument for %s constructor: argument %d should be %s but is %s.)", Variant::get_type_name(builtin_type), err.argument + 1, + push_error(vformat(R"(Invalid argument for %s constructor: argument %d should be "%s" but is "%s".)", Variant::get_type_name(builtin_type), err.argument + 1, Variant::get_type_name(Variant::Type(err.expected)), p_call->arguments[err.argument]->get_datatype().to_string()), p_call->arguments[err.argument]); break; @@ -2484,7 +2520,7 @@ void GDScriptAnalyzer::reduce_call(GDScriptParser::CallNode *p_call, bool p_is_a switch (err.error) { case Callable::CallError::CALL_ERROR_INVALID_ARGUMENT: { PropertyInfo wrong_arg = function_info.arguments[err.argument]; - push_error(vformat(R"*(Invalid argument for "%s()" function: argument %d should be %s but is %s.)*", function_name, err.argument + 1, + push_error(vformat(R"*(Invalid argument for "%s()" function: argument %d should be "%s" but is "%s".)*", function_name, err.argument + 1, type_from_property(wrong_arg).to_string(), p_call->arguments[err.argument]->get_datatype().to_string()), p_call->arguments[err.argument]); } break; @@ -2537,7 +2573,7 @@ void GDScriptAnalyzer::reduce_call(GDScriptParser::CallNode *p_call, bool p_is_a expected_type_name = Variant::get_type_name((Variant::Type)err.expected); } - push_error(vformat(R"*(Invalid argument for "%s()" function: argument %d should be %s but is %s.)*", function_name, err.argument + 1, + push_error(vformat(R"*(Invalid argument for "%s()" function: argument %d should be "%s" but is "%s".)*", function_name, err.argument + 1, expected_type_name, p_call->arguments[err.argument]->get_datatype().to_string()), p_call->arguments[err.argument]); } break; @@ -2683,8 +2719,10 @@ void GDScriptAnalyzer::reduce_call(GDScriptParser::CallNode *p_call, bool p_is_a } else { bool found = false; - // Check if the name exists as something else. - if (!p_call->is_super && callee_type != GDScriptParser::Node::NONE) { + // Enums do not have functions other than the built-in dictionary ones. + if (base_type.kind == GDScriptParser::DataType::ENUM && base_type.is_meta_type) { + push_error(vformat(R"*(Enums only have Dictionary built-in methods. Function "%s()" does not exist for enum "%s".)*", p_call->function_name, base_type.enum_type), p_call->callee); + } else if (!p_call->is_super && callee_type != GDScriptParser::Node::NONE) { // Check if the name exists as something else. GDScriptParser::IdentifierNode *callee_id; if (callee_type == GDScriptParser::Node::IDENTIFIER) { callee_id = static_cast<GDScriptParser::IdentifierNode *>(p_call->callee); @@ -2714,7 +2752,7 @@ void GDScriptAnalyzer::reduce_call(GDScriptParser::CallNode *p_call, bool p_is_a String base_name = is_self && !p_call->is_super ? "self" : base_type.to_string(); push_error(vformat(R"*(Function "%s()" not found in base %s.)*", p_call->function_name, base_name), p_call->is_super ? p_call : p_call->callee); } else if (!found && (!p_call->is_super && base_type.is_hard_type() && base_type.kind == GDScriptParser::DataType::NATIVE && base_type.is_meta_type)) { - push_error(vformat(R"*(Static function "%s()" not found in base "%s".)*", p_call->function_name, base_type.native_type.operator String()), p_call); + push_error(vformat(R"*(Static function "%s()" not found in base "%s".)*", p_call->function_name, base_type.native_type), p_call); } } @@ -2742,19 +2780,48 @@ void GDScriptAnalyzer::reduce_cast(GDScriptParser::CastNode *p_cast) { GDScriptParser::DataType op_type = p_cast->operand->get_datatype(); if (!op_type.is_variant()) { bool valid = false; + bool more_informative_error = false; if (op_type.kind == GDScriptParser::DataType::ENUM && cast_type.kind == GDScriptParser::DataType::ENUM) { - // Enum types are compatible between each other, so it's a safe cast. - valid = true; + // Enum casts are compatible when value from operand exists in target enum + if (p_cast->operand->is_constant && p_cast->operand->reduced) { + if (enum_get_value_name(cast_type, p_cast->operand->reduced_value) != StringName()) { + valid = true; + } else { + valid = false; + more_informative_error = true; + push_error(vformat(R"(Invalid cast. Enum "%s" does not have value corresponding to "%s.%s" (%d).)", + cast_type.to_string(), op_type.enum_type, + enum_get_value_name(op_type, p_cast->operand->reduced_value), // Can never be null + p_cast->operand->reduced_value.operator uint64_t()), + p_cast->cast_type); + } + } else { + // Can't statically tell whether int has a corresponding enum value. Valid but dangerous! + mark_node_unsafe(p_cast); + valid = true; + } } else if (op_type.kind == GDScriptParser::DataType::BUILTIN && op_type.builtin_type == Variant::INT && cast_type.kind == GDScriptParser::DataType::ENUM) { - // Convertint int to enum is always valid. - valid = true; + // Int assignment to enum not valid when exact int assigned is known but is not an enum value + if (p_cast->operand->is_constant && p_cast->operand->reduced) { + if (enum_get_value_name(cast_type, p_cast->operand->reduced_value) != StringName()) { + valid = true; + } else { + valid = false; + more_informative_error = true; + push_error(vformat(R"(Invalid cast. Enum "%s" does not have enum value %d.)", cast_type.to_string(), p_cast->operand->reduced_value.operator uint64_t()), p_cast->cast_type); + } + } else { + // Can't statically tell whether int has a corresponding enum value. Valid but dangerous! + mark_node_unsafe(p_cast); + valid = true; + } } else if (op_type.kind == GDScriptParser::DataType::BUILTIN && cast_type.kind == GDScriptParser::DataType::BUILTIN) { valid = Variant::can_convert(op_type.builtin_type, cast_type.builtin_type); } else if (op_type.kind != GDScriptParser::DataType::BUILTIN && cast_type.kind != GDScriptParser::DataType::BUILTIN) { valid = is_type_compatible(cast_type, op_type) || is_type_compatible(op_type, cast_type); } - if (!valid) { + if (!valid && !more_informative_error) { push_error(vformat(R"(Invalid cast. Cannot convert from "%s" to "%s".)", op_type.to_string(), cast_type.to_string()), p_cast->cast_type); } } @@ -2852,6 +2919,18 @@ GDScriptParser::DataType GDScriptAnalyzer::make_global_class_meta_type(const Str } } +void GDScriptAnalyzer::reduce_identifier_from_base_set_class(GDScriptParser::IdentifierNode *p_identifier, GDScriptParser::DataType p_identifier_datatype) { + ERR_FAIL_NULL(p_identifier); + + p_identifier->set_datatype(p_identifier_datatype); + Error err = OK; + GDScript *scr = GDScriptCache::get_full_script(p_identifier_datatype.script_path, err).ptr(); + ERR_FAIL_COND_MSG(err != OK, "Error while getting full script."); + scr = scr->find_class(p_identifier_datatype.class_type->fqcn); + p_identifier->reduced_value = scr; + p_identifier->is_constant = true; +} + void GDScriptAnalyzer::reduce_identifier_from_base(GDScriptParser::IdentifierNode *p_identifier, GDScriptParser::DataType *p_base) { if (!p_identifier->get_datatype().has_no_type()) { return; @@ -2869,26 +2948,14 @@ void GDScriptAnalyzer::reduce_identifier_from_base(GDScriptParser::IdentifierNod if (base.kind == GDScriptParser::DataType::ENUM) { if (base.is_meta_type) { if (base.enum_values.has(name)) { + p_identifier->set_datatype(type_from_metatype(base)); p_identifier->is_constant = true; p_identifier->reduced_value = base.enum_values[name]; - - GDScriptParser::DataType result; - result.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT; - result.kind = GDScriptParser::DataType::ENUM; - result.is_constant = true; - result.builtin_type = Variant::INT; - result.native_type = base.native_type; - result.enum_type = base.enum_type; - result.enum_values = base.enum_values; - p_identifier->set_datatype(result); return; - } else { - // Consider as a Dictionary, so it can be anything. - // This will be evaluated in the next if block. - base.kind = GDScriptParser::DataType::BUILTIN; - base.builtin_type = Variant::DICTIONARY; - base.is_meta_type = false; } + + // Enum does not have this value, return. + return; } else { push_error(R"(Cannot get property from enum value.)", p_identifier); return; @@ -2942,102 +3009,91 @@ void GDScriptAnalyzer::reduce_identifier_from_base(GDScriptParser::IdentifierNod } GDScriptParser::ClassNode *base_class = base.class_type; + List<GDScriptParser::ClassNode *> script_classes; + bool is_base = true; - // TODO: Switch current class/function/suite here to avoid misrepresenting identifiers (in recursive reduce calls). - while (base_class != nullptr) { - if (base_class->identifier && base_class->identifier->name == name) { - p_identifier->set_datatype(base_class->get_datatype()); + if (base_class != nullptr) { + get_class_node_current_scope_classes(base_class, &script_classes); + } + + for (GDScriptParser::ClassNode *script_class : script_classes) { + if (p_base == nullptr && script_class->identifier && script_class->identifier->name == name) { + reduce_identifier_from_base_set_class(p_identifier, script_class->get_datatype()); return; } - if (base_class->has_member(name)) { - resolve_class_member(base_class, name, p_identifier); + if (script_class->has_member(name)) { + resolve_class_member(script_class, name, p_identifier); - GDScriptParser::ClassNode::Member member = base_class->get_member(name); - p_identifier->set_datatype(member.get_datatype()); + GDScriptParser::ClassNode::Member member = script_class->get_member(name); switch (member.type) { - case GDScriptParser::ClassNode::Member::CONSTANT: + case GDScriptParser::ClassNode::Member::CONSTANT: { + p_identifier->set_datatype(member.get_datatype()); p_identifier->is_constant = true; p_identifier->reduced_value = member.constant->initializer->reduced_value; p_identifier->source = GDScriptParser::IdentifierNode::MEMBER_CONSTANT; p_identifier->constant_source = member.constant; - break; - case GDScriptParser::ClassNode::Member::ENUM_VALUE: + return; + } + + case GDScriptParser::ClassNode::Member::ENUM_VALUE: { + p_identifier->set_datatype(member.get_datatype()); p_identifier->is_constant = true; p_identifier->reduced_value = member.enum_value.value; p_identifier->source = GDScriptParser::IdentifierNode::MEMBER_CONSTANT; - break; - case GDScriptParser::ClassNode::Member::ENUM: + return; + } + + case GDScriptParser::ClassNode::Member::ENUM: { + p_identifier->set_datatype(member.get_datatype()); p_identifier->is_constant = true; p_identifier->reduced_value = member.m_enum->dictionary; p_identifier->source = GDScriptParser::IdentifierNode::MEMBER_CONSTANT; - break; - case GDScriptParser::ClassNode::Member::VARIABLE: - p_identifier->source = GDScriptParser::IdentifierNode::MEMBER_VARIABLE; - p_identifier->variable_source = member.variable; - member.variable->usages += 1; - break; - case GDScriptParser::ClassNode::Member::SIGNAL: - p_identifier->source = GDScriptParser::IdentifierNode::MEMBER_SIGNAL; - break; - case GDScriptParser::ClassNode::Member::FUNCTION: - p_identifier->set_datatype(make_callable_type(member.function->info)); - break; - case GDScriptParser::ClassNode::Member::CLASS: - if (p_base != nullptr && p_base->is_constant) { - p_identifier->is_constant = true; - p_identifier->source = GDScriptParser::IdentifierNode::MEMBER_CONSTANT; - - Error err = OK; - GDScript *scr = GDScriptCache::get_full_script(base.script_path, err).ptr(); - ERR_FAIL_COND_MSG(err != OK, "Error while getting subscript full script."); - scr = scr->find_class(p_identifier->get_datatype().class_type->fqcn); - p_identifier->reduced_value = scr; + return; + } + + case GDScriptParser::ClassNode::Member::VARIABLE: { + if (is_base && !base.is_meta_type) { + p_identifier->set_datatype(member.get_datatype()); + p_identifier->source = GDScriptParser::IdentifierNode::MEMBER_VARIABLE; + p_identifier->variable_source = member.variable; + member.variable->usages += 1; + return; } - break; - default: - break; // Type already set. - } - return; - } - // Check outer constants. - // TODO: Allow outer static functions. - if (base_class->outer != nullptr) { - List<GDScriptParser::ClassNode *> script_classes; - get_class_node_current_scope_classes(base_class->outer, &script_classes); - for (GDScriptParser::ClassNode *script_class : script_classes) { - if (script_class->has_member(name)) { - resolve_class_member(script_class, name, p_identifier); - - GDScriptParser::ClassNode::Member member = script_class->get_member(name); - switch (member.type) { - case GDScriptParser::ClassNode::Member::CONSTANT: - // TODO: Make sure loops won't cause problem. And make special error message for those. - p_identifier->set_datatype(member.get_datatype()); - p_identifier->is_constant = true; - p_identifier->reduced_value = member.constant->initializer->reduced_value; - return; - case GDScriptParser::ClassNode::Member::ENUM_VALUE: - p_identifier->set_datatype(member.get_datatype()); - p_identifier->is_constant = true; - p_identifier->reduced_value = member.enum_value.value; - return; - case GDScriptParser::ClassNode::Member::ENUM: - p_identifier->set_datatype(member.get_datatype()); - p_identifier->is_constant = true; - p_identifier->reduced_value = member.m_enum->dictionary; - return; - case GDScriptParser::ClassNode::Member::CLASS: - p_identifier->set_datatype(member.get_datatype()); - return; - default: - break; + } break; + + case GDScriptParser::ClassNode::Member::SIGNAL: { + if (is_base && !base.is_meta_type) { + p_identifier->set_datatype(member.get_datatype()); + p_identifier->source = GDScriptParser::IdentifierNode::MEMBER_SIGNAL; + return; + } + } break; + + case GDScriptParser::ClassNode::Member::FUNCTION: { + if (is_base && !base.is_meta_type) { + p_identifier->set_datatype(make_callable_type(member.function->info)); + return; } + } break; + + case GDScriptParser::ClassNode::Member::CLASS: { + reduce_identifier_from_base_set_class(p_identifier, member.get_datatype()); + return; + } + + default: { + // Do nothing } } } - base_class = base_class->base_type.class_type; + if (is_base) { + is_base = script_class->base_type.class_type != nullptr; + if (!is_base && p_base != nullptr) { + break; + } + } } // Check native members. No need for native class recursion because Node exposes all Object's properties. @@ -3067,35 +3123,39 @@ void GDScriptAnalyzer::reduce_identifier_from_base(GDScriptParser::IdentifierNod return; } if (ClassDB::has_enum(native, name)) { - p_identifier->set_datatype(make_native_enum_type(native, name)); + p_identifier->set_datatype(make_native_enum_type(name, native)); p_identifier->source = GDScriptParser::IdentifierNode::MEMBER_CONSTANT; return; } bool valid = false; + int64_t int_constant = ClassDB::get_integer_constant(native, name, &valid); if (valid) { p_identifier->is_constant = true; p_identifier->reduced_value = int_constant; - p_identifier->set_datatype(type_from_variant(int_constant, p_identifier)); p_identifier->source = GDScriptParser::IdentifierNode::MEMBER_CONSTANT; - return; + + // Check whether this constant, which exists, belongs to an enum + StringName enum_name = ClassDB::get_integer_constant_enum(native, name); + if (enum_name != StringName()) { + p_identifier->set_datatype(make_native_enum_type(enum_name, native, false)); + } else { + p_identifier->set_datatype(type_from_variant(int_constant, p_identifier)); + } } } } void GDScriptAnalyzer::reduce_identifier(GDScriptParser::IdentifierNode *p_identifier, bool can_be_builtin) { - // TODO: This is opportunity to further infer types. + // TODO: This is an opportunity to further infer types. - // Check if we are inside and enum. This allows enum values to access other elements of the same enum. + // Check if we are inside an enum. This allows enum values to access other elements of the same enum. if (current_enum) { for (int i = 0; i < current_enum->values.size(); i++) { const GDScriptParser::EnumNode::Value &element = current_enum->values[i]; if (element.identifier->name == p_identifier->name) { - GDScriptParser::DataType type; - type.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT; - type.kind = element.parent_enum->identifier ? GDScriptParser::DataType::ENUM : GDScriptParser::DataType::BUILTIN; - type.builtin_type = Variant::INT; - type.is_constant = true; + StringName enum_name = current_enum->identifier->name ? current_enum->identifier->name : UNNAMED_ENUM; + GDScriptParser::DataType type = make_enum_type(enum_name, parser->current_class->fqcn, false); if (element.parent_enum->identifier) { type.enum_type = element.parent_enum->identifier->name; } @@ -3164,18 +3224,20 @@ void GDScriptAnalyzer::reduce_identifier(GDScriptParser::IdentifierNode *p_ident } if (found_source) { - if ((p_identifier->source == GDScriptParser::IdentifierNode::MEMBER_VARIABLE || p_identifier->source == GDScriptParser::IdentifierNode::INHERITED_VARIABLE) && parser->current_function && parser->current_function->is_static) { + bool source_is_variable = p_identifier->source == GDScriptParser::IdentifierNode::MEMBER_VARIABLE || p_identifier->source == GDScriptParser::IdentifierNode::INHERITED_VARIABLE; + bool source_is_signal = p_identifier->source == GDScriptParser::IdentifierNode::MEMBER_SIGNAL; + if ((source_is_variable || source_is_signal) && parser->current_function && parser->current_function->is_static) { // Get the parent function above any lambda. GDScriptParser::FunctionNode *parent_function = parser->current_function; while (parent_function->source_lambda) { parent_function = parent_function->source_lambda->parent_function; } - push_error(vformat(R"*(Cannot access instance variable "%s" from the static function "%s()".)*", p_identifier->name, parent_function->identifier->name), p_identifier); + push_error(vformat(R"*(Cannot access %s "%s" from the static function "%s()".)*", source_is_signal ? "signal" : "instance variable", p_identifier->name, parent_function->identifier->name), p_identifier); } if (!lambda_stack.is_empty()) { - // If the identifier is a member variable (including the native class properties), we consider the lambda to be using `self`, so we keep a reference to the current instance. - if (p_identifier->source == GDScriptParser::IdentifierNode::MEMBER_VARIABLE || p_identifier->source == GDScriptParser::IdentifierNode::INHERITED_VARIABLE) { + // If the identifier is a member variable (including the native class properties) or a signal, we consider the lambda to be using `self`, so we keep a reference to the current instance. + if (source_is_variable || source_is_signal) { mark_lambda_use_self(); return; // No need to capture. } @@ -3411,9 +3473,9 @@ void GDScriptAnalyzer::reduce_subscript(GDScriptParser::SubscriptNode *p_subscri reduce_expression(p_subscript->base); if (p_subscript->base->type == GDScriptParser::Node::ARRAY) { - const_fold_array(static_cast<GDScriptParser::ArrayNode *>(p_subscript->base)); + const_fold_array(static_cast<GDScriptParser::ArrayNode *>(p_subscript->base), false); } else if (p_subscript->base->type == GDScriptParser::Node::DICTIONARY) { - const_fold_dictionary(static_cast<GDScriptParser::DictionaryNode *>(p_subscript->base)); + const_fold_dictionary(static_cast<GDScriptParser::DictionaryNode *>(p_subscript->base), false); } } @@ -3473,12 +3535,12 @@ void GDScriptAnalyzer::reduce_subscript(GDScriptParser::SubscriptNode *p_subscri Variant value = p_subscript->base->reduced_value.get(p_subscript->index->reduced_value, &valid); if (!valid) { push_error(vformat(R"(Cannot get index "%s" from "%s".)", p_subscript->index->reduced_value, p_subscript->base->reduced_value), p_subscript->index); + result_type.kind = GDScriptParser::DataType::VARIANT; } else { p_subscript->is_constant = true; p_subscript->reduced_value = value; result_type = type_from_variant(value, p_subscript); } - result_type.kind = GDScriptParser::DataType::VARIANT; } else { GDScriptParser::DataType base_type = p_subscript->base->get_datatype(); GDScriptParser::DataType index_type = p_subscript->index->get_datatype(); @@ -3738,20 +3800,17 @@ void GDScriptAnalyzer::reduce_unary_op(GDScriptParser::UnaryOpNode *p_unary_op) p_unary_op->set_datatype(result); } -void GDScriptAnalyzer::const_fold_array(GDScriptParser::ArrayNode *p_array) { - bool all_is_constant = true; - +void GDScriptAnalyzer::const_fold_array(GDScriptParser::ArrayNode *p_array, bool p_is_const) { for (int i = 0; i < p_array->elements.size(); i++) { GDScriptParser::ExpressionNode *element = p_array->elements[i]; if (element->type == GDScriptParser::Node::ARRAY) { - const_fold_array(static_cast<GDScriptParser::ArrayNode *>(element)); + const_fold_array(static_cast<GDScriptParser::ArrayNode *>(element), p_is_const); } else if (element->type == GDScriptParser::Node::DICTIONARY) { - const_fold_dictionary(static_cast<GDScriptParser::DictionaryNode *>(element)); + const_fold_dictionary(static_cast<GDScriptParser::DictionaryNode *>(element), p_is_const); } - all_is_constant = all_is_constant && element->is_constant; - if (!all_is_constant) { + if (!element->is_constant) { return; } } @@ -3761,24 +3820,24 @@ void GDScriptAnalyzer::const_fold_array(GDScriptParser::ArrayNode *p_array) { for (int i = 0; i < p_array->elements.size(); i++) { array[i] = p_array->elements[i]->reduced_value; } + if (p_is_const) { + array.set_read_only(true); + } p_array->is_constant = true; p_array->reduced_value = array; } -void GDScriptAnalyzer::const_fold_dictionary(GDScriptParser::DictionaryNode *p_dictionary) { - bool all_is_constant = true; - +void GDScriptAnalyzer::const_fold_dictionary(GDScriptParser::DictionaryNode *p_dictionary, bool p_is_const) { for (int i = 0; i < p_dictionary->elements.size(); i++) { const GDScriptParser::DictionaryNode::Pair &element = p_dictionary->elements[i]; if (element.value->type == GDScriptParser::Node::ARRAY) { - const_fold_array(static_cast<GDScriptParser::ArrayNode *>(element.value)); + const_fold_array(static_cast<GDScriptParser::ArrayNode *>(element.value), p_is_const); } else if (element.value->type == GDScriptParser::Node::DICTIONARY) { - const_fold_dictionary(static_cast<GDScriptParser::DictionaryNode *>(element.value)); + const_fold_dictionary(static_cast<GDScriptParser::DictionaryNode *>(element.value), p_is_const); } - all_is_constant = all_is_constant && element.key->is_constant && element.value->is_constant; - if (!all_is_constant) { + if (!element.key->is_constant || !element.value->is_constant) { return; } } @@ -3788,6 +3847,9 @@ void GDScriptAnalyzer::const_fold_dictionary(GDScriptParser::DictionaryNode *p_d const GDScriptParser::DictionaryNode::Pair &element = p_dictionary->elements[i]; dict[element.key->reduced_value] = element.value->reduced_value; } + if (p_is_const) { + dict.set_read_only(true); + } p_dictionary->is_constant = true; p_dictionary->reduced_value = dict; } @@ -3865,12 +3927,13 @@ GDScriptParser::DataType GDScriptAnalyzer::type_from_variant(const Variant &p_va return result; } -GDScriptParser::DataType GDScriptAnalyzer::type_from_metatype(const GDScriptParser::DataType &p_meta_type) const { +GDScriptParser::DataType GDScriptAnalyzer::type_from_metatype(const GDScriptParser::DataType &p_meta_type) { GDScriptParser::DataType result = p_meta_type; result.is_meta_type = false; - result.is_constant = false; if (p_meta_type.kind == GDScriptParser::DataType::ENUM) { result.builtin_type = Variant::INT; + } else { + result.is_constant = false; } return result; } @@ -3928,11 +3991,12 @@ bool GDScriptAnalyzer::get_function_signature(GDScriptParser::Node *p_source, bo r_default_arg_count = 0; StringName function_name = p_function; + bool was_enum = false; if (p_base_type.kind == GDScriptParser::DataType::ENUM) { + was_enum = true; if (p_base_type.is_meta_type) { // Enum type can be treated as a dictionary value. p_base_type.kind = GDScriptParser::DataType::BUILTIN; - p_base_type.builtin_type = Variant::DICTIONARY; p_base_type.is_meta_type = false; } else { push_error("Cannot call function on enum value.", p_source); @@ -3955,6 +4019,10 @@ bool GDScriptAnalyzer::get_function_signature(GDScriptParser::Node *p_source, bo if (E.name == p_function) { function_signature_from_info(E, r_return_type, r_par_types, r_default_arg_count, r_static, r_vararg); r_static = Variant::is_builtin_method_static(p_base_type.builtin_type, function_name); + // Cannot use non-const methods on enums. + if (!r_static && was_enum && !(E.flags & METHOD_FLAG_CONST)) { + push_error(vformat(R"*(Cannot call non-const Dictionary function "%s()" on enum "%s".)*", p_function, p_base_type.enum_type), p_source); + } return true; } } @@ -4102,7 +4170,7 @@ bool GDScriptAnalyzer::validate_call_arg(const List<GDScriptParser::DataType> &p // Supertypes are acceptable for dynamic compliance, but it's unsafe. mark_node_unsafe(p_call); if (!is_type_compatible(arg_type, par_type)) { - push_error(vformat(R"*(Invalid argument for "%s()" function: argument %d should be %s but is %s.)*", + push_error(vformat(R"*(Invalid argument for "%s()" function: argument %d should be "%s" but is "%s".)*", p_call->function_name, i + 1, par_type.to_string(), arg_type.to_string()), p_call->arguments[i]); valid = false; |