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

docs: Add a paragraph about self. #8928

Open
wants to merge 16 commits into
base: master
Choose a base branch
from

Conversation

elenakrittik
Copy link
Contributor

@elenakrittik elenakrittik commented Feb 8, 2024

Adds a paragraph about the self keyword and documents that it can be used to refer to variables
defined in subclasses of current class.

Closes godotengine/godot#87944

@AThousandShips AThousandShips added enhancement topic:gdscript area:manual Issues and PRs related to the Manual/Tutorials section of the documentation labels Feb 8, 2024
@AThousandShips AThousandShips requested a review from a team February 8, 2024 18:11
@dalexeev
Copy link
Member

dalexeev commented Feb 9, 2024

Thanks for the PR! This is greatly appreciated as it is one of the oldest undocumented features of GDScript. However, in my opinion, in the current edition the emphasis is shifted a little in a wrong direction. This feature is not intended solely for inheritance, as the reader might think. I think member implies a static context, and self.member implies a dynamic context. For example, you can declare in the same class dynamic properties using _set() and _get(), and to access them you can use set("member", value) and get("member") or self.member, but you can't use just member.

In GDScript there is no _call()/_invoke() method for dynamically defining methods, so the only example is defining a method in a descendant class. Note that often considered bad practice, the base class should not know about of its descendants. It is generally recommended to define an empty ("abstract" or "virtual") method in the base class. But for the purposes of illustrating the self syntax features, we could use an example like:

if has_method("test"):
    test() # Error.
    self.test() # OK.
    call("test") # OK.

@elenakrittik
Copy link
Contributor Author

elenakrittik commented Feb 9, 2024

@dalexeev I (hopefully) addressed most of your concerns in e7e4ac8, but i am still not sure how to approach the examples. I have something like this in mind:

class_name Item extends Node

# Returns the expected size an item will take up in
# player's inventory (or -1 if it cannot be collected).
func get_size() -> int:
	var size = get(&"size")

	return size if size else -1

# When player touches an item, collect it if it is
# collectible (i.e. has a `collect` method).
func on_player_touch() -> void:
	if has_method(&"collect"):
		# collect() # Would be an error!
		# self.collect() # @dalexeev This is also appears to be an error
						 # on latest 4.2, perhaps you misremembered this part?
		call(&"collect")

# .. another file ..

class_name Potion extends Item

var size := 2

func collect() -> void:
	print("Collected a potion!")

But it looks pretty verbose and (in my view) not immediately understandable without additional comments. Do you have any better ideas?

@elenakrittik
Copy link
Contributor Author

elenakrittik commented Mar 2, 2024

@dalexeev As i haven't heard anything negative towards the proposed example, i went ahead and replaced the weapon/pistol example with this new one. I also added a .. warning:: section, and would like to hear your opinion on it, especially the second paragraph (as i am not an expert on GDScript and am not sure what is considered the best solution for each problem).

@Calinou
Copy link
Member

Calinou commented Aug 2, 2024

@elenakrittik Could you look into replying to the above suggestions (or applying them directly) so this PR can be merged? Thanks in advance 🙂

@elenakrittik
Copy link
Contributor Author

Much sorry, i'll apply the automatic change right away but i unfortunately will not be able to address dalexeev's feedback until after a week or so. Once i'm back i'll make sure to drive this to the finish line!

@elenakrittik
Copy link
Contributor Author

Sorry for the long wait! I believe everything should be good now.

@dalexeev
Copy link
Member

set("not_exist", 123) # No error or warning.
self["not_exist"] = 123 # Runtime error.
self.not_exist = 123 # Runtime error.
not_exist = 123 # Compile time error.

print(get("not_exist")) # No error or warning.
print(self["not_exist"]) # Runtime error.
print(self.not_exist) # Runtime error.
print(not_exist) # Compile time error.

Copy link
Member

@dalexeev dalexeev left a comment

Choose a reason for hiding this comment

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

Looks good to me.

Copy link
Contributor

@tetrapod00 tetrapod00 left a comment

Choose a reason for hiding this comment

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

If dalexeev approves it's good.

I suggested a couple changes to better follow the style guide.

@@ -1530,6 +1530,36 @@ Here are some examples of expressions::
Identifiers, attributes, and subscripts are valid assignment targets. Other expressions cannot be on the left side of
an assignment.

self
^^^^
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
^^^^
~~~~

Maybe promote the header by one level? I don't see how self is semantically a subsection of Expressions.

I'm also not sure if this is exactly the right section of the page to put this - is Statements and control flow really more fitting than either Classes or Properties (setters and getters)?

However, since I'm not sure exactly where the best place to put it is, it is okay to merge this PR with the new section where it is, and consider moving it in a followup PR.

Copy link
Contributor Author

@elenakrittik elenakrittik Dec 26, 2024

Choose a reason for hiding this comment

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

If we ignore the specifics of how the engine treats self (most notably, for the engine it's a keyword and not a "name"/identifier), self is just another variable that refers to the current class instance, so, personally, it makes sense to me to display self as a special-case of an identifier expression.

If we assume that self does not indeed belong to Expressions and we should move it somewhere else, i also can't find a fitting place for it. Moving it one level up is definitely not a solution (self is not a statement (unless you write a line consisting of a standalone self, but that's a technicality)). The best i can find is the Classes section, however, given it's current content, i struggle to see self there without rewriting parts or all of the section.

tutorials/scripting/gdscript/gdscript_basics.rst Outdated Show resolved Hide resolved
tutorials/scripting/gdscript/gdscript_basics.rst Outdated Show resolved Hide resolved
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area:manual Issues and PRs related to the Manual/Tutorials section of the documentation enhancement topic:gdscript
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Signals' .connect() function's argument (callable) is not type-checked if it is accessed via self..
6 participants