diff options
author | Rémi Verschelde <rverschelde@gmail.com> | 2023-02-20 15:41:36 +0100 |
---|---|---|
committer | Rémi Verschelde <rverschelde@gmail.com> | 2023-02-20 15:41:36 +0100 |
commit | 561d9497399db6136a6cbac17d7736222039cf6a (patch) | |
tree | 062aa96da270791e295b3dc1e7e710287e16affb | |
parent | 6bf63a3542f77443f3453bd02ac316d6c9de6f98 (diff) | |
parent | 8fe023ad93119061cda4730d1b0ba1946f15c515 (diff) |
Merge pull request #73489 from vonagam/type-check-node
GDScript: Rework type check
-rw-r--r-- | modules/gdscript/doc_classes/@GDScript.xml | 7 | ||||
-rw-r--r-- | modules/gdscript/gdscript_analyzer.cpp | 90 | ||||
-rw-r--r-- | modules/gdscript/gdscript_analyzer.h | 1 | ||||
-rw-r--r-- | modules/gdscript/gdscript_byte_codegen.cpp | 50 | ||||
-rw-r--r-- | modules/gdscript/gdscript_byte_codegen.h | 3 | ||||
-rw-r--r-- | modules/gdscript/gdscript_codegen.h | 3 | ||||
-rw-r--r-- | modules/gdscript/gdscript_compiler.cpp | 60 | ||||
-rw-r--r-- | modules/gdscript/gdscript_disassembler.cpp | 49 | ||||
-rw-r--r-- | modules/gdscript/gdscript_editor.cpp | 14 | ||||
-rw-r--r-- | modules/gdscript/gdscript_function.h | 6 | ||||
-rw-r--r-- | modules/gdscript/gdscript_parser.cpp | 33 | ||||
-rw-r--r-- | modules/gdscript/gdscript_parser.h | 17 | ||||
-rw-r--r-- | modules/gdscript/gdscript_utility_functions.cpp | 77 | ||||
-rw-r--r-- | modules/gdscript/gdscript_vm.cpp | 134 | ||||
-rw-r--r-- | modules/gdscript/tests/scripts/analyzer/features/type_test_usage.gd | 127 | ||||
-rw-r--r-- | modules/gdscript/tests/scripts/analyzer/features/type_test_usage.out | 2 |
16 files changed, 491 insertions, 182 deletions
diff --git a/modules/gdscript/doc_classes/@GDScript.xml b/modules/gdscript/doc_classes/@GDScript.xml index 0b7e4e50e6..08c8763493 100644 --- a/modules/gdscript/doc_classes/@GDScript.xml +++ b/modules/gdscript/doc_classes/@GDScript.xml @@ -121,6 +121,13 @@ [/codeblock] </description> </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"> <return type="int" /> <param index="0" name="var" type="Variant" /> diff --git a/modules/gdscript/gdscript_analyzer.cpp b/modules/gdscript/gdscript_analyzer.cpp index e86f2b8fff..046158e24e 100644 --- a/modules/gdscript/gdscript_analyzer.cpp +++ b/modules/gdscript/gdscript_analyzer.cpp @@ -562,7 +562,6 @@ GDScriptParser::DataType GDScriptAnalyzer::resolve_datatype(GDScriptParser::Type GDScriptParser::DataType result; result.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT; - result.builtin_type = Variant::OBJECT; if (p_type->type_chain.is_empty()) { // void. @@ -584,6 +583,7 @@ GDScriptParser::DataType GDScriptAnalyzer::resolve_datatype(GDScriptParser::Type } else if (first == SNAME("Object")) { // Object is treated like a native type, not a built-in. result.kind = GDScriptParser::DataType::NATIVE; + result.builtin_type = Variant::OBJECT; result.native_type = SNAME("Object"); } else if (GDScriptParser::get_builtin_type(first) < Variant::VARIANT_MAX) { // Built-in types. @@ -604,6 +604,7 @@ GDScriptParser::DataType GDScriptAnalyzer::resolve_datatype(GDScriptParser::Type } else if (class_exists(first)) { // Native engine classes. result.kind = GDScriptParser::DataType::NATIVE; + result.builtin_type = Variant::OBJECT; result.native_type = first; } else if (ScriptServer::is_global_class(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::SUBSCRIPT: case GDScriptParser::Node::TERNARY_OPERATOR: + case GDScriptParser::Node::TYPE_TEST: case GDScriptParser::Node::UNARY_OPERATOR: reduce_expression(static_cast<GDScriptParser::ExpressionNode *>(p_node), p_is_root); break; @@ -2196,6 +2198,9 @@ void GDScriptAnalyzer::reduce_expression(GDScriptParser::ExpressionNode *p_expre case GDScriptParser::Node::TERNARY_OPERATOR: reduce_ternary_op(static_cast<GDScriptParser::TernaryOpNode *>(p_expression), p_is_root); break; + case GDScriptParser::Node::TYPE_TEST: + reduce_type_test(static_cast<GDScriptParser::TypeTestNode *>(p_expression)); + break; case GDScriptParser::Node::UNARY_OPERATOR: reduce_unary_op(static_cast<GDScriptParser::UnaryOpNode *>(p_expression)); break; @@ -2502,13 +2507,7 @@ void GDScriptAnalyzer::reduce_await(GDScriptParser::AwaitNode *p_await) { void GDScriptAnalyzer::reduce_binary_op(GDScriptParser::BinaryOpNode *p_binary_op) { 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); - } - // TODO: Right operand must be a valid type with the `is` operator. Need to check here. + reduce_expression(p_binary_op->right_operand); GDScriptParser::DataType left_type; if (p_binary_op->left_operand) { @@ -2546,19 +2545,7 @@ void GDScriptAnalyzer::reduce_binary_op(GDScriptParser::BinaryOpNode *p_binary_o } } } 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 { - 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)); @@ -2567,24 +2554,7 @@ void GDScriptAnalyzer::reduce_binary_op(GDScriptParser::BinaryOpNode *p_binary_o GDScriptParser::DataType result; - 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) && !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) && + 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))) { // "==" and "!=" operators always return a boolean when comparing to null. result.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT; @@ -4109,6 +4079,48 @@ void GDScriptAnalyzer::reduce_ternary_op(GDScriptParser::TernaryOpNode *p_ternar 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) { reduce_expression(p_unary_op->operand); diff --git a/modules/gdscript/gdscript_analyzer.h b/modules/gdscript/gdscript_analyzer.h index cdeba374c7..22004b899f 100644 --- a/modules/gdscript/gdscript_analyzer.h +++ b/modules/gdscript/gdscript_analyzer.h @@ -100,6 +100,7 @@ class GDScriptAnalyzer { void reduce_self(GDScriptParser::SelfNode *p_self); void reduce_subscript(GDScriptParser::SubscriptNode *p_subscript); 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); Variant make_expression_reduced_value(GDScriptParser::ExpressionNode *p_expression, bool &is_reduced); diff --git a/modules/gdscript/gdscript_byte_codegen.cpp b/modules/gdscript/gdscript_byte_codegen.cpp index 45008b0e87..a13bf8009f 100644 --- a/modules/gdscript/gdscript_byte_codegen.cpp +++ b/modules/gdscript/gdscript_byte_codegen.cpp @@ -612,18 +612,44 @@ void GDScriptByteCodeGenerator::write_binary_operator(const Address &p_target, V append(p_operator); } -void GDScriptByteCodeGenerator::write_type_test(const Address &p_target, const Address &p_source, const Address &p_type) { - append_opcode(GDScriptFunction::OPCODE_EXTENDS_TEST); - append(p_source); - append(p_type); - 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_type_test(const Address &p_target, const Address &p_source, const GDScriptDataType &p_type) { + 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(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); + } + } } void GDScriptByteCodeGenerator::write_and_left_operand(const Address &p_left_operand) { diff --git a/modules/gdscript/gdscript_byte_codegen.h b/modules/gdscript/gdscript_byte_codegen.h index 1d1b22e196..dc05de9fc6 100644 --- a/modules/gdscript/gdscript_byte_codegen.h +++ b/modules/gdscript/gdscript_byte_codegen.h @@ -481,8 +481,7 @@ public: 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_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_builtin(const Address &p_target, const Address &p_source, Variant::Type p_type) override; + virtual void write_type_test(const Address &p_target, const Address &p_source, const GDScriptDataType &p_type) 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_end_and(const Address &p_target) override; diff --git a/modules/gdscript/gdscript_codegen.h b/modules/gdscript/gdscript_codegen.h index 6d42d152b9..7847ab28c7 100644 --- a/modules/gdscript/gdscript_codegen.h +++ b/modules/gdscript/gdscript_codegen.h @@ -90,8 +90,7 @@ public: 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_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_builtin(const Address &p_target, const Address &p_source, Variant::Type p_type) = 0; + virtual void write_type_test(const Address &p_target, const Address &p_source, const GDScriptDataType &p_type) = 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_end_and(const Address &p_target) = 0; diff --git a/modules/gdscript/gdscript_compiler.cpp b/modules/gdscript/gdscript_compiler.cpp index b34be11169..efa75528fc 100644 --- a/modules/gdscript/gdscript_compiler.cpp +++ b/modules/gdscript/gdscript_compiler.cpp @@ -148,13 +148,8 @@ GDScriptDataType GDScriptCompiler::_gdtype_from_datatype(const GDScriptParser::D } } break; case GDScriptParser::DataType::ENUM: - result.has_type = true; result.kind = GDScriptDataType::BUILTIN; - if (p_datatype.is_meta_type) { - result.builtin_type = Variant::DICTIONARY; - } else { - result.builtin_type = Variant::INT; - } + result.builtin_type = p_datatype.builtin_type; break; case GDScriptParser::DataType::RESOLVING: case GDScriptParser::DataType::UNRESOLVED: { @@ -494,17 +489,10 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &code } break; case GDScriptParser::Node::CAST: { 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(og_cast_type, codegen.script); + GDScriptDataType cast_type = _gdtype_from_datatype(cn->get_datatype(), codegen.script); GDScriptCodeGenerator::Address result; 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. result = codegen.add_temporary(cast_type); @@ -817,28 +805,6 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &code gen->pop_temporary(); } } 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: { GDScriptCodeGenerator::Address left_operand = _parse_expression(codegen, r_error, binary->left_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; } 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: { const GDScriptParser::AssignmentNode *assignment = static_cast<const GDScriptParser::AssignmentNode *>(p_expression); diff --git a/modules/gdscript/gdscript_disassembler.cpp b/modules/gdscript/gdscript_disassembler.cpp index d4f4358ac1..0acc03be3d 100644 --- a/modules/gdscript/gdscript_disassembler.cpp +++ b/modules/gdscript/gdscript_disassembler.cpp @@ -135,23 +135,56 @@ void GDScriptFunction::disassemble(const Vector<String> &p_code_lines) const { incr += 5; } break; - case OPCODE_EXTENDS_TEST: { - text += "is object "; - text += DADDR(3); - text += " = "; + case OPCODE_TYPE_TEST_BUILTIN: { + text += "type test "; text += DADDR(1); - text += " is "; + text += " = "; text += DADDR(2); + text += " is "; + text += Variant::get_type_name(Variant::Type(_code_ptr[ip + 3])); incr += 4; } break; - case OPCODE_IS_BUILTIN: { - text += "is builtin "; + case OPCODE_TYPE_TEST_ARRAY: { + text += "type test "; + 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 += 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 += Variant::get_type_name(Variant::Type(_code_ptr[ip + 3])); + text += DADDR(3); incr += 4; } break; diff --git a/modules/gdscript/gdscript_editor.cpp b/modules/gdscript/gdscript_editor.cpp index 8cfd48b52b..f79ba40e71 100644 --- a/modules/gdscript/gdscript_editor.cpp +++ b/modules/gdscript/gdscript_editor.cpp @@ -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.. // Super dirty hack, but very useful. // Credit: Zylann. // TODO: this could be hacked to detect ANDed conditions too... - const GDScriptParser::BinaryOpNode *op = static_cast<const GDScriptParser::BinaryOpNode *>(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) { + const GDScriptParser::TypeTestNode *type_test = static_cast<const GDScriptParser::TypeTestNode *>(suite->parent_if->condition); + 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. 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; - GDScriptCompletionIdentifier is_type; - if (_guess_expression_type(c, op->right_operand, is_type)) { - id_type = is_type.type; - id_type.is_meta_type = false; + if ((!id_type.is_set() || id_type.is_variant()) && type_test->test_datatype.is_hard_type()) { + id_type = type_test->test_datatype; if (last_assign_line < c.current_line) { // Override last assignment. last_assign_line = c.current_line; diff --git a/modules/gdscript/gdscript_function.h b/modules/gdscript/gdscript_function.h index 2624fb8dd9..993d9f27d9 100644 --- a/modules/gdscript/gdscript_function.h +++ b/modules/gdscript/gdscript_function.h @@ -219,8 +219,10 @@ public: enum Opcode { OPCODE_OPERATOR, OPCODE_OPERATOR_VALIDATED, - OPCODE_EXTENDS_TEST, - OPCODE_IS_BUILTIN, + OPCODE_TYPE_TEST_BUILTIN, + OPCODE_TYPE_TEST_ARRAY, + OPCODE_TYPE_TEST_NATIVE, + OPCODE_TYPE_TEST_SCRIPT, OPCODE_SET_KEYED, OPCODE_SET_KEYED_VALIDATED, OPCODE_SET_INDEXED_VALIDATED, diff --git a/modules/gdscript/gdscript_parser.cpp b/modules/gdscript/gdscript_parser.cpp index b5cb5a4680..b32313dad4 100644 --- a/modules/gdscript/gdscript_parser.cpp +++ b/modules/gdscript/gdscript_parser.cpp @@ -2463,9 +2463,6 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_binary_operator(Expression operation->operation = BinaryOpNode::OP_LOGIC_OR; operation->variant_op = Variant::OP_OR; break; - case GDScriptTokenizer::Token::IS: - operation->operation = BinaryOpNode::OP_TYPE_TEST; - break; case GDScriptTokenizer::Token::IN: operation->operation = BinaryOpNode::OP_CONTENT_TEST; operation->variant_op = Variant::OP_IN; @@ -3161,6 +3158,22 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_lambda(ExpressionNode *p_p 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) { push_error(R"("yield" was removed in Godot 4.0. Use "await" instead.)"); return nullptr; @@ -3529,7 +3542,7 @@ GDScriptParser::ParseRule *GDScriptParser::get_rule(GDScriptTokenizer::Token::Ty { nullptr, nullptr, PREC_NONE }, // EXTENDS, { &GDScriptParser::parse_lambda, nullptr, PREC_NONE }, // FUNC, { 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, { &GDScriptParser::parse_preload, nullptr, PREC_NONE }, // PRELOAD, { &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: push_text(" OR "); break; - case BinaryOpNode::OP_TYPE_TEST: - push_text(" IS "); - break; case BinaryOpNode::OP_CONTENT_TEST: push_text(" IN "); break; @@ -4584,6 +4594,9 @@ void GDScriptParser::TreePrinter::print_expression(ExpressionNode *p_expression) case Node::TERNARY_OPERATOR: print_ternary_op(static_cast<TernaryOpNode *>(p_expression)); break; + case Node::TYPE_TEST: + print_type_test(static_cast<TypeTestNode *>(p_expression)); + break; case Node::UNARY_OPERATOR: print_unary_op(static_cast<UnaryOpNode *>(p_expression)); 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) { // Surround in parenthesis for disambiguation. push_text("("); diff --git a/modules/gdscript/gdscript_parser.h b/modules/gdscript/gdscript_parser.h index 0ba0d5b6da..7d50910e5d 100644 --- a/modules/gdscript/gdscript_parser.h +++ b/modules/gdscript/gdscript_parser.h @@ -91,6 +91,7 @@ public: struct SuiteNode; struct TernaryOpNode; struct TypeNode; + struct TypeTestNode; struct UnaryOpNode; struct VariableNode; struct WhileNode; @@ -288,6 +289,7 @@ public: SUITE, TERNARY_OPERATOR, TYPE, + TYPE_TEST, UNARY_OPERATOR, VARIABLE, WHILE, @@ -426,7 +428,6 @@ public: OP_BIT_XOR, OP_LOGIC_AND, OP_LOGIC_OR, - OP_TYPE_TEST, OP_CONTENT_TEST, OP_COMP_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 { enum OpType { OP_POSITIVE, @@ -1460,6 +1471,7 @@ private: ExpressionNode *parse_attribute(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_type_test(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); TypeNode *parse_type(bool p_allow_void = false); @@ -1541,8 +1553,9 @@ public: void print_statement(Node *p_statement); void print_subscript(SubscriptNode *p_subscript); void print_suite(SuiteNode *p_suite); - void print_type(TypeNode *p_type); 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_variable(VariableNode *p_variable); void print_while(WhileNode *p_while); diff --git a/modules/gdscript/gdscript_utility_functions.cpp b/modules/gdscript/gdscript_utility_functions.cpp index 758b61bb31..8862450121 100644 --- a/modules/gdscript/gdscript_utility_functions.cpp +++ b/modules/gdscript/gdscript_utility_functions.cpp @@ -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 { @@ -638,6 +714,7 @@ void GDScriptUtilityFunctions::register_functions() { REGISTER_FUNC_NO_ARGS(print_stack, false, Variant::NIL); REGISTER_FUNC_NO_ARGS(get_stack, false, Variant::ARRAY); REGISTER_FUNC(len, true, Variant::INT, VARARG("var")); + REGISTER_FUNC(is_instance_of, true, Variant::BOOL, VARARG("value"), VARARG("type")); } void GDScriptUtilityFunctions::unregister_functions() { diff --git a/modules/gdscript/gdscript_vm.cpp b/modules/gdscript/gdscript_vm.cpp index 9f7d27d841..ba400b8e15 100644 --- a/modules/gdscript/gdscript_vm.cpp +++ b/modules/gdscript/gdscript_vm.cpp @@ -201,8 +201,10 @@ void (*type_init_function_table[])(Variant *) = { static const void *switch_table_ops[] = { \ &&OPCODE_OPERATOR, \ &&OPCODE_OPERATOR_VALIDATED, \ - &&OPCODE_EXTENDS_TEST, \ - &&OPCODE_IS_BUILTIN, \ + &&OPCODE_TYPE_TEST_BUILTIN, \ + &&OPCODE_TYPE_TEST_ARRAY, \ + &&OPCODE_TYPE_TEST_NATIVE, \ + &&OPCODE_TYPE_TEST_SCRIPT, \ &&OPCODE_SET_KEYED, \ &&OPCODE_SET_KEYED_VALIDATED, \ &&OPCODE_SET_INDEXED_VALIDATED, \ @@ -743,91 +745,95 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a } DISPATCH_OPCODE; - OPCODE(OPCODE_EXTENDS_TEST) { + OPCODE(OPCODE_TYPE_TEST_BUILTIN) { CHECK_SPACE(4); - GET_VARIANT_PTR(a, 0); - GET_VARIANT_PTR(b, 1); - GET_VARIANT_PTR(dst, 2); - -#ifdef DEBUG_ENABLED - if (b->get_type() != Variant::OBJECT || b->operator Object *() == nullptr) { - err_text = "Right operand of 'is' is not a class."; - OPCODE_BREAK; - } -#endif + GET_VARIANT_PTR(dst, 0); + GET_VARIANT_PTR(value, 1); - bool extends_ok = false; - if (a->get_type() == Variant::OBJECT && a->operator Object *() != nullptr) { -#ifdef DEBUG_ENABLED - bool was_freed; - Object *obj_A = a->get_validated_object_with_check(was_freed); + Variant::Type builtin_type = (Variant::Type)_code_ptr[ip + 3]; + GD_ERR_BREAK(builtin_type < 0 || builtin_type >= Variant::VARIANT_MAX); - if (was_freed) { - err_text = "Left operand of 'is' is a previously freed instance."; - OPCODE_BREAK; - } + *dst = value->get_type() == builtin_type; + ip += 4; + } + DISPATCH_OPCODE; - Object *obj_B = b->get_validated_object_with_check(was_freed); + OPCODE(OPCODE_TYPE_TEST_ARRAY) { + CHECK_SPACE(6); - if (was_freed) { - err_text = "Right operand of 'is' is a previously freed instance."; - OPCODE_BREAK; - } -#else + GET_VARIANT_PTR(dst, 0); + GET_VARIANT_PTR(value, 1); - Object *obj_A = *a; - Object *obj_B = *b; -#endif // DEBUG_ENABLED + 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]; - GDScript *scr_B = Object::cast_to<GDScript>(obj_B); + 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; + } - 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. + *dst = result; + ip += 6; + } + DISPATCH_OPCODE; - 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; - } + OPCODE(OPCODE_TYPE_TEST_NATIVE) { + CHECK_SPACE(4); - cmp = cmp->_base; - } - } + GET_VARIANT_PTR(dst, 0); + GET_VARIANT_PTR(value, 1); - } else { - GDScriptNativeClass *nc = Object::cast_to<GDScriptNativeClass>(obj_B); + 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]; -#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()); - } + 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; } - *dst = extends_ok; + *dst = object && ClassDB::is_parent_class(object->get_class_name(), native_type); ip += 4; } DISPATCH_OPCODE; - OPCODE(OPCODE_IS_BUILTIN) { + OPCODE(OPCODE_TYPE_TEST_SCRIPT) { CHECK_SPACE(4); - GET_VARIANT_PTR(value, 0); - GET_VARIANT_PTR(dst, 1); - Variant::Type var_type = (Variant::Type)_code_ptr[ip + 3]; + GET_VARIANT_PTR(dst, 0); + GET_VARIANT_PTR(value, 1); - 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); + + 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 = value->get_type() == var_type; + *dst = result; ip += 4; } DISPATCH_OPCODE; diff --git a/modules/gdscript/tests/scripts/analyzer/features/type_test_usage.gd b/modules/gdscript/tests/scripts/analyzer/features/type_test_usage.gd new file mode 100644 index 0000000000..12dc0b93df --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/type_test_usage.gd @@ -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') diff --git a/modules/gdscript/tests/scripts/analyzer/features/type_test_usage.out b/modules/gdscript/tests/scripts/analyzer/features/type_test_usage.out new file mode 100644 index 0000000000..1b47ed10dc --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/type_test_usage.out @@ -0,0 +1,2 @@ +GDTEST_OK +ok |