Skip to content

Instantly share code, notes, and snippets.

@filip-sakel
Last active June 25, 2026 18:07
Show Gist options
  • Select an option

  • Save filip-sakel/b7aab655b4a81c09f0bd0901693b9424 to your computer and use it in GitHub Desktop.

Select an option

Save filip-sakel/b7aab655b4a81c09f0bd0901693b9424 to your computer and use it in GitHub Desktop.
TypeQualifier Design Outline

Believe it or not, the diagram below is a simplified version 😅. Here's the main idea: you give TypeQualifier some type syntax (no semantic information), and it resolves it to extended nominal types: nominal declarations and all their accessible extensions.

First, we convert the given type syntax to an array of references we can more easily look up; e.g., (Collection & MyProto).Element becomes Collection.Element and MyProto.Element. Then, we split each type reference up into the base type and its member type; in our example, the base is Collection with member type Element. Like all type syntax, Collection depends on its declaration context, so we perform unqualified type lookup to find the referenced type declaration. For now, let’s assume unqualified lookup returns a nominal declaration. If the declaration is nested within an extension, its qualified name depends on the extended type, so we have to recursively resolve that before continuing. For instance, if we don’t resolve the extended type B in the followoing code, the types A.C and B.C seem completely unrelated:

struct A {}
typealias B = A
extension B { struct C {} }

If the nominal type declaration isn’t nested in an extension, we can walk up the syntax tree to find the qualified name. The final qualified type should look like MyModule::A.MyModule::B.

Now that we have a qualified base type —in our case, Swift::Collection— we can start binding extensions. First, we gather up all the extensions available from the type syntax’s location (current file, module and imported modules). Then, we recursively resolve each extended type to a qualified type, and bind the extensions that match. At this point, we've resolved the extended nominal type for the base type. So, a simple direct type lookup gives us the member type’s declaration. Finally, to resolve the final extended nominal type, we follow the same steps we performed to resolve the base type declaration.

I’m glossing over some stuff. I assume unqualified and direct-type lookup also handle module selectors. Moreover, I assume unqualified lookup will return a non-local nominal type; here’s how we handle other type declarations:

  1. We skip generic parameters and associated types; during the proposal, we decided they’re out of scope for this project.
  2. We recursively resolve type aliases
  3. If the declaration context is local (say, inside a function), we don’t have to bind extensions, saving us some work

Lastly, I’m still exploring how to bind extensions in a way that avoids cycles. Though, even the compiler seems to struggle with that in certain edge cases.

Display the source blob
Display the rendered blob
Raw
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment