Godot: Typed GDScript named enums can't be used as types

Created on 22 Jul 2018  路  27Comments  路  Source: godotengine/godot

Godot version:

Godot master https://github.com/godotengine/godot/commit/4b549faaab40f09756b644d4ad4d9d63e3b5e70c

OS/device including version:

Windows 10

Issue description:

Typed GDScript doesn't let you use named enums as types.
https://i.imgur.com/UFSfsLe.png

archived feature proposal gdscript

Most helpful comment

I would definitely like to have Enums be user defined types. I understand we can define them as ints at the moment because that is what they are under the hood. But I would like to enforce that a value getting passed into a function is indeed a reference to an enum.

For example if I had a function call like this:
if (player.canMove(Direction.Left)):

That function can never be considered "safe" to me because I can't enforce it on the other end:

#not valid code
func canMove(direction: Direction):

Being able to enforce that a parameter comes from a particular enum would be a good addition in my opinion.

All 27 comments

I thought about this but currently enums are just shorthand for dictionary/constants, they are not a special type. Not sure how to implement it properly (i.e. whether it should accept any int or only the named constants, and whether the value should be checked to be a valid one).

As a workaround, you can use int as the type hint for now.

@vnen would it be possible to implement a fallback type search based on the same logic used for member variable suggestion? A challenge with that approach might be differentiating between variables and enums, but I'm not sure about that.

@mrzapp I'm not sure if I follow. Do you mean just showing the enum constants on code completion when assigning?

@vnen I was probably a bit quick with that idea. Take 2:

I don't think you need to check if the value is valid, C# doesn't do this either:

public class Program
{
    enum MY_ENUM {
        VALUE_1,
        VALUE_2
    }

    public static void Main()
    {
        MY_ENUM myEnum = (MY_ENUM)33; // <- This compiles and executes
    }
}

It also accepts any int (if you cast it). I personally think it's OK to let GDScript accept any int implicitly, as it still gets the job done. It would be neat to limit the accepted range to the amount of named constants, but it's not a vital feature.

I think it would be perfectly reasonable to add enum declarations to the "script_type" tokens (if that makes sense, I admit I didn't read all of the code in the PR)

I agree with @mrzapp. Enum typed variables shouldn't be limited to the enum values only. Making them an int implicitly would allow saving enums to files and reading them later. It could optionally limit the var to a certain range. Would be great for suggestions anyway.

We can certainly make an enum-typed variable simply be an int, but then we take a stand to not allow other types (such as String as asked in #20988), nor do we make tagged unions (as asked in #15141).

@vnen Is that a big problem?

enum Test {
    TEST = "foobar"
}

func something(t: Test):
    print(t)

func _ready():
    something(Test.TEST)

If Godot could be able to detect the type of enum values, it could make the enum work as the type of the values.

But what if you have:

enum Test {
    STRING_VALUE = "foobar",
    INT_VALUE = 42
}

var my_var: Test

What is the type that my_var expects?

@vnen I don't know. An union?

@vnen @Soaku I don't mean to be a buzzkill, but I think the feature requested in #20988 is highly unorthodox. I may be wrong about this, but I've never seen a language that treats enum values as anything but integers, and it certainly adds unnecessary complexity for very little gain. The problem mentioned in that issue could be solved with a dictionary in any case.

I could understand the point of string enums, but adding to my previous reply, why would an enum allow mixed types? This would cause inconsistency and comparing two values in the enum could fail. For this, a separate enum should be created.

@Soaku GADT enums allow for mixed types, it can be quite useful.

I'm all for flexibility type-wise with enums, I'd even love arbitrary-type enums. I do kind of think sticking to a single type within the same enum set is reasonable.

Mostly, though, I would like a degree of type safety regarding enums so that I can't say like:

enum Direction {up, down}
var dir : Direction = Color.red

(unless explicitly cast anyway)

Couldn't the type just be variant if you wanted arbitrary type enums?

I think we're not acknowledging the amount of work needed to make mixed type enums happen. If we settle on int-based enums for now, at least the problem that this issue was opened for (236 days ago ^^") will be solved. @vnen what is your opinion?

I don't see significant value in adding mixed-type enums, but I _do_ see value in being able to treat Enums as user-defined types and being able to enforce a specific enum type as a function argument type if the user wishes. Even if you don't enforce ranges (which I don't think is typical anyways), it at least pushes the programmer to think about the intention of the enum type instead of passing an arbitrary integer around.

I would definitely like to have Enums be user defined types. I understand we can define them as ints at the moment because that is what they are under the hood. But I would like to enforce that a value getting passed into a function is indeed a reference to an enum.

For example if I had a function call like this:
if (player.canMove(Direction.Left)):

That function can never be considered "safe" to me because I can't enforce it on the other end:

#not valid code
func canMove(direction: Direction):

Being able to enforce that a parameter comes from a particular enum would be a good addition in my opinion.

As a (probably bad) suggestion, what if enums could be typed and then their type is inferred when attempted to be used in static typing?

enum Fruit:int { APPLE = 0, BANANA = 1, ORANGE = 2 }
var current_fruit:Fruit

or maybe enum(int) ...

Not great since obviously the error still exists, and you'd have to know about this specific edge case to avoid it. Though I'd prefer this in lieu of leaving comments around saying that a function that expects an int is really expecting a specific enum. :)

Just stumbled upon this issue as I found the need for this in my code. Maybe in future versions of Godot it will be available. xD

@rafaelgdp Please don't bump issues without contributing significant new information; use the :+1: reaction button on the first post instead.

I think someone suggested above that the enum-as-type pattern is unorthodox, yet it is present in modern languages like typescript.

See this example from the TS docs:

enum Response {
    No = 0,
    Yes = 1,
}

function respond(recipient: string, message: Response): void {
    // ...
}

respond("Princess Caroline", Response.Yes)

The "enum-as-type" is quite common (often accepting everything of that type, like the example above from TS). I guess it's a start, but it doesn't check much - you could freely mismatch enums or pass invalid values. It would be nice to have true sum+product types like in Haskell:

-- compiler will check argument of type Bool to be either True or False,
-- nothing else is accepted (no random int)
data Bool = True | False

-- "enum" with values (I believe something similar is also in Scala in several forms)
data Shape = Circle Int | Box Int Int

I guess product types in the context of GDS would be typed Dictionaries (interfaces).

It's worth noting that even in TypeScript are unions, which can check if you are passing only allowed subset of other type (e.g. int, string) and are often used in place of enums because of better type checking:

type Bit = 0 | 1;
const b1: Bit = 0; // ok
const b2: Bit = 1; // ok
const b3: Bit = 2; // compilation error

type Animal = 'dog' | 'cat';
const a1: Animal = 'dog'; // ok
const a2: Animal = 'snake'; // compilation error

While I would love to see full algebraic data types ("enum"s with data like Shape above), I think for most users the sweet spot would be in fully typed unions (Bool, Bit and Animal examples).

I like how Rust or Haxe does that. You can have c-like enums:

enum Color {
    Red = 0xff0000,
    Green = 0x00ff00,
    Blue = 0x0000ff,
}

But also enums that contain different and more complex types:

enum Event {
    Init,
    AssetLoad = 0,
    AssetUnload = 1,
    KeyPress(char),
    Message(String),
    Click { x: int, y: int },
}

I think in the end its important how this works together with pattern matching. Sum types without pattern matching support are half as useful as they could be. If matching on the variant type of an enum is possible, this would be nice to have.

Javascript does not have runtime Typescript type information, so it cannot support matching on types. I don't know how that is with gdscript, how much type information it has or could have, but if matching on types would not be supported, doing as Typescript does with numeric enums and a union type could be a good way to go. I think its similar for Mypy.

Feature and improvement proposals for the Godot Engine are now being discussed and reviewed in a dedicated Godot Improvement Proposals (GIP) (godotengine/godot-proposals) issue tracker. The GIP tracker has a detailed issue template designed so that proposals include all the relevant information to start a productive discussion and help the community assess the validity of the proposal for the engine.

The main (godotengine/godot) tracker is now solely dedicated to bug reports and Pull Requests, enabling contributors to have a better focus on bug fixing work. Therefore, we are now closing all older feature proposals on the main issue tracker.

If you are interested in this feature proposal, please open a new proposal on the GIP tracker following the given issue template (after checking that it doesn't exist already). Be sure to reference this closed issue if it includes any relevant discussion (which you are also encouraged to summarize in the new proposal). Thanks in advance!

One, I would think it's a bug rather than enhancement, and two, isn't it covered in @vnen's rework?

@Zireael07 it is an enhancement, since currently enums in GDScript are more of a syntax sugar rather than a separate type. Also, I'm pretty sure this message is sent to most or all issues labeled "feature proposal".

It's not a bug because it's working as intended. But yes: I'll likely add enum as types in the new GDScript code.

Was this page helpful?
0 / 5 - 0 ratings