John Lam (IRONRUBY)
2008-Apr-22  13:55 UTC
[Ironruby-core] Protocols and implicit conversions
I wanted to call out a fairly simple pattern that shows up in our libraries that
folks largely haven''t caught onto in the patches that they''ve
been submitting:
Wayne recently submitted a patch for exist? and exists? which fixes bug #19626:
[RubyMethod("exist?", RubyMethodAttributes.PublicSingleton)]
[RubyMethod("exists?", RubyMethodAttributes.PublicSingleton)]
public static bool Exists(object self/*!*/, MutableString/*!*/ path) {
    return File.Exists(path.ToString()) || Directory.Exists(path.ToString());
}
While this works fine, if you pass it legal arguments, it blows up when the
invariants are broken (eg path == null). You can see it blow up when you run the
specs for this method from the command line via:
rake spec file exist dox
To make this patch complete, you''ll need to add an overload that
accepts a nullable object parameter, and add a [NotNull] attribute to the
previous overload:
[RubyMethod("exist?", RubyMethodAttributes.PublicSingleton)]
[RubyMethod("exists?", RubyMethodAttributes.PublicSingleton)]
public static bool Exists(object self/*!*/, [NotNull]MutableString/*!*/ path) {
    return File.Exists(path.ToString()) || Directory.Exists(path.ToString());
}
[RubyMethod("exist?", RubyMethodAttributes.PublicSingleton)]
[RubyMethod("exists?", RubyMethodAttributes.PublicSingleton)]
public static bool Exists(CodeContext/*!*/ context, object self/*!*/, object
path) {
    return Exists(self, Protocols.CastToString(context, path));
}
Notice how the null case is taken care of by Protocols.CastToString(). It also
will handle the case where the user passes a string that can ''act
like'' a string by implementing to_str. This is an *extremely* common
case in the Ruby libraries.
The [NotNull] attribute is used by the binder to ensure that a null is never
passed as a parameter to the first overload, but instead directs the caller to
the second overload. This is the case which correctly handles null (and is also
reflected by the fact that we are declaring via comments that path is *nullable*
eg missing the spec# bang).
Let me know if this isn''t clear or needs some further clarification.
Thanks!
-John
Just typo:
public static bool Exists(object self/*!*/, ...
should be
public static bool Exists(object/*!*/ self, ...
/*!*/ is a type modifier.
Anyway, note that [NotNull] and /*!*/ are two semantically different things.
The attribute is directing the overload resolution to select a different
overload if the argument is null.
The non-null contract /*!*/ is declaring that the method expects the argument
not to be null, otherwise an exception is thrown. Seems to be similar, but
consider this case:
[RubyMethod("eval")]
public static object Evaluate(CodeContext/*!*/ context, object self,
[NotNull]MutableString/*!*/ code, [Optional]Binding binding,
  [Optional, NotNull]MutableString file) { ... }
The last parameter is Optional and NotNull. That means, if I pass null as 5th
argument, this overload is not applicable. If I call this method only with 4
arguments, this overload is selected (provided that other arguments match the
parameters) and ''file'' parameter is assigned null. Therefore
this parameter is not marked by /*!*/. Or I can just call it with 5 arguments,
5th being a string.
Another difference is that the contract is not available at run-time.
It''s only used by tools that recognizes it (Spec#).
Tomas
-----Original Message-----
From: ironruby-core-bounces at rubyforge.org [mailto:ironruby-core-bounces at
rubyforge.org] On Behalf Of John Lam (IRONRUBY)
Sent: Tuesday, April 22, 2008 6:56 AM
To: ironruby-core at rubyforge.org
Subject: [Ironruby-core] Protocols and implicit conversions
I wanted to call out a fairly simple pattern that shows up in our libraries that
folks largely haven''t caught onto in the patches that they''ve
been submitting:
Wayne recently submitted a patch for exist? and exists? which fixes bug #19626:
[RubyMethod("exist?", RubyMethodAttributes.PublicSingleton)]
[RubyMethod("exists?", RubyMethodAttributes.PublicSingleton)]
public static bool Exists(object self/*!*/, MutableString/*!*/ path) {
    return File.Exists(path.ToString()) || Directory.Exists(path.ToString());
}
While this works fine, if you pass it legal arguments, it blows up when the
invariants are broken (eg path == null). You can see it blow up when you run the
specs for this method from the command line via:
rake spec file exist dox
To make this patch complete, you''ll need to add an overload that
accepts a nullable object parameter, and add a [NotNull] attribute to the
previous overload:
[RubyMethod("exist?", RubyMethodAttributes.PublicSingleton)]
[RubyMethod("exists?", RubyMethodAttributes.PublicSingleton)]
public static bool Exists(object self/*!*/, [NotNull]MutableString/*!*/ path) {
    return File.Exists(path.ToString()) || Directory.Exists(path.ToString());
}
[RubyMethod("exist?", RubyMethodAttributes.PublicSingleton)]
[RubyMethod("exists?", RubyMethodAttributes.PublicSingleton)]
public static bool Exists(CodeContext/*!*/ context, object self/*!*/, object
path) {
    return Exists(self, Protocols.CastToString(context, path));
}
Notice how the null case is taken care of by Protocols.CastToString(). It also
will handle the case where the user passes a string that can ''act
like'' a string by implementing to_str. This is an *extremely* common
case in the Ruby libraries.
The [NotNull] attribute is used by the binder to ensure that a null is never
passed as a parameter to the first overload, but instead directs the caller to
the second overload. This is the case which correctly handles null (and is also
reflected by the fact that we are declaring via comments that path is *nullable*
eg missing the spec# bang).
Let me know if this isn''t clear or needs some further clarification.
Thanks!
-John
_______________________________________________
Ironruby-core mailing list
Ironruby-core at rubyforge.org
http://rubyforge.org/mailman/listinfo/ironruby-core