What the eff is DeclNameRef?

As part of my work towards module-qualified names, I've added a new type to the compiler, DeclNameRef, and adopted it across much of the frontend. If you hack on the compiler a lot, you're suddenly going to see this thing everywhere, so I thought I'd explain a few things about it.

What is it?

DeclNameRef is used wherever the compiler is processing the name of a declaration that is defined somewhere else. (In SAT question terms, "DeclNameRef is to DeclName as DeclRefExpr is to Decl".)

For example, in this snippet of code:

struct Foo: Bar.Baz { ... }

Bar and Baz should be represented by DeclNameRefs because they refer to types declared elsewhere, while Foo should be represented by a DeclName since it is the place where that declaration's name is actually defined.

This notion is carried beyond the parser and AST themselves and into places where we're processing information about the AST. For example, the type checker should use DeclNameRef to refer to names extracted from the expression it's type checking and DeclName to refer to names of overloads it's trying to match to them. (In practice, there are a few workarounds in the type checker that I'll need to address in future patches, but this is the principle, at least.)

Where is it used?

At pretty much every point between where we parse various AST nodes:

  • The TypeRepr nodes that involve identifiers
  • UnresolvedDeclRefExpr
  • UnresolvedMemberExpr
  • UnresolvedDotExpr
  • KeyPathExpr::Component
  • EnumElementPattern
  • TypeAttributes::Convention
  • DynamicReplacementAttr
  • DifferentiableAttr
  • DerivativeAttr

And where we look up names:

  • Qualified lookup
  • Unqualified lookup
  • AnyObject lookup

(Not direct lookup; by that point we've started picking apart the user-written name.)

Why does it exist?

The immediate need is the module qualification proposal. Basically, anywhere you use a DeclNameRef, you'll be able to include a module name:

struct Foo: XKit::Bar.YKit::Baz { ... }

But anything represented by a DeclName would not support module names:

struct ZKit::Foo ...    // invalid

However, this is a generally useful distinction to make because we usually match DeclNameRefs to DeclNames with looser rules. When we're writing something, we should know which one we're trying to do here.

This refactoring has also effectively converted several parts of the compiler that previously used Identifier to use DeclName instead. That essentially makes progress towards supporting enum argument label matching more fully or adding the ability to label the generic parameters of types. But those are definitely ideas for the future; for now, this ought not to change any behavior.

How does it work?

Currently, DeclNameRef is a simple wrapper around DeclName. You can convert a DeclName to DeclNameRef using its constructors. Given an existing DeclNameRef, you can compute a version with different argument labels using withArgumentLabels(), or strip them using withoutArgumentLabels(). Finally, you can fetch a ValueDecl's name as a DeclNameRef (usually to look up its name as though the user wrote it) using ValueDecl::createNameRef().

Most DeclNameRef constructors are explicit. This is intended to reduce bugs where you mix up the name you're matching with the name it's supposed to match against, but I'm still trying to figure out if this is worth the trouble.

You can get the DeclName from a DeclNameRef using getFullName() (which is perhaps something of a misnomer); it also supports most of the members of DeclName. isSimpleName() seems to be a particularly important member in practice, because it lets you compare a DeclNameRef to one of the Context.Id_ constants.

Eventually, DeclNameRef will support an optional second field with a module name, but that will be part of the implementation of module qualification.

What if I don't like something about this type?

Then let's change it! I committed it in its current state because adding it touched so much of the compiler that the branch needed to be rebased constantly, but things like the set of members it should support (and their names) are totally open for discussion. Consider this thread to be an extended code review.

5 Likes

Thanks for writing this up! If I may suggest, could you add parts of this explanation to DeclNameRef's doc comment in the source?

Also, your point about

This refactoring has also effectively converted several parts of the compiler that previously used Identifier to use DeclName instead.

seems interesting -- maybe it would be useful to add something to Identifier's doc comment that goes like "if you want to do X, you probably want to use DeclName instead of Identifier", based on the mistakes you saw during your refactoring.

3 Likes