// run -gcflags=-G=3

// 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 main

import (
	"fmt"
	"strconv"
)

// Various implementations of fromStrings().

type Setter[B any] interface {
	Set(string)
	~*B
}

// Takes two type parameters where PT = *T
func fromStrings1[T any, PT Setter[T]](s []string) []T {
	result := make([]T, len(s))
	for i, v := range s {
		// The type of &result[i] is *T which is in the type list
		// of Setter, so we can convert it to PT.
		p := PT(&result[i])
		// PT has a Set method.
		p.Set(v)
	}
	return result
}

func fromStrings1a[T any, PT Setter[T]](s []string) []PT {
	result := make([]PT, len(s))
	for i, v := range s {
		// The type new(T) is *T which is in the type list
		// of Setter, so we can convert it to PT.
		result[i] = PT(new(T))
		p := result[i]
		// PT has a Set method.
		p.Set(v)
	}
	return result
}

// Takes one type parameter and a set function
func fromStrings2[T any](s []string, set func(*T, string)) []T {
	results := make([]T, len(s))
	for i, v := range s {
		set(&results[i], v)
	}
	return results
}

type Setter2 interface {
	Set(string)
}

// Takes only one type parameter, but causes a panic (see below)
func fromStrings3[T Setter2](s []string) []T {
	results := make([]T, len(s))
	for i, v := range s {
		// Panics if T is a pointer type because receiver is T(nil).
		results[i].Set(v)
	}
	return results
}

// Two concrete types with the appropriate Set method.

type SettableInt int

func (p *SettableInt) Set(s string) {
	i, err := strconv.Atoi(s)
	if err != nil {
		panic(err)
	}
	*p = SettableInt(i)
}

type SettableString struct {
	s string
}

func (x *SettableString) Set(s string) {
	x.s = s
}

func main() {
	s := fromStrings1[SettableInt, *SettableInt]([]string{"1"})
	if len(s) != 1 || s[0] != 1 {
		panic(fmt.Sprintf("got %v, want %v", s, []int{1}))
	}

	s2 := fromStrings1a[SettableInt, *SettableInt]([]string{"1"})
	if len(s2) != 1 || *s2[0] != 1 {
		x := 1
		panic(fmt.Sprintf("got %v, want %v", s2, []*int{&x}))
	}

	// Test out constraint type inference, which should determine that the second
	// type param is *SettableString.
	ps := fromStrings1[SettableString]([]string{"x", "y"})
	if len(ps) != 2 || ps[0] != (SettableString{"x"}) || ps[1] != (SettableString{"y"}) {
		panic(s)
	}

	s = fromStrings2([]string{"1"}, func(p *SettableInt, s string) { p.Set(s) })
	if len(s) != 1 || s[0] != 1 {
		panic(fmt.Sprintf("got %v, want %v", s, []int{1}))
	}

	defer func() {
		if recover() == nil {
			panic("did not panic as expected")
		}
	}()
	// This should type check but should panic at run time,
	// because it will make a slice of *SettableInt and then call
	// Set on a nil value.
	fromStrings3[*SettableInt]([]string{"1"})
}
