Nesting a type within an extension to a generic type requires indicating the "parent"'s generic type when used

Hi,

I've stumbled upon a (IMHO) weird behaviour of the Swift compiler, and would like the community's thoughts on it.

Declaration

Consider the following declaration, as part of modelling an analytics provider:

public struct Analytics<T: Hashable> {
    ...
    ...
    ...
}

Now - for namspacing purposes - one decides to declare an additional type within Analytics, as so:

extension Analytics {
    public struct Event {
        ...
        ...
        ...
    }
}

Please note that Analytics.Event doesn't have any generic arguments.

Call-Site

Assume we'd like to hold a reference to the nested Analytics.Event, in another type:

struct MyStruct {
    let event: Analytics.Event
}

As of Xcode 11.4.1 w/ Swift 5.2, this code will NOT compile. The compiler provides the following error message:

"Reference to generic type 'Analytics' requires arguments in <...>"

However, while Analytics is a generic type Analytics.Event is not. And assuming extending a type and nesting an additional type within it is a valid scenario - How come the non-generic nature of that nested type isn't taken into account?

Remarks

  1. Since nesting can be also achieved directly, without extending Analytics, I was curious to see if that was the issue. Unfortunately, even if re-written like so the error still persists:
    public struct Analytics<T: Hashable> {
        public struct Event {
            ...
            ...
            ...
        }
    
        ...
        ...
    }
    
2 Likes

The generic placeholder of the container type is always taken into account. Analytics<Int>.Event are treated as a different type from Analytics<String>.Event. You can use typealias instead:

public struct _AnalyticEvent { ... }

extension Analytics {
  typealias Event = _AnalyticEvent
}

So that you can use Event interchangably. Even then, you can not refer to Analytics.Event without specifying T.

2 Likes

Analytics.Event is in fact a generic type, generic on T. In other words, you can do the following:

struct Analytics<T: Hashable> {
    struct Event {
        var x: T
    }
}

Because of this, Analytics<Int>.Event (for example) is a seperate type from Analytics<String>.Event.

1 Like

@NobodyNada interesting observation. In that case, how would you go about restoring the Analytics namespacing?

And on a more general note - Isn't not having the ability to namespace using container types that are generic to big a toll that it should be addressed differently?

If you only want namespacing, maybe you can use non-generic Analytics. Then add generic back to the nested type as necessary.

This topic has received quite a bit of discussion in the past.

I suggested an approach in the Improving the UI of Generics thread and later refined the suggestion in the same thread. You can find related threads in the links on those posts as well.

1 Like

A related wish I've had is to be able to namespace enums inside protocols without inheriting any of the generic context, for cases (pun intended) where the enum is tightly related with the protocol — often with just a single method.

Having the ability to opt out of capturing the containing type's type parameters — “namespace only mode” nesting — would be useful.

1 Like