BurntSushi / go-sumtype

A simple utility for running exhaustiveness checks on Go "sum types."
The Unlicense
420 stars 22 forks source link

Is there some way to make this work with generics? #22

Open williammartin opened 4 months ago

williammartin commented 4 months ago

Description

Consider the following example, adapted from the README:

package main

//go-sumtype:decl MySumType

type MySumType[T any] interface {
    sealed()
}

type VariantA struct{}

func (*VariantA) sealed() {}

type VariantB[T any] struct{}

func (*VariantB[T]) sealed() {}

func main() {
    switch MySumType[string](nil).(type) {
    case *VariantA:
    case *VariantB[string]:
    }
}

We can see that VariantB is generic over T. Late night me (i.e. not trustworthy) thinks this should lint successfully, but go-sumtype rejects it. I suppose that a reasonable criticism might be "how would go-sumtype know that string is the correct concrete type for T e.g. the following is valid go but we would not want it to lint:

func main() {
    switch MySumType[int](nil).(type) {
    case *VariantA:
    case *VariantB[string]:
    }
}

Any thoughts? Cheers!

BurntSushi commented 4 months ago

I'm not sure generics plays a role here other than the fact that go-sumtype was written well before Go generics existed. So there might just be a bug in go-sumtype.

As far as semantics goes, the point here is exhaustiveness checking. A lint should occur whenever go-sumtype cannot prove that all cases are handled in the source code somehow.

williammartin commented 4 months ago

Thanks for the super quick response.

I suppose that naively I would think:

func main() {
    switch MySumType[string](nil).(type) {
    case *VariantA:
    case *VariantB[string]:
    }
}

Should be considered exhaustively checked, either we have a *VariantA or we have a *VariantB containing a string based on the type declaration. However I must admit that I'm not sure what kind of weird and wacky edge cases might be lurking behind the scenes.