Generic Compiler auto-suggest Type

When I implement objects or methods that use generics I find myself at times forgetting which types conform to the protocols required by the generic methods.

Normally I would define an enum, or static variables so that when my cursor is to the right of the first arrow <| the compiler suggest all the available options to me.

I can't do that when I define generics, and it means I have to search the codebase and hope that every developer that created a type that conforms to the protocol put the files in the same place.

It would be great if the compiler could search for all the Types that conform to the protocol that the generic needs to conform to and suggest them just like enums and static/class vars/functions.

5 Likes

When do you need to specialize for a type, without knowing which type?

When accessing types that conform to a protocol required by the generic.

This is why I sometimes find myself debating whether to use enums instead of protocols because specialization doesn’t work for presenting available types that conform to protocols.

For example let’s say there are 4 different types that conform to IsClassy in my project, and a method I want to use says that I can define a generic type so long as that generic type conforms to isClassy. I don’t happen to have those 4 types that conform to IsClassy memorized. It doesn’t seem outlandish for the compiler to be able to identify those 4 different types and suggest them to me, thereby saving the trip of seeking out every type that conforms to IsClassy.

1 Like

Seems very reasonable. As this doesn't involve a change to the rules of the language or standard library it's not really a Swift Evolution topic; could be reasonable to file a bug requesting this as a QoI enhancement, I think!

3 Likes

I think I would love it to be done enough that I would experience it as a feature. However, I’m happy to change the label, if you think and if it increases the odds of implementation.

Where can I find this label you mentioned? I’m mot seeing it in the label list for my original posts.

Bugs are filed at bugs.swift.org

Can you provide a more concrete example? I would say it’s pretty rare for me to think “I need to do this generic operation but I don’t know on which type”. Generally I’m coding for a specific use case and then reach for a generic data structure. For example, it’s more common for me to think:

I have this array of identifiers and I’d like to remove the duplicates. I know, I’ll use a Set!

Rather than:

I’d really like to use a Set but I don’t know which kind of Hashable objects to put inside of it.

2 Likes

Did you see this: Generic Compiler auto-suggest Type - #3 by scott-lydon?

Its mainly for defining type declarations where you have to decide which concrete generic representations to use.

Generics can be very useful when creating reusable components and its a logical conclusion to nest those generics, like so:

var myTable: Table<Section<Cell<SubView>>>>

As I am getting great benefit and reuse from nesting, I'm also facing the downside of having to search for the section that I want to use, the Cell that I want to use, the subView and so forth.

Without a concrete example I can‘t say for sure but it sounds like your use case should be covered by [SE-0299] Extending Static Member Lookup in Generic Contexts

I think I have a decent example of this that I experienced just earlier this week, I had set up a protocol PlayerProvider and a view with a property var provider: PlayerProvider, with two implementations of types conforming to PlayerProvider in the project, FilePlayerProvider and NetworkPlayerProvider (actual names simplified, assume there can be many such conformances).

The next day I made an instance of my view by typing:

let view = PlayerView()
/* other view setup here */
view.playerProvider = 

When I got to the equals, I realised I had temporarily forgotten what I had called my conforming types. If I understand correctly, this pitch would see Swift generate an autocomplete popup with a list of confirming types right there to fill in. It's a small convenience, but a convenience nonetheless.

4 Likes

@bzamayo has the right idea. I am pasting an image to show you the compiler support that I get when I try to fill a generic.

I imagine most developers that see the benefits of generics will find this very useful.

I provided a screen shot in addition to a code snippet to demonstrate how the compiler ui handles this. No suggestions provided, just an error. Any time you have to jump around in code to study types its really a drag. If the compiler could just search through, find the types that conform to the protocol requirement/s (HasStuff), then suggest them, that would be super duper awesome.

protocol HasStuff {
    var stuff: Int { get }
}

protocol HasInit {
    init()
}

struct Cat: HasStuff, HasInit {
    var stuff: Int { 2 }
}

struct Dog: HasStuff, HasInit {
    var stuff: Int { 1 }
}

struct NewThing<T: HasStuff> {
    var t: T
}

let new: NewThing<

Effectively I want the compiler to kick in when the carrot is typed: < and in this case suggest Dog, and Cat.

@dnadoba , @patrickgoley, @sveinhal does this satisfy the request for a concrete example? lmk if more details are needed. Thank you.

1 Like

Thanks Scott for the concrete example. With [SE-0299] and a bit of your help, the compile can suggest types in a generic context. You need to add an extension to the protocol HasStuff where Self is constrained to one of your types which conform to HasStuff e.g.:

extension HasStuff where Self == Cat {
    static var cat: Self { .init() }
}

After that you can use static member lookup (and therefore autocomplete) in a generic context e.g.:

let new = NewThing(t: .cat)
Full example:
protocol HasStuff {
    var stuff: Int { get }
}

protocol HasInit {
    init()
}

struct Cat: HasStuff, HasInit {
    var stuff: Int { 2 }
}

struct Dog: HasStuff, HasInit {
    var stuff: Int { 1 }
}

struct NewThing<T: HasStuff> {
    var t: T
}

extension HasStuff where Self == Cat {
    static var cat: Self { .init() }
}

extension HasStuff where Self == Dog {
    static var dog: Self { .init() }
}

let new = NewThing(t: .cat)

What you propose is still different and more automatic compared to [SE-0299]. I just want to highlight what is currently already possible.

1 Like

Right, it is available for concrete instances, I'd like it for type definition. I feel I'm only scratching the surface of what is possible with generics, along with the limitations of the compiler support. Imagine the pain of creating this type without compiler support...Yes it was painful.

typealias TeamSearchDataSource = DataSourceAlternator<
    TableDataSource3<
        SectionWithRowsHeader<
            Header<TeamSearchHeader>,
            RowAlternator2<
                Rows<TeamBarCell>,
                EmptyTeamSearchRow
            >
        >,
        SectionWithRowsHeader<
            Header<TeamSearchHeader>,
            RowAlternator2<
                Rows<TeamMemberCell>,
                EmptyTeamSearchRow
            >
        >,
        SectionWithoutHeader<Rows<VerticalTitleDetailsWithButtonCell>>
    >,
    TableDataSource1<SectionWithoutHeader<OneRow<ViewModelCell<Container<ImageLabel>>>>>
>

I would guess that it’s rare for most projects to have this sort of type level programming and deeply nested generics so naturally the language and IDE support haven’t been optimized for this. Is this being used to construct SwiftUI views? What’s the advantage of defining the hierarchy at the type level rather than at the value level in a View body?

1 Like

It's not SwiftUI. I built it on UIKit. The benefits granted this way are the same benefits granted by type safety. Just as defining a type as a String, you know you won't get a Double on accident, when a table is defined this way, I can guarantee the structure of the table without risking logic errors where cellForRow and numberOfRows etc. become out of sync, or the wrong cell appearing in the wrong place under the wrong condition. Furthermore the structure (whole or part) can be reused easily, and changed easily. After defining the type this way, the compiler will guide the developer on what data types and structures to assign/fill the table. I'm writing this post however because, this level of type nesting is not supported by autosuggestion features.