# Friday, 17 October 2003
« GNU Classpath Meeting | Main | Private Super Classes »
Ghosts

It took a bit longer than I anticipated, but I finally managed to put together a new snapshot and check in the changes I made in last month and a half.

What's new?

  • Fixed bug in class load failure diagnostics code.
  • Instead of having a single Type property on TypeWrapper, we now have different properties for different usages. I already know this isn't the final way I'm going to handle things, but for now it is a nice improvement that makes it easier to treat types differently based on where they are appearing (e.g. field, local, argument, base type).
  • Simple ghost references are translated as value type wrappers. See below for details.
  • Reflection support for ghost types.
  • Fixed a few ILGenerator.Emit() bugs where incorrect argument types were passed (int instead of short or byte).
  • Added a NoPackagePrefixAttribute to allow .NET types to not be prefixed with the "cli" prefix. (Suggested by Stuart Ballard)
  • Fix to prevent non-public delegates from being visible.
  • Several fixes in the handling of unloadable types.
  • Fixed java.lang.Comparable interface and method attributes to be identitical to the real interface.
  • Fixed java.lang.Runtime.exec() support for quoted arguments.

Ghost References

First of all, why did I feel the need to change it? Imagine that java.lang.StringBuffer had the following methods:

public void append(Object o) { ... }
public void append(CharSequence cs) { ... }

Previously, CharSequence would be erased to Object, so you'd have two methods with an identical signature, thus requiring name mangling. Using the method from C# would become very inconvenient, both because of the unexpected name, but also because of the fact that it isn't at all clear what argument type is expected (and passing an incorrect type will give odd results, like ClassCastException or IncompatibleClassChangeError).

Enter the value type wrapper. Here is how the java.lang.CharSequence interface is compiled now (pseudo code):

public struct CharSequence {
  public interface __Interface {
    char charAt(int i);
    // ... other methods ...
  }
  public object __ref;
  public static CharSequence Cast(object o) {
    CharSequence s;
    if(o is string) {
      s.__ref = o;
      return s;
    }
    s.__ref = (__Interface)o;
    return s;
  }
  public static bool IsInstance(object o) {
    return o is string || o is __Interface;
  }
  public object ToObject() {
    return __ref;
  }
  public static
    implicit operator CharSequence(string s) {
    CharSequence seq;
    seq.__ref = s;
    return seq;
  }
  public char charAt(int i) {
    if(__ref is string) {
      return StringHelper.charAt((string)__ref, i);
    }
    return ((__Interface)__ref).charAt(i);
  }
  // ... other methods ...
}


All types that implement CharSequence will be compiled to implement CharSequence.__Interface and will also get an implicit conversion operator to convert to CharSequence. This allows C# code to easily call any methods that take a CharSequence argument, because any valid type will be implicitly convertible to the CharSequence value type.

What Are The Downsides?

There are a few cons:

  • This trick doesn't work for arrays. So a CharSequence[] is still compiled as Object[]. Hopefully arrays are far less common, so this will not be a big issue.
  • C# code implementing CharSequence, needs to implement CharSequence.__Interface and also needs to provide an implicit conversion operator to the CharSequence value type.
  • The static methods Cast and IsInstance need to be used instead of the normal language features.
  • It is very easy to accidentally box the CharSequence value type in C#, when you pass this to Java things will get very confusing. The proper way to convert CharSequence to Object is by calling ToObject() on it.

Update: I forgot to mention that the reason that you have to implement the implicit conversion on all types that implement the interface is because C# doesn't allow implicit conversion from/to interfaces, otherwise the CharSequence value type could just have an implicit conversion from CharSequence.__Interface and we'd be done. Funnily enough, while C# doesn't allow you to define them, it turns out it does consume them, but I don't know if this is guaranteed behavior or a bug. I need to find this out. If it turns out to be correct, then I'll add the implicit conversion operator to the value types, that makes life for C# implementers a tiny bit easier.

All in all, I think this is an improvement, but obviously not perfect. As always, feedback is appreciated.

The new snapshots: binaries and complete.

Friday, 17 October 2003 15:57:05 (W. Europe Daylight Time, UTC+02:00)  #    Comments [2] Tracked by:
"diet pills to help lose weight" (diet pills to help lose weight) [Trackback]

Friday, 17 October 2003 20:41:02 (W. Europe Daylight Time, UTC+02:00)
Is there any way that CharSequence itself could actually implement CharSequence.__Interface? If a type can't implement an interface that's nested inside itself, name mangling could be used instead of nesting (eg CharSequence__Interface as a separate type).

If you did that, would it solve the problem of passing a boxed .NET CharSequence to a java method expecting a java CharSequence (which in .NET terms is CharSequence.__Interface)?

The answer to that depends on exactly how IKVM handles the case of boxed value types that implement interfaces when passing them to Java - and I have no idea of the answer to that.

I'm also a little nervous about what would happen if you had an implicit conversion from an interface to a class that implements that interface. What would (CharSequence) (CharSequence.__Interface) (CharSequence) "hello" do? My guess is it would create a CharSequence with ref containing "hello", then create another CharSequence with its ref containing the first CharSequence, each cast adding a new layer of wrapping. Unless the implicit cast operator detected this case and avoided it...

If CharSequence was a class rather than a struct, and you made sure that (CharSequence)(string) null returned null, that might actually avoid the boxing problem altogether. And help with the array problem. Is there any reason it has to be a struct?

(Hey, I told you I'd have comments once I got my head around it further... ;) )
Stuart
Monday, 20 October 2003 15:29:31 (W. Europe Daylight Time, UTC+02:00)
I considered having CharSequence implement the interface, but while this allows casting to the interface to work, it breaks identity and casting to the real type also fails. Basically, this would cause some scenarios to work transparently, but others to fail in more subtle ways. I don't like subtle failures.

Making CharSequence a class solves nothing, because you still have the problems I mentioned above and it also adds another level of indirection, making performance even worse.

Name
E-mail
Home page

I apologize for the lameness of this, but the comment spam was driving me nuts. In order to be able to post a comment, you need to answer a simple question. Hopefully this question is easy enough not to annoy serious commenters, but hard enough to keep the spammers away.

Anti-Spam Question: What method on java.lang.System returns an object's original hashcode (i.e. the one that would be returned by java.lang.Object.hashCode() if it wasn't overridden)? (case is significant)

Answer:  
Comment (HTML not allowed)  

Live Comment Preview