From 25a5a589503eb8ad44d3a80a2efee5ce31982abc Mon Sep 17 00:00:00 2001 From: Ting-Wei Lan Date: Wed, 17 Apr 2019 15:53:45 +0800 Subject: [PATCH] check: fix false positive when an interface implements a sum type Sometimes it is possible for a sum type interface to be implemented by another interface. For example, we have a sum type called T including 3 variants, A, B, C. We also have an U interface embedding T and thus implementing T. Assuming that B and C implement U, a type switch statement can be considered exhaustive if all of A, B, C are listed. It should also be considered exhaustive if only A and U are listed. However, go-sumtype does not distinguish between concrete and interface types. It fails in both cases, requiring all of A, B, C, U to be listed. It is unlikely to code to be written in this way. U already covers B and C, and it is unnecessary to list them in a type switch statement. This commit fixes the problem by handling interfaces specially. Closes: https://github.com/BurntSushi/go-sumtype/issues/1 Closes: https://github.com/BurntSushi/go-sumtype/issues/3 --- check_test.go | 44 ++++++++++++++++++++++++++++++++++++++++++++ def.go | 9 +++++++++ 2 files changed, 53 insertions(+) diff --git a/check_test.go b/check_test.go index 1261998..a9c7712 100644 --- a/check_test.go +++ b/check_test.go @@ -167,6 +167,50 @@ func main() { assert.Len(t, errs, 0) } +// TestNoMissingInterface tests that we correctly detect exhaustive case when +// there is an interface which implements the interface we are going to check. +func TestNoMissingInterface(t *testing.T) { + code := ` +package main + +//go-sumtype:decl T + +type T interface { + sealedT() +} + +type A struct {} +func (a *A) sealedT() {} + +type U interface { + T + sealedU() +} + +type B struct {} +func (b *B) sealedT() {} +func (b *B) sealedU() {} + +type C struct {} +func (c *C) sealedT() {} +func (c *C) sealedU() {} + +func main() { + switch T(nil).(type) { + case *A, *B, *C: + } + switch T(nil).(type) { + case *A, U: + } +} +` + tmpdir, pkgs := setupPackages(t, code) + defer teardownPackage(t, tmpdir) + + errs := run(pkgs) + assert.Len(t, errs, 0) +} + // TestNotSealed tests that we report an error if one tries to declare a sum // type with an unsealed interface. func TestNotSealed(t *testing.T) { diff --git a/def.go b/def.go index 01067f5..62ff7ce 100644 --- a/def.go +++ b/def.go @@ -107,6 +107,10 @@ func newSumTypeDef(pkg *types.Package, decl sumTypeDecl) (*sumTypeDef, error) { if types.Identical(ty.Underlying(), iface) { continue } + _, ok = ty.Underlying().(*types.Interface) + if ok { + continue + } if types.Implements(ty, iface) || types.Implements(types.NewPointer(ty), iface) { def.Variants = append(def.Variants, obj) } @@ -131,6 +135,11 @@ func (def *sumTypeDef) missing(tys []types.Type) []types.Object { if types.Identical(varty, ty) { found = true } + if iface, ok := ty.Underlying().(*types.Interface); ok { + if types.Implements(varty, iface) || types.Implements(types.NewPointer(varty), iface) { + found = true + } + } } if !found { missing = append(missing, v)