Unsafe_property_access

Godot Version

4.6

Question

I have a scene and an attached script that adds a property called foo

I would have several instances of such scene and retrieve them as children of their container in such way:

    var list:Array = container.get_children()
    for object:Object in list:
        if object.foo == something_something:
          do_stuff()

Enabling all warnings in the debuggers tells me that

GDScript::reload: The property "foo" is not present on the inferred type "Object" (but may be present on a subtype).
  <GDScript Error>UNSAFE_PROPERTY_ACCESS

Which makes sense of course, but I’m not sure how I could go about defining the retrieved children in such way so that the compiler won’t complain about this.
I know I could bombard my code with a plethora of

@warning_ignore(“unsafe_property_access”)

but it feels it defeats the point.
I’d like NOT to ignore those warning and to make sure I can differentiate between when I’m accessing a property that indeed exists VS dealing with a bug.

Same with UNSAFE_METHOD_ACCESS…

Don’t declare object’s type.

That just moves the problem elsewhere, doesn’t it?
You would get:

Line 127 (UNTYPED_DECLARATION):"for" iterator variable "object" has no static type.

You must cast the type of the object. While resolving the warning you could still get a null runtime error if as cannot cast up.

var list:Array = container.get_children()
for object:Object in list:
	var asFoobar := object as Foobar
	if asFoobar.foo == something_something:
		do_stuff()

Or you could assert that every object in list must be of the correct type for object: Foobar.

If you did not use a class_name for this object you can still declare the type through preloading their script

const Foobar = preload("res://foobar.gd")

# ...
for object: Foobar in list

If your script is built-in, or your object scripts are disparate yet share a var foo you could test if variable is within the object and either use get or again ignore the warning.

var list:Array = container.get_children()
for object:Object in list:
	if "foo" in object and object.get("foo") == something_something:
		do_stuff()
1 Like

If you did not use a class_name for this object you can still declare the type through preloading their script

const Foobar = preload("res://foobar.gd")

# ...
for object: Foobar in list

:backhand_index_pointing_up: This solved 90% of my cases, thanks!

The other 10% was something like:

func _input(event:InputEvent) -> void:
    if event is InputEventKey and event.is_released():
        if event.keycode == KEY_SPACE:
            next_turn()

which I now have to rewrite as:

func _input(event:InputEvent) -> void:
    if event is InputEventKey and event.is_released():
        if "keycode" in event:
            if event.get("keycode") == KEY_SPACE:
                next_turn()

It feels a bit of a convoluted way of doing the same thing (having to use .get() instead of treating that as a normal property), but it seems to do the job.

I’m also surprised that I have to do so for built-ins, where I had assumed that “of course _input() is designed to handle InputEventKey elegantly”, but not a big deal.

This leaves me with another question… :slight_smile:

How to deal with an UNSAFE_METHOD_ACCESS in case I’m working with shaders?
This:

var my_material:Material = object_with_assigned_shader.get_material()
my_material.set_shader_parameter("my_param", some_color)

Produces:

Line 21 (UNSAFE_METHOD_ACCESS):The method "set_shader_parameter()" is not present on the inferred type "Material" (but may be present on a subtype).

Name your classes instead. Doing it like this is rather clunky.

Same as in any other case: either declare the proper type that actually has the method you’re calling, or don’t use static typing at all.

var my_material: ShaderMaterial = object_with_assigned_shader.get_material()
1 Like

This may be a better use of as since you are checking the type anyways with is

var event_key := event as InputEventKey
if event_key != null and event_key.is_released():
    if event_key.keycode == KEY_SPACE:
        next_turn()

In this case you are hoping for the type ShaderMaterial, not just Material.

But again if there are disparate yet shared methods, you can use has_method("foo") and call("foo", foo_arg1). This is the worst option though, just like "foo" in object and get this turns your compile-time warnings into runtime errors without any autocomplete, it only exacerbates the problem.

Thank you both, ShaderMaterial was clearly the way to go here.

Name your classes instead. Doing it like this is rather clunky.

I’m starting to think this could be a good habit, just to avoid having to write all that stuff again if I need to access it from a different script

Definitely a very good habit.