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 * struct.go
12 *
13 *  Created on May 07, 2017
14 *      Author Massimiliano Ghilardi
15 */
16
17package xreflect
18
19import (
20	"fmt"
21	"go/ast"
22	"go/token"
23	"go/types"
24	"reflect"
25)
26
27// Field returns a struct type's i'th field.
28// It panics if the type's Kind is not Struct.
29// It panics if i is not in the range [0, NumField()).
30func (t *xtype) Field(i int) StructField {
31	if t.kind != reflect.Struct {
32		xerrorf(t, "Field of non-struct type %v", t)
33	}
34	v := t.universe
35	if v.ThreadSafe {
36		defer un(lock(v))
37	}
38	return t.field(i)
39}
40
41func (t *xtype) field(i int) StructField {
42	if t.kind != reflect.Struct {
43		xerrorf(t, "Field of non-struct type %v", t)
44	}
45	gtype := t.gtype.Underlying().(*types.Struct)
46
47	if i < 0 || i >= gtype.NumFields() {
48		xerrorf(t, "Field(%v) out of bounds, struct type has %v fields: %v", i, gtype.NumFields(), t)
49	}
50	va := gtype.Field(i)
51	var rf reflect.StructField
52	if t.rtype != rTypeOfForward {
53		rf = t.rtype.Field(i)
54	} else {
55		// cannot dig in a forward-declared type,
56		// so try to resolve it
57		it := t.universe.gmap.At(t.gtype)
58		if it != nil {
59			rtype := it.(Type).ReflectType()
60			if rtype.Kind() != t.kind {
61				debugf("mismatched Forward type: <%v> has reflect.Type <%v>", t, rtype)
62			}
63			rf = rtype.Field(i)
64		} else {
65			// populate  Field.Index and approximate Field.Type
66			rf.Index = []int{i}
67			rf.Type = rTypeOfForward
68		}
69	}
70
71	return StructField{
72		Name:      va.Name(),
73		Pkg:       (*Package)(va.Pkg()),
74		Type:      t.universe.maketype(va.Type(), rf.Type), // lock already held
75		Tag:       rf.Tag,
76		Offset:    rf.Offset,
77		Index:     rf.Index,
78		Anonymous: va.Anonymous(),
79	}
80}
81
82// NumField returns a struct type's field count.
83// It panics if the type's Kind is not Struct.
84func (t *xtype) NumField() int {
85	if t.kind != reflect.Struct {
86		xerrorf(t, "NumField of non-struct type %v", t)
87	}
88	gtype := t.gunderlying().(*types.Struct)
89	return gtype.NumFields()
90}
91
92func (field *StructField) toReflectField(forceExported bool) reflect.StructField {
93	var pkgpath string
94	if pkg := field.Pkg; pkg != nil && !forceExported {
95		pkgpath = pkg.Path()
96	}
97	name := field.Name
98	if forceExported {
99		name = toExportedFieldName(name, field.Type, field.Anonymous)
100	}
101	return reflect.StructField{
102		Name:    name,
103		PkgPath: pkgpath,
104		Type:    field.Type.ReflectType(),
105		Tag:     field.Tag,
106		Offset:  field.Offset,
107		Index:   field.Index,
108		// reflect.StructOf() has very limited support for anonymous fields,
109		// do not even try to use it.
110		Anonymous: false,
111	}
112}
113
114func toReflectFields(fields []StructField, forceExported bool) []reflect.StructField {
115	rfields := make([]reflect.StructField, len(fields))
116	for i := range fields {
117		rfields[i] = fields[i].toReflectField(forceExported)
118	}
119	return rfields
120}
121
122func (field *StructField) sanitize(i int) {
123	if len(field.Name) != 0 {
124		return
125	}
126	t := field.Type
127	name := t.Name()
128	if len(name) == 0 && t.Kind() == reflect.Ptr {
129		name = t.elem().Name()
130	}
131	if len(name) == 0 {
132		name = fmt.Sprintf("%s%d", StrGensymAnonymous, i)
133	}
134	field.Name = name
135	field.Anonymous = true
136}
137
138func (field *StructField) toGoField(i int) *types.Var {
139	field.sanitize(i)
140	return types.NewField(token.NoPos, (*types.Package)(field.Pkg), field.Name, field.Type.GoType(), field.Anonymous)
141}
142
143func toGoFields(fields []StructField) []*types.Var {
144	vars := make([]*types.Var, len(fields))
145	for i := range fields {
146		vars[i] = fields[i].toGoField(i)
147	}
148	return vars
149}
150
151func (field *StructField) toTag() string {
152	return string(field.Tag)
153}
154
155func toTags(fields []StructField) []string {
156	tags := make([]string, len(fields))
157	for i := range fields {
158		tags[i] = fields[i].toTag()
159	}
160	return tags
161}
162
163func toExportedFieldName(name string, t Type, anonymous bool) string {
164	if len(name) == 0 && unwrap(t) != nil {
165		if name = t.Name(); len(name) == 0 && t.Kind() == reflect.Ptr {
166			name = t.elem().Name()
167		}
168	}
169	if !ast.IsExported(name) {
170		if anonymous {
171			return GensymAnonymous(name)
172		} else {
173			return GensymPrivate(name)
174		}
175	}
176	return name
177}
178
179func (v *Universe) StructOf(fields []StructField) Type {
180	vars := toGoFields(fields)
181	tags := toTags(fields)
182	rfields := toReflectFields(fields, true)
183	return v.MakeType(
184		types.NewStruct(vars, tags),
185		reflect.StructOf(rfields),
186	)
187}
188