godotengine / godot-proposals

Godot Improvement Proposals (GIPs)
MIT License
1.15k stars 97 forks source link

Add function to stringify GDScript symbols #837

Open Sirosky opened 4 years ago

Sirosky commented 4 years ago

Describe the project you are working on: I am creating a tooltip for debugging purposes.

Describe the problem or limitation you are having in your project: There is no way to stringify GDScript symbols. For example, I would like the tooltip to display all of muh_array = [1,2,3]. It's easy enough to display the contents of the array itself, but not the name of the array. You have to do it manually.

Describe the feature / enhancement and how it helps to overcome the problem or limitation: Add some global function to use reflection and fetch the name of a variable. It would eliminate the need to pass on the name as a string manually.

Describe how your proposal will work, with code, pseudocode, mockups, and/or diagrams: Using muh_array is an example:

print(muh_array.get_name())

If this enhancement will not be used often, can it be worked around with a few lines of script?: Yes, just one extra line haha.

Is there a reason why this should be core and not an add-on in the asset library?: This touches upon the core of how GDScript works, and could be a very useful debugging option.

willnationsdev commented 4 years ago

Well, certainly, the workaround is "just one extra line", but this is presumably something you would be using often, across multiple projects, to create more detailed reporting (whether it be tooltips, logging, or app notifications of some sort). In addition to that, what cannot be reproduced with script code is a way of generically fetching the name, since the workaround is to just hardcode the string value which can't be done programmatically at runtime.

I don't think the syntax for it would be with a method, as you indicated, because that would imply that the Godot API is the thing that recognizes and returns the GDScript symbol name (which only the GDScript object itself could know, not the engine code it rests upon).

It could be a special global function in the @GDScript namespace. The soon-to-be-added annotations aren't a good place for it, as those are declarative and don't go into expressions. GDScript has no C-like macro system, and adding one just for the purpose of this would be overkill. So, yeah, I think an @GDScript function would be the best place for something like this.

Edit: the implementation could just be that this particular function is something that only the GDScriptParser sees, and it simply replaces it with the string of the identifier the function wraps. The compiler wouldn't even see it.

@vnen Thoughts? Since you're the one currently doing the rewrite?

Calinou commented 4 years ago

This could also be helpful for #538.

vnen commented 4 years ago

Well, we could add some magic functions for this. Not really reflection, but more like macro substitution. I've thought of adding __FILE__ and __FUNCTION__ but those are not super useful without actual macros (which I don't plan to implement).

So for this we could use: __nameof(my_var) which just spits the string "my_var" in the code. But then, it's much simpler to just use "my_var" directly.

Which is what @willnationsdev added:

Edit: the implementation could just be that this particular function is something that only the GDScriptParser sees, and it simply replaces it with the string of the identifier the function wraps. The compiler wouldn't even see it.

It's not useful without macros because if you want to do it in a function:

func print_var(variable):
    print("%s = %s" % [__nameof(variable), str(variable)])

It won't work as you expected:

var x = 42
var y = 23
print_var(x)
print_var(y)

You want this to print:

x = 42
y = 23

but it will actually print:

variable = 42
variable = 23

So in the end if you have to something like in the original post: print(muh_array.get_name()) it's easier to just do print("muh_array").

willnationsdev commented 4 years ago

Ahh, I see. Yeah, it isn't really possible without having actual macros or reflection. And I don't think there are any plans to add that sort of complexity to GDScript.

dalexeev commented 4 years ago

This could also be helpful for #538.

Yes, I also think that we need to separate print() and dump() functionality. And dump() can work like a macro. For example:

func _ready():
    # ...
    dump(a, b)
    dump("My error message", my_array, my_string)
    # ...

Will print:

res://script.gd:23 - _ready()
-----------------------------
a == 1
b == 2

res://script.gd:24 - _ready()
-----------------------------
My error message
my_array == [1, 2, 3]
my_string == "Hello!"
swarnimarun commented 4 years ago

@dalexeev I would be much more inclined to suggest to change, printerr() to print out to the console with some more information like script line and function details.

It's kinda annoying that it errs with just the string and doesn't even log to the debugger IIRC...

willnationsdev commented 4 years ago

@swarnimarun You are supposed to use push_error() to print text to both the console and the in-editor debugger. There is also push_warning() to match it for warnings instead of errors. Not sure if it highlights the script line and function details though...it definitely should if it doesn't.

Calinou commented 4 years ago

Not sure if it highlights the script line and function details though...it definitely should if it doesn't.

That's already implemented :slightly_smiling_face:

swarnimarun commented 4 years ago

@willnationsdev @Calinou yep it works.

Though while testing I noticed that when executed as a tool script. logs refer to the c++ source only not the gdscript source/stack trace which should be doable. image

Same for push_warning.

In works perfectly at the time of the active game.

swarnimarun commented 4 years ago

Also while trying to create an issue for above I noticed this issue, https://github.com/godotengine/godot/issues/28458

This proposes a nice idea that we can just have expression eval and return the value while debug printing it in the editor.

var val = dbg(op(a) + op(b) * 2.0)
# op(a) + op(b) * 2.0 = <someval>

Though it can just as well already be done by,

var val = dbg("op(a) + op(b) * 2.0", op(a) + op(b) * 2.0)
# op(a) + op(b) * 2.0 = <someval>

But dbg would increase the convenience of debugging a bunch. And should be achievable as per what @vnen commented earlier.

mnn commented 4 years ago

actual macros (which I don't plan to implement)

:cry:

So for this we could use: __nameof(my_var) which just spits the string "my_var" in the code

This looks exactly like nameof from C#:

var numbers = new List<int> { 1, 2, 3 };
Console.WriteLine(nameof(numbers));  // output: numbers

I personally don't really like the __ prefix, it seems to me like "really internal, don't even think about touching it" (possibly a result of being exposed to too much JS :smile:).

daveparadise commented 2 years ago

Trying something similar, I did:

func print_name_and_value_of_variable(string):
    var script_var_array = get_script().get_script_property_list()
    for i in script_var_array:
        if i["name"] == string:
            print(i["name"] + " is " + str(get(string)))
            break

And I can call that inside the script and just pass in a string of my variable:

print_name_and_value_of_variable("weight")

which yields a simple:

Screen Shot 2022-01-03 at 5 36 58 AM

or:

Screen Shot 2022-01-03 at 5 37 47 AM

I think this would work for the OP just by extending it to print/display where they want?

just-like-that commented 2 years ago

@dalexeev I would be much more inclined to suggest to change, printerr() to print out to the console with some more information like script line and function details.

It's kinda annoying that it errs with just the string and doesn't even log to the debugger IIRC...

OFFTOPIC: why is it called printerr() without underscore character? I recommend to align it and make it print_err() like in print_debug() (!)

Calinou commented 2 years ago

OFFTOPIC: why is it called printerr() without underscore character? I recommend to align it and make it print_err() like in print_debug() (!)

The conventions to refer to standard output and standard error are stdout and stderr respectively.

Also, to print an error message, use push_error() instead of printerr() as it performs automatic coloring in the console.

feois commented 1 year ago

I'd also like something similar as I want to override properties without modifying properties and I currently have methods take a dictionary argument with key being property name but I had to use enums for type safety. Although I prefer an operator like &property or `property` and also available for Class.property, i.e. &Class.property or `Class.property`

CookieBadger commented 10 months ago

An argument for a CSharp-like nameof function: let's assume I want to do an assertion, to prove something is not null: assert(my_control != null, "my_control != null") is how I would do that currently. However, I would much more prefer to do assert(my_control != null, nameof(my_control)+"!= null"), such that I get an error should I forget to update the name of the my_control variable, or such that it automatically updates when using refactor tools (as suggested in #899 ).

This functionality is however different to @vnen's suggestion, that calling func my_fun(variable): print(nameof(variable)) should print the variable name in the caller, whereas for my use case, it should just print "variable", as it is named in the callee. The two functionalities are different, and a CSharp-like nameof would be much easier to implement, and would greatly improve refactorability of GDScript (main reason for many people to prefer CSharp).

vnen commented 9 months ago

This functionality is however different to @vnen's suggestion

It was never a suggestion, I was just showing that this is way less useful without macros.

CookieBadger commented 9 months ago

I was just showing that this is way less useful without macros.

which is precisely what I disagree with. Using "my_var" instead of nameof(my_var) might seem shorter, but it is highly error-prone, since misspelling or forgetting to rename when refactoring will go undetected. The past has shown, that making GDScript rely on strings so much is a bad idea, so string usage (e.g. connect("my_sig", self, "my_fun")) has been reduced a lot from Godot 3 to Godot 4. Implementing nameof() would be the next step. I actually think it should have been included in 4.0 already.

geekley commented 2 days ago

I agree with the suggestion above that we should use an operator or special syntax in the language. I'd also like to suggest that it could have an optional prefix for scoping, that isn't part of the string.

IMO the best syntax would be with delimiters (but still color the inner part as code, not as string): `x` | SomeClass.etc.`some.prop` (resolves to a string if code would be valid) &`x` | SomeClass.etc.&`some.prop` (StringName version)

Instead of emitting the evaluation of that code, you emit the code itself as a String|StringName, but still check it.

Delimiters are better as they allow for a possible improvement where you could stringify any valid code. For example: MyMath.`sqrt(a.position.x ** 2 + a.position.y ** 2)` (error if it's an invalid expression grammatically or any identifier is not in scope; no evaluation is performed)

This feature is very necessary, as there's still many APIs relying on reflection/StringName. Theoretical example: Array(a, TYPE_OBJECT, &`Spatial`, null) -> error in Godot 4, Spatial is not an identifier anymore Array(a, TYPE_OBJECT, &`Node3D`, null) -> works after fixing