GDScript: Rework type check
This commit is contained in:
parent
28db611f0f
commit
8fe023ad93
|
@ -121,6 +121,13 @@
|
||||||
[/codeblock]
|
[/codeblock]
|
||||||
</description>
|
</description>
|
||||||
</method>
|
</method>
|
||||||
|
<method name="is_instance_of">
|
||||||
|
<return type="bool" />
|
||||||
|
<param index="0" name="value" type="Variant" />
|
||||||
|
<param index="1" name="type" type="Variant" />
|
||||||
|
<description>
|
||||||
|
</description>
|
||||||
|
</method>
|
||||||
<method name="len">
|
<method name="len">
|
||||||
<return type="int" />
|
<return type="int" />
|
||||||
<param index="0" name="var" type="Variant" />
|
<param index="0" name="var" type="Variant" />
|
||||||
|
|
|
@ -562,7 +562,6 @@ GDScriptParser::DataType GDScriptAnalyzer::resolve_datatype(GDScriptParser::Type
|
||||||
|
|
||||||
GDScriptParser::DataType result;
|
GDScriptParser::DataType result;
|
||||||
result.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT;
|
result.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT;
|
||||||
result.builtin_type = Variant::OBJECT;
|
|
||||||
|
|
||||||
if (p_type->type_chain.is_empty()) {
|
if (p_type->type_chain.is_empty()) {
|
||||||
// void.
|
// void.
|
||||||
|
@ -584,6 +583,7 @@ GDScriptParser::DataType GDScriptAnalyzer::resolve_datatype(GDScriptParser::Type
|
||||||
} else if (first == SNAME("Object")) {
|
} else if (first == SNAME("Object")) {
|
||||||
// Object is treated like a native type, not a built-in.
|
// Object is treated like a native type, not a built-in.
|
||||||
result.kind = GDScriptParser::DataType::NATIVE;
|
result.kind = GDScriptParser::DataType::NATIVE;
|
||||||
|
result.builtin_type = Variant::OBJECT;
|
||||||
result.native_type = SNAME("Object");
|
result.native_type = SNAME("Object");
|
||||||
} else if (GDScriptParser::get_builtin_type(first) < Variant::VARIANT_MAX) {
|
} else if (GDScriptParser::get_builtin_type(first) < Variant::VARIANT_MAX) {
|
||||||
// Built-in types.
|
// Built-in types.
|
||||||
|
@ -604,6 +604,7 @@ GDScriptParser::DataType GDScriptAnalyzer::resolve_datatype(GDScriptParser::Type
|
||||||
} else if (class_exists(first)) {
|
} else if (class_exists(first)) {
|
||||||
// Native engine classes.
|
// Native engine classes.
|
||||||
result.kind = GDScriptParser::DataType::NATIVE;
|
result.kind = GDScriptParser::DataType::NATIVE;
|
||||||
|
result.builtin_type = Variant::OBJECT;
|
||||||
result.native_type = first;
|
result.native_type = first;
|
||||||
} else if (ScriptServer::is_global_class(first)) {
|
} else if (ScriptServer::is_global_class(first)) {
|
||||||
if (parser->script_path == ScriptServer::get_global_class_path(first)) {
|
if (parser->script_path == ScriptServer::get_global_class_path(first)) {
|
||||||
|
@ -1338,6 +1339,7 @@ void GDScriptAnalyzer::resolve_node(GDScriptParser::Node *p_node, bool p_is_root
|
||||||
case GDScriptParser::Node::SELF:
|
case GDScriptParser::Node::SELF:
|
||||||
case GDScriptParser::Node::SUBSCRIPT:
|
case GDScriptParser::Node::SUBSCRIPT:
|
||||||
case GDScriptParser::Node::TERNARY_OPERATOR:
|
case GDScriptParser::Node::TERNARY_OPERATOR:
|
||||||
|
case GDScriptParser::Node::TYPE_TEST:
|
||||||
case GDScriptParser::Node::UNARY_OPERATOR:
|
case GDScriptParser::Node::UNARY_OPERATOR:
|
||||||
reduce_expression(static_cast<GDScriptParser::ExpressionNode *>(p_node), p_is_root);
|
reduce_expression(static_cast<GDScriptParser::ExpressionNode *>(p_node), p_is_root);
|
||||||
break;
|
break;
|
||||||
|
@ -2196,6 +2198,9 @@ void GDScriptAnalyzer::reduce_expression(GDScriptParser::ExpressionNode *p_expre
|
||||||
case GDScriptParser::Node::TERNARY_OPERATOR:
|
case GDScriptParser::Node::TERNARY_OPERATOR:
|
||||||
reduce_ternary_op(static_cast<GDScriptParser::TernaryOpNode *>(p_expression), p_is_root);
|
reduce_ternary_op(static_cast<GDScriptParser::TernaryOpNode *>(p_expression), p_is_root);
|
||||||
break;
|
break;
|
||||||
|
case GDScriptParser::Node::TYPE_TEST:
|
||||||
|
reduce_type_test(static_cast<GDScriptParser::TypeTestNode *>(p_expression));
|
||||||
|
break;
|
||||||
case GDScriptParser::Node::UNARY_OPERATOR:
|
case GDScriptParser::Node::UNARY_OPERATOR:
|
||||||
reduce_unary_op(static_cast<GDScriptParser::UnaryOpNode *>(p_expression));
|
reduce_unary_op(static_cast<GDScriptParser::UnaryOpNode *>(p_expression));
|
||||||
break;
|
break;
|
||||||
|
@ -2502,13 +2507,7 @@ void GDScriptAnalyzer::reduce_await(GDScriptParser::AwaitNode *p_await) {
|
||||||
|
|
||||||
void GDScriptAnalyzer::reduce_binary_op(GDScriptParser::BinaryOpNode *p_binary_op) {
|
void GDScriptAnalyzer::reduce_binary_op(GDScriptParser::BinaryOpNode *p_binary_op) {
|
||||||
reduce_expression(p_binary_op->left_operand);
|
reduce_expression(p_binary_op->left_operand);
|
||||||
|
|
||||||
if (p_binary_op->operation == GDScriptParser::BinaryOpNode::OP_TYPE_TEST && p_binary_op->right_operand && p_binary_op->right_operand->type == GDScriptParser::Node::IDENTIFIER) {
|
|
||||||
reduce_identifier(static_cast<GDScriptParser::IdentifierNode *>(p_binary_op->right_operand), true);
|
|
||||||
} else {
|
|
||||||
reduce_expression(p_binary_op->right_operand);
|
reduce_expression(p_binary_op->right_operand);
|
||||||
}
|
|
||||||
// TODO: Right operand must be a valid type with the `is` operator. Need to check here.
|
|
||||||
|
|
||||||
GDScriptParser::DataType left_type;
|
GDScriptParser::DataType left_type;
|
||||||
if (p_binary_op->left_operand) {
|
if (p_binary_op->left_operand) {
|
||||||
|
@ -2545,21 +2544,9 @@ void GDScriptAnalyzer::reduce_binary_op(GDScriptParser::BinaryOpNode *p_binary_o
|
||||||
p_binary_op);
|
p_binary_op);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
if (p_binary_op->operation == GDScriptParser::BinaryOpNode::OP_TYPE_TEST) {
|
|
||||||
GDScriptParser::DataType test_type = right_type;
|
|
||||||
test_type.is_meta_type = false;
|
|
||||||
|
|
||||||
if (!is_type_compatible(test_type, left_type)) {
|
|
||||||
push_error(vformat(R"(Expression is of type "%s" so it can't be of type "%s".)"), p_binary_op->left_operand);
|
|
||||||
p_binary_op->reduced_value = false;
|
|
||||||
} else {
|
|
||||||
p_binary_op->reduced_value = true;
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
ERR_PRINT("Parser bug: unknown binary operation.");
|
ERR_PRINT("Parser bug: unknown binary operation.");
|
||||||
}
|
}
|
||||||
}
|
|
||||||
p_binary_op->set_datatype(type_from_variant(p_binary_op->reduced_value, p_binary_op));
|
p_binary_op->set_datatype(type_from_variant(p_binary_op->reduced_value, p_binary_op));
|
||||||
|
|
||||||
return;
|
return;
|
||||||
|
@ -2567,24 +2554,7 @@ void GDScriptAnalyzer::reduce_binary_op(GDScriptParser::BinaryOpNode *p_binary_o
|
||||||
|
|
||||||
GDScriptParser::DataType result;
|
GDScriptParser::DataType result;
|
||||||
|
|
||||||
if (p_binary_op->operation == GDScriptParser::BinaryOpNode::OP_TYPE_TEST) {
|
if ((p_binary_op->variant_op == Variant::OP_EQUAL || p_binary_op->variant_op == Variant::OP_NOT_EQUAL) &&
|
||||||
GDScriptParser::DataType test_type = right_type;
|
|
||||||
test_type.is_meta_type = false;
|
|
||||||
|
|
||||||
if (!is_type_compatible(test_type, left_type) && !is_type_compatible(left_type, test_type)) {
|
|
||||||
if (left_type.is_hard_type()) {
|
|
||||||
push_error(vformat(R"(Expression is of type "%s" so it can't be of type "%s".)", left_type.to_string(), test_type.to_string()), p_binary_op->left_operand);
|
|
||||||
} else {
|
|
||||||
// TODO: Warning.
|
|
||||||
mark_node_unsafe(p_binary_op);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// "is" operator is always a boolean anyway.
|
|
||||||
result.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT;
|
|
||||||
result.kind = GDScriptParser::DataType::BUILTIN;
|
|
||||||
result.builtin_type = Variant::BOOL;
|
|
||||||
} else if ((p_binary_op->variant_op == Variant::OP_EQUAL || p_binary_op->variant_op == Variant::OP_NOT_EQUAL) &&
|
|
||||||
((left_type.kind == GDScriptParser::DataType::BUILTIN && left_type.builtin_type == Variant::NIL) || (right_type.kind == GDScriptParser::DataType::BUILTIN && right_type.builtin_type == Variant::NIL))) {
|
((left_type.kind == GDScriptParser::DataType::BUILTIN && left_type.builtin_type == Variant::NIL) || (right_type.kind == GDScriptParser::DataType::BUILTIN && right_type.builtin_type == Variant::NIL))) {
|
||||||
// "==" and "!=" operators always return a boolean when comparing to null.
|
// "==" and "!=" operators always return a boolean when comparing to null.
|
||||||
result.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT;
|
result.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT;
|
||||||
|
@ -4107,6 +4077,48 @@ void GDScriptAnalyzer::reduce_ternary_op(GDScriptParser::TernaryOpNode *p_ternar
|
||||||
p_ternary_op->set_datatype(result);
|
p_ternary_op->set_datatype(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void GDScriptAnalyzer::reduce_type_test(GDScriptParser::TypeTestNode *p_type_test) {
|
||||||
|
GDScriptParser::DataType result;
|
||||||
|
result.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT;
|
||||||
|
result.kind = GDScriptParser::DataType::BUILTIN;
|
||||||
|
result.builtin_type = Variant::BOOL;
|
||||||
|
p_type_test->set_datatype(result);
|
||||||
|
|
||||||
|
if (!p_type_test->operand || !p_type_test->test_type) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
reduce_expression(p_type_test->operand);
|
||||||
|
GDScriptParser::DataType operand_type = p_type_test->operand->get_datatype();
|
||||||
|
GDScriptParser::DataType test_type = type_from_metatype(resolve_datatype(p_type_test->test_type));
|
||||||
|
p_type_test->test_datatype = test_type;
|
||||||
|
|
||||||
|
if (!operand_type.is_set() || !test_type.is_set()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (p_type_test->operand->is_constant) {
|
||||||
|
p_type_test->is_constant = true;
|
||||||
|
p_type_test->reduced_value = false;
|
||||||
|
|
||||||
|
if (!is_type_compatible(test_type, operand_type)) {
|
||||||
|
push_error(vformat(R"(Expression is of type "%s" so it can't be of type "%s".)", operand_type.to_string(), test_type.to_string()), p_type_test->operand);
|
||||||
|
} else if (is_type_compatible(test_type, type_from_variant(p_type_test->operand->reduced_value, p_type_test->operand))) {
|
||||||
|
p_type_test->reduced_value = test_type.builtin_type != Variant::OBJECT || !p_type_test->operand->reduced_value.is_null();
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!is_type_compatible(test_type, operand_type) && !is_type_compatible(operand_type, test_type)) {
|
||||||
|
if (operand_type.is_hard_type()) {
|
||||||
|
push_error(vformat(R"(Expression is of type "%s" so it can't be of type "%s".)", operand_type.to_string(), test_type.to_string()), p_type_test->operand);
|
||||||
|
} else {
|
||||||
|
downgrade_node_type_source(p_type_test->operand);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void GDScriptAnalyzer::reduce_unary_op(GDScriptParser::UnaryOpNode *p_unary_op) {
|
void GDScriptAnalyzer::reduce_unary_op(GDScriptParser::UnaryOpNode *p_unary_op) {
|
||||||
reduce_expression(p_unary_op->operand);
|
reduce_expression(p_unary_op->operand);
|
||||||
|
|
||||||
|
|
|
@ -100,6 +100,7 @@ class GDScriptAnalyzer {
|
||||||
void reduce_self(GDScriptParser::SelfNode *p_self);
|
void reduce_self(GDScriptParser::SelfNode *p_self);
|
||||||
void reduce_subscript(GDScriptParser::SubscriptNode *p_subscript);
|
void reduce_subscript(GDScriptParser::SubscriptNode *p_subscript);
|
||||||
void reduce_ternary_op(GDScriptParser::TernaryOpNode *p_ternary_op, bool p_is_root = false);
|
void reduce_ternary_op(GDScriptParser::TernaryOpNode *p_ternary_op, bool p_is_root = false);
|
||||||
|
void reduce_type_test(GDScriptParser::TypeTestNode *p_type_test);
|
||||||
void reduce_unary_op(GDScriptParser::UnaryOpNode *p_unary_op);
|
void reduce_unary_op(GDScriptParser::UnaryOpNode *p_unary_op);
|
||||||
|
|
||||||
Variant make_expression_reduced_value(GDScriptParser::ExpressionNode *p_expression, bool &is_reduced);
|
Variant make_expression_reduced_value(GDScriptParser::ExpressionNode *p_expression, bool &is_reduced);
|
||||||
|
|
|
@ -607,18 +607,44 @@ void GDScriptByteCodeGenerator::write_binary_operator(const Address &p_target, V
|
||||||
append(p_operator);
|
append(p_operator);
|
||||||
}
|
}
|
||||||
|
|
||||||
void GDScriptByteCodeGenerator::write_type_test(const Address &p_target, const Address &p_source, const Address &p_type) {
|
void GDScriptByteCodeGenerator::write_type_test(const Address &p_target, const Address &p_source, const GDScriptDataType &p_type) {
|
||||||
append_opcode(GDScriptFunction::OPCODE_EXTENDS_TEST);
|
switch (p_type.kind) {
|
||||||
|
case GDScriptDataType::BUILTIN: {
|
||||||
|
if (p_type.builtin_type == Variant::ARRAY && p_type.has_container_element_type()) {
|
||||||
|
const GDScriptDataType &element_type = p_type.get_container_element_type();
|
||||||
|
append_opcode(GDScriptFunction::OPCODE_TYPE_TEST_ARRAY);
|
||||||
|
append(p_target);
|
||||||
append(p_source);
|
append(p_source);
|
||||||
append(p_type);
|
append(get_constant_pos(element_type.script_type) | (GDScriptFunction::ADDR_TYPE_CONSTANT << GDScriptFunction::ADDR_BITS));
|
||||||
|
append(element_type.builtin_type);
|
||||||
|
append(element_type.native_type);
|
||||||
|
} else {
|
||||||
|
append_opcode(GDScriptFunction::OPCODE_TYPE_TEST_BUILTIN);
|
||||||
|
append(p_target);
|
||||||
|
append(p_source);
|
||||||
|
append(p_type.builtin_type);
|
||||||
|
}
|
||||||
|
} break;
|
||||||
|
case GDScriptDataType::NATIVE: {
|
||||||
|
append_opcode(GDScriptFunction::OPCODE_TYPE_TEST_NATIVE);
|
||||||
|
append(p_target);
|
||||||
|
append(p_source);
|
||||||
|
append(p_type.native_type);
|
||||||
|
} break;
|
||||||
|
case GDScriptDataType::SCRIPT:
|
||||||
|
case GDScriptDataType::GDSCRIPT: {
|
||||||
|
const Variant &script = p_type.script_type;
|
||||||
|
append_opcode(GDScriptFunction::OPCODE_TYPE_TEST_SCRIPT);
|
||||||
|
append(p_target);
|
||||||
|
append(p_source);
|
||||||
|
append(get_constant_pos(script) | (GDScriptFunction::ADDR_TYPE_CONSTANT << GDScriptFunction::ADDR_BITS));
|
||||||
|
} break;
|
||||||
|
default: {
|
||||||
|
ERR_PRINT("Compiler bug: unresolved type in type test.");
|
||||||
|
append_opcode(GDScriptFunction::OPCODE_ASSIGN_FALSE);
|
||||||
append(p_target);
|
append(p_target);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
void GDScriptByteCodeGenerator::write_type_test_builtin(const Address &p_target, const Address &p_source, Variant::Type p_type) {
|
|
||||||
append_opcode(GDScriptFunction::OPCODE_IS_BUILTIN);
|
|
||||||
append(p_source);
|
|
||||||
append(p_target);
|
|
||||||
append(p_type);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void GDScriptByteCodeGenerator::write_and_left_operand(const Address &p_left_operand) {
|
void GDScriptByteCodeGenerator::write_and_left_operand(const Address &p_left_operand) {
|
||||||
|
|
|
@ -454,8 +454,7 @@ public:
|
||||||
virtual void write_type_adjust(const Address &p_target, Variant::Type p_new_type) override;
|
virtual void write_type_adjust(const Address &p_target, Variant::Type p_new_type) override;
|
||||||
virtual void write_unary_operator(const Address &p_target, Variant::Operator p_operator, const Address &p_left_operand) override;
|
virtual void write_unary_operator(const Address &p_target, Variant::Operator p_operator, const Address &p_left_operand) override;
|
||||||
virtual void write_binary_operator(const Address &p_target, Variant::Operator p_operator, const Address &p_left_operand, const Address &p_right_operand) override;
|
virtual void write_binary_operator(const Address &p_target, Variant::Operator p_operator, const Address &p_left_operand, const Address &p_right_operand) override;
|
||||||
virtual void write_type_test(const Address &p_target, const Address &p_source, const Address &p_type) override;
|
virtual void write_type_test(const Address &p_target, const Address &p_source, const GDScriptDataType &p_type) override;
|
||||||
virtual void write_type_test_builtin(const Address &p_target, const Address &p_source, Variant::Type p_type) override;
|
|
||||||
virtual void write_and_left_operand(const Address &p_left_operand) override;
|
virtual void write_and_left_operand(const Address &p_left_operand) override;
|
||||||
virtual void write_and_right_operand(const Address &p_right_operand) override;
|
virtual void write_and_right_operand(const Address &p_right_operand) override;
|
||||||
virtual void write_end_and(const Address &p_target) override;
|
virtual void write_end_and(const Address &p_target) override;
|
||||||
|
|
|
@ -90,8 +90,7 @@ public:
|
||||||
virtual void write_type_adjust(const Address &p_target, Variant::Type p_new_type) = 0;
|
virtual void write_type_adjust(const Address &p_target, Variant::Type p_new_type) = 0;
|
||||||
virtual void write_unary_operator(const Address &p_target, Variant::Operator p_operator, const Address &p_left_operand) = 0;
|
virtual void write_unary_operator(const Address &p_target, Variant::Operator p_operator, const Address &p_left_operand) = 0;
|
||||||
virtual void write_binary_operator(const Address &p_target, Variant::Operator p_operator, const Address &p_left_operand, const Address &p_right_operand) = 0;
|
virtual void write_binary_operator(const Address &p_target, Variant::Operator p_operator, const Address &p_left_operand, const Address &p_right_operand) = 0;
|
||||||
virtual void write_type_test(const Address &p_target, const Address &p_source, const Address &p_type) = 0;
|
virtual void write_type_test(const Address &p_target, const Address &p_source, const GDScriptDataType &p_type) = 0;
|
||||||
virtual void write_type_test_builtin(const Address &p_target, const Address &p_source, Variant::Type p_type) = 0;
|
|
||||||
virtual void write_and_left_operand(const Address &p_left_operand) = 0;
|
virtual void write_and_left_operand(const Address &p_left_operand) = 0;
|
||||||
virtual void write_and_right_operand(const Address &p_right_operand) = 0;
|
virtual void write_and_right_operand(const Address &p_right_operand) = 0;
|
||||||
virtual void write_end_and(const Address &p_target) = 0;
|
virtual void write_end_and(const Address &p_target) = 0;
|
||||||
|
|
|
@ -148,13 +148,8 @@ GDScriptDataType GDScriptCompiler::_gdtype_from_datatype(const GDScriptParser::D
|
||||||
}
|
}
|
||||||
} break;
|
} break;
|
||||||
case GDScriptParser::DataType::ENUM:
|
case GDScriptParser::DataType::ENUM:
|
||||||
result.has_type = true;
|
|
||||||
result.kind = GDScriptDataType::BUILTIN;
|
result.kind = GDScriptDataType::BUILTIN;
|
||||||
if (p_datatype.is_meta_type) {
|
result.builtin_type = p_datatype.builtin_type;
|
||||||
result.builtin_type = Variant::DICTIONARY;
|
|
||||||
} else {
|
|
||||||
result.builtin_type = Variant::INT;
|
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
case GDScriptParser::DataType::RESOLVING:
|
case GDScriptParser::DataType::RESOLVING:
|
||||||
case GDScriptParser::DataType::UNRESOLVED: {
|
case GDScriptParser::DataType::UNRESOLVED: {
|
||||||
|
@ -494,17 +489,10 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &code
|
||||||
} break;
|
} break;
|
||||||
case GDScriptParser::Node::CAST: {
|
case GDScriptParser::Node::CAST: {
|
||||||
const GDScriptParser::CastNode *cn = static_cast<const GDScriptParser::CastNode *>(p_expression);
|
const GDScriptParser::CastNode *cn = static_cast<const GDScriptParser::CastNode *>(p_expression);
|
||||||
GDScriptParser::DataType og_cast_type = cn->get_datatype();
|
GDScriptDataType cast_type = _gdtype_from_datatype(cn->get_datatype(), codegen.script);
|
||||||
GDScriptDataType cast_type = _gdtype_from_datatype(og_cast_type, codegen.script);
|
|
||||||
|
|
||||||
GDScriptCodeGenerator::Address result;
|
GDScriptCodeGenerator::Address result;
|
||||||
if (cast_type.has_type) {
|
if (cast_type.has_type) {
|
||||||
if (og_cast_type.kind == GDScriptParser::DataType::ENUM) {
|
|
||||||
// Enum types are usually treated as dictionaries, but in this case we want to cast to an integer.
|
|
||||||
cast_type.kind = GDScriptDataType::BUILTIN;
|
|
||||||
cast_type.builtin_type = Variant::INT;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create temporary for result first since it will be deleted last.
|
// Create temporary for result first since it will be deleted last.
|
||||||
result = codegen.add_temporary(cast_type);
|
result = codegen.add_temporary(cast_type);
|
||||||
|
|
||||||
|
@ -817,28 +805,6 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &code
|
||||||
gen->pop_temporary();
|
gen->pop_temporary();
|
||||||
}
|
}
|
||||||
} break;
|
} break;
|
||||||
case GDScriptParser::BinaryOpNode::OP_TYPE_TEST: {
|
|
||||||
GDScriptCodeGenerator::Address operand = _parse_expression(codegen, r_error, binary->left_operand);
|
|
||||||
|
|
||||||
if (binary->right_operand->type == GDScriptParser::Node::IDENTIFIER && GDScriptParser::get_builtin_type(static_cast<const GDScriptParser::IdentifierNode *>(binary->right_operand)->name) != Variant::VARIANT_MAX) {
|
|
||||||
// `is` with builtin type)
|
|
||||||
Variant::Type type = GDScriptParser::get_builtin_type(static_cast<const GDScriptParser::IdentifierNode *>(binary->right_operand)->name);
|
|
||||||
gen->write_type_test_builtin(result, operand, type);
|
|
||||||
} else {
|
|
||||||
GDScriptCodeGenerator::Address type = _parse_expression(codegen, r_error, binary->right_operand);
|
|
||||||
if (r_error) {
|
|
||||||
return GDScriptCodeGenerator::Address();
|
|
||||||
}
|
|
||||||
gen->write_type_test(result, operand, type);
|
|
||||||
if (type.mode == GDScriptCodeGenerator::Address::TEMPORARY) {
|
|
||||||
gen->pop_temporary();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (operand.mode == GDScriptCodeGenerator::Address::TEMPORARY) {
|
|
||||||
gen->pop_temporary();
|
|
||||||
}
|
|
||||||
} break;
|
|
||||||
default: {
|
default: {
|
||||||
GDScriptCodeGenerator::Address left_operand = _parse_expression(codegen, r_error, binary->left_operand);
|
GDScriptCodeGenerator::Address left_operand = _parse_expression(codegen, r_error, binary->left_operand);
|
||||||
GDScriptCodeGenerator::Address right_operand = _parse_expression(codegen, r_error, binary->right_operand);
|
GDScriptCodeGenerator::Address right_operand = _parse_expression(codegen, r_error, binary->right_operand);
|
||||||
|
@ -894,6 +860,28 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &code
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
} break;
|
} break;
|
||||||
|
case GDScriptParser::Node::TYPE_TEST: {
|
||||||
|
const GDScriptParser::TypeTestNode *type_test = static_cast<const GDScriptParser::TypeTestNode *>(p_expression);
|
||||||
|
GDScriptCodeGenerator::Address result = codegen.add_temporary(_gdtype_from_datatype(type_test->get_datatype(), codegen.script));
|
||||||
|
|
||||||
|
GDScriptCodeGenerator::Address operand = _parse_expression(codegen, r_error, type_test->operand);
|
||||||
|
GDScriptDataType test_type = _gdtype_from_datatype(type_test->test_datatype, codegen.script);
|
||||||
|
if (r_error) {
|
||||||
|
return GDScriptCodeGenerator::Address();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (test_type.has_type) {
|
||||||
|
gen->write_type_test(result, operand, test_type);
|
||||||
|
} else {
|
||||||
|
gen->write_assign_true(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (operand.mode == GDScriptCodeGenerator::Address::TEMPORARY) {
|
||||||
|
gen->pop_temporary();
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
} break;
|
||||||
case GDScriptParser::Node::ASSIGNMENT: {
|
case GDScriptParser::Node::ASSIGNMENT: {
|
||||||
const GDScriptParser::AssignmentNode *assignment = static_cast<const GDScriptParser::AssignmentNode *>(p_expression);
|
const GDScriptParser::AssignmentNode *assignment = static_cast<const GDScriptParser::AssignmentNode *>(p_expression);
|
||||||
|
|
||||||
|
|
|
@ -135,23 +135,56 @@ void GDScriptFunction::disassemble(const Vector<String> &p_code_lines) const {
|
||||||
|
|
||||||
incr += 5;
|
incr += 5;
|
||||||
} break;
|
} break;
|
||||||
case OPCODE_EXTENDS_TEST: {
|
case OPCODE_TYPE_TEST_BUILTIN: {
|
||||||
text += "is object ";
|
text += "type test ";
|
||||||
text += DADDR(3);
|
|
||||||
text += " = ";
|
|
||||||
text += DADDR(1);
|
text += DADDR(1);
|
||||||
text += " is ";
|
text += " = ";
|
||||||
text += DADDR(2);
|
text += DADDR(2);
|
||||||
|
text += " is ";
|
||||||
|
text += Variant::get_type_name(Variant::Type(_code_ptr[ip + 3]));
|
||||||
|
|
||||||
incr += 4;
|
incr += 4;
|
||||||
} break;
|
} break;
|
||||||
case OPCODE_IS_BUILTIN: {
|
case OPCODE_TYPE_TEST_ARRAY: {
|
||||||
text += "is builtin ";
|
text += "type test ";
|
||||||
text += DADDR(2);
|
|
||||||
text += " = ";
|
|
||||||
text += DADDR(1);
|
text += DADDR(1);
|
||||||
|
text += " = ";
|
||||||
|
text += DADDR(2);
|
||||||
|
text += " is Array[";
|
||||||
|
|
||||||
|
Ref<Script> script_type = get_constant(_code_ptr[ip + 3] & GDScriptFunction::ADDR_MASK);
|
||||||
|
Variant::Type builtin_type = (Variant::Type)_code_ptr[ip + 4];
|
||||||
|
StringName native_type = get_global_name(_code_ptr[ip + 5]);
|
||||||
|
|
||||||
|
if (script_type.is_valid() && script_type->is_valid()) {
|
||||||
|
text += script_type->get_path();
|
||||||
|
} else if (native_type != StringName()) {
|
||||||
|
text += native_type;
|
||||||
|
} else {
|
||||||
|
text += Variant::get_type_name(builtin_type);
|
||||||
|
}
|
||||||
|
|
||||||
|
text += "]";
|
||||||
|
|
||||||
|
incr += 6;
|
||||||
|
} break;
|
||||||
|
case OPCODE_TYPE_TEST_NATIVE: {
|
||||||
|
text += "type test ";
|
||||||
|
text += DADDR(1);
|
||||||
|
text += " = ";
|
||||||
|
text += DADDR(2);
|
||||||
text += " is ";
|
text += " is ";
|
||||||
text += Variant::get_type_name(Variant::Type(_code_ptr[ip + 3]));
|
text += get_global_name(_code_ptr[ip + 3]);
|
||||||
|
|
||||||
|
incr += 4;
|
||||||
|
} break;
|
||||||
|
case OPCODE_TYPE_TEST_SCRIPT: {
|
||||||
|
text += "type test ";
|
||||||
|
text += DADDR(1);
|
||||||
|
text += " = ";
|
||||||
|
text += DADDR(2);
|
||||||
|
text += " is ";
|
||||||
|
text += DADDR(3);
|
||||||
|
|
||||||
incr += 4;
|
incr += 4;
|
||||||
} break;
|
} break;
|
||||||
|
|
|
@ -1918,21 +1918,19 @@ static bool _guess_identifier_type(GDScriptParser::CompletionContext &p_context,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (suite->parent_if && suite->parent_if->condition && suite->parent_if->condition->type == GDScriptParser::Node::BINARY_OPERATOR && static_cast<const GDScriptParser::BinaryOpNode *>(suite->parent_if->condition)->operation == GDScriptParser::BinaryOpNode::OP_TYPE_TEST) {
|
if (suite->parent_if && suite->parent_if->condition && suite->parent_if->condition->type == GDScriptParser::Node::TYPE_TEST) {
|
||||||
// Operator `is` used, check if identifier is in there! this helps resolve in blocks that are (if (identifier is value)): which are very common..
|
// Operator `is` used, check if identifier is in there! this helps resolve in blocks that are (if (identifier is value)): which are very common..
|
||||||
// Super dirty hack, but very useful.
|
// Super dirty hack, but very useful.
|
||||||
// Credit: Zylann.
|
// Credit: Zylann.
|
||||||
// TODO: this could be hacked to detect ANDed conditions too...
|
// TODO: this could be hacked to detect ANDed conditions too...
|
||||||
const GDScriptParser::BinaryOpNode *op = static_cast<const GDScriptParser::BinaryOpNode *>(suite->parent_if->condition);
|
const GDScriptParser::TypeTestNode *type_test = static_cast<const GDScriptParser::TypeTestNode *>(suite->parent_if->condition);
|
||||||
if (op->left_operand && op->right_operand && op->left_operand->type == GDScriptParser::Node::IDENTIFIER && static_cast<const GDScriptParser::IdentifierNode *>(op->left_operand)->name == p_identifier) {
|
if (type_test->operand && type_test->test_type && type_test->operand->type == GDScriptParser::Node::IDENTIFIER && static_cast<const GDScriptParser::IdentifierNode *>(type_test->operand)->name == p_identifier) {
|
||||||
// Bingo.
|
// Bingo.
|
||||||
GDScriptParser::CompletionContext c = p_context;
|
GDScriptParser::CompletionContext c = p_context;
|
||||||
c.current_line = op->left_operand->start_line;
|
c.current_line = type_test->operand->start_line;
|
||||||
c.current_suite = suite;
|
c.current_suite = suite;
|
||||||
GDScriptCompletionIdentifier is_type;
|
if ((!id_type.is_set() || id_type.is_variant()) && type_test->test_datatype.is_hard_type()) {
|
||||||
if (_guess_expression_type(c, op->right_operand, is_type)) {
|
id_type = type_test->test_datatype;
|
||||||
id_type = is_type.type;
|
|
||||||
id_type.is_meta_type = false;
|
|
||||||
if (last_assign_line < c.current_line) {
|
if (last_assign_line < c.current_line) {
|
||||||
// Override last assignment.
|
// Override last assignment.
|
||||||
last_assign_line = c.current_line;
|
last_assign_line = c.current_line;
|
||||||
|
|
|
@ -219,8 +219,10 @@ public:
|
||||||
enum Opcode {
|
enum Opcode {
|
||||||
OPCODE_OPERATOR,
|
OPCODE_OPERATOR,
|
||||||
OPCODE_OPERATOR_VALIDATED,
|
OPCODE_OPERATOR_VALIDATED,
|
||||||
OPCODE_EXTENDS_TEST,
|
OPCODE_TYPE_TEST_BUILTIN,
|
||||||
OPCODE_IS_BUILTIN,
|
OPCODE_TYPE_TEST_ARRAY,
|
||||||
|
OPCODE_TYPE_TEST_NATIVE,
|
||||||
|
OPCODE_TYPE_TEST_SCRIPT,
|
||||||
OPCODE_SET_KEYED,
|
OPCODE_SET_KEYED,
|
||||||
OPCODE_SET_KEYED_VALIDATED,
|
OPCODE_SET_KEYED_VALIDATED,
|
||||||
OPCODE_SET_INDEXED_VALIDATED,
|
OPCODE_SET_INDEXED_VALIDATED,
|
||||||
|
|
|
@ -2463,9 +2463,6 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_binary_operator(Expression
|
||||||
operation->operation = BinaryOpNode::OP_LOGIC_OR;
|
operation->operation = BinaryOpNode::OP_LOGIC_OR;
|
||||||
operation->variant_op = Variant::OP_OR;
|
operation->variant_op = Variant::OP_OR;
|
||||||
break;
|
break;
|
||||||
case GDScriptTokenizer::Token::IS:
|
|
||||||
operation->operation = BinaryOpNode::OP_TYPE_TEST;
|
|
||||||
break;
|
|
||||||
case GDScriptTokenizer::Token::IN:
|
case GDScriptTokenizer::Token::IN:
|
||||||
operation->operation = BinaryOpNode::OP_CONTENT_TEST;
|
operation->operation = BinaryOpNode::OP_CONTENT_TEST;
|
||||||
operation->variant_op = Variant::OP_IN;
|
operation->variant_op = Variant::OP_IN;
|
||||||
|
@ -3161,6 +3158,22 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_lambda(ExpressionNode *p_p
|
||||||
return lambda;
|
return lambda;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
GDScriptParser::ExpressionNode *GDScriptParser::parse_type_test(ExpressionNode *p_previous_operand, bool p_can_assign) {
|
||||||
|
TypeTestNode *type_test = alloc_node<TypeTestNode>();
|
||||||
|
reset_extents(type_test, p_previous_operand);
|
||||||
|
update_extents(type_test);
|
||||||
|
|
||||||
|
type_test->operand = p_previous_operand;
|
||||||
|
type_test->test_type = parse_type();
|
||||||
|
complete_extents(type_test);
|
||||||
|
|
||||||
|
if (type_test->test_type == nullptr) {
|
||||||
|
push_error(R"(Expected type specifier after "is".)");
|
||||||
|
}
|
||||||
|
|
||||||
|
return type_test;
|
||||||
|
}
|
||||||
|
|
||||||
GDScriptParser::ExpressionNode *GDScriptParser::parse_yield(ExpressionNode *p_previous_operand, bool p_can_assign) {
|
GDScriptParser::ExpressionNode *GDScriptParser::parse_yield(ExpressionNode *p_previous_operand, bool p_can_assign) {
|
||||||
push_error(R"("yield" was removed in Godot 4.0. Use "await" instead.)");
|
push_error(R"("yield" was removed in Godot 4.0. Use "await" instead.)");
|
||||||
return nullptr;
|
return nullptr;
|
||||||
|
@ -3529,7 +3542,7 @@ GDScriptParser::ParseRule *GDScriptParser::get_rule(GDScriptTokenizer::Token::Ty
|
||||||
{ nullptr, nullptr, PREC_NONE }, // EXTENDS,
|
{ nullptr, nullptr, PREC_NONE }, // EXTENDS,
|
||||||
{ &GDScriptParser::parse_lambda, nullptr, PREC_NONE }, // FUNC,
|
{ &GDScriptParser::parse_lambda, nullptr, PREC_NONE }, // FUNC,
|
||||||
{ nullptr, &GDScriptParser::parse_binary_operator, PREC_CONTENT_TEST }, // IN,
|
{ nullptr, &GDScriptParser::parse_binary_operator, PREC_CONTENT_TEST }, // IN,
|
||||||
{ nullptr, &GDScriptParser::parse_binary_operator, PREC_TYPE_TEST }, // IS,
|
{ nullptr, &GDScriptParser::parse_type_test, PREC_TYPE_TEST }, // IS,
|
||||||
{ nullptr, nullptr, PREC_NONE }, // NAMESPACE,
|
{ nullptr, nullptr, PREC_NONE }, // NAMESPACE,
|
||||||
{ &GDScriptParser::parse_preload, nullptr, PREC_NONE }, // PRELOAD,
|
{ &GDScriptParser::parse_preload, nullptr, PREC_NONE }, // PRELOAD,
|
||||||
{ &GDScriptParser::parse_self, nullptr, PREC_NONE }, // SELF,
|
{ &GDScriptParser::parse_self, nullptr, PREC_NONE }, // SELF,
|
||||||
|
@ -4379,9 +4392,6 @@ void GDScriptParser::TreePrinter::print_binary_op(BinaryOpNode *p_binary_op) {
|
||||||
case BinaryOpNode::OP_LOGIC_OR:
|
case BinaryOpNode::OP_LOGIC_OR:
|
||||||
push_text(" OR ");
|
push_text(" OR ");
|
||||||
break;
|
break;
|
||||||
case BinaryOpNode::OP_TYPE_TEST:
|
|
||||||
push_text(" IS ");
|
|
||||||
break;
|
|
||||||
case BinaryOpNode::OP_CONTENT_TEST:
|
case BinaryOpNode::OP_CONTENT_TEST:
|
||||||
push_text(" IN ");
|
push_text(" IN ");
|
||||||
break;
|
break;
|
||||||
|
@ -4584,6 +4594,9 @@ void GDScriptParser::TreePrinter::print_expression(ExpressionNode *p_expression)
|
||||||
case Node::TERNARY_OPERATOR:
|
case Node::TERNARY_OPERATOR:
|
||||||
print_ternary_op(static_cast<TernaryOpNode *>(p_expression));
|
print_ternary_op(static_cast<TernaryOpNode *>(p_expression));
|
||||||
break;
|
break;
|
||||||
|
case Node::TYPE_TEST:
|
||||||
|
print_type_test(static_cast<TypeTestNode *>(p_expression));
|
||||||
|
break;
|
||||||
case Node::UNARY_OPERATOR:
|
case Node::UNARY_OPERATOR:
|
||||||
print_unary_op(static_cast<UnaryOpNode *>(p_expression));
|
print_unary_op(static_cast<UnaryOpNode *>(p_expression));
|
||||||
break;
|
break;
|
||||||
|
@ -4943,6 +4956,12 @@ void GDScriptParser::TreePrinter::print_type(TypeNode *p_type) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void GDScriptParser::TreePrinter::print_type_test(TypeTestNode *p_test) {
|
||||||
|
print_expression(p_test->operand);
|
||||||
|
push_text(" IS ");
|
||||||
|
print_type(p_test->test_type);
|
||||||
|
}
|
||||||
|
|
||||||
void GDScriptParser::TreePrinter::print_unary_op(UnaryOpNode *p_unary_op) {
|
void GDScriptParser::TreePrinter::print_unary_op(UnaryOpNode *p_unary_op) {
|
||||||
// Surround in parenthesis for disambiguation.
|
// Surround in parenthesis for disambiguation.
|
||||||
push_text("(");
|
push_text("(");
|
||||||
|
|
|
@ -91,6 +91,7 @@ public:
|
||||||
struct SuiteNode;
|
struct SuiteNode;
|
||||||
struct TernaryOpNode;
|
struct TernaryOpNode;
|
||||||
struct TypeNode;
|
struct TypeNode;
|
||||||
|
struct TypeTestNode;
|
||||||
struct UnaryOpNode;
|
struct UnaryOpNode;
|
||||||
struct VariableNode;
|
struct VariableNode;
|
||||||
struct WhileNode;
|
struct WhileNode;
|
||||||
|
@ -288,6 +289,7 @@ public:
|
||||||
SUITE,
|
SUITE,
|
||||||
TERNARY_OPERATOR,
|
TERNARY_OPERATOR,
|
||||||
TYPE,
|
TYPE,
|
||||||
|
TYPE_TEST,
|
||||||
UNARY_OPERATOR,
|
UNARY_OPERATOR,
|
||||||
VARIABLE,
|
VARIABLE,
|
||||||
WHILE,
|
WHILE,
|
||||||
|
@ -426,7 +428,6 @@ public:
|
||||||
OP_BIT_XOR,
|
OP_BIT_XOR,
|
||||||
OP_LOGIC_AND,
|
OP_LOGIC_AND,
|
||||||
OP_LOGIC_OR,
|
OP_LOGIC_OR,
|
||||||
OP_TYPE_TEST,
|
|
||||||
OP_CONTENT_TEST,
|
OP_CONTENT_TEST,
|
||||||
OP_COMP_EQUAL,
|
OP_COMP_EQUAL,
|
||||||
OP_COMP_NOT_EQUAL,
|
OP_COMP_NOT_EQUAL,
|
||||||
|
@ -1150,6 +1151,16 @@ public:
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct TypeTestNode : public ExpressionNode {
|
||||||
|
ExpressionNode *operand = nullptr;
|
||||||
|
TypeNode *test_type = nullptr;
|
||||||
|
DataType test_datatype;
|
||||||
|
|
||||||
|
TypeTestNode() {
|
||||||
|
type = TYPE_TEST;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
struct UnaryOpNode : public ExpressionNode {
|
struct UnaryOpNode : public ExpressionNode {
|
||||||
enum OpType {
|
enum OpType {
|
||||||
OP_POSITIVE,
|
OP_POSITIVE,
|
||||||
|
@ -1460,6 +1471,7 @@ private:
|
||||||
ExpressionNode *parse_attribute(ExpressionNode *p_previous_operand, bool p_can_assign);
|
ExpressionNode *parse_attribute(ExpressionNode *p_previous_operand, bool p_can_assign);
|
||||||
ExpressionNode *parse_subscript(ExpressionNode *p_previous_operand, bool p_can_assign);
|
ExpressionNode *parse_subscript(ExpressionNode *p_previous_operand, bool p_can_assign);
|
||||||
ExpressionNode *parse_lambda(ExpressionNode *p_previous_operand, bool p_can_assign);
|
ExpressionNode *parse_lambda(ExpressionNode *p_previous_operand, bool p_can_assign);
|
||||||
|
ExpressionNode *parse_type_test(ExpressionNode *p_previous_operand, bool p_can_assign);
|
||||||
ExpressionNode *parse_yield(ExpressionNode *p_previous_operand, bool p_can_assign);
|
ExpressionNode *parse_yield(ExpressionNode *p_previous_operand, bool p_can_assign);
|
||||||
ExpressionNode *parse_invalid_token(ExpressionNode *p_previous_operand, bool p_can_assign);
|
ExpressionNode *parse_invalid_token(ExpressionNode *p_previous_operand, bool p_can_assign);
|
||||||
TypeNode *parse_type(bool p_allow_void = false);
|
TypeNode *parse_type(bool p_allow_void = false);
|
||||||
|
@ -1541,8 +1553,9 @@ public:
|
||||||
void print_statement(Node *p_statement);
|
void print_statement(Node *p_statement);
|
||||||
void print_subscript(SubscriptNode *p_subscript);
|
void print_subscript(SubscriptNode *p_subscript);
|
||||||
void print_suite(SuiteNode *p_suite);
|
void print_suite(SuiteNode *p_suite);
|
||||||
void print_type(TypeNode *p_type);
|
|
||||||
void print_ternary_op(TernaryOpNode *p_ternary_op);
|
void print_ternary_op(TernaryOpNode *p_ternary_op);
|
||||||
|
void print_type(TypeNode *p_type);
|
||||||
|
void print_type_test(TypeTestNode *p_type_test);
|
||||||
void print_unary_op(UnaryOpNode *p_unary_op);
|
void print_unary_op(UnaryOpNode *p_unary_op);
|
||||||
void print_variable(VariableNode *p_variable);
|
void print_variable(VariableNode *p_variable);
|
||||||
void print_while(WhileNode *p_while);
|
void print_while(WhileNode *p_while);
|
||||||
|
|
|
@ -523,6 +523,82 @@ struct GDScriptUtilityFunctionsDefinitions {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static inline void is_instance_of(Variant *r_ret, const Variant **p_args, int p_arg_count, Callable::CallError &r_error) {
|
||||||
|
VALIDATE_ARG_COUNT(2);
|
||||||
|
|
||||||
|
if (p_args[1]->get_type() == Variant::INT) {
|
||||||
|
int builtin_type = *p_args[1];
|
||||||
|
if (builtin_type < 0 || builtin_type >= Variant::VARIANT_MAX) {
|
||||||
|
*r_ret = RTR("Invalid type argument for is_instance_of(), use TYPE_* constants for built-in types.");
|
||||||
|
r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT;
|
||||||
|
r_error.argument = 1;
|
||||||
|
r_error.expected = Variant::NIL;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
*r_ret = p_args[0]->get_type() == builtin_type;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool was_type_freed = false;
|
||||||
|
Object *type_object = p_args[1]->get_validated_object_with_check(was_type_freed);
|
||||||
|
if (was_type_freed) {
|
||||||
|
*r_ret = RTR("Type argument is a previously freed instance.");
|
||||||
|
r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT;
|
||||||
|
r_error.argument = 1;
|
||||||
|
r_error.expected = Variant::NIL;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!type_object) {
|
||||||
|
*r_ret = RTR("Invalid type argument for is_instance_of(), should be a TYPE_* constant, a class or a script.");
|
||||||
|
r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT;
|
||||||
|
r_error.argument = 1;
|
||||||
|
r_error.expected = Variant::NIL;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool was_value_freed = false;
|
||||||
|
Object *value_object = p_args[0]->get_validated_object_with_check(was_value_freed);
|
||||||
|
if (was_value_freed) {
|
||||||
|
*r_ret = RTR("Value argument is a previously freed instance.");
|
||||||
|
r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT;
|
||||||
|
r_error.argument = 0;
|
||||||
|
r_error.expected = Variant::NIL;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!value_object) {
|
||||||
|
*r_ret = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
GDScriptNativeClass *native_type = Object::cast_to<GDScriptNativeClass>(type_object);
|
||||||
|
if (native_type) {
|
||||||
|
*r_ret = ClassDB::is_parent_class(value_object->get_class_name(), native_type->get_name());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Script *script_type = Object::cast_to<Script>(type_object);
|
||||||
|
if (script_type) {
|
||||||
|
bool result = false;
|
||||||
|
if (value_object->get_script_instance()) {
|
||||||
|
Script *script_ptr = value_object->get_script_instance()->get_script().ptr();
|
||||||
|
while (script_ptr) {
|
||||||
|
if (script_ptr == script_type) {
|
||||||
|
result = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
script_ptr = script_ptr->get_base_script().ptr();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*r_ret = result;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
*r_ret = RTR("Invalid type argument for is_instance_of(), should be a TYPE_* constant, a class or a script.");
|
||||||
|
r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT;
|
||||||
|
r_error.argument = 1;
|
||||||
|
r_error.expected = Variant::NIL;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
struct GDScriptUtilityFunctionInfo {
|
struct GDScriptUtilityFunctionInfo {
|
||||||
|
@ -638,6 +714,7 @@ void GDScriptUtilityFunctions::register_functions() {
|
||||||
REGISTER_FUNC_NO_ARGS(print_stack, false, Variant::NIL);
|
REGISTER_FUNC_NO_ARGS(print_stack, false, Variant::NIL);
|
||||||
REGISTER_FUNC_NO_ARGS(get_stack, false, Variant::ARRAY);
|
REGISTER_FUNC_NO_ARGS(get_stack, false, Variant::ARRAY);
|
||||||
REGISTER_FUNC(len, true, Variant::INT, VARARG("var"));
|
REGISTER_FUNC(len, true, Variant::INT, VARARG("var"));
|
||||||
|
REGISTER_FUNC(is_instance_of, true, Variant::BOOL, VARARG("value"), VARARG("type"));
|
||||||
}
|
}
|
||||||
|
|
||||||
void GDScriptUtilityFunctions::unregister_functions() {
|
void GDScriptUtilityFunctions::unregister_functions() {
|
||||||
|
|
|
@ -201,8 +201,10 @@ void (*type_init_function_table[])(Variant *) = {
|
||||||
static const void *switch_table_ops[] = { \
|
static const void *switch_table_ops[] = { \
|
||||||
&&OPCODE_OPERATOR, \
|
&&OPCODE_OPERATOR, \
|
||||||
&&OPCODE_OPERATOR_VALIDATED, \
|
&&OPCODE_OPERATOR_VALIDATED, \
|
||||||
&&OPCODE_EXTENDS_TEST, \
|
&&OPCODE_TYPE_TEST_BUILTIN, \
|
||||||
&&OPCODE_IS_BUILTIN, \
|
&&OPCODE_TYPE_TEST_ARRAY, \
|
||||||
|
&&OPCODE_TYPE_TEST_NATIVE, \
|
||||||
|
&&OPCODE_TYPE_TEST_SCRIPT, \
|
||||||
&&OPCODE_SET_KEYED, \
|
&&OPCODE_SET_KEYED, \
|
||||||
&&OPCODE_SET_KEYED_VALIDATED, \
|
&&OPCODE_SET_KEYED_VALIDATED, \
|
||||||
&&OPCODE_SET_INDEXED_VALIDATED, \
|
&&OPCODE_SET_INDEXED_VALIDATED, \
|
||||||
|
@ -743,91 +745,95 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a
|
||||||
}
|
}
|
||||||
DISPATCH_OPCODE;
|
DISPATCH_OPCODE;
|
||||||
|
|
||||||
OPCODE(OPCODE_EXTENDS_TEST) {
|
OPCODE(OPCODE_TYPE_TEST_BUILTIN) {
|
||||||
CHECK_SPACE(4);
|
CHECK_SPACE(4);
|
||||||
|
|
||||||
GET_VARIANT_PTR(a, 0);
|
GET_VARIANT_PTR(dst, 0);
|
||||||
GET_VARIANT_PTR(b, 1);
|
GET_VARIANT_PTR(value, 1);
|
||||||
GET_VARIANT_PTR(dst, 2);
|
|
||||||
|
|
||||||
#ifdef DEBUG_ENABLED
|
Variant::Type builtin_type = (Variant::Type)_code_ptr[ip + 3];
|
||||||
if (b->get_type() != Variant::OBJECT || b->operator Object *() == nullptr) {
|
GD_ERR_BREAK(builtin_type < 0 || builtin_type >= Variant::VARIANT_MAX);
|
||||||
err_text = "Right operand of 'is' is not a class.";
|
|
||||||
OPCODE_BREAK;
|
*dst = value->get_type() == builtin_type;
|
||||||
|
ip += 4;
|
||||||
}
|
}
|
||||||
#endif
|
DISPATCH_OPCODE;
|
||||||
|
|
||||||
bool extends_ok = false;
|
OPCODE(OPCODE_TYPE_TEST_ARRAY) {
|
||||||
if (a->get_type() == Variant::OBJECT && a->operator Object *() != nullptr) {
|
CHECK_SPACE(6);
|
||||||
#ifdef DEBUG_ENABLED
|
|
||||||
bool was_freed;
|
|
||||||
Object *obj_A = a->get_validated_object_with_check(was_freed);
|
|
||||||
|
|
||||||
|
GET_VARIANT_PTR(dst, 0);
|
||||||
|
GET_VARIANT_PTR(value, 1);
|
||||||
|
|
||||||
|
GET_VARIANT_PTR(script_type, 2);
|
||||||
|
Variant::Type builtin_type = (Variant::Type)_code_ptr[ip + 4];
|
||||||
|
int native_type_idx = _code_ptr[ip + 5];
|
||||||
|
GD_ERR_BREAK(native_type_idx < 0 || native_type_idx >= _global_names_count);
|
||||||
|
const StringName native_type = _global_names_ptr[native_type_idx];
|
||||||
|
|
||||||
|
bool result = false;
|
||||||
|
if (value->get_type() == Variant::ARRAY) {
|
||||||
|
Array *array = VariantInternal::get_array(value);
|
||||||
|
result = array->get_typed_builtin() == ((uint32_t)builtin_type) && array->get_typed_class_name() == native_type && array->get_typed_script() == *script_type && array->get_typed_class_name() == native_type;
|
||||||
|
}
|
||||||
|
|
||||||
|
*dst = result;
|
||||||
|
ip += 6;
|
||||||
|
}
|
||||||
|
DISPATCH_OPCODE;
|
||||||
|
|
||||||
|
OPCODE(OPCODE_TYPE_TEST_NATIVE) {
|
||||||
|
CHECK_SPACE(4);
|
||||||
|
|
||||||
|
GET_VARIANT_PTR(dst, 0);
|
||||||
|
GET_VARIANT_PTR(value, 1);
|
||||||
|
|
||||||
|
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];
|
||||||
|
|
||||||
|
bool was_freed = false;
|
||||||
|
Object *object = value->get_validated_object_with_check(was_freed);
|
||||||
if (was_freed) {
|
if (was_freed) {
|
||||||
err_text = "Left operand of 'is' is a previously freed instance.";
|
err_text = "Left operand of 'is' is a previously freed instance.";
|
||||||
OPCODE_BREAK;
|
OPCODE_BREAK;
|
||||||
}
|
}
|
||||||
|
|
||||||
Object *obj_B = b->get_validated_object_with_check(was_freed);
|
*dst = object && ClassDB::is_parent_class(object->get_class_name(), native_type);
|
||||||
|
|
||||||
if (was_freed) {
|
|
||||||
err_text = "Right operand of 'is' is a previously freed instance.";
|
|
||||||
OPCODE_BREAK;
|
|
||||||
}
|
|
||||||
#else
|
|
||||||
|
|
||||||
Object *obj_A = *a;
|
|
||||||
Object *obj_B = *b;
|
|
||||||
#endif // DEBUG_ENABLED
|
|
||||||
|
|
||||||
GDScript *scr_B = Object::cast_to<GDScript>(obj_B);
|
|
||||||
|
|
||||||
if (scr_B) {
|
|
||||||
//if B is a script, the only valid condition is that A has an instance which inherits from the script
|
|
||||||
//in other situation, this should return false.
|
|
||||||
|
|
||||||
if (obj_A->get_script_instance() && obj_A->get_script_instance()->get_language() == GDScriptLanguage::get_singleton()) {
|
|
||||||
GDScript *cmp = static_cast<GDScript *>(obj_A->get_script_instance()->get_script().ptr());
|
|
||||||
//bool found=false;
|
|
||||||
while (cmp) {
|
|
||||||
if (cmp == scr_B) {
|
|
||||||
//inherits from script, all ok
|
|
||||||
extends_ok = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
cmp = cmp->_base;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
} else {
|
|
||||||
GDScriptNativeClass *nc = Object::cast_to<GDScriptNativeClass>(obj_B);
|
|
||||||
|
|
||||||
#ifdef DEBUG_ENABLED
|
|
||||||
if (!nc) {
|
|
||||||
err_text = "Right operand of 'is' is not a class (type: '" + obj_B->get_class() + "').";
|
|
||||||
OPCODE_BREAK;
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
extends_ok = ClassDB::is_parent_class(obj_A->get_class_name(), nc->get_name());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
*dst = extends_ok;
|
|
||||||
ip += 4;
|
ip += 4;
|
||||||
}
|
}
|
||||||
DISPATCH_OPCODE;
|
DISPATCH_OPCODE;
|
||||||
|
|
||||||
OPCODE(OPCODE_IS_BUILTIN) {
|
OPCODE(OPCODE_TYPE_TEST_SCRIPT) {
|
||||||
CHECK_SPACE(4);
|
CHECK_SPACE(4);
|
||||||
|
|
||||||
GET_VARIANT_PTR(value, 0);
|
GET_VARIANT_PTR(dst, 0);
|
||||||
GET_VARIANT_PTR(dst, 1);
|
GET_VARIANT_PTR(value, 1);
|
||||||
Variant::Type var_type = (Variant::Type)_code_ptr[ip + 3];
|
|
||||||
|
|
||||||
GD_ERR_BREAK(var_type < 0 || var_type >= Variant::VARIANT_MAX);
|
GET_VARIANT_PTR(type, 2);
|
||||||
|
Script *script_type = Object::cast_to<Script>(type->operator Object *());
|
||||||
|
GD_ERR_BREAK(!script_type);
|
||||||
|
|
||||||
*dst = value->get_type() == var_type;
|
bool was_freed = false;
|
||||||
|
Object *object = value->get_validated_object_with_check(was_freed);
|
||||||
|
if (was_freed) {
|
||||||
|
err_text = "Left operand of 'is' is a previously freed instance.";
|
||||||
|
OPCODE_BREAK;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool result = false;
|
||||||
|
if (object && object->get_script_instance()) {
|
||||||
|
Script *script_ptr = object->get_script_instance()->get_script().ptr();
|
||||||
|
while (script_ptr) {
|
||||||
|
if (script_ptr == script_type) {
|
||||||
|
result = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
script_ptr = script_ptr->get_base_script().ptr();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
*dst = result;
|
||||||
ip += 4;
|
ip += 4;
|
||||||
}
|
}
|
||||||
DISPATCH_OPCODE;
|
DISPATCH_OPCODE;
|
||||||
|
|
|
@ -0,0 +1,127 @@
|
||||||
|
class A extends RefCounted:
|
||||||
|
pass
|
||||||
|
|
||||||
|
class B extends A:
|
||||||
|
pass
|
||||||
|
|
||||||
|
@warning_ignore("assert_always_true")
|
||||||
|
func test():
|
||||||
|
var builtin: Variant = 3
|
||||||
|
assert((builtin is Variant) == true)
|
||||||
|
assert((builtin is int) == true)
|
||||||
|
assert(is_instance_of(builtin, TYPE_INT) == true)
|
||||||
|
assert((builtin is float) == false)
|
||||||
|
assert(is_instance_of(builtin, TYPE_FLOAT) == false)
|
||||||
|
|
||||||
|
const const_builtin: Variant = 3
|
||||||
|
assert((const_builtin is Variant) == true)
|
||||||
|
assert((const_builtin is int) == true)
|
||||||
|
assert(is_instance_of(const_builtin, TYPE_INT) == true)
|
||||||
|
assert((const_builtin is float) == false)
|
||||||
|
assert(is_instance_of(const_builtin, TYPE_FLOAT) == false)
|
||||||
|
|
||||||
|
var int_array: Variant = [] as Array[int]
|
||||||
|
assert((int_array is Variant) == true)
|
||||||
|
assert((int_array is Array) == true)
|
||||||
|
assert(is_instance_of(int_array, TYPE_ARRAY) == true)
|
||||||
|
assert((int_array is Array[int]) == true)
|
||||||
|
assert((int_array is Array[float]) == false)
|
||||||
|
assert((int_array is int) == false)
|
||||||
|
assert(is_instance_of(int_array, TYPE_INT) == false)
|
||||||
|
|
||||||
|
var const_int_array: Variant = [] as Array[int]
|
||||||
|
assert((const_int_array is Variant) == true)
|
||||||
|
assert((const_int_array is Array) == true)
|
||||||
|
assert(is_instance_of(const_int_array, TYPE_ARRAY) == true)
|
||||||
|
assert((const_int_array is Array[int]) == true)
|
||||||
|
assert((const_int_array is Array[float]) == false)
|
||||||
|
assert((const_int_array is int) == false)
|
||||||
|
assert(is_instance_of(const_int_array, TYPE_INT) == false)
|
||||||
|
|
||||||
|
var b_array: Variant = [] as Array[B]
|
||||||
|
assert((b_array is Variant) == true)
|
||||||
|
assert((b_array is Array) == true)
|
||||||
|
assert(is_instance_of(b_array, TYPE_ARRAY) == true)
|
||||||
|
assert((b_array is Array[B]) == true)
|
||||||
|
assert((b_array is Array[A]) == false)
|
||||||
|
assert((b_array is Array[int]) == false)
|
||||||
|
assert((b_array is int) == false)
|
||||||
|
assert(is_instance_of(b_array, TYPE_INT) == false)
|
||||||
|
|
||||||
|
var const_b_array: Variant = [] as Array[B]
|
||||||
|
assert((const_b_array is Variant) == true)
|
||||||
|
assert((const_b_array is Array) == true)
|
||||||
|
assert(is_instance_of(const_b_array, TYPE_ARRAY) == true)
|
||||||
|
assert((const_b_array is Array[B]) == true)
|
||||||
|
assert((const_b_array is Array[A]) == false)
|
||||||
|
assert((const_b_array is Array[int]) == false)
|
||||||
|
assert((const_b_array is int) == false)
|
||||||
|
assert(is_instance_of(const_b_array, TYPE_INT) == false)
|
||||||
|
|
||||||
|
var native: Variant = RefCounted.new()
|
||||||
|
assert((native is Variant) == true)
|
||||||
|
assert((native is Object) == true)
|
||||||
|
assert(is_instance_of(native, TYPE_OBJECT) == true)
|
||||||
|
assert(is_instance_of(native, Object) == true)
|
||||||
|
assert((native is RefCounted) == true)
|
||||||
|
assert(is_instance_of(native, RefCounted) == true)
|
||||||
|
assert((native is Node) == false)
|
||||||
|
assert(is_instance_of(native, Node) == false)
|
||||||
|
assert((native is int) == false)
|
||||||
|
assert(is_instance_of(native, TYPE_INT) == false)
|
||||||
|
|
||||||
|
var a_script: Variant = A.new()
|
||||||
|
assert((a_script is Variant) == true)
|
||||||
|
assert((a_script is Object) == true)
|
||||||
|
assert(is_instance_of(a_script, TYPE_OBJECT) == true)
|
||||||
|
assert(is_instance_of(a_script, Object) == true)
|
||||||
|
assert((a_script is RefCounted) == true)
|
||||||
|
assert(is_instance_of(a_script, RefCounted) == true)
|
||||||
|
assert((a_script is A) == true)
|
||||||
|
assert(is_instance_of(a_script, A) == true)
|
||||||
|
assert((a_script is B) == false)
|
||||||
|
assert(is_instance_of(a_script, B) == false)
|
||||||
|
assert((a_script is Node) == false)
|
||||||
|
assert(is_instance_of(a_script, Node) == false)
|
||||||
|
assert((a_script is int) == false)
|
||||||
|
assert(is_instance_of(a_script, TYPE_INT) == false)
|
||||||
|
|
||||||
|
var b_script: Variant = B.new()
|
||||||
|
assert((b_script is Variant) == true)
|
||||||
|
assert((b_script is Object) == true)
|
||||||
|
assert(is_instance_of(b_script, TYPE_OBJECT) == true)
|
||||||
|
assert(is_instance_of(b_script, Object) == true)
|
||||||
|
assert((b_script is RefCounted) == true)
|
||||||
|
assert(is_instance_of(b_script, RefCounted) == true)
|
||||||
|
assert((b_script is A) == true)
|
||||||
|
assert(is_instance_of(b_script, A) == true)
|
||||||
|
assert((b_script is B) == true)
|
||||||
|
assert(is_instance_of(b_script, B) == true)
|
||||||
|
assert((b_script is Node) == false)
|
||||||
|
assert(is_instance_of(b_script, Node) == false)
|
||||||
|
assert((b_script is int) == false)
|
||||||
|
assert(is_instance_of(b_script, TYPE_INT) == false)
|
||||||
|
|
||||||
|
var var_null: Variant = null
|
||||||
|
assert((var_null is Variant) == true)
|
||||||
|
assert((var_null is int) == false)
|
||||||
|
assert(is_instance_of(var_null, TYPE_INT) == false)
|
||||||
|
assert((var_null is Object) == false)
|
||||||
|
assert(is_instance_of(var_null, TYPE_OBJECT) == false)
|
||||||
|
assert((var_null is RefCounted) == false)
|
||||||
|
assert(is_instance_of(var_null, RefCounted) == false)
|
||||||
|
assert((var_null is A) == false)
|
||||||
|
assert(is_instance_of(var_null, A) == false)
|
||||||
|
|
||||||
|
const const_null: Variant = null
|
||||||
|
assert((const_null is Variant) == true)
|
||||||
|
assert((const_null is int) == false)
|
||||||
|
assert(is_instance_of(const_null, TYPE_INT) == false)
|
||||||
|
assert((const_null is Object) == false)
|
||||||
|
assert(is_instance_of(const_null, TYPE_OBJECT) == false)
|
||||||
|
assert((const_null is RefCounted) == false)
|
||||||
|
assert(is_instance_of(const_null, RefCounted) == false)
|
||||||
|
assert((const_null is A) == false)
|
||||||
|
assert(is_instance_of(const_null, A) == false)
|
||||||
|
|
||||||
|
print('ok')
|
|
@ -0,0 +1,2 @@
|
||||||
|
GDTEST_OK
|
||||||
|
ok
|
Loading…
Reference in New Issue