Godot: It's impossible to check 'self' object type because of the Parse error

Created on 17 Sep 2018  路  15Comments  路  Source: godotengine/godot

Godot version:
47bf26c40d7d832482dcb9048590c7997ab4d8f4

OS/device including version:
Ubuntu 18.04, GTX1060

Issue description:
When having a script that can be attached to different nodes it's impossible to check it's type from inside of the script because of the Parse Error: A value of type self will never be an instance of Spatial

Steps to reproduce:

  1. Create new script
  2. Fill it with this code:
extends Node
func _ready():
    if (self is Spatial):
        pass
    if (self is Node2D):
        pass
  1. Observe the error:
    built-in:5 - Parse Error: A value of type 'self' will never be an instance of 'Spatial'

Minimal reproduction project:
SelfTypeCheckParseError.zip

discussion enhancement gdscript

Most helpful comment

See #20340 and #16958 for previous discussions.

@avencherus did a great job explaining the gist of it.

Essentially when you add static checks into the mix, the script must know the inheritance chain without taking the node into consideration. Otherwise a lot of the checks can't be done and static typing becomes pointless.

Can't really believe that this is a bug that we are using as a feature since this seems to be quite normal behavior across programming languages.

It's not normal, because it's relying on a concept that is non-existing on other languages: attaching scripts to objects. In other languages the scripts _are_ the objects. My idea was to make the scripts work on their own, since it's impossible to know to which node they'll be attached, especially when they can be attached to multiple nodes.

Unless you're thinking of multiple inheritance, which not all languages allow (and it's not a thing in GDScript and not used in Godot).

An example to illustrate the issue: say you make a script that extends Node, with the intention of attaching it to multiple nodes descending Node. Then you create a property called priority since it's relevant to the processing. Finally you attach this script to an Area node. But Area already has a property called priority already so you are overriding the parent's property, which is something that was forbidden since properties became a thing. How would the script behave? Should it use the property of the script or the native node? Should it give a runtime error? The script itself has no idea that the attached object has a property with the same name, so it's impossible to act on it without explicit runtime checks.

My proposal to solve the problem of "reusing code in different nodes" would be adding "mixins" (AKA "traits") that let you embed snippets into other scripts without using explicit composition. This would make it a compile-time feature, allowing proper static checks.


I want to point out that many of the current static checks will eventually be used to optimize GDScript, making the bytecode more specific and more efficient, leading to JIT compilation if all goes well.

All 15 comments

cc @vnen

It seems it's possible to workaround this issue by doing something like:

var selfRef = self;
if(selfRef is Spatial)...
    (...)

I think this is good to prevent the hacky thing that was allowed up to 3.0, that was the possibility to attach a script extending a base class to a node of a derived one.

I know that is useful in some cases but should be discouraged.
If the error is removed, keep a warning at least.

@eon-s can you elaborate more on why this is discouraged?

If I remember right, this was mentioned somewhere that is a bug used as a feature, and I have used this too with CollisionObjects (to mix areas and bodies) but leads to horrible design and magical practices.

That is why I think that if is kept, it must show a warning.

Well in 99% of time I'm using new script for new scene, but I don't really see a technical difference between creating a blank 'inherited' script vs directly attaching a base one when that's possible. Can't really believe that this is a bug that we are using as a feature since this seems to be quite normal behavior across programming languages.
The way how in pre 3.0 collision shapes were implemented, was quite hackish when it comes to their 'node' representation, and there were many problems around collision shapes (when users were trying to treat them as nodes). I think this is the reason of your doubts.

This was indeed a bug that it worked in the past, which @vnen fixed in his GDScript typing work.

I don't remember in what issue it was discussed though.

It is subtle, but if I understand it correctly, the way this settles out leads to multiple-inheritance. So it has been something that is being actively cleaned up in the latest versions. The code you posted would also be something getting swept away in the process, since that script would be restricted now from being added to a Spatial.

Once you use extends Node that object extends directly out of Node, and you're adding code on top of it, and it can never then become anything more than a Node. Again if I understand what's been discussed about it, it would need to be trying to do something like this:

Object > Node > my_script.gd > Spatial

When you try to extend another script, you're just building on top of it, as:

Node > base.gd > new_script.gd

Having no Spatial in between.

If you try to attach to this a Spatial, the way I think it resolves is that the Spatial will inherit all the native classes all the way back up the chain. Then try to apply your script which is just an extended Node.

Object > Node > Spatial > new_script.gd
_Object > Node > base.gd > new_script.gd_

I may have it all wrong, but in order for it to be a Spatial and apply the script would need something like multiple inheritance to merge things together.

Whereas it wants to be organized like this:

Object > Node > Spatial > base.gd > new_script.gd

Take that all with a grain of salt, I'm not at all versed in this area. I know @vnen could certainly weigh in better on this, and I'm sure he's tired of doing so at this point. XD

Personally, I'd like to see some feature where you can leave a casting option on your scripts to have them dynamically type the inheriting scripts to match up when they're being loaded. Of course only where it's valid to do so. Like these cases, a Spatial can validly cast a Node extended script up to a Spatial, etc. It should error if it's a Sprite trying to be casted into a Spatial.

This would make for some nice ways to eliminate redundant code. I personally make a lot of use of enemies that split between Kinematic and Rigid body, while sharing a lot of AI code that has nothing to do with the bodies. I have to duplicate their base classes and maintain these cloned files, each extending from different native types.

I certainly do agree with the technical aims regarding the back end of it, but I would like to believe there is some reasonable way to make a feature that can allow for this way of coding. All while not violating the inheritance model.

my_script.gd

extends PhysicsBody2D

enemy scripts

extends "my_script.gd" as RigidBody2D
extends "my_script.gd" as KinematicBody2D

Each one would takes the my_script.gd and swaps out the PhysicsBody2D with their corresponding types when each of the scripts load.

I was thinking about posting a suggestion topic for this stuff sometime soon. Just to see if anyone knows if there exists some sort of compromise that can stay in line with these back-end requirements.

See #20340 and #16958 for previous discussions.

@avencherus did a great job explaining the gist of it.

Essentially when you add static checks into the mix, the script must know the inheritance chain without taking the node into consideration. Otherwise a lot of the checks can't be done and static typing becomes pointless.

Can't really believe that this is a bug that we are using as a feature since this seems to be quite normal behavior across programming languages.

It's not normal, because it's relying on a concept that is non-existing on other languages: attaching scripts to objects. In other languages the scripts _are_ the objects. My idea was to make the scripts work on their own, since it's impossible to know to which node they'll be attached, especially when they can be attached to multiple nodes.

Unless you're thinking of multiple inheritance, which not all languages allow (and it's not a thing in GDScript and not used in Godot).

An example to illustrate the issue: say you make a script that extends Node, with the intention of attaching it to multiple nodes descending Node. Then you create a property called priority since it's relevant to the processing. Finally you attach this script to an Area node. But Area already has a property called priority already so you are overriding the parent's property, which is something that was forbidden since properties became a thing. How would the script behave? Should it use the property of the script or the native node? Should it give a runtime error? The script itself has no idea that the attached object has a property with the same name, so it's impossible to act on it without explicit runtime checks.

My proposal to solve the problem of "reusing code in different nodes" would be adding "mixins" (AKA "traits") that let you embed snippets into other scripts without using explicit composition. This would make it a compile-time feature, allowing proper static checks.


I want to point out that many of the current static checks will eventually be used to optimize GDScript, making the bytecode more specific and more efficient, leading to JIT compilation if all goes well.

Oh ok I can see now that this is a bigger hassle than it seemed for me at the first glance.
I just need the ability for simple type checks on objects (including the inheritance) so as long as both spatialObj is Node and spatialObj is Spatial would return true it's great for me.

Oh also since in that particular case I'm using it in plugin which is attaching script to a node in runtime, it would be nice if is keyword is still able to take node type into consideration... If it's unable to do that I guess we will need some additional api?

My proposal to solve the problem of "reusing code in different nodes" would be adding "mixins" (AKA "traits") that let you embed snippets into other scripts without using explicit composition. This would make it a compile-time feature, allowing proper static checks.

Been trying out the Mixin suggestion, it seems worthwhile, but there does tend to be some odd bits to it.

It adds quite some extra code in the form of prefixes.

I also notice the way to get back export vars is making them as sub-scenes, which puts it down a node in the tree. Meaning on some occasions the root scene inside another scene will have to be expanded to Editable Children to get at them, which can be clicking nightmare if you have dozens of objects in a scene that need individual adjustments.

Also have to add some careful logic at times to have them cross reference each other if they need data to travel both ways.

It'll take some getting used to and doesn't quite feel like it fits in, comparative with the rest of Godot.

@avencherus wrong issue I believe :)

Was replying to vnen's suggestion. Modified my response to include the quote.

I just need the ability for simple type checks on objects (including the inheritance) so as long as both spatialObj is Node and spatialObj is Spatial would return true it's great for me.

This use case seems to work fine:

extends Area2D
func _ready():
    print(self is Area2D) # True
    print(self is Node2D) # True

The original use case with a generic Node-extending script that would be used for higher level nodes was indeed deprecated on purpose, as explained above, so closing.

Was this page helpful?
0 / 5 - 0 ratings