Skip to content

Instantly share code, notes, and snippets.

@dalexeev
Created October 8, 2023 17:18
Show Gist options
  • Save dalexeev/bd4e952a601b18ae3a94919f89b98928 to your computer and use it in GitHub Desktop.
Save dalexeev/bd4e952a601b18ae3a94919f89b98928 to your computer and use it in GitHub Desktop.
GodotType
/**************************************************************************/
/* 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.
@nlupugla
Copy link

nlupugla commented Feb 22, 2024

  1. 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).

@unfallible
Copy link

I have a couple questions about this (unrelated to my new questions on #737), which are in :

  1. 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's Class class, which would replace the engine's decentralized type systems where e.g. typeof() and getclass() 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, the Script resource, and, when compiling GDScript, GDScriptParser::DataType). The GodotType here seems more narrowly focused though (for example, there's no get_methods() function), but I'm don't understand what that focus is. What are the intended use cases for GodotType? Is it just supposed to provide a way to check whether one type "inherits" another type's features?
  2. Are you planning to represent type safe Callable and Signal objects with this? If so, it seems like at minimum, GodotType would need some way to check if a Callable returns something, such as a has_return_type flag to tell you whether params contains the return type or a return_type instance variable that points to a GodotType if the type object represents a Callable with a return type.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment