Closed Mercerenies closed 3 years ago
Minor issue: GDScriptNativeClass
is a class which doesn't have a name, as far as Godot is concerned; I can't directly reference it. It may be possible to bootstrap a reference to the class from some other instance, but we may or may not have access to it.
Attached is a diagram of the current type hierarchy I am considering, with the four categories of types from the comment above color-coded.
type-checking
shall be the tracking branch for this feature.
81bc850 adds primitive types. Primitive types are instance variables on the GDLisp
singleton (accessible from static scope using sys/declare
trickery). They're instances of the private class PrimitiveType
, which defines the satisfies?
method.
Here is a full list of the primitives we define.
Null
Bool
Int
Float
String
Vector2
Rect2
Vector3
Transform2D
Plane
Quat
AABB
Basis
Transform
Color
NodePath
RID
Object
[1]Dictionary
Array
[2]PoolByteArray
PoolIntArray
PoolRealArray
PoolStringArray
PoolVector2Array
PoolVector3Array
PoolColorArray
[1] Note: In the ODG table above, this is a subtype of AnyRef
, unlike the other primitives. This will be special-cased going forward.
[2] Note: This only covers GDScript values of type Array
, not the more specialized ones. A common supertype will be added soon.
A note, since it came up as a possible point of confusion during development.
Object
is, as of right now, the sole direct subtype of AnyRef
. As such, every AnyRef
is an Object
and vice versa right now. However, strictly speaking, we are treating Object
as a subtype of AnyRef
and not the other way around. Every value in GDLisp is an instance of exactly one of AnyRef
or AnyVal
. It so happens that, right now, every AnyRef
is an Object
. In the future, Godot might add a new root to its object hierarchy, or a new type of value which isn't primitive. Or GDLisp might add a new type of object that doesn't fit into Godot's traditional hierarchy. Those types would be new direct subtypes of AnyRef
.
The point I'm trying to make is that right now AnyRef
and Object
are synonymous, but I reserve the right to change that later and to add new subtypes to AnyRef
in the future. I won't be considering such changes breaking, since I make no guarantee that Object
is the only direct subtype of AnyRef
.
ded23e9e0fc7ed5a15f3fb3d4dc36c3b33a2ac54 adds synthetic types. The following synthetic types exist.
Any
- All values are of this type.AnyRef
- All Godot Object
instances are AnyRef
.AnyVal
- All Godot primitives are AnyVal
. Alternatively, every value which is not AnyRef
is AnyVal
by default.Number
- Number
is exactly the union of Int
and Float
.BaseArray
- BaseArray
is the union of the various array primitive types.Nothing
- No values are of this type.Note that, currently, AnyRef
does not include arrays or dictionaries, which as far as Godot is concerned are primitives. This is an unfortunate difference in what Godot considers "primitive" (everything that's not an Object
), as opposed to what we intend by AnyVal
(namely, the Scala notion of AnyVal
includes only value types, those that are not passed by sharing)
Just a reminder for the moment. =
is only guaranteed to work on strings and numbers right now in GDLisp, so types should not be compared with =
. In particular, the integer object you get from (typeof 3)
may not be =
equal to the object Int
, only isomorphic to it. We have no generalized equality operators (#61) yet.
22ddfbc5821f8ac4155280cd6d02def86383aef0 adds typeof
(and 91b10356fd5c6ad6053868d9506002da24c23d3e adds testing for it). This issue's original proposal involved the implementation of four "kinds" of types: primitive types, script types, native class types, and synthetic types. It also proposed two functions: instance?
and typeof
. All of these features have been implemented.
We need to clean up type-checking. Godot's type system is, to put it mildly, a large uncontrollable mass of duct tape and glue.
Here's a summary of Godot's typing rules, to the best of my understanding.
typeof
, which returns an integer. It is an error to callget_script
orget_class
on these.TYPE_ARRAY
) to 26 (TYPE_COLOR_ARRAY
), which mercifully is a contiguous range.typeof
of 17 (TYPE_OBJECT
). On these objects, you can callget_script
to get the GDScript instance which defines the behavior (basically, the closest thing we have to the object's "class"). You can also callget_class
to get the name of the narrowest Godot built-in class (such asNode2D
orReference
) which the script inherits from.Script
,Node2D
, orReference
) which do not have a more specific type can only be accessed usingget_class
, which returns the string name as in the above bulletpoint.get_script
returns a strange, unidentified object which prints as[Object:null]
, counts as an object undertypeof
, and compares equal to the primitivenull
.All of this is overly complicated. Currently, GDLisp defines one primitive for type checking:
instance?
. The current behavior of this function is as follows:typeof
.is
for type-checking.This works fairly well for the cases above, but it's still quite ad-hoc. I want a consistent GDLisp-side type hierarchy.
Here's what this issue proposes. The GDLisp-side type hierarchy is as follows. There is a single root type
Any
. The root type has two direct children:AnyVal
andAnyRef
.The children of
AnyVal
are all of the primitive types, such asString
andInt
. Most children ofAnyVal
have trivial subtyping relations with one another. We'll define aNumber
type which is a common supertype toInt
andFloat
, and we'll defineBaseArray
, the common parent to the several array types defined in Godot.The children of
AnyRef
are all of the other types, including nodes, objects, and references. For the most part, children ofAnyRef
mirror the usual Godot type hierarchy, whichObject
being the only direct child, and its children areNode
andReference
, and so on.We define two type-checking functions:
instance?
(which behaves similarly to how it does now, but under the new type system) andtypeof
(which returns the most specific type for an instance).Semantically, that describes a type system. And users of GDLisp should only care about the above paragraphs. I intend to hide all of the messier details from the users as much as possible. Now let's talk about implementation details.
As far as implementation goes, there are four "kinds" of types we're dealing with.
Int
orFloat
, which correspond to GodotTYPE_*
constants.Node
andReference
. Godot's runtime reification of these type names is asGDScriptNativeClass
instances.GDScript
.BaseArray
andAnyVal
, which exist only in GDLisp and have no official Godot presence on the GDScript side.(1) are always concrete, final types (i.e. no subtypes). (2) and (3) follow conventional Java-like single-inheritance rules. Generally, all types in (2) or (3) are constructible (I can't think of a way offhand to make an abstract class in GDScript). (4) are always abstract classes and exist only for subtyping relationships.
Now, as for how these will be represented.
Int
is a name in GDLisp whose value is an integer constant (TYPE_INT
, to be precise). I'll change this, so that all primitives (such asInt
) are instances of a wrapper class (PrimitiveType
).GDScriptNativeClass
. I aim to leave these alone for now and use them as-is.GDScript
. Again, I aim to leave these alone.SyntheticType
class which I'll design.instance?
will work roughly as it does now. If the type argument is aPrimitiveType
, check thetypeof
ID. If it'sGDScript
orGDScriptNativeClass
, useis
. If it's a synthetic type, use whatever special behavior the synthetic type defines (probably as a method on its class).typeof
will first check the Godottypeof
. If it returns something other thanTYPE_OBJECT
, then an appropriatePrimitiveType
will be returned. If it returnsTYPE_OBJECT
, then we checkget_script
. Ifget_script
returns non-null, then return that value. Otherwise, we checkget_class
and compare the name against known GDScript class names to get the native instance.