Add typed arrays to GDScript
- Use `Array[type]` for type-hints. e.g.: `var array: Array[int] = [1, 2, 3]` - Array literals are typed if their storage is typed (variable asssignment of as argument in function all). Otherwise they are untyped.
This commit is contained in:
parent
997a8ae9e8
commit
85e316a5d5
|
@ -1310,21 +1310,29 @@ bool GDScriptInstance::set(const StringName &p_name, const Variant &p_value) {
|
|||
return true; //function exists, call was successful
|
||||
}
|
||||
} else {
|
||||
if (!member->data_type.is_type(p_value)) {
|
||||
// Try conversion
|
||||
Callable::CallError ce;
|
||||
const Variant *value = &p_value;
|
||||
Variant converted;
|
||||
Variant::construct(member->data_type.builtin_type, converted, &value, 1, ce);
|
||||
if (ce.error == Callable::CallError::CALL_OK) {
|
||||
members.write[member->index] = converted;
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
if (member->data_type.has_type) {
|
||||
if (member->data_type.builtin_type == Variant::ARRAY && member->data_type.has_container_element_type()) {
|
||||
// Typed array.
|
||||
if (p_value.get_type() == Variant::ARRAY) {
|
||||
return VariantInternal::get_array(&members.write[member->index])->typed_assign(p_value);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
} else if (!member->data_type.is_type(p_value)) {
|
||||
// Try conversion
|
||||
Callable::CallError ce;
|
||||
const Variant *value = &p_value;
|
||||
Variant converted;
|
||||
Variant::construct(member->data_type.builtin_type, converted, &value, 1, ce);
|
||||
if (ce.error == Callable::CallError::CALL_OK) {
|
||||
members.write[member->index] = converted;
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
members.write[member->index] = p_value;
|
||||
}
|
||||
members.write[member->index] = p_value;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
|
|
@ -413,6 +413,14 @@ GDScriptParser::DataType GDScriptAnalyzer::resolve_datatype(GDScriptParser::Type
|
|||
}
|
||||
result.kind = GDScriptParser::DataType::BUILTIN;
|
||||
result.builtin_type = GDScriptParser::get_builtin_type(first);
|
||||
|
||||
if (result.builtin_type == Variant::ARRAY) {
|
||||
GDScriptParser::DataType container_type = resolve_datatype(p_type->container_type);
|
||||
|
||||
if (container_type.kind != GDScriptParser::DataType::VARIANT) {
|
||||
result.set_container_element_type(container_type);
|
||||
}
|
||||
}
|
||||
} else if (class_exists(first)) {
|
||||
// Native engine classes.
|
||||
result.kind = GDScriptParser::DataType::NATIVE;
|
||||
|
@ -513,6 +521,10 @@ GDScriptParser::DataType GDScriptAnalyzer::resolve_datatype(GDScriptParser::Type
|
|||
}
|
||||
}
|
||||
|
||||
if (result.builtin_type != Variant::ARRAY && p_type->container_type != nullptr) {
|
||||
push_error("Only arrays can specify the collection element type.", p_type);
|
||||
}
|
||||
|
||||
p_type->set_datatype(result);
|
||||
return result;
|
||||
}
|
||||
|
@ -535,9 +547,23 @@ void GDScriptAnalyzer::resolve_class_interface(GDScriptParser::ClassNode *p_clas
|
|||
datatype.kind = GDScriptParser::DataType::VARIANT;
|
||||
datatype.type_source = GDScriptParser::DataType::UNDETECTED;
|
||||
|
||||
GDScriptParser::DataType specified_type;
|
||||
if (member.variable->datatype_specifier != nullptr) {
|
||||
specified_type = resolve_datatype(member.variable->datatype_specifier);
|
||||
specified_type.is_meta_type = false;
|
||||
}
|
||||
|
||||
if (member.variable->initializer != nullptr) {
|
||||
member.variable->set_datatype(datatype); // Allow recursive usage.
|
||||
reduce_expression(member.variable->initializer);
|
||||
if ((member.variable->infer_datatype || (member.variable->datatype_specifier != nullptr && specified_type.has_container_element_type())) && member.variable->initializer->type == GDScriptParser::Node::ARRAY) {
|
||||
// Typed array.
|
||||
GDScriptParser::ArrayNode *array = static_cast<GDScriptParser::ArrayNode *>(member.variable->initializer);
|
||||
// Can only infer typed array if it has elements.
|
||||
if ((member.variable->infer_datatype && array->elements.size() > 0) || member.variable->datatype_specifier != nullptr) {
|
||||
update_array_literal_element_type(specified_type, array);
|
||||
}
|
||||
}
|
||||
datatype = member.variable->initializer->get_datatype();
|
||||
if (datatype.type_source != GDScriptParser::DataType::UNDETECTED) {
|
||||
datatype.type_source = GDScriptParser::DataType::INFERRED;
|
||||
|
@ -545,8 +571,7 @@ void GDScriptAnalyzer::resolve_class_interface(GDScriptParser::ClassNode *p_clas
|
|||
}
|
||||
|
||||
if (member.variable->datatype_specifier != nullptr) {
|
||||
datatype = resolve_datatype(member.variable->datatype_specifier);
|
||||
datatype.is_meta_type = false;
|
||||
datatype = specified_type;
|
||||
|
||||
if (member.variable->initializer != nullptr) {
|
||||
if (!is_type_compatible(datatype, member.variable->initializer->get_datatype(), true)) {
|
||||
|
@ -609,10 +634,23 @@ void GDScriptAnalyzer::resolve_class_interface(GDScriptParser::ClassNode *p_clas
|
|||
case GDScriptParser::ClassNode::Member::CONSTANT: {
|
||||
reduce_expression(member.constant->initializer);
|
||||
|
||||
GDScriptParser::DataType specified_type;
|
||||
|
||||
if (member.constant->datatype_specifier != nullptr) {
|
||||
specified_type = resolve_datatype(member.constant->datatype_specifier);
|
||||
specified_type.is_meta_type = false;
|
||||
}
|
||||
|
||||
GDScriptParser::DataType datatype = member.constant->get_datatype();
|
||||
if (member.constant->initializer) {
|
||||
if (member.constant->initializer->type == GDScriptParser::Node::ARRAY) {
|
||||
const_fold_array(static_cast<GDScriptParser::ArrayNode *>(member.constant->initializer));
|
||||
GDScriptParser::ArrayNode *array = static_cast<GDScriptParser::ArrayNode *>(member.variable->initializer);
|
||||
const_fold_array(array);
|
||||
|
||||
// Can only infer typed array if it has elements.
|
||||
if (array->elements.size() > 0 || (member.variable->datatype_specifier != nullptr && specified_type.has_container_element_type())) {
|
||||
update_array_literal_element_type(specified_type, array);
|
||||
}
|
||||
} else if (member.constant->initializer->type == GDScriptParser::Node::DICTIONARY) {
|
||||
const_fold_dictionary(static_cast<GDScriptParser::DictionaryNode *>(member.constant->initializer));
|
||||
}
|
||||
|
@ -622,8 +660,7 @@ void GDScriptAnalyzer::resolve_class_interface(GDScriptParser::ClassNode *p_clas
|
|||
}
|
||||
|
||||
if (member.constant->datatype_specifier != nullptr) {
|
||||
datatype = resolve_datatype(member.constant->datatype_specifier);
|
||||
datatype.is_meta_type = false;
|
||||
datatype = specified_type;
|
||||
|
||||
if (!is_type_compatible(datatype, member.constant->initializer->get_datatype(), true)) {
|
||||
push_error(vformat(R"(Value of type "%s" cannot be initialized to constant of type "%s".)", member.constant->initializer->get_datatype().to_string(), datatype.to_string()), member.constant->initializer);
|
||||
|
@ -1092,8 +1129,23 @@ void GDScriptAnalyzer::resolve_variable(GDScriptParser::VariableNode *p_variable
|
|||
GDScriptParser::DataType type;
|
||||
type.kind = GDScriptParser::DataType::VARIANT; // By default.
|
||||
|
||||
GDScriptParser::DataType specified_type;
|
||||
if (p_variable->datatype_specifier != nullptr) {
|
||||
specified_type = resolve_datatype(p_variable->datatype_specifier);
|
||||
specified_type.is_meta_type = false;
|
||||
}
|
||||
|
||||
if (p_variable->initializer != nullptr) {
|
||||
reduce_expression(p_variable->initializer);
|
||||
if ((p_variable->infer_datatype || (p_variable->datatype_specifier != nullptr && specified_type.has_container_element_type())) && p_variable->initializer->type == GDScriptParser::Node::ARRAY) {
|
||||
// Typed array.
|
||||
GDScriptParser::ArrayNode *array = static_cast<GDScriptParser::ArrayNode *>(p_variable->initializer);
|
||||
// Can only infer typed array if it has elements.
|
||||
if ((p_variable->infer_datatype && array->elements.size() > 0) || p_variable->datatype_specifier != nullptr) {
|
||||
update_array_literal_element_type(specified_type, array);
|
||||
}
|
||||
}
|
||||
|
||||
type = p_variable->initializer->get_datatype();
|
||||
|
||||
if (p_variable->infer_datatype) {
|
||||
|
@ -1117,7 +1169,7 @@ void GDScriptAnalyzer::resolve_variable(GDScriptParser::VariableNode *p_variable
|
|||
}
|
||||
|
||||
if (p_variable->datatype_specifier != nullptr) {
|
||||
type = resolve_datatype(p_variable->datatype_specifier);
|
||||
type = specified_type;
|
||||
type.is_meta_type = false;
|
||||
|
||||
if (p_variable->initializer != nullptr) {
|
||||
|
@ -1362,6 +1414,12 @@ void GDScriptAnalyzer::resolve_return(GDScriptParser::ReturnNode *p_return) {
|
|||
|
||||
if (p_return->return_value != nullptr) {
|
||||
reduce_expression(p_return->return_value);
|
||||
if (p_return->return_value->type == GDScriptParser::Node::ARRAY) {
|
||||
// Check if assigned value is an array literal, so we can make it a typed array too if appropriate.
|
||||
if (parser->current_function->get_datatype().has_container_element_type() && p_return->return_value->type == GDScriptParser::Node::ARRAY) {
|
||||
update_array_literal_element_type(parser->current_function->get_datatype(), static_cast<GDScriptParser::ArrayNode *>(p_return->return_value));
|
||||
}
|
||||
}
|
||||
result = p_return->return_value->get_datatype();
|
||||
} else {
|
||||
// Return type is null by default.
|
||||
|
@ -1498,6 +1556,52 @@ void GDScriptAnalyzer::reduce_array(GDScriptParser::ArrayNode *p_array) {
|
|||
p_array->set_datatype(arr_type);
|
||||
}
|
||||
|
||||
// When an array literal is stored (or passed as function argument) to a typed context, we then assume the array is typed.
|
||||
// This function determines which type is that (if any).
|
||||
void GDScriptAnalyzer::update_array_literal_element_type(const GDScriptParser::DataType &p_base_type, GDScriptParser::ArrayNode *p_array_literal) {
|
||||
GDScriptParser::DataType array_type = p_array_literal->get_datatype();
|
||||
if (p_array_literal->elements.size() == 0) {
|
||||
// Empty array literal, just make the same type as the storage.
|
||||
array_type.set_container_element_type(p_base_type.get_container_element_type());
|
||||
} else {
|
||||
// Check if elements match.
|
||||
bool all_same_type = true;
|
||||
bool all_have_type = true;
|
||||
|
||||
GDScriptParser::DataType element_type;
|
||||
for (int i = 0; i < p_array_literal->elements.size(); i++) {
|
||||
if (i == 0) {
|
||||
element_type = p_array_literal->elements[0]->get_datatype();
|
||||
} else {
|
||||
GDScriptParser::DataType this_element_type = p_array_literal->elements[i]->get_datatype();
|
||||
if (this_element_type.has_no_type()) {
|
||||
all_same_type = false;
|
||||
all_have_type = false;
|
||||
break;
|
||||
} else if (element_type != this_element_type) {
|
||||
if (!is_type_compatible(element_type, this_element_type, false)) {
|
||||
if (is_type_compatible(this_element_type, element_type, false)) {
|
||||
// This element is a super-type to the previous type, so we use the super-type.
|
||||
element_type = this_element_type;
|
||||
} else {
|
||||
// It's incompatible.
|
||||
all_same_type = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (all_same_type) {
|
||||
array_type.set_container_element_type(element_type);
|
||||
} else if (all_have_type) {
|
||||
push_error(vformat(R"(Variant array is not compatible with an array of type "%s".)", p_base_type.get_container_element_type().to_string()), p_array_literal);
|
||||
}
|
||||
}
|
||||
// Update the type on the value itself.
|
||||
p_array_literal->set_datatype(array_type);
|
||||
}
|
||||
|
||||
void GDScriptAnalyzer::reduce_assignment(GDScriptParser::AssignmentNode *p_assignment) {
|
||||
reduce_expression(p_assignment->assignee);
|
||||
reduce_expression(p_assignment->assigned_value);
|
||||
|
@ -1506,24 +1610,33 @@ void GDScriptAnalyzer::reduce_assignment(GDScriptParser::AssignmentNode *p_assig
|
|||
return;
|
||||
}
|
||||
|
||||
if (p_assignment->assignee->get_datatype().is_constant) {
|
||||
GDScriptParser::DataType assignee_type = p_assignment->assignee->get_datatype();
|
||||
|
||||
// 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));
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
if (!p_assignment->assignee->get_datatype().is_variant() && !p_assignment->assigned_value->get_datatype().is_variant()) {
|
||||
if (!assignee_type.is_variant() && !assigned_value_type.is_variant()) {
|
||||
bool compatible = true;
|
||||
GDScriptParser::DataType op_type = p_assignment->assigned_value->get_datatype();
|
||||
GDScriptParser::DataType op_type = assigned_value_type;
|
||||
if (p_assignment->operation != GDScriptParser::AssignmentNode::OP_NONE) {
|
||||
op_type = get_operation_type(p_assignment->variant_op, p_assignment->assignee->get_datatype(), p_assignment->assigned_value->get_datatype(), compatible, p_assignment->assigned_value);
|
||||
op_type = get_operation_type(p_assignment->variant_op, assignee_type, assigned_value_type, compatible, p_assignment->assigned_value);
|
||||
}
|
||||
|
||||
if (compatible) {
|
||||
compatible = is_type_compatible(p_assignment->assignee->get_datatype(), op_type, true);
|
||||
compatible = is_type_compatible(assignee_type, op_type, true);
|
||||
if (!compatible) {
|
||||
if (p_assignment->assignee->get_datatype().is_hard_type()) {
|
||||
if (assignee_type.is_hard_type()) {
|
||||
// Try reverse test since it can be a masked subtype.
|
||||
if (!is_type_compatible(op_type, p_assignment->assignee->get_datatype(), true)) {
|
||||
push_error(vformat(R"(Cannot assign a value of type "%s" to a target of type "%s".)", p_assignment->assigned_value->get_datatype().to_string(), p_assignment->assignee->get_datatype().to_string()), p_assignment->assigned_value);
|
||||
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);
|
||||
} else {
|
||||
// TODO: Add warning.
|
||||
mark_node_unsafe(p_assignment);
|
||||
|
@ -1534,11 +1647,11 @@ void GDScriptAnalyzer::reduce_assignment(GDScriptParser::AssignmentNode *p_assig
|
|||
}
|
||||
}
|
||||
} else {
|
||||
push_error(vformat(R"(Invalid operands "%s" and "%s" for assignment operator.)", p_assignment->assignee->get_datatype().to_string(), p_assignment->assigned_value->get_datatype().to_string()), p_assignment);
|
||||
push_error(vformat(R"(Invalid operands "%s" and "%s" for assignment operator.)", assignee_type.to_string(), assigned_value_type.to_string()), p_assignment);
|
||||
}
|
||||
}
|
||||
|
||||
if (p_assignment->assignee->get_datatype().has_no_type() || p_assignment->assigned_value->get_datatype().is_variant()) {
|
||||
if (assignee_type.has_no_type() || assigned_value_type.is_variant()) {
|
||||
mark_node_unsafe(p_assignment);
|
||||
}
|
||||
|
||||
|
@ -1558,7 +1671,7 @@ void GDScriptAnalyzer::reduce_assignment(GDScriptParser::AssignmentNode *p_assig
|
|||
case GDScriptParser::IdentifierNode::LOCAL_VARIABLE: {
|
||||
GDScriptParser::DataType id_type = identifier->variable_source->get_datatype();
|
||||
if (!id_type.is_hard_type()) {
|
||||
id_type = p_assignment->assigned_value->get_datatype();
|
||||
id_type = assigned_value_type;
|
||||
id_type.type_source = GDScriptParser::DataType::INFERRED;
|
||||
id_type.is_constant = false;
|
||||
identifier->variable_source->set_datatype(id_type);
|
||||
|
@ -1567,7 +1680,7 @@ void GDScriptAnalyzer::reduce_assignment(GDScriptParser::AssignmentNode *p_assig
|
|||
case GDScriptParser::IdentifierNode::LOCAL_ITERATOR: {
|
||||
GDScriptParser::DataType id_type = identifier->bind_source->get_datatype();
|
||||
if (!id_type.is_hard_type()) {
|
||||
id_type = p_assignment->assigned_value->get_datatype();
|
||||
id_type = assigned_value_type;
|
||||
id_type.type_source = GDScriptParser::DataType::INFERRED;
|
||||
id_type.is_constant = false;
|
||||
identifier->variable_source->set_datatype(id_type);
|
||||
|
@ -1579,12 +1692,10 @@ void GDScriptAnalyzer::reduce_assignment(GDScriptParser::AssignmentNode *p_assig
|
|||
}
|
||||
}
|
||||
|
||||
GDScriptParser::DataType assignee_type = p_assignment->assignee->get_datatype();
|
||||
GDScriptParser::DataType assigned_type = p_assignment->assigned_value->get_datatype();
|
||||
#ifdef DEBUG_ENABLED
|
||||
if (p_assignment->assigned_value->type == GDScriptParser::Node::CALL && assigned_type.kind == GDScriptParser::DataType::BUILTIN && assigned_type.builtin_type == Variant::NIL) {
|
||||
if (p_assignment->assigned_value->type == GDScriptParser::Node::CALL && assigned_value_type.kind == GDScriptParser::DataType::BUILTIN && assigned_value_type.builtin_type == Variant::NIL) {
|
||||
parser->push_warning(p_assignment->assigned_value, GDScriptWarning::VOID_ASSIGNMENT, static_cast<GDScriptParser::CallNode *>(p_assignment->assigned_value)->function_name);
|
||||
} else if (assignee_type.is_hard_type() && assignee_type.builtin_type == Variant::INT && assigned_type.builtin_type == Variant::FLOAT) {
|
||||
} else if (assignee_type.is_hard_type() && assignee_type.builtin_type == Variant::INT && assigned_value_type.builtin_type == Variant::FLOAT) {
|
||||
parser->push_warning(p_assignment->assigned_value, GDScriptWarning::NARROWING_CONVERSION);
|
||||
}
|
||||
#endif
|
||||
|
@ -1728,8 +1839,12 @@ void GDScriptAnalyzer::reduce_binary_op(GDScriptParser::BinaryOpNode *p_binary_o
|
|||
|
||||
void GDScriptAnalyzer::reduce_call(GDScriptParser::CallNode *p_call, bool is_await) {
|
||||
bool all_is_constant = true;
|
||||
Map<int, GDScriptParser::ArrayNode *> arrays; // For array literal to potentially type when passing.
|
||||
for (int i = 0; i < p_call->arguments.size(); i++) {
|
||||
reduce_expression(p_call->arguments[i]);
|
||||
if (p_call->arguments[i]->type == GDScriptParser::Node::ARRAY) {
|
||||
arrays[i] = static_cast<GDScriptParser::ArrayNode *>(p_call->arguments[i]);
|
||||
}
|
||||
all_is_constant = all_is_constant && p_call->arguments[i]->is_constant;
|
||||
}
|
||||
|
||||
|
@ -2007,6 +2122,13 @@ void GDScriptAnalyzer::reduce_call(GDScriptParser::CallNode *p_call, bool is_awa
|
|||
List<GDScriptParser::DataType> par_types;
|
||||
|
||||
if (get_function_signature(p_call, base_type, p_call->function_name, return_type, par_types, default_arg_count, is_static, is_vararg)) {
|
||||
// If the function require typed arrays we must make literals be typed.
|
||||
for (Map<int, GDScriptParser::ArrayNode *>::Element *E = arrays.front(); E; E = E->next()) {
|
||||
int index = E->key();
|
||||
if (index < par_types.size() && par_types[index].has_container_element_type()) {
|
||||
update_array_literal_element_type(par_types[index], E->get());
|
||||
}
|
||||
}
|
||||
validate_call_arg(par_types, default_arg_count, is_vararg, p_call);
|
||||
|
||||
if (is_self && parser->current_function != nullptr && parser->current_function->is_static && !is_static) {
|
||||
|
@ -2752,11 +2874,20 @@ void GDScriptAnalyzer::reduce_subscript(GDScriptParser::SubscriptNode *p_subscri
|
|||
case Variant::TRANSFORM:
|
||||
case Variant::PLANE:
|
||||
case Variant::COLOR:
|
||||
case Variant::ARRAY:
|
||||
case Variant::DICTIONARY:
|
||||
result_type.kind = GDScriptParser::DataType::VARIANT;
|
||||
result_type.type_source = GDScriptParser::DataType::UNDETECTED;
|
||||
break;
|
||||
// Can have an element type.
|
||||
case Variant::ARRAY:
|
||||
if (base_type.has_container_element_type()) {
|
||||
result_type = base_type.get_container_element_type();
|
||||
result_type.type_source = base_type.type_source;
|
||||
} else {
|
||||
result_type.kind = GDScriptParser::DataType::VARIANT;
|
||||
result_type.type_source = GDScriptParser::DataType::UNDETECTED;
|
||||
}
|
||||
break;
|
||||
// Here for completeness.
|
||||
case Variant::OBJECT:
|
||||
case Variant::VARIANT_MAX:
|
||||
|
@ -2979,6 +3110,34 @@ GDScriptParser::DataType GDScriptAnalyzer::type_from_property(const PropertyInfo
|
|||
result.native_type = p_property.class_name == StringName() ? "Object" : p_property.class_name;
|
||||
} else {
|
||||
result.kind = GDScriptParser::DataType::BUILTIN;
|
||||
result.builtin_type = p_property.type;
|
||||
if (p_property.type == Variant::ARRAY && p_property.hint == PROPERTY_HINT_ARRAY_TYPE) {
|
||||
// Check element type.
|
||||
StringName elem_type_name = p_property.hint_string;
|
||||
GDScriptParser::DataType elem_type;
|
||||
elem_type.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT;
|
||||
|
||||
Variant::Type elem_builtin_type = GDScriptParser::get_builtin_type(elem_type_name);
|
||||
if (elem_builtin_type < Variant::VARIANT_MAX) {
|
||||
// Builtin type.
|
||||
elem_type.kind = GDScriptParser::DataType::BUILTIN;
|
||||
elem_type.builtin_type = elem_builtin_type;
|
||||
} else if (class_exists(elem_type_name)) {
|
||||
elem_type.kind = GDScriptParser::DataType::NATIVE;
|
||||
elem_type.builtin_type = Variant::OBJECT;
|
||||
elem_type.native_type = p_property.hint_string;
|
||||
} else if (ScriptServer::is_global_class(elem_type_name)) {
|
||||
// Just load this as it shouldn't be a GDScript.
|
||||
Ref<Script> script = ResourceLoader::load(ScriptServer::get_global_class_path(elem_type_name));
|
||||
elem_type.kind = GDScriptParser::DataType::SCRIPT;
|
||||
elem_type.builtin_type = Variant::OBJECT;
|
||||
elem_type.native_type = script->get_instance_base_type();
|
||||
elem_type.script_type = script;
|
||||
} else {
|
||||
ERR_FAIL_V_MSG(result, "Could not find element type from property hint of a typed array.");
|
||||
}
|
||||
result.set_container_element_type(elem_type);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
@ -3257,6 +3416,18 @@ bool GDScriptAnalyzer::is_type_compatible(const GDScriptParser::DataType &p_targ
|
|||
// Enum value is also integer.
|
||||
valid = true;
|
||||
}
|
||||
if (valid && p_target.builtin_type == Variant::ARRAY && p_source.builtin_type == Variant::ARRAY) {
|
||||
// Check the element type.
|
||||
if (p_target.has_container_element_type()) {
|
||||
if (!p_source.has_container_element_type()) {
|
||||
// TODO: Maybe this is valid but unsafe?
|
||||
// Variant array can't be appended to typed array.
|
||||
valid = false;
|
||||
} else {
|
||||
valid = is_type_compatible(p_target.get_container_element_type(), p_source.get_container_element_type(), false);
|
||||
}
|
||||
}
|
||||
}
|
||||
return valid;
|
||||
}
|
||||
|
||||
|
@ -3385,7 +3556,7 @@ void GDScriptAnalyzer::mark_node_unsafe(const GDScriptParser::Node *p_node) {
|
|||
#endif
|
||||
}
|
||||
|
||||
bool GDScriptAnalyzer::class_exists(const StringName &p_class) {
|
||||
bool GDScriptAnalyzer::class_exists(const StringName &p_class) const {
|
||||
StringName real_name = get_real_class_name(p_class);
|
||||
return ClassDB::class_exists(real_name) && ClassDB::is_class_exposed(real_name);
|
||||
}
|
||||
|
|
|
@ -103,10 +103,11 @@ class GDScriptAnalyzer {
|
|||
bool validate_call_arg(const MethodInfo &p_method, const GDScriptParser::CallNode *p_call);
|
||||
GDScriptParser::DataType get_operation_type(Variant::Operator p_operation, const GDScriptParser::DataType &p_a, const GDScriptParser::DataType &p_b, bool &r_valid, const GDScriptParser::Node *p_source);
|
||||
GDScriptParser::DataType get_operation_type(Variant::Operator p_operation, const GDScriptParser::DataType &p_a, bool &r_valid, const GDScriptParser::Node *p_source);
|
||||
void update_array_literal_element_type(const GDScriptParser::DataType &p_base_type, GDScriptParser::ArrayNode *p_array_literal);
|
||||
bool is_type_compatible(const GDScriptParser::DataType &p_target, const GDScriptParser::DataType &p_source, bool p_allow_implicit_conversion = false) const;
|
||||
void push_error(const String &p_message, const GDScriptParser::Node *p_origin);
|
||||
void mark_node_unsafe(const GDScriptParser::Node *p_node);
|
||||
bool class_exists(const StringName &p_class);
|
||||
bool class_exists(const StringName &p_class) const;
|
||||
Ref<GDScriptParserRef> get_parser_for(const String &p_path);
|
||||
#ifdef DEBUG_ENABLED
|
||||
bool is_shadowing(GDScriptParser::IdentifierNode *p_local, const String &p_context);
|
||||
|
|
|
@ -599,10 +599,16 @@ void GDScriptByteCodeGenerator::write_assign(const Address &p_target, const Addr
|
|||
// Typed assignment.
|
||||
switch (p_target.type.kind) {
|
||||
case GDScriptDataType::BUILTIN: {
|
||||
append(GDScriptFunction::OPCODE_ASSIGN_TYPED_BUILTIN, 2);
|
||||
append(p_target);
|
||||
append(p_source);
|
||||
append(p_target.type.builtin_type);
|
||||
if (p_target.type.builtin_type == Variant::ARRAY && p_target.type.has_container_element_type()) {
|
||||
append(GDScriptFunction::OPCODE_ASSIGN_TYPED_ARRAY, 2);
|
||||
append(p_target);
|
||||
append(p_source);
|
||||
} else {
|
||||
append(GDScriptFunction::OPCODE_ASSIGN_TYPED_BUILTIN, 2);
|
||||
append(p_target);
|
||||
append(p_source);
|
||||
append(p_target.type.builtin_type);
|
||||
}
|
||||
} break;
|
||||
case GDScriptDataType::NATIVE: {
|
||||
int class_idx = GDScriptLanguage::get_singleton()->get_global_map()[p_target.type.native_type];
|
||||
|
@ -633,7 +639,11 @@ void GDScriptByteCodeGenerator::write_assign(const Address &p_target, const Addr
|
|||
}
|
||||
}
|
||||
} else {
|
||||
if (p_target.type.kind == GDScriptDataType::BUILTIN && p_source.type.kind == GDScriptDataType::BUILTIN && p_target.type.builtin_type != p_source.type.builtin_type) {
|
||||
if (p_target.type.kind == GDScriptDataType::BUILTIN && p_target.type.builtin_type == Variant::ARRAY && p_target.type.has_container_element_type()) {
|
||||
append(GDScriptFunction::OPCODE_ASSIGN_TYPED_ARRAY, 2);
|
||||
append(p_target);
|
||||
append(p_source);
|
||||
} else if (p_target.type.kind == GDScriptDataType::BUILTIN && p_source.type.kind == GDScriptDataType::BUILTIN && p_target.type.builtin_type != p_source.type.builtin_type) {
|
||||
// Need conversion..
|
||||
append(GDScriptFunction::OPCODE_ASSIGN_TYPED_BUILTIN, 2);
|
||||
append(p_target);
|
||||
|
@ -980,6 +990,25 @@ void GDScriptByteCodeGenerator::write_construct_array(const Address &p_target, c
|
|||
append(p_arguments.size());
|
||||
}
|
||||
|
||||
void GDScriptByteCodeGenerator::write_construct_typed_array(const Address &p_target, const GDScriptDataType &p_element_type, const Vector<Address> &p_arguments) {
|
||||
append(GDScriptFunction::OPCODE_CONSTRUCT_TYPED_ARRAY, 2 + p_arguments.size());
|
||||
for (int i = 0; i < p_arguments.size(); i++) {
|
||||
append(p_arguments[i]);
|
||||
}
|
||||
append(p_target);
|
||||
if (p_element_type.script_type) {
|
||||
Variant script_type = Ref<Script>(p_element_type.script_type);
|
||||
int addr = get_constant_pos(script_type);
|
||||
addr |= GDScriptFunction::ADDR_TYPE_LOCAL_CONSTANT << GDScriptFunction::ADDR_BITS;
|
||||
append(addr);
|
||||
} else {
|
||||
append(Address()); // null.
|
||||
}
|
||||
append(p_arguments.size());
|
||||
append(p_element_type.builtin_type);
|
||||
append(p_element_type.native_type);
|
||||
}
|
||||
|
||||
void GDScriptByteCodeGenerator::write_construct_dictionary(const Address &p_target, const Vector<Address> &p_arguments) {
|
||||
append(GDScriptFunction::OPCODE_CONSTRUCT_DICTIONARY, 1 + p_arguments.size());
|
||||
for (int i = 0; i < p_arguments.size(); i++) {
|
||||
|
|
|
@ -445,6 +445,7 @@ public:
|
|||
virtual void write_call_script_function(const Address &p_target, const Address &p_base, const StringName &p_function_name, const Vector<Address> &p_arguments) override;
|
||||
virtual void write_construct(const Address &p_target, Variant::Type p_type, const Vector<Address> &p_arguments) override;
|
||||
virtual void write_construct_array(const Address &p_target, const Vector<Address> &p_arguments) override;
|
||||
virtual void write_construct_typed_array(const Address &p_target, const GDScriptDataType &p_element_type, const Vector<Address> &p_arguments) override;
|
||||
virtual void write_construct_dictionary(const Address &p_target, const Vector<Address> &p_arguments) override;
|
||||
virtual void write_await(const Address &p_target, const Address &p_operand) override;
|
||||
virtual void write_if(const Address &p_condition) override;
|
||||
|
|
|
@ -137,6 +137,7 @@ public:
|
|||
virtual void write_call_script_function(const Address &p_target, const Address &p_base, const StringName &p_function_name, const Vector<Address> &p_arguments) = 0;
|
||||
virtual void write_construct(const Address &p_target, Variant::Type p_type, const Vector<Address> &p_arguments) = 0;
|
||||
virtual void write_construct_array(const Address &p_target, const Vector<Address> &p_arguments) = 0;
|
||||
virtual void write_construct_typed_array(const Address &p_target, const GDScriptDataType &p_element_type, const Vector<Address> &p_arguments) = 0;
|
||||
virtual void write_construct_dictionary(const Address &p_target, const Vector<Address> &p_arguments) = 0;
|
||||
virtual void write_await(const Address &p_target, const Address &p_operand) = 0;
|
||||
virtual void write_if(const Address &p_condition) = 0;
|
||||
|
|
|
@ -153,6 +153,10 @@ GDScriptDataType GDScriptCompiler::_gdtype_from_datatype(const GDScriptParser::D
|
|||
}
|
||||
}
|
||||
|
||||
if (p_datatype.has_container_element_type()) {
|
||||
result.set_container_element_type(_gdtype_from_datatype(p_datatype.get_container_element_type()));
|
||||
}
|
||||
|
||||
// Only hold strong reference to the script if it's not the owner of the
|
||||
// element qualified with this type, to avoid cyclic references (leaks).
|
||||
if (result.script_type && result.script_type == p_owner) {
|
||||
|
@ -376,10 +380,7 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &code
|
|||
Vector<GDScriptCodeGenerator::Address> values;
|
||||
|
||||
// Create the result temporary first since it's the last to be killed.
|
||||
GDScriptDataType array_type;
|
||||
array_type.has_type = true;
|
||||
array_type.kind = GDScriptDataType::BUILTIN;
|
||||
array_type.builtin_type = Variant::ARRAY;
|
||||
GDScriptDataType array_type = _gdtype_from_datatype(an->get_datatype());
|
||||
GDScriptCodeGenerator::Address result = codegen.add_temporary(array_type);
|
||||
|
||||
for (int i = 0; i < an->elements.size(); i++) {
|
||||
|
@ -390,7 +391,11 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &code
|
|||
values.push_back(val);
|
||||
}
|
||||
|
||||
gen->write_construct_array(result, values);
|
||||
if (array_type.has_container_element_type()) {
|
||||
gen->write_construct_typed_array(result, array_type.get_container_element_type(), values);
|
||||
} else {
|
||||
gen->write_construct_array(result, values);
|
||||
}
|
||||
|
||||
for (int i = 0; i < values.size(); i++) {
|
||||
if (values[i].mode == GDScriptCodeGenerator::Address::TEMPORARY) {
|
||||
|
@ -1733,8 +1738,17 @@ Error GDScriptCompiler::_parse_block(CodeGen &codegen, const GDScriptParser::Sui
|
|||
const GDScriptParser::VariableNode *lv = static_cast<const GDScriptParser::VariableNode *>(s);
|
||||
// Should be already in stack when the block began.
|
||||
GDScriptCodeGenerator::Address local = codegen.locals[lv->identifier->name];
|
||||
GDScriptParser::DataType local_type = lv->get_datatype();
|
||||
|
||||
if (lv->initializer != nullptr) {
|
||||
// For typed arrays we need to make sure this is already initialized correctly so typed assignment work.
|
||||
if (local_type.is_hard_type() && local_type.builtin_type == Variant::ARRAY) {
|
||||
if (local_type.has_container_element_type()) {
|
||||
codegen.generator->write_construct_typed_array(local, _gdtype_from_datatype(local_type.get_container_element_type(), codegen.script), Vector<GDScriptCodeGenerator::Address>());
|
||||
} else {
|
||||
codegen.generator->write_construct_array(local, Vector<GDScriptCodeGenerator::Address>());
|
||||
}
|
||||
}
|
||||
GDScriptCodeGenerator::Address src_address = _parse_expression(codegen, error, lv->initializer);
|
||||
if (error) {
|
||||
return error;
|
||||
|
@ -1743,6 +1757,14 @@ Error GDScriptCompiler::_parse_block(CodeGen &codegen, const GDScriptParser::Sui
|
|||
if (src_address.mode == GDScriptCodeGenerator::Address::TEMPORARY) {
|
||||
codegen.generator->pop_temporary();
|
||||
}
|
||||
} else if (lv->get_datatype().is_hard_type()) {
|
||||
// Initialize with default for type.
|
||||
if (local_type.has_container_element_type()) {
|
||||
codegen.generator->write_construct_typed_array(local, _gdtype_from_datatype(local_type.get_container_element_type(), codegen.script), Vector<GDScriptCodeGenerator::Address>());
|
||||
} else if (local_type.kind == GDScriptParser::DataType::BUILTIN) {
|
||||
codegen.generator->write_construct(local, local_type.builtin_type, Vector<GDScriptCodeGenerator::Address>());
|
||||
}
|
||||
// The `else` branch is for objects, in such case we leave it as `null`.
|
||||
}
|
||||
} break;
|
||||
case GDScriptParser::Node::CONSTANT: {
|
||||
|
@ -1844,21 +1866,41 @@ Error GDScriptCompiler::_parse_function(GDScript *p_script, const GDScriptParser
|
|||
continue;
|
||||
}
|
||||
|
||||
GDScriptParser::DataType field_type = field->get_datatype();
|
||||
|
||||
GDScriptCodeGenerator::Address dst_address(GDScriptCodeGenerator::Address::MEMBER, codegen.script->member_indices[field->identifier->name].index, _gdtype_from_datatype(field->get_datatype()));
|
||||
if (field->initializer) {
|
||||
// Emit proper line change.
|
||||
codegen.generator->write_newline(field->initializer->start_line);
|
||||
|
||||
// For typed arrays we need to make sure this is already initialized correctly so typed assignment work.
|
||||
if (field_type.is_hard_type() && field_type.builtin_type == Variant::ARRAY && field_type.has_container_element_type()) {
|
||||
if (field_type.has_container_element_type()) {
|
||||
codegen.generator->write_construct_typed_array(dst_address, _gdtype_from_datatype(field_type.get_container_element_type(), codegen.script), Vector<GDScriptCodeGenerator::Address>());
|
||||
} else {
|
||||
codegen.generator->write_construct_array(dst_address, Vector<GDScriptCodeGenerator::Address>());
|
||||
}
|
||||
}
|
||||
GDScriptCodeGenerator::Address src_address = _parse_expression(codegen, error, field->initializer, false, true);
|
||||
if (error) {
|
||||
memdelete(codegen.generator);
|
||||
return error;
|
||||
}
|
||||
GDScriptCodeGenerator::Address dst_address(GDScriptCodeGenerator::Address::MEMBER, codegen.script->member_indices[field->identifier->name].index, _gdtype_from_datatype(field->get_datatype()));
|
||||
|
||||
codegen.generator->write_assign(dst_address, src_address);
|
||||
if (src_address.mode == GDScriptCodeGenerator::Address::TEMPORARY) {
|
||||
codegen.generator->pop_temporary();
|
||||
}
|
||||
} else if (field->get_datatype().is_hard_type()) {
|
||||
codegen.generator->write_newline(field->start_line);
|
||||
|
||||
// Initialize with default for type.
|
||||
if (field_type.has_container_element_type()) {
|
||||
codegen.generator->write_construct_typed_array(dst_address, _gdtype_from_datatype(field_type.get_container_element_type(), codegen.script), Vector<GDScriptCodeGenerator::Address>());
|
||||
} else if (field_type.kind == GDScriptParser::DataType::BUILTIN) {
|
||||
codegen.generator->write_construct(dst_address, field_type.builtin_type, Vector<GDScriptCodeGenerator::Address>());
|
||||
}
|
||||
// The `else` branch is for objects, in such case we leave it as `null`.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -322,6 +322,14 @@ void GDScriptFunction::disassemble(const Vector<String> &p_code_lines) const {
|
|||
|
||||
incr += 4;
|
||||
} break;
|
||||
case OPCODE_ASSIGN_TYPED_ARRAY: {
|
||||
text += "assign typed array ";
|
||||
text += DADDR(1);
|
||||
text += " = ";
|
||||
text += DADDR(2);
|
||||
|
||||
incr += 3;
|
||||
} break;
|
||||
case OPCODE_ASSIGN_TYPED_NATIVE: {
|
||||
text += "assign typed native (";
|
||||
text += DADDR(3);
|
||||
|
@ -426,6 +434,39 @@ void GDScriptFunction::disassemble(const Vector<String> &p_code_lines) const {
|
|||
|
||||
incr += 3 + argc;
|
||||
} break;
|
||||
case OPCODE_CONSTRUCT_TYPED_ARRAY: {
|
||||
int argc = _code_ptr[ip + 1 + instr_var_args];
|
||||
|
||||
Ref<Script> script_type = get_constant(_code_ptr[ip + argc + 2]);
|
||||
Variant::Type builtin_type = (Variant::Type)_code_ptr[ip + argc + 4];
|
||||
StringName native_type = get_global_name(_code_ptr[ip + argc + 5]);
|
||||
|
||||
String type_name;
|
||||
if (script_type.is_valid() && script_type->is_valid()) {
|
||||
type_name = script_type->get_path();
|
||||
} else if (native_type != StringName()) {
|
||||
type_name = native_type;
|
||||
} else {
|
||||
type_name = Variant::get_type_name(builtin_type);
|
||||
}
|
||||
|
||||
text += " make_typed_array (";
|
||||
text += type_name;
|
||||
text += ") ";
|
||||
|
||||
text += DADDR(1 + argc);
|
||||
text += " = [";
|
||||
|
||||
for (int i = 0; i < argc; i++) {
|
||||
if (i > 0)
|
||||
text += ", ";
|
||||
text += DADDR(1 + i);
|
||||
}
|
||||
|
||||
text += "]";
|
||||
|
||||
incr += 3 + argc;
|
||||
} break;
|
||||
case OPCODE_CONSTRUCT_DICTIONARY: {
|
||||
int argc = _code_ptr[ip + 1 + instr_var_args];
|
||||
text += "make_dict ";
|
||||
|
|
|
@ -43,7 +43,11 @@
|
|||
class GDScriptInstance;
|
||||
class GDScript;
|
||||
|
||||
struct GDScriptDataType {
|
||||
class GDScriptDataType {
|
||||
private:
|
||||
GDScriptDataType *container_element_type = nullptr;
|
||||
|
||||
public:
|
||||
enum Kind {
|
||||
UNINITIALIZED,
|
||||
BUILTIN,
|
||||
|
@ -71,7 +75,24 @@ struct GDScriptDataType {
|
|||
case BUILTIN: {
|
||||
Variant::Type var_type = p_variant.get_type();
|
||||
bool valid = builtin_type == var_type;
|
||||
if (!valid && p_allow_implicit_conversion) {
|
||||
if (valid && builtin_type == Variant::ARRAY && has_container_element_type()) {
|
||||
Array array = p_variant;
|
||||
if (array.is_typed()) {
|
||||
Variant::Type array_builtin_type = (Variant::Type)array.get_typed_builtin();
|
||||
StringName array_native_type = array.get_typed_class_name();
|
||||
Ref<Script> array_script_type_ref = array.get_typed_script();
|
||||
|
||||
if (array_script_type_ref.is_valid()) {
|
||||
valid = (container_element_type->kind == SCRIPT || container_element_type->kind == GDSCRIPT) && container_element_type->script_type == array_script_type_ref.ptr();
|
||||
} else if (array_native_type != StringName()) {
|
||||
valid = container_element_type->kind == NATIVE && container_element_type->native_type == array_native_type;
|
||||
} else {
|
||||
valid = container_element_type->kind == BUILTIN && container_element_type->builtin_type == array_builtin_type;
|
||||
}
|
||||
} else {
|
||||
valid = false;
|
||||
}
|
||||
} else if (!valid && p_allow_implicit_conversion) {
|
||||
valid = Variant::can_convert_strict(var_type, builtin_type);
|
||||
}
|
||||
return valid;
|
||||
|
@ -153,7 +174,49 @@ struct GDScriptDataType {
|
|||
return info;
|
||||
}
|
||||
|
||||
GDScriptDataType() {}
|
||||
void set_container_element_type(const GDScriptDataType &p_element_type) {
|
||||
container_element_type = memnew(GDScriptDataType(p_element_type));
|
||||
}
|
||||
|
||||
GDScriptDataType get_container_element_type() const {
|
||||
ERR_FAIL_COND_V(container_element_type == nullptr, GDScriptDataType());
|
||||
return *container_element_type;
|
||||
}
|
||||
|
||||
bool has_container_element_type() const {
|
||||
return container_element_type != nullptr;
|
||||
}
|
||||
|
||||
void unset_container_element_type() {
|
||||
if (container_element_type) {
|
||||
memdelete(container_element_type);
|
||||
}
|
||||
container_element_type = nullptr;
|
||||
}
|
||||
|
||||
GDScriptDataType() = default;
|
||||
|
||||
GDScriptDataType &operator=(const GDScriptDataType &p_other) {
|
||||
kind = p_other.kind;
|
||||
has_type = p_other.has_type;
|
||||
builtin_type = p_other.builtin_type;
|
||||
native_type = p_other.native_type;
|
||||
script_type = p_other.script_type;
|
||||
script_type_ref = p_other.script_type_ref;
|
||||
unset_container_element_type();
|
||||
if (p_other.has_container_element_type()) {
|
||||
set_container_element_type(p_other.get_container_element_type());
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
GDScriptDataType(const GDScriptDataType &p_other) {
|
||||
*this = p_other;
|
||||
}
|
||||
|
||||
~GDScriptDataType() {
|
||||
unset_container_element_type();
|
||||
}
|
||||
};
|
||||
|
||||
class GDScriptFunction {
|
||||
|
@ -179,6 +242,7 @@ public:
|
|||
OPCODE_ASSIGN_TRUE,
|
||||
OPCODE_ASSIGN_FALSE,
|
||||
OPCODE_ASSIGN_TYPED_BUILTIN,
|
||||
OPCODE_ASSIGN_TYPED_ARRAY,
|
||||
OPCODE_ASSIGN_TYPED_NATIVE,
|
||||
OPCODE_ASSIGN_TYPED_SCRIPT,
|
||||
OPCODE_CAST_TO_BUILTIN,
|
||||
|
@ -187,6 +251,7 @@ public:
|
|||
OPCODE_CONSTRUCT, // Only for basic types!
|
||||
OPCODE_CONSTRUCT_VALIDATED, // Only for basic types!
|
||||
OPCODE_CONSTRUCT_ARRAY,
|
||||
OPCODE_CONSTRUCT_TYPED_ARRAY,
|
||||
OPCODE_CONSTRUCT_DICTIONARY,
|
||||
OPCODE_CALL,
|
||||
OPCODE_CALL_RETURN,
|
||||
|
|
|
@ -2674,6 +2674,19 @@ GDScriptParser::TypeNode *GDScriptParser::parse_type(bool p_allow_void) {
|
|||
|
||||
type->type_chain.push_back(type_element);
|
||||
|
||||
if (match(GDScriptTokenizer::Token::BRACKET_OPEN)) {
|
||||
// Typed collection (like Array[int]).
|
||||
type->container_type = parse_type(false); // Don't allow void for array element type.
|
||||
if (type->container_type == nullptr) {
|
||||
push_error(R"(Expected type for collection after "[".)");
|
||||
type = nullptr;
|
||||
} else if (type->container_type->container_type != nullptr) {
|
||||
push_error("Nested typed collections are not supported.");
|
||||
}
|
||||
consume(GDScriptTokenizer::Token::BRACKET_CLOSE, R"(Expected closing "]" after collection type.)");
|
||||
return type;
|
||||
}
|
||||
|
||||
int chain_index = 1;
|
||||
while (match(GDScriptTokenizer::Token::PERIOD)) {
|
||||
make_completion_context(COMPLETION_TYPE_ATTRIBUTE, type, chain_index++);
|
||||
|
@ -3278,6 +3291,9 @@ String GDScriptParser::DataType::to_string() const {
|
|||
if (builtin_type == Variant::NIL) {
|
||||
return "null";
|
||||
}
|
||||
if (builtin_type == Variant::ARRAY && has_container_element_type()) {
|
||||
return vformat("Array[%s]", container_element_type->to_string());
|
||||
}
|
||||
return Variant::get_type_name(builtin_type);
|
||||
case NATIVE:
|
||||
if (is_meta_type) {
|
||||
|
|
|
@ -94,7 +94,12 @@ public:
|
|||
struct VariableNode;
|
||||
struct WhileNode;
|
||||
|
||||
struct DataType {
|
||||
class DataType {
|
||||
private:
|
||||
// Private access so we can control memory management.
|
||||
DataType *container_element_type = nullptr;
|
||||
|
||||
public:
|
||||
enum Kind {
|
||||
BUILTIN,
|
||||
NATIVE,
|
||||
|
@ -104,7 +109,6 @@ public:
|
|||
ENUM_VALUE, // Value from enumeration.
|
||||
VARIANT, // Can be any type.
|
||||
UNRESOLVED,
|
||||
// TODO: Enum
|
||||
};
|
||||
Kind kind = UNRESOLVED;
|
||||
|
||||
|
@ -136,6 +140,26 @@ public:
|
|||
_FORCE_INLINE_ bool is_hard_type() const { return type_source > INFERRED; }
|
||||
String to_string() const;
|
||||
|
||||
_FORCE_INLINE_ void set_container_element_type(const DataType &p_type) {
|
||||
container_element_type = memnew(DataType(p_type));
|
||||
}
|
||||
|
||||
_FORCE_INLINE_ DataType get_container_element_type() const {
|
||||
ERR_FAIL_COND_V(container_element_type == nullptr, DataType());
|
||||
return *container_element_type;
|
||||
}
|
||||
|
||||
_FORCE_INLINE_ bool has_container_element_type() const {
|
||||
return container_element_type != nullptr;
|
||||
}
|
||||
|
||||
_FORCE_INLINE_ void unset_container_element_type() {
|
||||
if (container_element_type) {
|
||||
memdelete(container_element_type);
|
||||
};
|
||||
container_element_type = nullptr;
|
||||
}
|
||||
|
||||
bool operator==(const DataType &p_other) const {
|
||||
if (type_source == UNDETECTED || p_other.type_source == UNDETECTED) {
|
||||
return true; // Can be consireded equal for parsing purposes.
|
||||
|
@ -173,6 +197,37 @@ public:
|
|||
bool operator!=(const DataType &p_other) const {
|
||||
return !(this->operator==(p_other));
|
||||
}
|
||||
|
||||
DataType &operator=(const DataType &p_other) {
|
||||
kind = p_other.kind;
|
||||
type_source = p_other.type_source;
|
||||
is_constant = p_other.is_constant;
|
||||
is_meta_type = p_other.is_meta_type;
|
||||
is_coroutine = p_other.is_coroutine;
|
||||
builtin_type = p_other.builtin_type;
|
||||
native_type = p_other.native_type;
|
||||
enum_type = p_other.enum_type;
|
||||
script_type = p_other.script_type;
|
||||
script_path = p_other.script_path;
|
||||
class_type = p_other.class_type;
|
||||
method_info = p_other.method_info;
|
||||
enum_values = p_other.enum_values;
|
||||
unset_container_element_type();
|
||||
if (p_other.has_container_element_type()) {
|
||||
set_container_element_type(p_other.get_container_element_type());
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
DataType() = default;
|
||||
|
||||
DataType(const DataType &p_other) {
|
||||
*this = p_other;
|
||||
}
|
||||
|
||||
~DataType() {
|
||||
unset_container_element_type();
|
||||
}
|
||||
};
|
||||
|
||||
struct ParserError {
|
||||
|
@ -987,6 +1042,7 @@ public:
|
|||
|
||||
struct TypeNode : public Node {
|
||||
Vector<IdentifierNode *> type_chain;
|
||||
TypeNode *container_type = nullptr;
|
||||
|
||||
TypeNode() {
|
||||
type = TYPE;
|
||||
|
|
|
@ -127,6 +127,14 @@ Variant *GDScriptFunction::_get_variant(int p_address, GDScriptInstance *p_insta
|
|||
}
|
||||
|
||||
#ifdef DEBUG_ENABLED
|
||||
static String _get_script_name(const Ref<Script> p_script) {
|
||||
if (p_script->get_name().is_empty()) {
|
||||
return p_script->get_path().get_file();
|
||||
} else {
|
||||
return p_script->get_name();
|
||||
}
|
||||
}
|
||||
|
||||
static String _get_var_type(const Variant *p_var) {
|
||||
String basestr;
|
||||
|
||||
|
@ -140,15 +148,30 @@ static String _get_var_type(const Variant *p_var) {
|
|||
basestr = "previously freed";
|
||||
}
|
||||
} else {
|
||||
basestr = bobj->get_class();
|
||||
if (bobj->get_script_instance()) {
|
||||
basestr = bobj->get_class() + " (" + bobj->get_script_instance()->get_script()->get_path().get_file() + ")";
|
||||
} else {
|
||||
basestr = bobj->get_class();
|
||||
basestr += " (" + _get_script_name(bobj->get_script_instance()->get_script()) + ")";
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
basestr = Variant::get_type_name(p_var->get_type());
|
||||
if (p_var->get_type() == Variant::ARRAY) {
|
||||
basestr = "Array";
|
||||
const Array *p_array = VariantInternal::get_array(p_var);
|
||||
Variant::Type builtin_type = (Variant::Type)p_array->get_typed_builtin();
|
||||
StringName native_type = p_array->get_typed_class_name();
|
||||
Ref<Script> script_type = p_array->get_typed_script();
|
||||
|
||||
if (script_type.is_valid() && script_type->is_valid()) {
|
||||
basestr += "[" + _get_script_name(script_type) + "]";
|
||||
} else if (native_type != StringName()) {
|
||||
basestr += "[" + native_type.operator String() + "]";
|
||||
} else if (builtin_type != Variant::NIL) {
|
||||
basestr += "[" + Variant::get_type_name(builtin_type) + "]";
|
||||
}
|
||||
} else {
|
||||
basestr = Variant::get_type_name(p_var->get_type());
|
||||
}
|
||||
}
|
||||
|
||||
return basestr;
|
||||
|
@ -207,6 +230,7 @@ String GDScriptFunction::_get_call_error(const Callable::CallError &p_err, const
|
|||
&&OPCODE_ASSIGN_TRUE, \
|
||||
&&OPCODE_ASSIGN_FALSE, \
|
||||
&&OPCODE_ASSIGN_TYPED_BUILTIN, \
|
||||
&&OPCODE_ASSIGN_TYPED_ARRAY, \
|
||||
&&OPCODE_ASSIGN_TYPED_NATIVE, \
|
||||
&&OPCODE_ASSIGN_TYPED_SCRIPT, \
|
||||
&&OPCODE_CAST_TO_BUILTIN, \
|
||||
|
@ -215,6 +239,7 @@ String GDScriptFunction::_get_call_error(const Callable::CallError &p_err, const
|
|||
&&OPCODE_CONSTRUCT, \
|
||||
&&OPCODE_CONSTRUCT_VALIDATED, \
|
||||
&&OPCODE_CONSTRUCT_ARRAY, \
|
||||
&&OPCODE_CONSTRUCT_TYPED_ARRAY, \
|
||||
&&OPCODE_CONSTRUCT_DICTIONARY, \
|
||||
&&OPCODE_CALL, \
|
||||
&&OPCODE_CALL_RETURN, \
|
||||
|
@ -1077,6 +1102,31 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a
|
|||
}
|
||||
DISPATCH_OPCODE;
|
||||
|
||||
OPCODE(OPCODE_ASSIGN_TYPED_ARRAY) {
|
||||
CHECK_SPACE(3);
|
||||
GET_INSTRUCTION_ARG(dst, 0);
|
||||
GET_INSTRUCTION_ARG(src, 1);
|
||||
|
||||
Array *dst_arr = VariantInternal::get_array(dst);
|
||||
|
||||
if (src->get_type() != Variant::ARRAY) {
|
||||
#ifdef DEBUG_ENABLED
|
||||
err_text = "Trying to assign value of type '" + Variant::get_type_name(src->get_type()) +
|
||||
"' to a variable of type '" + +"'.";
|
||||
OPCODE_BREAK;
|
||||
#endif
|
||||
}
|
||||
if (!dst_arr->typed_assign(*src)) {
|
||||
#ifdef DEBUG_ENABLED
|
||||
err_text = "Trying to assign a typed array with an array of different type.'";
|
||||
OPCODE_BREAK;
|
||||
#endif
|
||||
}
|
||||
|
||||
ip += 3;
|
||||
}
|
||||
DISPATCH_OPCODE;
|
||||
|
||||
OPCODE(OPCODE_ASSIGN_TYPED_NATIVE) {
|
||||
CHECK_SPACE(4);
|
||||
GET_INSTRUCTION_ARG(dst, 0);
|
||||
|
@ -1308,6 +1358,7 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a
|
|||
}
|
||||
|
||||
GET_INSTRUCTION_ARG(dst, argc);
|
||||
*dst = Variant(); // Clear potential previous typed array.
|
||||
|
||||
*dst = array;
|
||||
|
||||
|
@ -1315,6 +1366,35 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a
|
|||
}
|
||||
DISPATCH_OPCODE;
|
||||
|
||||
OPCODE(OPCODE_CONSTRUCT_TYPED_ARRAY) {
|
||||
CHECK_SPACE(3 + instr_arg_count);
|
||||
ip += instr_arg_count;
|
||||
|
||||
int argc = _code_ptr[ip + 1];
|
||||
|
||||
GET_INSTRUCTION_ARG(script_type, argc + 1);
|
||||
Variant::Type builtin_type = (Variant::Type)_code_ptr[ip + 2];
|
||||
int native_type_idx = _code_ptr[ip + 3];
|
||||
GD_ERR_BREAK(native_type_idx < 0 || native_type_idx >= _global_names_count);
|
||||
const StringName native_type = _global_names_ptr[native_type_idx];
|
||||
|
||||
Array array;
|
||||
array.set_typed(builtin_type, native_type, script_type);
|
||||
array.resize(argc);
|
||||
|
||||
for (int i = 0; i < argc; i++) {
|
||||
array[i] = *(instruction_args[i]);
|
||||
}
|
||||
|
||||
GET_INSTRUCTION_ARG(dst, argc);
|
||||
*dst = Variant(); // Clear potential previous typed array.
|
||||
|
||||
*dst = array;
|
||||
|
||||
ip += 4;
|
||||
}
|
||||
DISPATCH_OPCODE;
|
||||
|
||||
OPCODE(OPCODE_CONSTRUCT_DICTIONARY) {
|
||||
CHECK_SPACE(2 + instr_arg_count);
|
||||
|
||||
|
|
Loading…
Reference in New Issue