Matt Diephouse

Entry 1: A better “no subscripts” error

14 June 2018

While working on SR-7380, I came across this error in Swift 4.1:

error: type 'Int' has no subscript members
_ = 1[foo: 2]
    ^

It makes sense now, but I was a little confused at the time. (And it was more confusing since it was in a case where it should have been valid except for the bug I was trying to fix.)

But this seemed like something I could improve, so I thought I’d try. (And improving diagnostics is one of the reasons why I’m interested in the type checker.)

Where does this error come from?

I actually like debugging from diagnostics (a term that includes errors, warnings, and “notes”) because they’re easy to pin down. In this case, I just had to search for “has no subscript members”.

(As an aside: I finding searching to be one of the best ways to navigate a codebase. I use Xcode’s “Find in Project” and a tool like the Silver Searcher constantly. A search tool and some basic regexes can get you really far.)

Searching for that error turns up a rather large file, DiagnosticsSema.def. This file is full of definitions like this:

ERROR(type_not_subscriptable,none,
      "type %0 has no subscript members"
      (Type))

This defines the diagnostic. This one is an error. It’s referred to from the code as type_not_subscriptable. And it has a format string that defines the error and takes a Type as its only argument.

Simple enough.

What should the error be?

Deciding what to replace it with was actually the hardest part.

Looking around the definitions file a bit, I eventually noticed errors like this:

error: value of type 'Int' has no member 'foo'
_ = 1.foo()
    ^ ~~~

I don’t think I’ve ever been confused by this error, so I decided to try to get closer to it.

First, I noticed that it said value of type instead of type. That seemed to make things a lot clearer.

Then I thought that subscript members seemed a little weird. I searched the Swift subscript documentation and never saw anything refer to them as members. (Although I’ve seen that in the code of the compiler.) Member, I think, is generally used in Swift because it encompasses both methods and properties. (And you can’t tell whether foo.bar() is a method or a property from looking at its usage.)

Instead, most of the diagnostics and documentation referred to them as just subscripts. That’s what I’d call them too, so that seemed like a better description.

error: value of type 'Int' has no subscripts
_ = 1[foo: 2]
    ^

That seemed hugely better to me.

Finishing Up

After getting the description in order, I noticed that the name was also a little different than the names of the similar “has no member” errors. So I renamed type_not_subscriptable to could_not_find_value_subscript to match. This was an easy search-and-replace.

The last step was to update the tests. The generated Xcode project doesn’t include the tests files, so I searched from the command line using the Silver Searcher.

Diagnostic tests look something like this:

func almostSubscriptableValueMismatch(_ as1: AlmostSubscriptable, a: A) {
  as1[a] // expected-error{{type 'AlmostSubscriptable' has no subscript members}}
}

A comment describes what the expected error or warning is. Updating them is straightforward: you just change the text.

Once I had that, I ran the tests locally to make sure I didn’t miss anything. You can see my PR here.

Updating an existing diagnostic like this is surprisingly easy. Hopefully you’ll keep that in mind next time you’re confused by a Swift error. This is a great way to start contributing to Swift and an easy way that the community could greatly improve the daily lives of Swift developers. (Although it’s often the case that a confusing error is just a bad diagnostic, which is different altogether.)

Next, I hope to add more and better detail to a diagnostic. That’s a bit more involved than this, but combines what I learned here and in Entry 0.