1/*
2 * gomacro - A Go interpreter with Lisp-like macros
3 *
4 * Copyright (C) 2017-2019 Massimiliano Ghilardi
5 *
6 *     This Source Code Form is subject to the terms of the Mozilla Public
7 *     License, v. 2.0. If a copy of the MPL was not distributed with this
8 *     file, You can obtain one at http://mozilla.org/MPL/2.0/.
9 *
10 *
11 * interface.go
12 *
13 *  Created on: Mar 29, 2017
14 *      Author: Massimiliano Ghilardi
15 */
16
17package fast
18
19import (
20	"fmt"
21	"go/ast"
22	r "reflect"
23
24	"github.com/cosmos72/gomacro/base/reflect"
25
26	"github.com/cosmos72/gomacro/base"
27	xr "github.com/cosmos72/gomacro/xreflect"
28)
29
30// compile an interface definition
31func (c *Comp) TypeInterface(node *ast.InterfaceType) xr.Type {
32	if node.Methods == nil || len(node.Methods.List) == 0 {
33		return c.TypeOfInterface()
34	}
35	types, names := c.TypeFields(node.Methods)
36
37	// parser returns embedded interfaces as unnamed fields
38	var methodnames []string
39	var methodtypes, embeddedtypes []xr.Type
40	for i, typ := range types {
41		if i < len(names) && len(names[i]) != 0 {
42			methodnames = append(methodnames, names[i])
43			methodtypes = append(methodtypes, typ)
44		} else {
45			embeddedtypes = append(embeddedtypes, typ)
46		}
47	}
48	universe := c.Universe
49	pkg := universe.LoadPackage(c.FileComp().Path)
50	return universe.InterfaceOf(pkg, methodnames, methodtypes, embeddedtypes)
51}
52
53// InterfaceProxy returns the proxy struct that implements a compiled interface
54func (c *Comp) InterfaceProxy(t xr.Type) r.Type {
55	ret := c.interf2proxy[t.ReflectType()]
56	if ret == nil {
57		c.Errorf("internal error: proxy not found for %s type <%v>", t.Kind(), t)
58	}
59	return ret
60}
61
62// converterToProxy compiles a conversion from 'tin' into a proxy struct that implements the interface type 'tout'
63// and returns a function that performs such conversion
64func (c *Comp) converterToProxy(tin xr.Type, tout xr.Type) func(val r.Value) r.Value {
65	rtout := tout.ReflectType()       // a compiled interface
66	rtproxy := c.InterfaceProxy(tout) // one of our proxies that pre-implement the compiled interface
67
68	vtable := r.New(rtproxy).Elem()
69	n := rtout.NumMethod()
70	for i := 0; i < n; i++ {
71		mtdout := rtout.Method(i)
72		mtdin, count := tin.MethodByName(mtdout.Name, mtdout.PkgPath)
73		if count == 0 {
74			c.Errorf("cannot convert type <%v> to interface <%v>: missing method %s %s", tin, rtout, mtdout.PkgPath, mtdout.Name)
75		} else if count > 1 {
76			c.Errorf("type <%v> has %d wrapper methods %s %s all at the same depth=%d - cannot convert to interface <%v>",
77				tin, count, mtdout.PkgPath, mtdout.Name, len(mtdin.FieldIndex), tout)
78		}
79		e := c.compileMethodAsFunc(tin, mtdin)
80		setProxyField(vtable.Field(i+1), r.ValueOf(e.Value))
81	}
82	extractor := c.extractor(tin)
83	if extractor == nil {
84		return func(val r.Value) r.Value {
85			vaddr := r.New(rtproxy)
86			vproxy := vaddr.Elem()
87			vproxy.Set(vtable)
88			vproxy.Field(0).Set(r.ValueOf(xr.MakeInterfaceHeader(val, tin)))
89			return convert(vaddr, rtout)
90		}
91	}
92	// extract object from tin proxy or emulated interface (if any),
93	// and wrap it in tout proxy
94	return func(val r.Value) r.Value {
95		v, t := extractor(val)
96		vaddr := r.New(rtproxy)
97		vproxy := vaddr.Elem()
98		vproxy.Set(vtable)
99		vproxy.Field(0).Set(r.ValueOf(xr.MakeInterfaceHeader(v, t)))
100		return convert(vaddr, rtout)
101	}
102}
103
104func setProxyField(place r.Value, mtd r.Value) {
105	rtin := mtd.Type()
106	rtout := place.Type()
107	if rtin == rtout {
108		place.Set(mtd)
109	} else if rtin.ConvertibleTo(rtout) {
110		place.Set(mtd.Convert(rtout))
111	} else {
112		place.Set(r.MakeFunc(rtout, func(args []r.Value) []r.Value {
113			args[0] = args[0].Interface().(xr.InterfaceHeader).Value()
114			return mtd.Call(args)
115		}))
116	}
117}
118
119// extract a value from a proxy struct (one of the imports.* structs) that implements an interface
120// this is the inverse of the function returned by Comp.converterToProxy() above
121func (g *CompGlobals) extractFromProxy(v r.Value) (r.Value, xr.Type) {
122	// base.Debugf("type assertion: value = %v <%v>", v, base.Type(v))
123
124	// v.Kind() is allowed also on invalid r.Value, and it returns r.Invalid
125	if v.Kind() == r.Interface {
126		v = v.Elem() // extract concrete type
127	}
128	if !v.IsValid() || v == base.None {
129		// cannot extract concrete type
130		return v, nil
131	}
132	rt := v.Type()
133	var xt xr.Type
134	// base.Debugf("type assertion: concrete value = %v <%v>", i, t)
135	if rt != nil && rt.Kind() == r.Ptr && g.proxy2interf[rt.Elem()] != nil {
136		v = v.Elem().Field(0)
137		if j, ok := reflect.Interface(v).(xr.InterfaceHeader); ok {
138			// base.Debugf("type assertion: unwrapped value = %v <%T>", j, j)
139			v = j.Value()
140			xt = j.Type()
141		} else {
142			// base.Debugf("type assertion: failed to unwrap value = %v <%T>", i, i)
143			if v.Kind() == r.Interface {
144				v = v.Elem() // extract concrete type
145			}
146		}
147	}
148	return v, xt
149}
150
151// converterToProxy compiles a conversion from 'tin' into the emulated interface type 'tout'
152// and returns a function that performs such conversion
153func (c *Comp) converterToEmulatedInterface(tin, tout xr.Type) func(val r.Value) r.Value {
154	if !tin.Implements(tout) {
155		c.Errorf("cannot convert from <%v> to <%v>", tin, tout)
156	}
157	n := tout.NumMethod()
158	obj2methodFuncs := make([]func(r.Value) r.Value, n)
159
160	tsrc := tin
161	if tin.Kind() == r.Ptr {
162		// xr.Type.MethodByName wants T, not *T, even for methods with pointer receiver
163		tsrc = tin.Elem()
164	}
165	debug := c.Options&base.OptDebugMethod != 0
166	for i := 0; i < n; i++ {
167		mtdout := tout.Method(i)
168		mtdin, count := tsrc.MethodByName(mtdout.Name, c.PackagePath) // pkgpath is ignored for exported names
169
170		if count == 0 {
171			c.Errorf("cannot convert from <%v> to <%v>: missing method %s %s", tin, tout, mtdout.Name, mtdout.Type)
172		} else if count > 1 {
173			c.Errorf("cannot convert from <%v> to <%v>: multiple methods match %s %s", tin, tout, mtdout.Name, mtdout.Type)
174		}
175		if !mtdin.Type.AssignableTo(mtdout.Type) {
176			c.Errorf("cannot convert from <%v> to <%v>: mismatched method %s: expecting %v, found %v",
177				tin, tout, mtdout.Name, mtdout.Type, mtdin.Type)
178		}
179		obj2methodFuncs[i] = c.compileObjGetMethod(tin, mtdin)
180		if debug {
181			c.Debugf("compiled  method conversion from %v.%s <%v> (concrete method %d) to %v.%s <%v> (interface method %d)",
182				tin, mtdin.Name, mtdin.Type, mtdin.Index, tout, mtdout.Name, mtdout.Type, mtdout.Index)
183		}
184	}
185	rtout := tout.ReflectType()
186
187	extractor := c.extractor(tin)
188	if extractor == nil {
189		return func(obj r.Value) r.Value {
190			return xr.ToEmulatedInterface(rtout, obj, tin, obj2methodFuncs)
191		}
192	}
193	// extract object from tin proxy or emulated interface (if any),
194	// and wrap it in tout emulated interface
195	return func(obj r.Value) r.Value {
196		v, t := extractor(obj)
197		return xr.ToEmulatedInterface(rtout, v, t, obj2methodFuncs)
198	}
199}
200
201// return a function that extracts value wrapped in a proxy or emulated interface
202// returns nil if no extraction is needed
203func (g *CompGlobals) extractor(tin xr.Type) func(r.Value) (r.Value, xr.Type) {
204	if tin.Kind() != r.Interface {
205		return nil
206	} else if xr.IsEmulatedInterface(tin) {
207		return xr.FromEmulatedInterface
208	} else {
209		return g.extractFromProxy
210	}
211}
212
213// return the error "\n\treason: t does not implement tinterf: missing method <method>"
214func interfaceMissingMethod(t, tinterf xr.Type) string {
215	var s string
216	if tinterf.Kind() == r.Interface {
217		s = fmt.Sprintf("\n\treason: %v does not implement %v", t, tinterf)
218		missingmtd := xr.MissingMethod(t, tinterf)
219		if missingmtd != nil {
220			s = fmt.Sprintf("%s: missing method %s", s, missingmtd.String())
221		}
222	}
223	return s
224}
225