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

Support covariant return types for overriding functions #7450

Closed
bitbrain opened this issue Aug 5, 2023 · 8 comments · Fixed by godotengine/godot#82477
Closed

Support covariant return types for overriding functions #7450

bitbrain opened this issue Aug 5, 2023 · 8 comments · Fixed by godotengine/godot#82477

Comments

@bitbrain
Copy link

bitbrain commented Aug 5, 2023

Describe the project you are working on

I am working on an addon that manages RPG data.

Describe the problem or limitation you are having in your project

It is currently impossible to specify a more 'specific' type of a return value of any overriden function, if that returned value is a subtype of the overriding function. Many common languages like Java and C# C++ support such feature (see table), as it unlocks additional functionality for polymorphism.

Currently, Godot 4.1 produces an error like this:

Screenshot 2023-08-05 151230

Describe the feature / enhancement and how it helps to overcome the problem or limitation

Support covariant return types on functions if the return value is a sub-type of the overriding function's return value.

Describe how your proposal will work, with code, pseudo-code, mock-ups, and/or diagrams

This script does not compile as part of Godot 4.1 but after this proposal was implemented it would work as expected:

class BaseClass extends RefCounted:

	func greetings() -> void:
		print("Hello!")

class SubClass extends BaseClass:
	
	func greetings() -> void:
		print("Hello Sub Class!")

class BaseGreeting extends RefCounted:
	
	func get_obj() -> BaseClass:
		return BaseClass.new()

class SubGreeting extends BaseGreeting:
	
	func get_obj() -> SubClass:
		return SubClass.new()

func _ready():
	var base:BaseGreeting = SubGreeting.new()
	var obj = base.get_obj()
	obj.greetings() # should print 'Hello Sub Class!'

Example project of the above:

Covariant-Example.zip

If this enhancement will not be used often, can it be worked around with a few lines of script?

It can't since this is a GDScript core language feature that cannot be worked around easily.

Is there a reason why this should be core and not an add-on in the asset library?

Cannot be done via addons nor GDExtension.

@AThousandShips
Copy link
Member

AThousandShips commented Aug 5, 2023

The code without covariance will still work just won't be explicit about the type, no?

@bitbrain
Copy link
Author

bitbrain commented Aug 5, 2023

The code without covariance will still work just won't be explicit about the type, no?

The point of this proposal is to use return types explicitly - yes, functionality like this may be possible by not using any return types at all but in my project I want to enforce the use of types, as it has a lot of advantages (readability, performance, better auto-completion)

@AThousandShips
Copy link
Member

In this case you've explicitly typed base as the base type, so you wouldn't be able to get any type hinting, so I'm not sure what the value would be here?

@bitbrain
Copy link
Author

bitbrain commented Aug 5, 2023

so I'm not sure what the value would be here?

If I wanted to use SubGreeting somewhere, its returned objects can have additional methods that I want to access with type safety.

For example, let's say I have code like this:

func some_method(obj:SubClass) -> void:
   var sub_greeting = obj.get_obj()
   sub_greeting.some_other_method_unique_to_subgreeting()

This would not be possible in your example, but I'd have to guess or do a type check first. The above code is an extreme simplification but this is a common use case in Object Oriented Programming. Especially with the introduction of traits in GDScript at some point, it is even more common that the parent function may specify a very vague type and I want to provide a specific return type (but still be compatible with the trait itself)

@adamscott
Copy link
Member

adamscott commented Aug 6, 2023

I did some tests for some context.

Language Test Behavior
Java Test Behaves as the proposal suggests
Swift Test Behaves as the proposal suggests
Typescript/Javascript Test Behaves as the proposal suggests
*it don't really checks for signature changes
C# Test Fails.
error CS0115: 'SubGreeting.greetings()': no suitable method found to override
It cannot override the greetings() function as the parent class doesn't have the signature virtual public SubClass greetings()
C++ Test Behaves as the proposal suggests
OCaml Test Behaves as the proposal suggests
*it don't really checks for signature changes
PHP Test Behaves as the proposal suggests

edit: added PHP

@dalexeev
Copy link
Member

dalexeev commented Aug 6, 2023

Note that in addition to method return value covariance, the contravariance of parameters also makes sense (for example PHP). Both cases are consistent with the Liskov substitution principle.

@bitbrain
Copy link
Author

💖

@gcardozo123
Copy link

gcardozo123 commented Sep 28, 2023

This thread is already closed, but just wanna point out that it also works in C#, but the linked example used the wrong method name when overriding it. It did:

class SubGreeting : BaseGreeting {
  override public SubClass greetings() {
    return new SubClass();
  }
}

instead of

class SubGreeting : BaseGreeting {
  override public SubClass getObj() {
    return new SubClass();
  }
}

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