tim: Tim with short hair, smiling, wearing a black jacket over a white T-shirt (work)
[personal profile] tim

I'm going to see if I can productively write these posts a bit at a time, during long compiles. Whee!

I mentioned how yesterday, I got everything to stop being broken and returned to a state in which the only thing that was breaking was my new test case. Here it is, btw:

// xfail-fast
// aux-build:cci_class_cast.rs
use cci_class_cast;
import cci_class_cast::kitty::*;
import to_str::*;
import to_str::to_str;

fn print_out<T: to_str>(thing: T, expected: str) {
  let actual = thing.to_str();
  #debug("%s", actual);
  assert(actual == expected);
}

fn main() {
  let nyan : to_str  = cat(0u, 2, "nyan") as to_str;
  print_out(nyan, "nyan");
}

I'm going to make the bold assumption that you already can read Rust (but if not, there's a tutorial that may or may not reflect the version of the language I'm using.) The test depends on an auxiliary crate; the "aux-build" directive in a comment tells our test runner to build it; the use directive links this program with the other crate, called cci_class_cast, and finally, the import directive imports the module kitty within the crate cci_class_cast. And yes, I try to make as many of my examples as possible involve cats. There's something wrong with that?

Here's the contents of cci_class_cast, which happens to just be a single file containing a single module in this case:

import to_str::*;
import to_str::to_str;

mod kitty {

class cat implements to_str {
  priv {
    let mut meows : uint;
    fn meow() {
      #error("Meow");
      self.meows += 1u;
    }
  }

  let name : str;

  new(in_x : uint, in_name: str)
    { self.meows = in_x; self.name = in_name; }

  fn speak() { self.meow(); }

  fn to_str() -> str { self.name }
}
}

(This is actually simplified a bit. Doesn't take away from the main point.) The feature I'm trying to implement, then, is that since the class cat implements the interface to_str (roughly, "the class of types that can be converted to strings"), I should be able to cast something of type cat to something of the more general type to_str (usually this is called an "upcast"). By the way, interfaces in Rust are both sort of like type classes in Haskell, but are also part of Rust's object system (classes -- not in the type-class sense, and traits (not yet implemented) being the other ingredients). Yeah, that's a bit confusing. This cast is not strictly necessary to call a function like print_out which, in Rust's syntax for bounded polymorphism, takes a T: to_str (that is, any type T that implements the to_str interface) -- it would be type-correct in the example to pass nyan to print_out without casting it. But, to make the test case clearer, I put in an explicit upcast.

I already implemented upcasts for classes in the case where the class, the interface it implements, and the cast expression are all in the same crate. So what I was working on just now was upcasts when the class, interface, and use of the class-as-an-interface are all in different crates. To do that, I had to represent "the interfaces this class implements" in the metadata representation of a class. I talked about crate metadata yesterday. I'm realizing that the reason I've had so much trouble extending it for the features I'm implementing is that the format isn't documented (outside of the executable specifications -- the parser and prettyprinter, which are easy to get out of sync) and, as I talked about yesterday, there's no way to see the metadata that's being written and read. But I was eventually able to make the change, which involved removing an existing tag (tag_item_method, used to represent methods within a class, impl, or iface) and creating two new tags (tag_iface_method and tag_impl_method). Since classes are treated both like ifaces sometimes (because classes, like ifaces, are types) and like impls sometimes (because classes, like impls, implement ifaces), classes' representation contains both elements; ifaces' and impls' representations contain just one or the other. And then, without too much duplication of code between classes and impls, everything somehow works.

The rest of this post is quite a bit sketchier, as I wrote it meaning to edit later, but now it's 10:07 PM and that's probably never going to happen! I made a test for issue 2286 - which is kind of a non-issue, because I was confused in the first place. Enum variants are first-class, whereas class method and field names are not -- hence the latter can't be imported/exported selectively anyway. But in case that ever changes, it's good to have a test for it. Next was 2287, which turned out just to be a matter of making the test case. I guessed that this would already work out of the box, but wasn't sure if self types would work correctly within separate impls of ifaces for class types. But it turned out they already worked!

Next, Issue 2288, which is harder. Both classes and ifaces have type parameters. (Rust has an interesting way of handling polymorphism in which there are no general polymorphic types as such, only formal and actual type parameters that appear in various places. I wasn't even sure about our syntax for a parameterized class implementing a parameterized iface (or even a monomorphic class implementing a parameterized iface -- though it turns out the latter at least parses correctly with no extra effort).

First, I had to fix an easy bug in the visitor in resolve (that was my fault); I overrode a visitor field to look at an iface ref (the thing representing A<B>in "implements A<B>) and forgot to also do the default behavior of the visitor: visiting the path (in the previous example, A) to resolve the type actual-parameters (in the previous example, B). (Resolution is a pass in the Rust compiler in between parsing and typechecking, which maps every use of a name onto a unique ID representing its definition site.) But once I fixed that bug, I got the same internal error, so I think the problem is that I didn't update the type visitor to look at iface refs. More generally, the problem here is keeping code up-to-date when we change data types (in this case, data types related to the AST) -- it's a common problem to make a change and forget to update the visitor accordingly. Or at least, it is for me!

Finally, I'll plaintively wail about a couple of obvious issues. It's a pain to have to recompile an entire crate (the compiler front-end is a single crate, and the compiler middle-and-back-end is another single crate, so that's a lot of code) every time you change one line of code. Having incremental recompilation would be great. I don't know of any reason why it couldn't be done for Rust (something like SML/NJ's compilation manager or ghc --make), it's just that no one has had time. A more minor issue is that a lot of type errors give a span (source line and column number intervals) that's too wide: a type error about a mismatch between expected and actual argument mode could print out the entire function and not just the problematic argument. The typechecker also suffers from an odd form of left/right confusion that sometimes leads it to print the expected type as the actual type, and vice versa, when reporting a unification error. And finally, I'm unsure about my own approach when it comes to fixing type errors: as in, whether to fix an error, recompile, and fix the next error, or to fix all the errors the typechecker reports and the recompile. The latter is surely preferably, given how long a compile takes -- but there's just something overwhelming about seeing a wall of errors. I keep wanting to just fix one, and see what it says next. Sometimes that's the right thing to do (as when errors cascade), sometimes it just ends up being inefficient for me. I wonder if there's a friendlier way to print out multiple error messages, perhaps using colors or indentation more effectively, so that printing all the type errors in the source at once is actually more useful than printing out just the first one.

Profile

tim: Tim with short hair, smiling, wearing a black jacket over a white T-shirt (Default)
Tim Chevalier

November 2021

S M T W T F S
 123456
78 910111213
14151617181920
21222324252627
282930    

Most Popular Tags

Style Credit

Expand Cut Tags

No cut tags