Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement typed dictionaries #78656

Open
wants to merge 1 commit into
base: master
Choose a base branch
from

Conversation

Repiteo
Copy link
Contributor

@Repiteo Repiteo commented Jun 24, 2023

This aims to add the ability to bind typed dictionaries to script & GDScript. The implementation takes heavy inspiration from the existing typed array format. The primary difference is the ability to specify type for just a key, leaving the value as typeless much like current dictionaries. Both key and value need their types specified for typed dictionaries, with Variant allowing for a typeless equivalent.

Syntax

var typed_key_value: Dictionary[int, String] = { 1: "first value", 2: "second value", 3: "etc" }
var typed_key: Dictionary[int, Variant] = { 0: "any value", 10: 3.14, 100: null }
var typed_value: Dictionary[Variant, int] = { "any value": 0, 123: 456, null: -1 }

Editor

23-06-26 10-02-25 godot windows editor dev x86_64 san

Issues

All core issues are resolved!
There are a fair number of areas that still require fixes, in addition to whatever lingering bugs/errors there are. In particular:

  • No way currently to make a typeless key and a typed value
  • Variant text appears as white instead of green in GDSCript if used as the value:
    23-06-26 10-18-36 godot windows editor dev x86_64 san
  • Editor fails to update creation key/value sections if changing type
  • Editor incorrectly perceives the initial typed key/value as null
  • Editor fails to setup exported dictionaries during runtime
  • Editor won't recognize a typed key passed with the incorrect type as invalid in a GDScript function (that is, when using the [] operator); it throws an error if attempting to do so in runtime & does recognize invalid keys when creating a dictionary initially
  • Editor will treat any Object variant as a "Resource" regardless of provided type
  • Dictionary fails to recognize local script types in curly-bracket initialization during runtime
  • Potential memory leak with container_element_type being a list now instead of a single item. (This is inexperience with the engine on my end, dunno if it needs something special to memdelete array entries)
  • Implementation of GDScript tests

Closes godotengine/godot-proposals#56

@lufog
Copy link
Contributor

lufog commented Jun 25, 2023

I would prefer:

var my_dictionary: Dictionary[int, Variant]
var my_dictionary: Dictionary[Variant, int]

Just Dictionary[int] a little confusing.

core/object/object.h Outdated Show resolved Hide resolved
@Repiteo
Copy link
Contributor Author

Repiteo commented Jun 25, 2023

@lufog Great point! I adjusted the code to handle exactly that, but it revealed some other pitfalls in the process (mainly the process of transfering container types from one DataType to another) so I'll have to iron that out before showing off a new version

In the meantime I've added documentation descriptions & made it possible to use a constructor to make a typed dictionary in GDScript, much like typed arrays

@YuriSizov YuriSizov added this to the 4.x milestone Jun 26, 2023
@Repiteo Repiteo force-pushed the typed-dictionary branch 3 times, most recently from 8b50554 to 6c41e2e Compare June 28, 2023 20:13
@Repiteo
Copy link
Contributor Author

Repiteo commented Jun 28, 2023

GDScript tests implemented! They're largely lifted from the tests given to typed Arrays so more specialization will be required, but they're a solid starting point to see what's awry off the bat

Namely, typed arrays seem to be instantiating with null keys & values, though they're recognized as typed. I'm not quite sure yet where the incorrect implementation is happening, but that's my current focus as it very well may be the linchpin of the editor errors

@Bromeon
Copy link
Contributor

Bromeon commented Jun 29, 2023

With typed arrays, we have the variance problem:

var typed: Array[int] = [1, 2, 3, 4]
var as_untyped: Array = typed

as_untyped[0] = "string" # silently fails at runtime

I guess the same problem exists here, but for both keys and values?
How do we deal with that?

@dalexeev
Copy link
Member

With typed arrays, we have the variance problem

The exception for Array (aka Array[Variant]) was intentionally added as a convenience to users of untyped GDScript.

var a: Array[Node2D]
var b: Array[Node] = a # Error
var c: Array[Variant] = a # Unsafe, but ok.
var d: Array = a # Unsafe, but ok.

@Bromeon
Copy link
Contributor

Bromeon commented Jun 29, 2023

The exception for Array (aka Array[Variant]) was intentionally added as a convenience to users of untyped GDScript.

Yes, but it's still a hole in the type system. I understand the desire to pass typed arrays to APIs accepting untyped ones, and for reading, this is completely sound. The covariance violation occurs when writing to an Array that points to a Array[T]. This cannot be checked at parse time, it's a runtime check (and IIRC it's silent do-nothing, no error at the moment).

My question is, is the same covariance conversion ("upcast") planned for dictionaries on each of the generic parameters? That is:

  • Dictionary[int, String] -> Dictionary[int, Variant]
  • Dictionary[int, String] -> Dictionary[Variant, String]
  • Dictionary[int, Variant] -> Dictionary[Variant, Variant] == Dictionary

And if yes, are we fully aware of the pitfalls?

@dalexeev
Copy link
Member

This cannot be checked at parse time, it's a runtime check (and IIRC it's silent do-nothing, no error at the moment).

However, this is not directly related to the PR. PRs of this kind tend to garner large numbers of comments that are difficult to navigate. If you'd like to discuss this, I suggest the #gdscript channel on Rocket Chat.

@Bromeon
Copy link
Contributor

Bromeon commented Jun 29, 2023

Thanks! My point was not to discuss typed arrays, but the covariance problem regarding dictionaries, and what conversions we allow (see above). As such it's quite on-topic I believe.

@Repiteo
Copy link
Contributor Author

Repiteo commented Jun 29, 2023

The holes in the type system certainly have been a bit of a headache to navigate, but attempting to overhaul that system as a whole is probably a bit beyond this PR (at least as I initially envisioned it). This is more about setting up the system that currently exists to dictionaries, warts and all. If the current implementation were to somehow inhibit the ability to implement typed dictionaries in the first place, then changes to the system would make sense

On that note, I've figured out why variants were appearing as null & not changing when swapping types, so both those fixes are in place. As it stands, the two biggest remaining issues are:

  • The editor treats any Object variant as "Resource", regardless of the provided type (need to figure out how typed arrays handle this)
  • A dictionary fails to properly parse provided key/value if there's unorthodox syntax (eg: typed_dictionary_assign_wrong_to_typed.out has the wrong error, thinks an entirely different type is passed on as the value (from reading the key?))

@Repiteo Repiteo force-pushed the typed-dictionary branch 3 times, most recently from 39e959b to 56daf46 Compare July 2, 2023 20:13
@Repiteo Repiteo force-pushed the typed-dictionary branch 3 times, most recently from c398aff to 38c8e89 Compare January 3, 2024 18:34
@Jakepc007
Copy link

Jakepc007 commented Jan 23, 2024

Would it be in the scope (or goal) of this PR to add type information to existing dictionaries? Such as intersect_ray?

var some_func():
  var result := space_state.intersect_ray(query)
  handle_result(result)

func handle_result(result: 🤔):
  if result.collider:
    pass

I would imagine this would have some overlap with #76843 if such is the case. As those properties aren't even guaranteed.

@AThousandShips
Copy link
Member

It would break compatibility to do so imo so not guaranteed

@YuriSizov
Copy link
Contributor

Would it be in the scope (or goal) of this PR to add type information to existing dictionaries?

It wouldn't be in scope for this PR, but it can be discussed as a follow-up to it.

@IntangibleMatter
Copy link

Would it be in the scope (or goal) of this PR to add type information to existing dictionaries? Such as intersect_ray?

var some_func():
  var result := space_state.intersect_ray(query)
  handle_result(result)

func handle_result(result: 🤔):
  if result.collider:
    pass

I would imagine this would have some overlap with #76843 if such is the case. As those properties aren't even guaranteed.

For Dictionaries with a precise type schema, but with multiple different types, a struct would likely make more sense. godotengine/godot-proposals#7903

@DaloLorn
Copy link

Looks like the C# editor is failing to build?

@IntangibleMatter
Copy link

Looks like all tests are passing... Can someone review this ASAP? It'd be awesome to have in 4.3, afaik feature freeze hasn't hit yet

@Bromeon
Copy link
Contributor

Bromeon commented Feb 26, 2024

There are still open variance problems with Array[T] for which no one has a solution, see my response here and problem elaboration here. The same would happen for Dictionary[K, V] but for both key/value.

I'm not sure if it's wise to rush an implementation as long as we don't have a proper strategy for this (unless people agree that this is not an issue worth addressing).

@DaloLorn
Copy link

DaloLorn commented Feb 26, 2024

I'd say "not worth addressing". I'm biased, though: I'm positive I'm not going to have trouble with it, and I desperately want to be able to tell the inspector what kind of object I want to put into my dictionaries.

(The proposal this PR is addressing is one of several prospective language upgrades I've been watching with great interest because they eliminate a lot of unnecessary busywork and complexity from my work on Fleet Ops and should allow me to continue development.)

@IntangibleMatter
Copy link

I agree with @DaloLorn . This feels like the kind of thing that's just... part of how the language works, and if/when it does get addressed, it'd make more sense to do at the same time as Arrays. There are exponentially more problems solved short-term and long-term than if we wait for every possible issue to be resolved. The covariance thing feels really nitpicky, and not like the kind of think 90% of users will worry about.

IMO it should be pushed as it is, Covariance is its own issue that needs to be resolved in its own discussion. This is just about getting statically typed dictionaries to finally be in the engine

@Repiteo Repiteo force-pushed the typed-dictionary branch 3 times, most recently from b12d8f2 to ead5931 Compare March 1, 2024 20:22
Copy link
Contributor

@ajreckof ajreckof left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Only checked the part that I know best (ie EditorPropertyDictionary)

editor/editor_properties_array_dict.cpp Outdated Show resolved Hide resolved
editor/editor_properties_array_dict.h Outdated Show resolved Hide resolved
editor/editor_properties_array_dict.cpp Outdated Show resolved Hide resolved
editor/editor_properties_array_dict.cpp Outdated Show resolved Hide resolved
editor/editor_properties_array_dict.cpp Outdated Show resolved Hide resolved
@Repiteo Repiteo force-pushed the typed-dictionary branch 3 times, most recently from 29aa374 to c33d20c Compare March 3, 2024 17:02
@AThousandShips

This comment was marked as outdated.

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

Successfully merging this pull request may close these issues.

Add static type hints for dictionary members