// +build ignore

package main

type I interface {
	f()
}

type C int

func (*C) f() {}

type D struct{ ptr *int }

func (D) f() {}

type E struct{}

func (*E) f() {}

var a, b int

var unknown bool // defeat dead-code elimination

func interface1() {
	var i interface{} = &a
	var j interface{} = D{&b}
	k := j
	if unknown {
		k = i
	}

	print(i) // @types *int
	print(j) // @types D
	print(k) // @types *int | D

	print(i.(*int)) // @pointsto main.a
	print(j.(*int)) // @pointsto
	print(k.(*int)) // @pointsto main.a

	print(i.(D).ptr) // @pointsto
	print(j.(D).ptr) // @pointsto main.b
	print(k.(D).ptr) // @pointsto main.b
}

func interface2() {
	var i I = (*C)(&a)
	var j I = D{&a}
	k := j
	if unknown {
		k = i
	}

	print(i) // @types *C
	print(j) // @types D
	print(k) // @types *C | D
	print(k) // @pointsto makeinterface:main.D | makeinterface:*main.C

	k.f()
	// @calls main.interface2 -> (*main.C).f
	// @calls main.interface2 -> (main.D).f

	print(i.(*C))    // @pointsto main.a
	print(j.(D).ptr) // @pointsto main.a
	print(k.(*C))    // @pointsto main.a

	switch x := k.(type) {
	case *C:
		print(x) // @pointsto main.a
	case D:
		print(x.ptr) // @pointsto main.a
	case *E:
		print(x) // @pointsto
	}
}

func interface3() {
	// There should be no backflow of concrete types from the type-switch to x.
	var x interface{} = 0
	print(x) // @types int
	switch x.(type) {
	case int:
	case string:
	}
}

func interface4() {
	var i interface{} = D{&a}
	if unknown {
		i = 123
	}

	print(i) // @types int | D

	j := i.(I)       // interface narrowing type-assertion
	print(j)         // @types D
	print(j.(D).ptr) // @pointsto main.a

	var l interface{} = j // interface widening assignment.
	print(l)              // @types D
	print(l.(D).ptr)      // @pointsto main.a

	m := j.(interface{}) // interface widening type-assertion.
	print(m)             // @types D
	print(m.(D).ptr)     // @pointsto main.a
}

// Interface method calls and value flow:

type J interface {
	f(*int) *int
}

type P struct {
	x int
}

func (p *P) f(pi *int) *int {
	print(p)  // @pointsto p@i5p:6
	print(pi) // @pointsto i@i5i:6
	return &p.x
}

func interface5() {
	var p P // @line i5p
	var j J = &p
	var i int      // @line i5i
	print(j.f(&i)) // @pointsto p.x@i5p:6
	print(&i)      // @pointsto i@i5i:6

	print(j) // @pointsto makeinterface:*main.P
}

// @calls main.interface5 -> (*main.P).f

func interface6() {
	f := I.f
	print(f) // @pointsto (main.I).f$thunk
	f(new(struct{ D }))
}

// @calls main.interface6 -> (main.I).f$thunk
// @calls (main.I).f$thunk -> (*struct{main.D}).f

func main() {
	interface1()
	interface2()
	interface3()
	interface4()
	interface5()
	interface6()
}
