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 * universe.go
12 *
13 *  Created on May 14, 2017
14 *      Author Massimiliano Ghilardi
15 */
16
17package xreflect
18
19import (
20	"go/types"
21	"reflect"
22	// "runtime/debug"
23	"sync"
24
25	"github.com/cosmos72/gomacro/typeutil"
26)
27
28type Types struct {
29	gmap typeutil.Map
30}
31
32type Universe struct {
33	Types
34	// FromReflectType() map of types under construction.
35	// v.addmethods() will be invoked on them once the topmost FromReflectType() finishes.
36	partialTypes    Types
37	ReflectTypes    map[reflect.Type]Type
38	BasicTypes      []Type
39	TypeOfInterface Type
40	TypeOfForward   Type
41	TypeOfError     Type
42	TryResolve      func(name, pkgpath string) Type
43	Packages        map[string]*Package
44	Importer        types.ImporterFrom
45	RebuildDepth    int
46	DebugDepth      int
47	mutex           sync.Mutex
48	debugmutex      int
49	ThreadSafe      bool
50	methodcache     bool
51	fieldcache      bool
52}
53
54func lock(v *Universe) *Universe {
55	if v.debugmutex != 0 {
56		errorf(nil, "deadlocking universe %p", v)
57	}
58	v.mutex.Lock()
59	v.debugmutex++
60	return v
61}
62
63func un(v *Universe) {
64	// debugf("unlocking universe %p", v)
65	v.mutex.Unlock()
66	v.debugmutex--
67}
68
69func (v *Universe) rebuild() bool {
70	return v.RebuildDepth > 0
71}
72
73func (v *Universe) cache(rt reflect.Type, t Type) Type {
74	if v.ReflectTypes == nil {
75		v.ReflectTypes = make(map[reflect.Type]Type)
76	}
77	v.ReflectTypes[rt] = t
78	// debugf("added rtype to cache: %v -> %v (%v)", rt, t, t.ReflectType())
79	return t
80}
81
82// cachePackage0 recursively adds pkg and its imports to Universe.Packages if not cached already
83func (v *Universe) cachePackage0(pkg *types.Package) {
84	path := pkg.Path()
85	if _, ok := v.Packages[path]; ok {
86		return
87	}
88	v.Packages[path] = (*Package)(pkg)
89	for _, imp := range pkg.Imports() {
90		v.cachePackage0(imp)
91	}
92}
93
94// cachePackage unconditionally adds pkg to Universe.Packages,
95// then also adds its imports if not cached already
96func (v *Universe) cachePackage(pkg *types.Package) {
97	if pkg == nil {
98		return
99	}
100	if v.Packages == nil {
101		v.Packages = make(map[string]*Package)
102	}
103	v.Packages[pkg.Path()] = (*Package)(pkg)
104	for _, imp := range pkg.Imports() {
105		v.cachePackage0(imp)
106	}
107}
108
109// CachePackage unconditionally adds pkg to Universe.Packages,
110// then also adds its imports if not cached already
111func (v *Universe) CachePackage(pkg *types.Package) {
112	if pkg == nil {
113		return
114	}
115	if v.ThreadSafe {
116		defer un(lock(v))
117	}
118	v.cachePackage(pkg)
119}
120
121// cacheMissingPackage adds a nil entry to Universe.Packages, if an entry is not present already.
122// Used to cache failures of Importer.Import.
123func (v *Universe) cacheMissingPackage(path string) {
124	if _, cached := v.Packages[path]; cached || len(path) == 0 {
125		return
126	}
127	if v.Packages == nil {
128		v.Packages = make(map[string]*Package)
129	}
130	v.Packages[path] = nil
131}
132
133func (v *Universe) importPackage(path string) *Package {
134	cachepkg, cached := v.Packages[path]
135	if cachepkg != nil {
136		return cachepkg
137	}
138	if v.Importer == nil {
139		v.Importer = DefaultImporter()
140	}
141	pkg, err := v.Importer.Import(path)
142	if err != nil || pkg == nil {
143		if !cached {
144			if v.debug() {
145				debugf("importer: cannot find package %q metadata, approximating it with reflection", path)
146			}
147			v.cacheMissingPackage(path)
148		}
149		return nil
150	}
151	// debugf("imported package %q", path)
152	v.cachePackage(pkg)
153	return (*Package)(pkg)
154}
155
156func (v *Universe) namedTypeFromImport(rtype reflect.Type) Type {
157	t := v.namedTypeFromPackageCache(rtype)
158	if unwrap(t) != nil {
159		return t
160	}
161	pkg := v.loadPackage(rtype.PkgPath())
162	if pkg == nil {
163		return nil
164	}
165	return v.namedTypeFromPackage(rtype, (*types.Package)(pkg))
166}
167
168func (v *Universe) namedTypeFromPackageCache(rtype reflect.Type) Type {
169	pkgpath := rtype.PkgPath()
170	pkg := (*types.Package)(v.Packages[pkgpath])
171	if pkg != nil {
172		return v.namedTypeFromPackage(rtype, pkg)
173	}
174	return nil
175}
176
177func (v *Universe) namedTypeFromPackage(rtype reflect.Type, pkg *types.Package) Type {
178	name := rtype.Name()
179	if scope := pkg.Scope(); scope != nil && len(name) != 0 {
180		if obj := scope.Lookup(name); obj != nil {
181			if gtype := obj.Type(); gtype != nil {
182				// debugf("imported named type %v for %v", gtype, rtype)
183				// not v.MakeType, because we already hold the lock
184				return v.maketype3(gtypeToKind(nil, gtype), gtype, rtype)
185			}
186		}
187	}
188	return nil
189}
190