// Copyright 2021 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package types_test

import (
	"testing"

	"go/internal/typeparams"
	. "go/types"
)

func TestNewMethodSet(t *testing.T) {
	type method struct {
		name     string
		index    []int
		indirect bool
	}

	// Tests are expressed src -> methods, for simplifying the composite literal.
	// Should be kept in sync with TestLookupFieldOrMethod.
	tests := map[string][]method{
		// Named types
		"var a T; type T struct{}; func (T) f() {}":   {{"f", []int{0}, false}},
		"var a *T; type T struct{}; func (T) f() {}":  {{"f", []int{0}, true}},
		"var a T; type T struct{}; func (*T) f() {}":  {},
		"var a *T; type T struct{}; func (*T) f() {}": {{"f", []int{0}, true}},

		// Interfaces
		"var a T; type T interface{ f() }":                           {{"f", []int{0}, true}},
		"var a T1; type ( T1 T2; T2 interface{ f() } )":              {{"f", []int{0}, true}},
		"var a T1; type ( T1 interface{ T2 }; T2 interface{ f() } )": {{"f", []int{0}, true}},

		// Embedding
		"var a struct{ E }; type E interface{ f() }":            {{"f", []int{0, 0}, true}},
		"var a *struct{ E }; type E interface{ f() }":           {{"f", []int{0, 0}, true}},
		"var a struct{ E }; type E struct{}; func (E) f() {}":   {{"f", []int{0, 0}, false}},
		"var a struct{ *E }; type E struct{}; func (E) f() {}":  {{"f", []int{0, 0}, true}},
		"var a struct{ E }; type E struct{}; func (*E) f() {}":  {},
		"var a struct{ *E }; type E struct{}; func (*E) f() {}": {{"f", []int{0, 0}, true}},

		// collisions
		"var a struct{ E1; *E2 }; type ( E1 interface{ f() }; E2 struct{ f int })":            {},
		"var a struct{ E1; *E2 }; type ( E1 struct{ f int }; E2 struct{} ); func (E2) f() {}": {},
	}

	genericTests := map[string][]method{
		// By convention, look up a in the scope of "g"
		"type C interface{ f() }; func g[T C](a T){}":                       {{"f", []int{0}, true}},
		"type C interface{ f() }; func g[T C]() { var a T; _ = a }":         {{"f", []int{0}, true}},
		"type C interface{ f() }; func g[T C]() { var a struct{T}; _ = a }": {{"f", []int{0, 0}, true}},

		// Issue #45639.
		"type C interface{ f() }; func g[T C]() { type Y T; var a Y; _ = a }": {},
	}

	check := func(src string, methods []method, generic bool) {
		pkgName := "p"
		if generic {
			// The generic_ prefix causes pkgFor to allow generic code.
			pkgName = "generic_p"
		}
		pkg, err := pkgFor("test", "package "+pkgName+";"+src, nil)
		if err != nil {
			t.Errorf("%s: incorrect test case: %s", src, err)
			return
		}

		scope := pkg.Scope()
		if generic {
			fn := pkg.Scope().Lookup("g").(*Func)
			scope = fn.Scope()
		}
		obj := scope.Lookup("a")
		if obj == nil {
			t.Errorf("%s: incorrect test case - no object a", src)
			return
		}

		ms := NewMethodSet(obj.Type())
		if got, want := ms.Len(), len(methods); got != want {
			t.Errorf("%s: got %d methods, want %d", src, got, want)
			return
		}
		for i, m := range methods {
			sel := ms.At(i)
			if got, want := sel.Obj().Name(), m.name; got != want {
				t.Errorf("%s [method %d]: got name = %q at, want %q", src, i, got, want)
			}
			if got, want := sel.Index(), m.index; !sameSlice(got, want) {
				t.Errorf("%s [method %d]: got index = %v, want %v", src, i, got, want)
			}
			if got, want := sel.Indirect(), m.indirect; got != want {
				t.Errorf("%s [method %d]: got indirect = %v, want %v", src, i, got, want)
			}
		}
	}

	for src, methods := range tests {
		check(src, methods, false)
	}

	if typeparams.Enabled {
		for src, methods := range genericTests {
			check(src, methods, true)
		}
	}
}
