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:
George Marques 2021-03-09 12:32:35 -03:00
parent 997a8ae9e8
commit 85e316a5d5
No known key found for this signature in database
GPG key ID: 046BD46A3201E43D
12 changed files with 569 additions and 58 deletions

View file

@ -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;
}

View file

@ -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);
}

View file

@ -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);

View file

@ -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++) {

View file

@ -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;

View file

@ -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;

View file

@ -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`.
}
}
}

View file

@ -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 ";

View file

@ -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,

View file

@ -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) {

View file

@ -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;

View file

@ -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);