-
-
Save dalexeev/bd4e952a601b18ae3a94919f89b98928 to your computer and use it in GitHub Desktop.
/**************************************************************************/ | |
/* godot_type.cpp */ | |
/**************************************************************************/ | |
/* This file is part of: */ | |
/* GODOT ENGINE */ | |
/* https://godotengine.org */ | |
/**************************************************************************/ | |
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ | |
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ | |
/* */ | |
/* Permission is hereby granted, free of charge, to any person obtaining */ | |
/* a copy of this software and associated documentation files (the */ | |
/* "Software"), to deal in the Software without restriction, including */ | |
/* without limitation the rights to use, copy, modify, merge, publish, */ | |
/* distribute, sublicense, and/or sell copies of the Software, and to */ | |
/* permit persons to whom the Software is furnished to do so, subject to */ | |
/* the following conditions: */ | |
/* */ | |
/* The above copyright notice and this permission notice shall be */ | |
/* included in all copies or substantial portions of the Software. */ | |
/* */ | |
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ | |
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ | |
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ | |
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ | |
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ | |
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ | |
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ | |
/**************************************************************************/ | |
#include "godot_type.h" | |
GodotType GodotType::make_builtin_type(Variant::Type p_builtin_type, const Vector<GodotType> &p_params) { | |
ERR_FAIL_INDEX_V_MSG(p_builtin_type, Variant::VARIANT_MAX, make_variant_type(), "..."); // TODO | |
GodotType type; | |
type.builtin_type = p_builtin_type; | |
bool param_count_valid = false; | |
switch (p_builtin_type) { | |
case Variant::ARRAY: | |
if (p_params.is_empty()) { | |
param_count_valid = true; | |
type.params.push_back(make_variant_type()); | |
} else if (p_params.size() == 1) { | |
ERR_FAIL_COND_V_MSG(p_params[0].builtin_type == Variant::ARRAY && !p_params[0].params[0].is_variant(), make_variant_type(), "..."); // TODO | |
param_count_valid = true; | |
type.params.push_back(p_params[0]); | |
} | |
break; | |
case Variant::OBJECT: | |
param_count_valid = p_params.is_empty(); | |
type.name = SNAME("Object"); | |
break; | |
default: | |
param_count_valid = p_params.is_empty(); | |
break; | |
} | |
ERR_FAIL_COND_V_MSG(!param_count_valid, make_variant_type(), "..."); // TODO | |
return type; | |
} | |
GodotType GodotType::make_native_type(const StringName &p_native_type) { | |
ERR_FAIL_COND_V_MSG(!ClassDB::class_exists(p_native_type), make_builtin_type(Variant::OBJECT), "..."); // TODO | |
GodotType type; | |
type.builtin_type = Variant::OBJECT; | |
type.name = p_native_type; | |
return type; | |
} | |
GodotType GodotType::make_script_type(const Ref<Script> &p_script_type) { | |
ERR_FAIL_COND_V_MSG(p_script_type.is_null(), make_builtin_type(Variant::OBJECT), "..."); // TODO | |
GodotType type; | |
type.builtin_type = Variant::OBJECT; | |
type.name = p_script_type->get_instance_base_type(); | |
type.script_type = p_script_type; | |
return type; | |
} | |
GodotType GodotType::make_generic_type(const StringName &p_generic_name) { | |
ERR_FAIL_COND_V_MSG(p_generic_name == StringName(), "..."); // TODO | |
GodotType type; | |
type.kind = GENERIC; | |
type.name = p_generic_name; | |
return type; | |
} | |
GodotType GodotType::make_enum_type(const StringName &p_enum_name) { // TODO: scopes?! | |
ERR_FAIL_COND_V_MSG(/*...*/, make_builtin_type(Variant::INT), "..."); // TODO | |
GodotType type; | |
type.kind = ENUM; | |
type.builtin_type = Variant::INT; | |
type.name = p_enum_name; // TODO | |
return type; | |
} | |
GodotType GodotType::make_bitfield_type(const GodotType &p_enum_type) { | |
ERR_FAIL_COND_V_MSG(!is_enum() && !is_generic(), make_builtin_type(Variant::INT), "..."); // TODO | |
GodotType type; | |
type.kind = BITFIELD; | |
type.builtin_type = Variant::INT; | |
type.params.push_back(p_enum_type); | |
return type; | |
} | |
GodotType GodotType::from_variant_value(const Variant &p_value) { | |
GodotType type; | |
type.builtin_type = p_value.get_type(); | |
switch (type.builtin_type) { | |
case Variant::ARRAY: { | |
const Array array = p_value; | |
const Ref<Script> script = array.get_typed_script(); | |
if (script.is_valid()) { | |
type.params.push_back(make_script_type(script)); | |
} else if (array.get_typed_class_name() != StringName()) { | |
type.params.push_back(make_native_type(array.get_typed_class_name())); | |
} else if ((Variant::Type)array.get_typed_builtin() != Variant::NIL) { | |
type.params.push_back(make_builtin_type((Variant::Type)array.get_typed_builtin())); | |
} else { | |
type.params.push_back(make_variant_type()); | |
} | |
} break; | |
case Variant::OBJECT: | |
// TODO | |
break; | |
default: | |
break; | |
} | |
return type; | |
} | |
GodotType GodotType::from_property_info(const PropertyInfo &p_info) { | |
GodotType type; | |
ERR_FAIL_INDEX_V_MSG(p_info.type, Variant::VARIANT_MAX, make_variant_type(), "..."); // TODO | |
type.builtin_type = p_info.type; | |
switch (type.builtin_type) { | |
case Variant::NIL: | |
if (p_info.hint == PROPERTY_HINT_GENERIC_TYPE && !info.hint_string.is_empty()) { | |
type.kind = GENERIC; | |
type.name = info.hint_string; | |
} else if (p_info.usage & PROPERTY_USAGE_NIL_IS_VARIANT) { | |
type.kind = VARIANT; | |
} | |
break; | |
case Variant::INT: | |
if (p_info.class_name != StringName()) { | |
if (p_info.usage & PROPERTY_USAGE_CLASS_IS_ENUM) { | |
type.kind = ENUM; | |
//type.name = p_info.class_name; // TODO | |
} else if (p_info.usage & PROPERTY_USAGE_CLASS_IS_BITFIELD) { | |
GodotType enum_type; | |
enum_type.kind = ENUM; | |
enum_type.builtin_type = Variant::INT; | |
//enum_type.name = p_info.class_name; // TODO | |
type.kind = BITFIELD; | |
type.params.push_back(enum_type); | |
} | |
} | |
break; | |
case Variant::ARRAY: | |
// TODO | |
break; | |
case Variant::OBJECT: | |
// TODO | |
break; | |
default: | |
break; | |
} | |
return type; | |
} | |
PropertyInfo GodotType::to_property_info(const String &p_property_name) const { | |
PropertyInfo info; | |
info.name = p_property_name; | |
info.type = builtin_type; | |
switch (kind) { | |
case BUILTIN: | |
break; | |
case VARIANT: | |
info.usage |= PROPERTY_USAGE_NIL_IS_VARIANT; | |
break; | |
case GENERIC: | |
info.hint = PROPERTY_HINT_GENERIC_TYPE; | |
info.hint_string = name; | |
info.usage |= PROPERTY_USAGE_NIL_IS_VARIANT; // ... // TODO | |
break; | |
case ENUM: | |
info.usage |= PROPERTY_USAGE_CLASS_IS_ENUM; | |
info.class_name = /*...*/; // TODO | |
break; | |
case BITFIELD: | |
info.usage |= PROPERTY_USAGE_CLASS_IS_BITFIELD; | |
info.class_name = /*...*/; // TODO | |
break; | |
} | |
switch (builtin_type) { | |
case Variant::ARRAY: | |
if (!params[0].is_variant()) { | |
info.hint == PROPERTY_HINT_ARRAY_TYPE; | |
info.hint_string = /*...*/; // TODO | |
} | |
break; | |
case Variant::OBJECT: | |
// PROPERTY_HINT_RESOURCE_TYPE | |
// PROPERTY_HINT_NODE_TYPE | |
// TODO | |
break; | |
default: | |
break; | |
} | |
return info; | |
} | |
bool GodotType::is_recursively_generic() const { | |
if (is_generic()) { | |
return true; | |
} | |
for (int i = 0; i < params.size(); i++) { | |
if (params[i].is_recursively_generic()) { | |
return true; | |
} | |
} | |
return false; | |
} | |
void GodotType::set_param(int p_index, const GodotType &p_type) { | |
ERR_FAIL_INDEX_MSG(p_index, params.size(), "..."); // TODO | |
if (kind == BITFIELD) { | |
ERR_FAIL_COND_MSG(!p_type.is_enum() && !p_type.is_generic(), "..."); // TODO | |
} else if (builtin_type == Variant::ARRAY) { | |
ERR_FAIL_COND_MSG(p_type.builtin_type == Variant::ARRAY && !p_type.params[0].is_variant(), "..."); // TODO | |
} else { | |
ERR_FAIL_MSG("..."); // TODO | |
} | |
params.write[p_index] = p_type; | |
} | |
void apply_generic(const StringName &p_generic_name, const GodotType &p_new_type) { | |
if (is_generic()) { | |
if (name == p_generic_name) { | |
// **Only top-level** generic type can be replaced without checks. | |
*this = p_new_type; | |
} | |
return; // A generic type has no parameters, exit anyway. | |
} | |
for (int i = 0; i < params.size(); i++) { | |
if (params[i].is_generic()) { | |
if (params[i].name == p_generic_name) { | |
// The parameter must be changed **via setter**. | |
set_param(i, p_new_type); | |
} // else: Skipping the generic parameter. | |
} else { | |
// Recursively call the method on **non-generic** parameters. | |
params.write[i].apply_generic(p_generic_name, p_new_type); | |
} | |
} | |
} | |
GodotType GodotType::get_base_type() const { | |
if (builtin_type == Variant::OBJECT) { | |
if (script_type.is_valid()) { | |
const Ref<Script> base_script = script_type->get_base_script(); | |
if (base_script.is_valid()) { | |
return make_script_type(base_script); | |
} else { | |
return make_native_type(script_type->get_instance_base_type()); | |
} | |
} else { | |
if (name == SNAME("Object")) { | |
return make_variant_type(); | |
} else { | |
return make_native_type(ClassDB::get_parent_class(name)); | |
} | |
} | |
} else { | |
return make_variant_type(); | |
} | |
} | |
bool GodotType::is_supertype_of(const GodotType &p_other) const { | |
// TODO | |
} |
/**************************************************************************/ | |
/* godot_type.h */ | |
/**************************************************************************/ | |
/* This file is part of: */ | |
/* GODOT ENGINE */ | |
/* https://godotengine.org */ | |
/**************************************************************************/ | |
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ | |
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ | |
/* */ | |
/* Permission is hereby granted, free of charge, to any person obtaining */ | |
/* a copy of this software and associated documentation files (the */ | |
/* "Software"), to deal in the Software without restriction, including */ | |
/* without limitation the rights to use, copy, modify, merge, publish, */ | |
/* distribute, sublicense, and/or sell copies of the Software, and to */ | |
/* permit persons to whom the Software is furnished to do so, subject to */ | |
/* the following conditions: */ | |
/* */ | |
/* The above copyright notice and this permission notice shall be */ | |
/* included in all copies or substantial portions of the Software. */ | |
/* */ | |
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ | |
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ | |
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ | |
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ | |
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ | |
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ | |
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ | |
/**************************************************************************/ | |
#ifndef GODOT_TYPE_H | |
#define GODOT_TYPE_H | |
#include "core/variant/variant.h" | |
class GodotType { | |
enum Kind { | |
BUILTIN, | |
VARIANT, // `builtin_type == Variant::NIL`. | |
GENERIC, // `builtin_type == Variant::NIL`. | |
ENUM, // `builtin_type == Variant::INT`. | |
BITFIELD, // `builtin_type == Variant::INT`. | |
}; | |
Kind kind = BUILTIN; | |
Variant::Type builtin_type = Variant::NIL; | |
// `kind == GENERIC`: generic type name (for example `T`). | |
// `kind == ENUM`: enum name. | |
// `builtin_type == Variant::OBJECT`: native class name. | |
StringName name; | |
// `builtin_type == Variant::OBJECT`. | |
Ref<Script> script_type; // TODO: WeakRef? | |
Vector<GodotType> params; // TODO: `Vector<Variant>`? | |
public: | |
_FORCE_INLINE_ static GodotType make_nil_type() { return GodotType(); } | |
_FORCE_INLINE_ static GodotType make_variant_type() { | |
GodotType type; | |
type.kind = VARIANT; | |
return type; | |
} | |
static GodotType make_builtin_type(Variant::Type p_builtin_type, const Vector<GodotType> &p_params = Vector<GodotType>()); | |
static GodotType make_native_type(const StringName &p_native_type); | |
static GodotType make_script_type(const Ref<Script> &p_script_type); | |
static GodotType make_generic_type(const StringName &p_generic_name); | |
static GodotType make_enum_type(const StringName &p_enum_name); // TODO: scopes?! | |
static GodotType make_bitfield_type(const GodotType &p_enum_type); | |
static GodotType from_variant_value(const Variant &p_value); | |
static GodotType from_property_info(const PropertyInfo &p_info); | |
PropertyInfo to_property_info(const String &p_property_name = String()) const; | |
_FORCE_INLINE_ bool is_nil() const { return kind == BUILTIN && builtin_type == Variant::NIL; } | |
_FORCE_INLINE_ bool is_variant() const { return kind == VARIANT; } | |
_FORCE_INLINE_ Variant::Type get_builtin_type() const { return builtin_type; } | |
_FORCE_INLINE_ StringName get_native_type() const { return (builtin_type == Variant::OBJECT) ? name : StringName(); } | |
_FORCE_INLINE_ Ref<Script> get_script_type() const { return script_type; } | |
bool is_recursively_generic() const; // Is generic or contains generic. // TODO: Rename? | |
_FORCE_INLINE_ bool is_generic() const { return kind == GENERIC; } | |
_FORCE_INLINE_ StringName get_generic_name() const { return (kind == GENERIC) ? name : StringName(); } | |
_FORCE_INLINE_ bool is_enum() const { return kind == ENUM; } | |
_FORCE_INLINE_ StringName get_enum_name() const { return (kind == ENUM) ? name : StringName(); } // TODO: scopes?! | |
_FORCE_INLINE_ bool is_bitfield() const { return kind == BITFIELD; } | |
_FORCE_INLINE_ int get_param_count() const { return params.size(); } | |
_FORCE_INLINE_ GodotType get_param(int p_index) const { | |
ERR_FAIL_INDEX_V(p_index, params.size(), make_variant_type()); | |
return params[p_index]; | |
} | |
void set_param(int p_index, const GodotType &p_type); | |
void apply_generic(const StringName &p_generic_name, const GodotType &p_new_type); | |
GodotType get_base_type() const; | |
bool is_supertype_of(const GodotType &p_other) const; | |
_FORCE_INLINE_ bool is_subtype_of(const GodotType &p_other) const { return p_other.is_supertype_of(*this); } | |
bool operator==(const GodotType &p_other) const { | |
// TODO | |
} | |
bool operator!=(const GodotType &p_other) const { | |
return !(*this == p_other); | |
} | |
void operator=(const GodotType &p_other) { | |
kind = p_other.kind; | |
builtin_type = p_other.builtin_type; | |
name = p_other.name; | |
script_type = p_other.script_type; | |
params = p_other.params; | |
} | |
GodotType() {} | |
GodotType(const GodotType &p_other) { | |
*this = p_other; | |
} | |
}; | |
#endif // GODOT_TYPE_H | |
// ScriptLanguage methods for is_subtype_of and etc. | |
// methods to conversion from/to string / docdata | |
// methods to make string type hints, taking into account scopes context*, language | |
// * - contextualize type name | |
// * - scopes/namespaces need to be represented too, even if it can be not a type? | |
// For exposing we need a separated class (extends RefCounted) with GDCLASS macro. |
2. I don't understand you. By "lightweight structure" I didn't mean the structs you're currently working on. What I meant was that the only way to expose a non-Object
class is to add a new built-in Variant
type. Yes, it's difficult, but not impossible, see PackedVector4Array
PR, which will probably be merged soon. As for Array
structs, they are a modification of Variant::ARRAY
, not a separate Variant
type. But at the same time they can be represented in GodotType
as a separate kind = STRUCT
, just as for Variant::INT
there are 3 kinds (BUILTIN
, ENUM
and BITFIELD
).
4. In case of custom generics this will work too:
BinaryTree[T]
:{ kind = SCRIPT, script_type = Ref<Script>(...), params = { <GodotType GenericType(T)> } }
BinaryTree[int]
:{ kind = SCRIPT, script_type = Ref<Script>(...), params = { <GodotType int> } }
BinaryTree[BitField[T]]
:{ kind = SCRIPT, script_type = Ref<Script>(...), params = { <bitfield_type> } }
- where
<bitfield_type>
is{ kind = BITFIELD, params = { <GodotType GenericType(T)> } }
- where
5. This is not GodotType
's responsibility, it only represents the type. We might even expose static methods to create GodotType
s at runtime, it should be ok. GDScript checks constancy for type specifiers and const
initializers. We could potentially allow the following:
const MyType: GodotType = <GodotType>
var my_var: MyType
But not the following:
var MyType: GodotType = <GodotType>
var my_var: MyType
As for the type order, what you suggest can be achieved with kind = METATYPE
too. The advantage is that you can universally use generic types, substituting specific types instead of generic placeholders (i.e. instantiate a generic type). In my experience with GDScriptAnalyzer
, I really think that the is_meta_type
flag (or integer order
as you suggested) is a bad thing, as it is bug prone due to insufficient differentiation between first order types and metatypes. Maybe this is less critical in the case of GodotType
since all properties are in the private
section, unlike GDScriptParser::DataType
. Also, with your approach we can't do something like Array[MetaType[T]]
.
- Didn't know about about
PackedVector4Array
, I'll have to check out that PR!
.4. Ah, that makes a lot of sense! Another approach might be to separate the Kind
enum into a Source
enum with values BUILTIN, NATIVE, SCRIPT
and a TypeConstructor
enum with values like ENUM, BITFIELD, STRUCT, UNION, ARRAY, DICTIONARY, ...(etc), USER
. This way, GodotType
can tell whether e.g. an ENUM
is native or user-defined.
.5. I think it's fair to put the responsibility of determining constancy/meta-ness/order with the analyzer. That said, I could see it being potentially useful for a user to be able to reflect on whether a particular value is constant or not. The idea to use nested MetaType
wrappers so encode order is neat, but I think it has some limitations. If we say T
has order 0, MetaType[T]
has order 1, and MetaType[MetaType[T]]
has order 2, what is the order of something like Array[MetaType[T]]
or MetaType[Dictionary[T, MetaType[T]]]
? The algorithm would have to know how to properly "unwrap" these situations wouldn't it? On the other hand, an order
index makes determining the order pretty straightforward: Array[MetaType[T]]
has order 1 = min(0 + 1,)
and MetaType[Dictionary[T, MetaType[T]]]
has order 1 = 1 + min(0, 0 + 1)
.
I have a couple questions about this (unrelated to my new questions on #737), which are in :
- When you mentioned drafting a proposal for first-class types in the discussion of proposal #737, I assumed you meant something akin to C#'s
Type
class or Java'sClass
class, which would replace the engine's decentralized type systems where e.g.typeof()
andgetclass()
being not only separate functions but also functions with different return types and if you want to query the features of a type, you have to do it from at least 3 different places (ClassDB
, theScript
resource, and, when compiling GDScript,GDScriptParser::DataType
). TheGodotType
here seems more narrowly focused though (for example, there's noget_methods()
function), but I'm don't understand what that focus is. What are the intended use cases forGodotType
? Is it just supposed to provide a way to check whether one type "inherits" another type's features? - Are you planning to represent type safe
Callable
andSignal
objects with this? If so, it seems like at minimum,GodotType
would need some way to check if aCallable
returns something, such as ahas_return_type
flag to tell you whetherparams
contains the return type or areturn_type
instance variable that points to aGodotType
if the type object represents aCallable
with a return type.
GodotType
a new built-in variant does make sense, but isn't adding a new built-in type pretty hard? IfGodotType
were to be its own built-in, I'd think structs should be as well. I suppose the problem with makingGodotType
a struct is thatGodotType
really wants to have methods, which is not currently planned for structs.String|int
is represented by someUNION
identifier along withparams = { GodotTypeString, GodotTypeInt }
. My concern is that if that identifier is an enum value, it becomes hard to represent user-defined type constructors. For example, a user might want to defineBinaryTree<T>
. If the identifier is aStringName
, they just set it toBinaryTree
, but if the identifier is an enum, it seems like we'd have to anticipate with aKind::BINARY_TREE
value.GodotType
instance know whether it is a compile-time constant or not, especially if it is possible for the user to create them at run time? Perhaps it's the analyzer's job to figure that out, though it would be cool if the type representation used by the analyzer were as close as possible toGodotType
itself imo, and potentially easier to maintain I'd think.I was thinking about an approach that might address both points 1. and 5. The idea is to add an
int order
field toGodotType
, where0th
order is basically a value,1st
order is a type,2nd
order is a type of types (metatype) and so on. Then there would be a rule that the order of a type annotation must be greater than the order of the value it annotates and the order of aGodotType
instance is the minimum order of its parameters. Most types likeint
andString
would haveorder = 1
. A literal value like4
could be represented as aGodotType
havingorder = INT_MAX
for the purposes of type constructors. This way, you could doIt's likely that this idea is overcomplicating things to be honest, but it might make for a pretty elegant system.