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

Impossible to call overloaded methods between languages #90946

Closed
paulloz opened this issue Apr 20, 2024 · 12 comments · Fixed by #90968
Closed

Impossible to call overloaded methods between languages #90946

paulloz opened this issue Apr 20, 2024 · 12 comments · Fixed by #90968

Comments

@paulloz
Copy link
Member

paulloz commented Apr 20, 2024

Tested versions

System information

Windows 10.0.19045 - Vulkan (Forward+) - dedicated NVIDIA GeForce RTX 4060 (NVIDIA; 31.0.15.5222) - Intel(R) Core(TM) i5-10400F CPU @ 2.90GHz (12 Threads)

Issue description

Calling overloaded methods across language boundaries is not possible any more. Given the Bar.cs script provided below, trying to call Bar.SayHello() from GDScript is raising the following error:

Too few arguments for "SayHello()" call. Expected at least 1 but received 0.

Such a call was possible without any issue in earlier versions (up to, including, 4.2.1).

Steps to reproduce

  • Make a scene with the following structure.
    Foo [Foo.gd]
    └── Bar [Bar.cs]
    
  • Attach Foo.gd.
    extends Node
    
    @onready
    var bar : Bar = get_node("Bar") as Bar
    
    func _ready() -> void:
        bar.SayHello()
        bar.SayHello("World")
  • Attach Bar.cs.
    [GlobalClass]
    public partial class Bar : Node
    {
        public void SayHello(string name)
        {
            GD.Print($"Hello {name}!");
        }
        
        public void SayHello()
        {
            GD.Print("Hello World!");
        }
    }
  • Build.
  • Witness the error.

Minimal reproduction project (MRP)

MRP.zip

@akien-mga
Copy link
Member

CC @vnen @raulsntos

@raulsntos
Copy link
Member

Overloaded methods are not really supported but I feel like you should be able to workaround this:

  • C# will register all the overloaded methods but the order matters, so put the overload you want to win first.
  • In order to make GDScript find all the overloads you can fake it by making an overload with the biggest number of parameters.
  • Make all of these parameters optional and typed as Variant or something that would be compatible with every overload (think of it like the overloading in TypeScript).

Following all these steps, you should be able to fix this by adding a method like this (make sure it's the first one in the .cs file):

public void SayHello(Variant name = default)
{
    GD.Print($"Hello {name}!");
}

This would work if we supported default parameter values, but it looks we don't:

Also, keep in mind that this may add ambiguity in C# which may break compat for your C# users so it's not a perfect solution.

@paulloz
Copy link
Member Author

paulloz commented Apr 20, 2024

Well, I'm not arguing if that was supported voluntarily or not. But the fact remains that this behaviour changes between 4.2.1 and 4.2.2, so it is a regression / a breaking change. I (and thus, maybe other users) am/was relying on it, as a side note: to work around the lack of default parameters. If we consider the new behaviour is the intended one, and this won't be fixed, we should communicate on it explicitly in the release notes.

Also, keep in mind that this may add ambiguity in C# which may break compat for your C# users so it's not a perfect solution.

For my personal use case, those methods are all EB never in C#, they are only there because of the lack of default params.

@AThousandShips
Copy link
Member

Did it work reliably before though? Did it work for all methods of calling things etc.?

@paulloz
Copy link
Member Author

paulloz commented Apr 20, 2024

Did it work reliably before though? Did it work for all methods of calling things etc.?

As far as I know. I won't say I'll assure you I tested everything, but I've never witnessed any issue in my own testing. Nor have I had feedback from my user base (a majority of which makes calls to my API from GDScript) before this was reported on the 18th (I think 4.2.2 was released on the 17th).

@AThousandShips
Copy link
Member

AThousandShips commented Apr 20, 2024

What happens if you create a callable from a method like this? Did it work reliably previously? And calling with call by name rather than the method directly? If it worked reliably on all areas then that'd be relevant for "fixing" this, but since this doesn't seem to have been an intended feature I suspect it might not work uniformly, and that'd be a larger thing to fix and might be problematic, and a partially working thing wouldn't be good

Also did it work with an overload of a parent class method (unfamiliar with C# and the permissiveness there but it works in c++ so assuming)

@paulloz
Copy link
Member Author

paulloz commented Apr 20, 2024

That is fair, I agree. I will test more cases, and report back.

Whatever the results, I still believe this would have its place in the release notes.

@AThousandShips
Copy link
Member

I still believe this would have its place in the release notes.

I agree

@paulloz
Copy link
Member Author

paulloz commented Apr 20, 2024

Here are a few test cases you highlighted. The results correlate to what I'd expect.

The C# code

public partial class Bar : Node
{
    public void SayHello(string name)
    {
        GD.Print($"Hello {name}!");
    }

    public void SayHello()
    {
        GD.Print("Hello World!");
    }
}

public partial class ExtendedBar : Bar
{
    public void SayHello(string name, int n)
    {
        for (var i = 0; i < n; ++i)
        {
            GD.Print($"Hello {name}! ({i})");
        }
    }
}

The calls in GDScript

print("--- Direct calls")
bar.SayHello()
bar.SayHello("paulloz")

print("--- Calls by name")
bar.call("SayHello")
bar.call("SayHello", "paulloz")

print("--- Calls through callable")
var callable = Callable(bar, "SayHello")
callable.call()
callable.call("paulloz")
callable.bind("bounded paulloz").call()

print("--- Calls to the one in ExtendedBar")
bar.SayHello("paulloz", 2)
bar.call("SayHello", "paulloz", 2)
callable.call("paulloz", 2)

print("--- Everything deferred")
bar.call_deferred("SayHello")
bar.call_deferred("SayHello", "paulloz")
bar.call_deferred("SayHello", "paulloz", 2)
callable.call_deferred()
callable.call_deferred("paulloz")
callable.call_deferred("paulloz", 2)

Output

--- Direct calls
Hello World!
Hello paulloz!
--- Calls by name
Hello World!
Hello paulloz!
--- Calls through callable
Hello World!
Hello paulloz!
Hello bounded paulloz!
--- Calls to the one in ExtendedBar
Hello paulloz! (0)
Hello paulloz! (1)
Hello paulloz! (0)
Hello paulloz! (1)
Hello paulloz! (0)
Hello paulloz! (1)
--- Everything deferred
Hello World!
Hello paulloz!
Hello paulloz! (0)
Hello paulloz! (1)
Hello World!
Hello paulloz!
Hello paulloz! (0)
Hello paulloz! (1)

@raulsntos
Copy link
Member

I think #90968 should fix this. When trying to get the method info for a method that has overloads, instead of returning the first method info we find, we now return an empty one. This means overloaded methods lose typing information for the GDScript analyzer but that's probably fine.

@dalexeev
Copy link
Member

@raulsntos
Copy link
Member

Oh I missed that issue, it sounds like this is a duplicate then.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
Status: Release Blocker
Development

Successfully merging a pull request may close this issue.

5 participants