jhass / crystal-gobject

gobject-introspection for Crystal
BSD 3-Clause "New" or "Revised" License
127 stars 13 forks source link

Cast problems if a widget is stored in a member variable. #59

Closed hugopl closed 4 years ago

hugopl commented 4 years ago

The following program fail to compile

require "gobject/gtk"
require_gobject "GtkSource"

class TroubleMaker
  getter widget : Gtk::Label

  def initialize(builder)
    @widget = Gtk::Label.cast(builder["label"])
  end
end

LibGtk.init pointerof(ARGC_UNSAFE), pointerof(ARGV_UNSAFE)

GtkSource.init

builder = Gtk::Builder.new_from_string(<<-EOF, -1)
<?xml version="1.0" encoding="UTF-8"?>
<!-- Generated with glade 3.36.0 -->
<interface>
  <requires lib="gtk+" version="3.22"/>
  <object class="GtkApplicationWindow" id="main_window">
    <property name="can_focus">False</property>
    <signal name="destroy" handler="gtk_main_quit" swapped="no"/>
    <child>
      <placeholder/>
    </child>
    <child type="titlebar">
      <placeholder/>
    </child>
  </object>
  <object class="GtkLabel" id="label">
    <property name="visible">True</property>
    <property name="can_focus">False</property>
    <property name="label" translatable="yes">label</property>
  </object>
</interface>
EOF

builder.connect_signals
main_window = Gtk::Window.cast(builder["main_window"])

trouble = TroubleMaker.new(builder)
# Compilation fail: Error: can't cast (Pointer(LibGtk::AccelLabel) | Pointer(LibGtk::Label)) to Pointer(LibGtk::Widget)
main_window.add(trouble.widget)

# But this works
# widget = Gtk::Label.cast(builder["label"])
# main_window.add(widget)

main_window.show_all
LibGtk.main

It fails with the error:

Which expanded to:

 > 13937 | def add(widget : Gtk::Widget)
 > 13938 |   LibGtk.container_add(@pointer.as(LibGtk::Container*), widget.to_unsafe.as(LibGtk::Widget*))
 > 13939 |   nil
                                                                   ^
Error: can't cast (Pointer(LibGtk::AccelLabel) | Pointer(LibGtk::Label)) to Pointer(LibGtk::Widget)

If I use Gtk::Widget instead of Gtk::Label it fails as well, just the union is bigger with all know widget subclasses.

Expected behavior

Show a small window with a label inside.

Current behavior

It fails to compile.

jhass commented 4 years ago

Ah, well. Minimal example: https://carc.in/#/r/91nz

So the issue is that while for a local variable Crystal can track the possible types pretty well, a instance variable is shared state and could change its value at any point (from another thread). That's why Crystal has to treat it like its class or any subclass.

I'll figure something out to workaround this, probably a distinct to_unsafe method per parent.

hugopl commented 4 years ago

Nice, I was wondering if it was a bug in the compiler, but makes totally sense, as the compiler know all uses of the local variable, it also know the right type at the right moment of it.