Created
October 8, 2023 17:18
-
-
Save dalexeev/bd4e952a601b18ae3a94919f89b98928 to your computer and use it in GitHub Desktop.
GodotType
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/**************************************************************************/ | |
/* 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 | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/**************************************************************************/ | |
/* 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. |
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.
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
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 aSource
enum with valuesBUILTIN, NATIVE, SCRIPT
and aTypeConstructor
enum with values likeENUM, BITFIELD, STRUCT, UNION, ARRAY, DICTIONARY, ...(etc), USER
. This way,GodotType
can tell whether e.g. anENUM
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 sayT
has order 0,MetaType[T]
has order 1, andMetaType[MetaType[T]]
has order 2, what is the order of something likeArray[MetaType[T]]
orMetaType[Dictionary[T, MetaType[T]]]
? The algorithm would have to know how to properly "unwrap" these situations wouldn't it? On the other hand, anorder
index makes determining the order pretty straightforward:Array[MetaType[T]]
has order1 = min(0 + 1,)
andMetaType[Dictionary[T, MetaType[T]]]
hasorder 1 = 1 + min(0, 0 + 1)
.