Type system: distinction between type and interface type

Can someone please explain the difference between a type and an interface type as seen throughout the Swift compiler?

Thanks!

Interface type is documented in Lexicon.rst:

The type of a value or declaration outside its generic context. These types
are written using "formal" generic types, which only have meaning when
combined with a particular generic declaration's "generic signature".
Unlike contextual types <contextual type>, interface types store
conformances and requirements in the generic signature and not in the types themselves. They can be compared across declarations but cannot be used directly from within the context.

For example, say you have something like a polymorphic function foo<T>(t: T) -> Int? where T: Collection, T.Element == Int. Then the T: Collection and the T.Element == Int are not stored in the interface type but in the corresponding GenericSignature.

With that, I think parts of the explanation make more sense:

which only have meaning when
combined with a particular generic declaration's "generic signature"

This explains why you need the GenericSignature -- it doesn't make sense to interpret the interface type without it (or at least, not fully, you can still understand some of the structure though) because you are missing out on meaningful information. For example, if you look at Types.h/GenericFunctionType, you'll see that it the factory constructor GenericFunctionType::get takes a GenericSignature to keep track of this.


"type" itself is an umbrella term that encompasses all the different .*Type data structures (and TypeBase, I guess) in the compiler. If you are referring the Type data structure itself, there's a doc comment there which might help:

/// Type - This is a simple value object that contains a pointer to a type
/// class. This is potentially sugared. We use this throughout the codebase
/// instead of a raw "TypeBase*" to disable equality comparison, which is unsafe
/// for sugared types.

"type class" here means some class like FunctionType or BuiltinIntegerType, i.e. something which is a subclass (directly or transitively) of TypeBase. The note about sugar is there because when types are being compared for equality, you want to compare the types without sugar, so pointer equality would have the wrong semantics. There is a comment below reflecting the same point

// Direct comparison is disabled for types, because they may not be canonical.
void operator==(Type T) const = delete;
void operator!=(Type T) const = delete;

By contrast, these operators are overloaded for CanType, which don't have sugar.


You might also find this blog post useful if you're hacking around the AST: https://medium.com/@slavapestov/the-secret-life-of-types-in-swift-ff83c3c000a5

4 Likes

Thanks for the information Varun! Definitely clears up the distinction some. I'm still a little bit puzzled about when a construct would have one vs. the other (or both). For instance, a VarDecl can have both a type and an interface type (as far as I can tell from the AST). I'm trying to get out of the business of writing code like: "if (hasInterfaceType) { ... } else if (hasType()) { ... }". This seems wrong.

Thanks for any further clarification.

I think it depends on where you are in type-checking? Not sure (cc @codafi, he can answer this question better). IIUC, you probably want to use getInterfaceType() or getType() depending on what you are trying to do, instead of checking hasInterfaceType() or hasType().

Yes, please do not use hasInterfaceType(). It is a crutch from a bygone era that we are exclusively using for breaking cycles in semantic analysis.

In fact, if you see any uses that you can just remove, tag me and let's get it committed.

You also shouldn't have to ask hasType() for VarDecl nodes. The type of a declaration is now (mostly) derived from its interface type mapped into the enclosing declaration context, and that is exactly how it is implemented for variables in particular.

@Varun_Gandhi has given you a great deal of good information about the distinction we make in the AST between these kinds of types. I'll offer a shorthand:

The interface type is what the user writes, its counterpart the contextual type (often seen without the "contextual" part) is the type the compiler writes.

You'll notice the user-facing parts of the compiler mostly speak in terms of interface types, while the gnarly (often lower-level in the stack) implementation bits mostly speak in terms of contextual types.

The "interface type" is what you want most of the time. VarDecl::getType() is a convenience method that is equivalent to var->getDeclContext()->mapTypeIntoContext(var->getInterfaceType()).

Thanks again @Varun_Gandhi, @codafi, and @Slava_Pestov. Very helpful.

Terms of Service

Privacy Policy

Cookie Policy