gojuno / minimock

Powerful mock generation tool for Go programming language
MIT License
636 stars 38 forks source link

Unexpected behavior: embedded generic interface require their type arguments to be interfaces #113

Open danvolchek opened 3 months ago

danvolchek commented 3 months ago

When using this file:


//go:generate minimock -i Foo

type Foo interface {
    Bar[Baz]
}

type Bar[T any] interface {
    DoBar(T)
}

type Baz struct {
    // fields
}

The resulting error is:

minimock: failed to parse interface declaration: Baz: embedded type is not an interface

Why does Baz need to be an interface? As a type parameter to Bar it doesn't matter what type it is - the generated code would be the same - right? Am I misunderstanding?

Thanks!

danvolchek commented 3 months ago

Seems like the error originates here:

https://github.com/hexdigest/gowrap/blob/2c275b2bff7eafa9ac70c46d97785c88f8ceffc1/generator/generator.go#L614-L638

var errNotAnInterface = errors.New("embedded type is not an interface")

func processIdent(i *ast.Ident, input targetProcessInput) (methodsList, error) {
    var embeddedInterface *ast.InterfaceType
    var genericsTypes genericTypes
    for _, t := range input.types {
        if t.Name.Name == i.Name {
            var ok bool
            embeddedInterface, ok = t.Type.(*ast.InterfaceType)
            if !ok {
                return nil, errors.Wrap(errNotAnInterface, t.Name.Name)
            }

            genericsTypes = buildGenericTypesFromSpec(t, input.types, input.typesPrefix)
            break
        }
    }

    if embeddedInterface == nil {
        return nil, nil
    }

    input.genericTypes = genericsTypes
    return processInterface(embeddedInterface, input)
}

This also fails with the same error:

//go:generate minimock -i Foo

type Foo interface {
    Baz
}

type Baz struct {
  // fields
}

Maybe the parser thinks that Baz is a type directly embedded inside Foo? That seems like a bug.

Even in that case, since Go.18, arbitrary types can be embedded into interfaces (https://go.dev/ref/spec#Go_1.18):

Interface types may embed arbitrary types (not just type names of interfaces) as well as union and ~T type elements.

Should I open an issue for gowrap?

danvolchek commented 3 months ago

Also, using an interface as the type argument rather than the struct generates code, but it's not correct.

The generated file has a bunch of references to *T, but T is undefined and should be the name of the type argument.

E.g. in funcDoBar func() tp1 *T T is supposed to Baz.

Seems like gowrap isn't handling embedded interfaces correctly.

zcolleen commented 1 month ago

Hey @danvolchek thank for issue! Lets clarify few things. The first example with

type Foo interface {
    Bar[Baz]
}

is a clear bug, i agree. But second example im not sure about how to behave with such interfaces that are used as constraints, not as usual interface. Probably we should just skip generation for such interfaces.

The last point about E.g. in funcDoBar func() tp1 *T T is supposed to Baz. i didnt get, could you please provide an example of interface that is generated incorrectly.