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

[RFC] respond_to? for library functions #1688

Open
technorama opened this issue Oct 4, 2015 · 12 comments
Open

[RFC] respond_to? for library functions #1688

technorama opened this issue Oct 4, 2015 · 12 comments

Comments

@technorama
Copy link
Contributor

Many libraries have functions that may or may not exist depending on version, configure options, or platform. Optional functions allow additional features/methods to be used depending on the specifics of the library installed. Querying for the availability of a function can be done using dlopen and dlsym.

Suggested syntax:

lib LibC
  fun? foo
end

With

def foo
  if LibC.foo
    LibC.foo
  end

  LibC.try &.foo
end

Or

def foo
  if LibC.respond_to?(:foo)
    LibC.foo
  end
end

Please note the above can be evaluated at compile time and no runtime conditionals are necessary.

@jhass
Copy link
Member

jhass commented Oct 4, 2015

This would probably not be feasible before integration with https://github.com/manastech/crystal_lib is done.

@technorama
Copy link
Contributor Author

I'm not so sure about that. The logic is pretty simple and can be implemented as compiler directives.

Pseudo code to run during compilation.

def fun?(...)
  if library_has_function ...
    fun(*args)
  else
    fun(name, nil)
  end
end

def library_has_function path, name
  dlopen(...)
  dlsym(handle, name) != NULL
end

@luislavena
Copy link
Contributor

@technorama what about usage of static libraries, which cannot be dlopen or dlsym?

@straight-shoota
Copy link
Member

I think this can be devided into two separate features:

A) Determining if a linked library includes a specific symbol. This can be achieved using macros - which should be easier and more flexible solution than introducing new keyword like fun?.

For example something like this:

lib LibFoo
  {% if symbol_defined?(:foo) %}
  fun foo
  {% end %}
end

For shared libraries this should be relatively easy to do using the already existing bindings to dlopen and dlsym from libc. I'm not sure about the approach for static libraries. As a crude hack, parsing the output of nm should work but it should be better than that.

B) Make responds_to? work with types to check if a class method exists.

if LibFoo.responds_to?(:foo)
  LibFoo.foo
end

This nearly works, but currently it is required to assign the type to a variable:

libfoo = LibFoo
if libfoo.responds_to?(:foo)
  libfoo.foo
end

An alternative would be to provide a macro method for this purpose. There is already TypeNode#has_method? for instance methods (see #4474), but not for class methods. In fact, the macro approach is used for this in several implementations in Crystal::System, for example:

{% if LibC.methods.includes?("clock_gettime".id) %}
ret = LibC.clock_gettime(LibC::CLOCK_REALTIME, out timespec)
raise Errno.new("clock_gettime") unless ret == 0
{timespec.tv_sec.to_i64 + UnixEpochInSeconds, timespec.tv_nsec.to_i}
{% else %}

But: I don't think that actually works.

{% if !LibC.methods.includes?("printf".id) %}
  LibC.printf("foo") # => foo
{% end %}

@bew
Copy link
Contributor

bew commented Jan 2, 2018

Might be related to #5244

@straight-shoota
Copy link
Member

straight-shoota commented Jan 3, 2018

If someone's interested, I tried a stab at implementing the macro method in this branch.

A few notes on that:

  • I have no idea how it could be implemented as a "plain" responds_to? magic method without using macros (or if that's even possible). It works when assigned to a variable, so it probably can be done somehow. Using a macro was the easier path for a start.
  • I didn't get it to work on LibDef (but other types), because I couldn't find out how the declared fun names can be accessed.
  • The branch also contains a commit for enhancing DL which would be useful for implementing symbol_defined?.

@bew
Copy link
Contributor

bew commented Jan 3, 2018

@straight-shoota I have a working thing using LibDef on bew/crystal@add-lib-macro-methods, this is a bit old, but it should works well with the following syntax:

{% if SomeLib.has_function? "some_fun" %}
  # do stuff
{% end %}

I plan to make a PR in the next few days to discuss my implementation tentative, but feel free to play with it ;)

@Heaven31415
Copy link
Contributor

Hi, what is going on with this addition?

@asterite
Copy link
Member

This already works, but only inside methods (not at the top-level):

lib LibFoo
  fun foo
  fun bar(x : Int32) : Int32
end

def foo
  {{ LibFoo.class.methods.stringify }}
end

p foo

Just do LibFoo.class.methods.any? { |m| m.name == "some_name".id }

@Heaven31415
Copy link
Contributor

Hmm, I tried something like this, however I'm always getting false no matter what name I pass into it:

lib LibFoo
  fun version = version : Int32
end

macro exists?(name)
  {{ LibFoo.class.methods.any? { |m| m.name == name.id } }}
end

p exists?("version") # Always false, IDK why

@asterite
Copy link
Member

@Heaven31415 As I said, it only works when you use it inside a method, not at the top-level...

@Heaven31415
Copy link
Contributor

Sorry, I just didn't get it first time, now I see, thanks for help.

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

No branches or pull requests

8 participants