godot icon indicating copy to clipboard operation
godot copied to clipboard

GDScript: Out of order member resolution

Open rune-scape opened this issue 2 years ago • 27 comments

Implements out of order member resolution -- this was allowed in some specific places, but this PR pulls it out into its own function to make the behavior consistent -- and allow more complex ordering

class members are now resolved just before they are needed -- This means class members will always have resolved types before used, else an error is pushed same goes for members of external classes -- This fixes alot of dependency errors.

also fixes a few minor bugs: -- singleton datatypes now get properly resolved in extends -- Object and Variant no longer allow array syntax (Variant[int] doesn't parse) -- pushes an error if a native enum doesn't exist when resolving a type those were all just missing a line or 2

fixes #68069 fixes #69479 fixes #67085

rune-scape avatar Dec 02 '22 05:12 rune-scape

Does this address https://github.com/godotengine/godot/issues/61159?

MewPurPur avatar Dec 02 '22 15:12 MewPurPur

@MewPurPur i can check in a bit, but my instinct is no

rune-scape avatar Dec 02 '22 21:12 rune-scape

As is, this PR fixes #69479. I just tested it. @rune-scape Can you add it in the description?

adamscott avatar Dec 02 '22 22:12 adamscott

@anvilfolk and @adamscott thank you for reviewing!

rune-scape avatar Dec 03 '22 00:12 rune-scape

Technically this could be expanded to allow the analyzer to resolve individual members of other scripts, allowing member dependencies both ways between script interfaces, but i'm not sure anyone has a use for that 🙂

rune-scape avatar Dec 03 '22 00:12 rune-scape

Technically this could be expanded to allow the analyzer to resolve individual members of other scripts, allowing member dependencies both ways between script interfaces, but i'm not sure anyone has a use for that 🙂

Wouldn't this actually be really neat now that cyclic dependencies are possible? Could probably come up with a small interesting case like:

# A.gd

var a := B.b_func()

static func a_func() -> int:
  return 42


# B.gd

static func b_func() -> int:
  return A.a_func()

Something like that?

anvilfolk avatar Dec 03 '22 00:12 anvilfolk

@anvilfolk exactly! i do kinda wanna see how difficult it is, so i'll put it on my todo list 😄 who knows, maybe its dead simple

rune-scape avatar Dec 03 '22 00:12 rune-scape

i added braces inside resolve_class_member() to make the diff much more readable

rune-scape avatar Dec 03 '22 00:12 rune-scape

@MewPurPur no, this doesn't fix https://github.com/godotengine/godot/issues/61159, but i found the bug. https://github.com/godotengine/godot/pull/69518 fixes that

rune-scape avatar Dec 03 '22 01:12 rune-scape

I cannot NOT underline the great teamwork between you both, @rune-scape and @anvilfolk. <3

adamscott avatar Dec 03 '22 02:12 adamscott

Technically this could be expanded to allow the analyzer to resolve individual members of other scripts, allowing member dependencies both ways between script interfaces, but i'm not sure anyone has a use for that slightly_smiling_face

Wouldn't this actually be really neat now that cyclic dependencies are possible? Could probably come up with a small interesting case like:

# A.gd

var a := B.b_func()

static func a_func() -> int:
  return 42


# B.gd

static func b_func() -> int:
  return A.a_func()

Something like that?

Here's what I added in the GDScript tests with #67714:

https://github.com/godotengine/godot/blob/5704055d30499cc63672d44001760a98abfbfc08/modules/gdscript/tests/scripts/analyzer/features/preload_cyclic_reference.gd#L1-L4

https://github.com/godotengine/godot/blob/6f1d4fd8871155efcb29d115a7168879948e1cf3/modules/gdscript/tests/scripts/analyzer/features/preload_cyclic_reference_a.notest.gd#L1-L12

https://github.com/godotengine/godot/blob/6f1d4fd8871155efcb29d115a7168879948e1cf3/modules/gdscript/tests/scripts/analyzer/features/preload_cyclic_reference_b.notest.gd#L1-L10

adamscott avatar Dec 03 '22 02:12 adamscott

oh, thats right, the status is set to resolved Before it gets resolved, so that does already work, huh looks like it was dead simple!

rune-scape avatar Dec 03 '22 03:12 rune-scape

On that note, I think it'd be really neat to add a unit test showcasing something that wasn't possible before but is now possible, so that out-of-order member resolution is never lost from this point forward :)

All in all, I think this is an excellent PR! Really appreciate @rune-scape and their patience in letting me pick at their code and generally being an annoying little .... :)

After being stuck in line-by-line code, I just want to double check that I understand the big picture:

Instead of resolving functions first, then class members, and having a specific order in which things are allowed to depend on one another, this PR basically does "on demand" resolution. If a function, variable, subclass, whatever, called member depends on something else, you immediately attempt to resolve it.

If, while resolving one of those dependencies you find that one of the things you are attempting to resolve is RESOLVING, i.e., in the process of being resolved as well, then you have cyclic types and are unable to resolve from then on and you throw an error.

However, if you managed to resolve this big long line of type dependency all the way to the end (probably at builtin/native types or GDScript classes with no further dependencies?), then everything along that line, including the original member, has been resolved. You're likely to eventually try to re-resolve some of those (e.g. the for loop for members in resolve_class_interface/body) but every resolve function has a check on whether something's already been resolved, and you can skip doing that work again. No harm no foul.

Ultimately, it passes every unit test, fixes a couple of issues, and makes GDScript slightly more flexible. The code looks lovely, needing only a couple teeny tiny optional tweaks. With the addition of a unit test showcasing GDScript's new capabilities, I'd say merge merge merge :)

This was a lovely opportunity to learn more GDScript internals - thanks @rune-scape <3

anvilfolk avatar Dec 03 '22 03:12 anvilfolk

Congrats for your PR, @rune-scape! That's really huge!!

I second the idea to create unit tests though! A great way to make sure we don't mess up your work in the future. 🙃

OH MY. Just saw your differential: +676 −367 *jaw drop*

adamscott avatar Dec 03 '22 03:12 adamscott

@anvilfolk Yes! Exactly! i am working on unit tests, and also now that @adamscott pointed out that dependencies between members in scripts can already exist, im working on resolving individual members of other scripts, for better error messages

rune-scape avatar Dec 03 '22 03:12 rune-scape

letting me pick at their code and generally being an annoying little .... :)

thank you for picking at my code! it's better now :)

rune-scape avatar Dec 03 '22 03:12 rune-scape

@rune-scape Don't forget to rebase your branch with master, just to be sure to test your code with the latest merges.

adamscott avatar Dec 03 '22 03:12 adamscott

I ran @KoBeWi's huge project as a test for your PR. Here's an error that I got.

extends Control

@onready var total_progress := $TextureRect/TextureProgress as TextureProgressBar
@onready var scene_progress := $TextureRect/TextureProgress2 as TextureProgressBar

Error at (3, 32): Cannot infer the type of "total_progress" variable because the initial value is "null"

Seems that it doesn't like casting of nodes.

adamscott avatar Dec 03 '22 03:12 adamscott

I really do think that there ought to be a big project that peeps working on GDScript should test against. I don't know whether @KoBeWi's project is public, but I know @unfa works liblast, which is open-source, so maybe that's the way to go?

anvilfolk avatar Dec 03 '22 04:12 anvilfolk

I ran @KoBeWi's huge project as a test for your PR. Here's an error that I got.

Seems that it doesn't like casting of nodes.

Here's the MRP: 69471_oomr_infer.zip

adamscott avatar Dec 03 '22 05:12 adamscott

@anvilfolk Sure, we can use Liblast too to test!

adamscott avatar Dec 03 '22 05:12 adamscott

@adamscott thanks for that info, i'm about to push a fix for that

rune-scape avatar Dec 03 '22 08:12 rune-scape

Wanna test? Be my guest!

Here's instructions for cloning the Liblast repo (some people have trouble because they forget about LFS): https://codeberg.org/Liblast/Liblast#_how_to_edit_the_game

unfa avatar Dec 03 '22 09:12 unfa

@rune-scape It fixed KoBeWi's project! Runs flawlessly, from what I could tell. Thanks!

adamscott avatar Dec 03 '22 16:12 adamscott

there's currently nothing crazy this PR allows, mostly because there's still limitations on resolution order (mostly between scripts), but i'm looking at the changes needed to lift most of them and make dependencies between scripts do what you expect (or at least what i expect) so the tests will mostly just show small bugfixes (when i get around to them)

rune-scape avatar Dec 04 '22 10:12 rune-scape

@rune-scape got another test for you that's giving me trouble.

class_name OuterClass

class InnerClass:
	enum MyEnum { V0, V2, V1 }
	
func test(e : InnerClass.MyEnum) -> void:
	print(e)

Does this work in this PR? The issue is that function parameters call reduce_datatype rather than reduce_identifier or reduce_identifier_from_base.

anvilfolk avatar Dec 05 '22 23:12 anvilfolk

@anvilfolk yes, that resolves fine in this branch

rune-scape avatar Dec 06 '22 05:12 rune-scape

Needs rebase.

akien-mga avatar Dec 10 '22 10:12 akien-mga

i added a single tiny test, but https://github.com/godotengine/godot/pull/69907 is needed to test dependencies between scripts, and a couple more type resolution bugs are also making my life hard 😢 i also took the opportunity to improve my code :)

rune-scape avatar Dec 11 '22 15:12 rune-scape

Looks like the PR you mentioned is in! :) I take it merging should wait until the bugs are ironed out?

anvilfolk avatar Dec 11 '22 15:12 anvilfolk