Skip to content

Commit d36157e

Browse files
committed
Trait type check and trait type cast
1 parent bc0b0f5 commit d36157e

File tree

11 files changed

+239
-13
lines changed

11 files changed

+239
-13
lines changed

modules/gdscript/gdscript.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -186,6 +186,7 @@ class GDScript : public Script {
186186
StringName global_name; // `class_name`.
187187
String fully_qualified_name;
188188
String simplified_icon_path;
189+
Vector<StringName> traits_fqtn; // Fully-qualified trait names used by script.
189190
SelfList<GDScript> script_list;
190191

191192
SelfList<GDScriptFunctionState>::List pending_func_states;

modules/gdscript/gdscript_analyzer.cpp

Lines changed: 36 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2009,7 +2009,7 @@ void GDScriptAnalyzer::resolve_class_uses(GDScriptParser::ClassNode *p_class, co
20092009

20102010
// Store references to used traits for type checks.
20112011
p_class->traits_fqtn.append(trait->fqcn);
2012-
for (String other_trait_fqcn : trait->traits_fqtn) {
2012+
for (StringName other_trait_fqcn : trait->traits_fqtn) {
20132013
if (!p_class->traits_fqtn.has(other_trait_fqcn)) {
20142014
p_class->traits_fqtn.append(other_trait_fqcn);
20152015
}
@@ -4279,8 +4279,22 @@ void GDScriptAnalyzer::reduce_cast(GDScriptParser::CastNode *p_cast) {
42794279
cast_type.get_container_element_type_or_variant(0), cast_type.get_container_element_type_or_variant(1));
42804280
}
42814281

4282-
if (p_cast->operand->get_datatype().kind == GDScriptParser::DataType::TRAIT || cast_type.kind == GDScriptParser::DataType::TRAIT) {
4283-
push_error(vformat(R"(Invalid cast. Cannot convert from "%s" to "%s".)", p_cast->operand->get_datatype().to_string(), cast_type.to_string()), p_cast->cast_type);
4282+
if ((p_cast->operand->get_datatype().kind == GDScriptParser::DataType::CLASS || p_cast->operand->get_datatype().kind == GDScriptParser::DataType::TRAIT) && cast_type.kind == GDScriptParser::DataType::TRAIT) {
4283+
if (p_cast->operand->is_constant) {
4284+
push_error(vformat(R"(Invalid cast. Cannot convert from "%s" to "%s".)", p_cast->operand->get_datatype().to_string(), cast_type.to_string()), p_cast->cast_type);
4285+
} else {
4286+
GDScriptParser::DataType operand_type = p_cast->operand->get_datatype();
4287+
bool is_using_trait = false;
4288+
if (operand_type.class_type && cast_type.class_type) {
4289+
is_using_trait = operand_type.class_type->traits_fqtn.has(cast_type.class_type->fqcn);
4290+
}
4291+
if (operand_type.is_hard_type() && !is_using_trait && operand_type.kind != GDScriptParser::DataType::VARIANT) {
4292+
push_error(vformat(R"(Invalid cast. Cannot convert from "%s" to "%s".)", p_cast->operand->get_datatype().to_string(), cast_type.to_string()), p_cast->cast_type);
4293+
} else {
4294+
mark_node_unsafe(p_cast);
4295+
}
4296+
}
4297+
return;
42844298
}
42854299

42864300
if (!cast_type.is_variant()) {
@@ -4299,9 +4313,6 @@ void GDScriptAnalyzer::reduce_cast(GDScriptParser::CastNode *p_cast) {
42994313
valid = true;
43004314
} else if (op_type.kind == GDScriptParser::DataType::BUILTIN && cast_type.kind == GDScriptParser::DataType::BUILTIN) {
43014315
valid = Variant::can_convert(op_type.builtin_type, cast_type.builtin_type);
4302-
} else if (cast_type.kind == GDScriptParser::DataType::TRAIT) {
4303-
mark_node_unsafe(p_cast);
4304-
valid = true;
43054316
} else if (op_type.kind != GDScriptParser::DataType::BUILTIN && cast_type.kind != GDScriptParser::DataType::BUILTIN) {
43064317
valid = is_type_compatible(cast_type, op_type) || is_type_compatible(op_type, cast_type);
43074318
}
@@ -5707,7 +5718,25 @@ void GDScriptAnalyzer::reduce_type_test(GDScriptParser::TypeTestNode *p_type_tes
57075718
}
57085719

57095720
if (test_type.kind == GDScriptParser::DataType::TRAIT) {
5710-
push_error("Can't test for traits.", p_type_test->operand);
5721+
bool is_using_trait = false;
5722+
if (operand_type.class_type && test_type.class_type) {
5723+
is_using_trait = operand_type.class_type->traits_fqtn.has(test_type.class_type->fqcn);
5724+
}
5725+
5726+
if (p_type_test->operand->is_constant) {
5727+
p_type_test->is_constant = true;
5728+
p_type_test->reduced_value = is_using_trait;
5729+
5730+
if (!p_type_test->reduced_value) {
5731+
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);
5732+
}
5733+
} else {
5734+
if (operand_type.is_hard_type() && !is_using_trait && operand_type.kind != GDScriptParser::DataType::VARIANT) {
5735+
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);
5736+
} else {
5737+
downgrade_node_type_source(p_type_test->operand);
5738+
}
5739+
}
57115740
return;
57125741
}
57135742

modules/gdscript/gdscript_byte_codegen.cpp

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -674,6 +674,12 @@ void GDScriptByteCodeGenerator::write_type_test(const Address &p_target, const A
674674
append(p_source);
675675
append(p_type.native_type);
676676
} break;
677+
case GDScriptDataType::GDTRAIT: {
678+
append_opcode(GDScriptFunction::OPCODE_TYPE_TEST_TRAIT);
679+
append(p_target);
680+
append(p_source);
681+
append(p_type.trait_type);
682+
} break;
677683
case GDScriptDataType::SCRIPT:
678684
case GDScriptDataType::GDSCRIPT: {
679685
const Variant &script = p_type.script_type;
@@ -1049,6 +1055,13 @@ void GDScriptByteCodeGenerator::write_cast(const Address &p_target, const Addres
10491055
append_opcode(GDScriptFunction::OPCODE_CAST_TO_NATIVE);
10501056
index = get_constant_pos(nc) | (GDScriptFunction::ADDR_TYPE_CONSTANT << GDScriptFunction::ADDR_BITS);
10511057
} break;
1058+
case GDScriptDataType::GDTRAIT: {
1059+
append_opcode(GDScriptFunction::OPCODE_CAST_TO_TRAIT);
1060+
append(p_source);
1061+
append(p_target);
1062+
append(p_target.type.trait_type);
1063+
return;
1064+
} break;
10521065
case GDScriptDataType::SCRIPT:
10531066
case GDScriptDataType::GDSCRIPT: {
10541067
Variant script = p_type.script_type;

modules/gdscript/gdscript_compiler.cpp

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -138,7 +138,10 @@ GDScriptDataType GDScriptCompiler::_gdtype_from_datatype(const GDScriptParser::D
138138
result.script_type = result.script_type_ref.ptr();
139139
result.native_type = p_datatype.native_type;
140140
} break;
141-
case GDScriptParser::DataType::TRAIT:
141+
case GDScriptParser::DataType::TRAIT: {
142+
result.kind = GDScriptDataType::GDTRAIT;
143+
result.trait_type = p_datatype.class_type->fqcn;
144+
} break;
142145
case GDScriptParser::DataType::CLASS: {
143146
if (p_handle_metatype && p_datatype.is_meta_type) {
144147
result.kind = GDScriptDataType::NATIVE;
@@ -150,9 +153,6 @@ GDScriptDataType GDScriptCompiler::_gdtype_from_datatype(const GDScriptParser::D
150153
result.kind = GDScriptDataType::GDSCRIPT;
151154
result.builtin_type = p_datatype.builtin_type;
152155
result.native_type = p_datatype.native_type;
153-
if (p_datatype.kind == GDScriptParser::DataType::TRAIT) {
154-
result.kind = GDScriptDataType::GDTRAIT;
155-
}
156156

157157
bool is_local_class = parser->has_class(p_datatype.class_type);
158158

@@ -3143,6 +3143,7 @@ void GDScriptCompiler::make_scripts(GDScript *p_script, const GDScriptParser::Cl
31433143
p_script->local_name = p_class->identifier ? p_class->identifier->name : StringName();
31443144
p_script->global_name = p_class->get_global_name();
31453145
p_script->simplified_icon_path = p_class->simplified_icon_path;
3146+
p_script->traits_fqtn = p_class->traits_fqtn;
31463147

31473148
HashMap<StringName, Ref<GDScript>> old_subclasses;
31483149

modules/gdscript/gdscript_function.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ class GDScriptDataType {
6060

6161
Variant::Type builtin_type = Variant::NIL;
6262
StringName native_type;
63+
StringName trait_type;
6364
Script *script_type = nullptr;
6465
Ref<Script> script_type_ref;
6566

@@ -251,6 +252,7 @@ class GDScriptDataType {
251252
kind = p_other.kind;
252253
builtin_type = p_other.builtin_type;
253254
native_type = p_other.native_type;
255+
trait_type = p_other.trait_type;
254256
script_type = p_other.script_type;
255257
script_type_ref = p_other.script_type_ref;
256258
container_element_types = p_other.container_element_types;
@@ -272,6 +274,7 @@ class GDScriptFunction {
272274
OPCODE_TYPE_TEST_ARRAY,
273275
OPCODE_TYPE_TEST_DICTIONARY,
274276
OPCODE_TYPE_TEST_NATIVE,
277+
OPCODE_TYPE_TEST_TRAIT,
275278
OPCODE_TYPE_TEST_SCRIPT,
276279
OPCODE_SET_KEYED,
277280
OPCODE_SET_KEYED_VALIDATED,
@@ -298,6 +301,7 @@ class GDScriptFunction {
298301
OPCODE_ASSIGN_TYPED_SCRIPT,
299302
OPCODE_CAST_TO_BUILTIN,
300303
OPCODE_CAST_TO_NATIVE,
304+
OPCODE_CAST_TO_TRAIT,
301305
OPCODE_CAST_TO_SCRIPT,
302306
OPCODE_CONSTRUCT, // Only for basic types!
303307
OPCODE_CONSTRUCT_VALIDATED, // Only for basic types!
@@ -569,6 +573,7 @@ class GDScriptFunction {
569573
String _get_call_error(const String &p_where, const Variant **p_argptrs, int p_argcount, const Variant &p_ret, const Callable::CallError &p_err) const;
570574
String _get_callable_call_error(const String &p_where, const Callable &p_callable, const Variant **p_argptrs, int p_argcount, const Variant &p_ret, const Callable::CallError &p_err) const;
571575
Variant _get_default_variant_for_data_type(const GDScriptDataType &p_data_type);
576+
bool _is_class_using_trait(Script *p_class_script, const StringName &trait_type);
572577

573578
public:
574579
static constexpr int MAX_CALL_DEPTH = 2048; // Limit to try to avoid crash because of a stack overflow.

modules/gdscript/gdscript_parser.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -774,7 +774,7 @@ class GDScriptParser {
774774
String fqcn; // Fully-qualified class name. Identifies uniquely any class in the project.
775775
// Used Traits.
776776
Vector<UsesNode *> traits;
777-
Vector<String> traits_fqtn; // Fully-qualified trait names. Identifies uniquely any trait used by this class.
777+
Vector<StringName> traits_fqtn; // Fully-qualified trait names. Identifies uniquely any trait used by this class.
778778
#ifdef TOOLS_ENABLED
779779
ClassDocData doc_data;
780780

@@ -1276,7 +1276,7 @@ class GDScriptParser {
12761276
String path;
12771277
Vector<IdentifierNode *> name; // List for indexing Trait: uses A.B.C
12781278
String fqtn; // Fully-qualified trait names.
1279-
Vector<String> traits_fqtn; // From traits used by this trait.
1279+
Vector<StringName> traits_fqtn; // From traits used by this trait.
12801280

12811281
UsesNode() {
12821282
type = USES;

modules/gdscript/gdscript_vm.cpp

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -207,6 +207,14 @@ String GDScriptFunction::_get_callable_call_error(const String &p_where, const C
207207
}
208208
}
209209

210+
bool GDScriptFunction::_is_class_using_trait(Script *p_class_script, const StringName &trait_type) {
211+
GDScript *gdscript = Object::cast_to<GDScript>(p_class_script);
212+
if (gdscript && gdscript->traits_fqtn.has(trait_type)) {
213+
return true;
214+
}
215+
return false;
216+
}
217+
210218
void (*type_init_function_table[])(Variant *) = {
211219
nullptr, // NIL (shouldn't be called).
212220
&VariantInitializer<bool>::init, // BOOL.
@@ -258,6 +266,7 @@ void (*type_init_function_table[])(Variant *) = {
258266
&&OPCODE_TYPE_TEST_ARRAY, \
259267
&&OPCODE_TYPE_TEST_DICTIONARY, \
260268
&&OPCODE_TYPE_TEST_NATIVE, \
269+
&&OPCODE_TYPE_TEST_TRAIT, \
261270
&&OPCODE_TYPE_TEST_SCRIPT, \
262271
&&OPCODE_SET_KEYED, \
263272
&&OPCODE_SET_KEYED_VALIDATED, \
@@ -284,6 +293,7 @@ void (*type_init_function_table[])(Variant *) = {
284293
&&OPCODE_ASSIGN_TYPED_SCRIPT, \
285294
&&OPCODE_CAST_TO_BUILTIN, \
286295
&&OPCODE_CAST_TO_NATIVE, \
296+
&&OPCODE_CAST_TO_TRAIT, \
287297
&&OPCODE_CAST_TO_SCRIPT, \
288298
&&OPCODE_CONSTRUCT, \
289299
&&OPCODE_CONSTRUCT_VALIDATED, \
@@ -938,6 +948,34 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a
938948
}
939949
DISPATCH_OPCODE;
940950

951+
OPCODE(OPCODE_TYPE_TEST_TRAIT) {
952+
CHECK_SPACE(4);
953+
954+
GET_VARIANT_PTR(dst, 0);
955+
GET_VARIANT_PTR(value, 1);
956+
957+
int trait_type_idx = _code_ptr[ip + 3];
958+
GD_ERR_BREAK(trait_type_idx < 0 || trait_type_idx >= _global_names_count);
959+
const StringName trait_type = _global_names_ptr[trait_type_idx];
960+
961+
bool was_freed = false;
962+
Object *object = value->get_validated_object_with_check(was_freed);
963+
if (was_freed) {
964+
err_text = "Left operand of 'is' is a previously freed instance.";
965+
OPCODE_BREAK;
966+
}
967+
968+
bool result = false;
969+
if (object && object->get_script_instance()) {
970+
Script *script_ptr = object->get_script_instance()->get_script().ptr();
971+
result = _is_class_using_trait(script_ptr, trait_type);
972+
}
973+
974+
*dst = result;
975+
ip += 4;
976+
}
977+
DISPATCH_OPCODE;
978+
941979
OPCODE(OPCODE_TYPE_TEST_SCRIPT) {
942980
CHECK_SPACE(4);
943981

@@ -1671,6 +1709,47 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a
16711709
}
16721710
DISPATCH_OPCODE;
16731711

1712+
OPCODE(OPCODE_CAST_TO_TRAIT) {
1713+
CHECK_SPACE(4);
1714+
GET_VARIANT_PTR(src, 0);
1715+
GET_VARIANT_PTR(dst, 1);
1716+
GET_VARIANT_PTR(to_type, 2);
1717+
1718+
int trait_type_idx = _code_ptr[ip + 3];
1719+
GD_ERR_BREAK(trait_type_idx < 0 || trait_type_idx >= _global_names_count);
1720+
const StringName trait_type = _global_names_ptr[trait_type_idx];
1721+
1722+
#ifdef DEBUG_ENABLED
1723+
if (src->operator Object *() && !src->get_validated_object()) {
1724+
err_text = "Trying to cast a freed object.";
1725+
OPCODE_BREAK;
1726+
}
1727+
if (src->get_type() != Variant::OBJECT && src->get_type() != Variant::NIL) {
1728+
err_text = "Trying to assign a non-object value to a variable of trait '" + String(trait_type).replace("::", ".") + "'.";
1729+
OPCODE_BREAK;
1730+
}
1731+
#endif
1732+
bool valid = false;
1733+
1734+
if (src->get_type() != Variant::NIL && src->operator Object *() != nullptr) {
1735+
ScriptInstance *scr_inst = src->operator Object *()->get_script_instance();
1736+
1737+
if (scr_inst) {
1738+
Script *src_type = src->operator Object *()->get_script_instance()->get_script().ptr();
1739+
valid = _is_class_using_trait(src_type, trait_type);
1740+
}
1741+
}
1742+
1743+
if (valid) {
1744+
*dst = *src; // Valid cast, copy the source object
1745+
} else {
1746+
*dst = Variant(); // invalid cast, assign NULL
1747+
}
1748+
1749+
ip += 4;
1750+
}
1751+
DISPATCH_OPCODE;
1752+
16741753
OPCODE(OPCODE_CAST_TO_SCRIPT) {
16751754
CHECK_SPACE(4);
16761755
GET_VARIANT_PTR(src, 0);
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
trait Shearable:
2+
func shear() -> void:
3+
print("Sheared")
4+
5+
trait Milkable:
6+
func milk() -> void:
7+
print("Milked")
8+
9+
class FluffySheep:
10+
uses Shearable, Milkable
11+
12+
class FluffyRam:
13+
uses Shearable
14+
15+
class Hen:
16+
pass
17+
18+
func test():
19+
print(Hen is Shearable)
20+
print(Hen.new() is Milkable)
21+
print(FluffySheep as Shearable)
22+
print(FluffyRam.new() as Milkable)
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
GDTEST_ANALYZER_ERROR
2+
>> ERROR at line 19: Expression is of type "Hen" so it can't be of type "Shearable".
3+
>> ERROR at line 20: Expression is of type "Hen" so it can't be of type "Milkable".
4+
>> ERROR at line 21: Invalid cast. Cannot convert from "FluffySheep" to "Shearable".
5+
>> ERROR at line 22: Invalid cast. Cannot convert from "FluffyRam" to "Milkable".
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
trait Shearable:
2+
func shear() -> void:
3+
print("Sheared")
4+
5+
trait Milkable:
6+
func milk() -> void:
7+
print("Milked")
8+
9+
class FluffySheep:
10+
uses Shearable, Milkable
11+
12+
class FluffyRam:
13+
uses Shearable
14+
15+
class Hen:
16+
pass
17+
18+
func test():
19+
var my_animals : Array = []
20+
my_animals.append(FluffySheep.new())
21+
my_animals.append(FluffyRam.new())
22+
my_animals.append(Hen.new())
23+
24+
# 'is' operator tests
25+
print("FluffySheep is Shearable:", FluffySheep is Shearable)
26+
print("FluffySheep is Milkable:", FluffySheep is Milkable)
27+
28+
var Ram = FluffyRam.new()
29+
print("FluffyRam is Shearable:", Ram is Shearable)
30+
print("FluffyRam is Milkable:", Ram is Milkable)
31+
print()
32+
33+
var count := 1
34+
for animal in my_animals:
35+
# test 'as' casts
36+
print("Animal: " + str(count))
37+
var cast1 = animal as Shearable
38+
var cast2 = animal as Milkable
39+
print("Success Shearable cast : ", cast1 != null)
40+
print("Success Milkable cast : ", cast2 != null)
41+
42+
if animal is Shearable:
43+
animal.shear()
44+
if animal is Milkable:
45+
animal.milk()
46+
count += 1
47+
48+
print("ok")

0 commit comments

Comments
 (0)