1// Copyright 2011 The Go Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style
3// license that can be found in the LICENSE file.
4
5// This file implements export filtering of an AST.
6
7package doc
8
9import (
10	"go/ast"
11	"go/token"
12)
13
14// filterIdentList removes unexported names from list in place
15// and returns the resulting list.
16func filterIdentList(list []*ast.Ident) []*ast.Ident {
17	j := 0
18	for _, x := range list {
19		if token.IsExported(x.Name) {
20			list[j] = x
21			j++
22		}
23	}
24	return list[0:j]
25}
26
27var underscore = ast.NewIdent("_")
28
29func filterCompositeLit(lit *ast.CompositeLit, filter Filter, export bool) {
30	n := len(lit.Elts)
31	lit.Elts = filterExprList(lit.Elts, filter, export)
32	if len(lit.Elts) < n {
33		lit.Incomplete = true
34	}
35}
36
37func filterExprList(list []ast.Expr, filter Filter, export bool) []ast.Expr {
38	j := 0
39	for _, exp := range list {
40		switch x := exp.(type) {
41		case *ast.CompositeLit:
42			filterCompositeLit(x, filter, export)
43		case *ast.KeyValueExpr:
44			if x, ok := x.Key.(*ast.Ident); ok && !filter(x.Name) {
45				continue
46			}
47			if x, ok := x.Value.(*ast.CompositeLit); ok {
48				filterCompositeLit(x, filter, export)
49			}
50		}
51		list[j] = exp
52		j++
53	}
54	return list[0:j]
55}
56
57// updateIdentList replaces all unexported identifiers with underscore
58// and reports whether at least one exported name exists.
59func updateIdentList(list []*ast.Ident) (hasExported bool) {
60	for i, x := range list {
61		if token.IsExported(x.Name) {
62			hasExported = true
63		} else {
64			list[i] = underscore
65		}
66	}
67	return hasExported
68}
69
70// hasExportedName reports whether list contains any exported names.
71func hasExportedName(list []*ast.Ident) bool {
72	for _, x := range list {
73		if x.IsExported() {
74			return true
75		}
76	}
77	return false
78}
79
80// removeAnonymousField removes anonymous fields named name from an interface.
81func removeAnonymousField(name string, ityp *ast.InterfaceType) {
82	list := ityp.Methods.List // we know that ityp.Methods != nil
83	j := 0
84	for _, field := range list {
85		keepField := true
86		if n := len(field.Names); n == 0 {
87			// anonymous field
88			if fname, _ := baseTypeName(field.Type); fname == name {
89				keepField = false
90			}
91		}
92		if keepField {
93			list[j] = field
94			j++
95		}
96	}
97	if j < len(list) {
98		ityp.Incomplete = true
99	}
100	ityp.Methods.List = list[0:j]
101}
102
103// filterFieldList removes unexported fields (field names) from the field list
104// in place and reports whether fields were removed. Anonymous fields are
105// recorded with the parent type. filterType is called with the types of
106// all remaining fields.
107func (r *reader) filterFieldList(parent *namedType, fields *ast.FieldList, ityp *ast.InterfaceType) (removedFields bool) {
108	if fields == nil {
109		return
110	}
111	list := fields.List
112	j := 0
113	for _, field := range list {
114		keepField := false
115		if n := len(field.Names); n == 0 {
116			// anonymous field or embedded type or union element
117			fname := r.recordAnonymousField(parent, field.Type)
118			if fname != "" {
119				if token.IsExported(fname) {
120					keepField = true
121				} else if ityp != nil && predeclaredTypes[fname] {
122					// possibly an embedded predeclared type; keep it for now but
123					// remember this interface so that it can be fixed if name is also
124					// defined locally
125					keepField = true
126					r.remember(fname, ityp)
127				}
128			} else {
129				// If we're operating on an interface, assume that this is an embedded
130				// type or union element.
131				//
132				// TODO(rfindley): consider traversing into approximation/unions
133				// elements to see if they are entirely unexported.
134				keepField = ityp != nil
135			}
136		} else {
137			field.Names = filterIdentList(field.Names)
138			if len(field.Names) < n {
139				removedFields = true
140			}
141			if len(field.Names) > 0 {
142				keepField = true
143			}
144		}
145		if keepField {
146			r.filterType(nil, field.Type)
147			list[j] = field
148			j++
149		}
150	}
151	if j < len(list) {
152		removedFields = true
153	}
154	fields.List = list[0:j]
155	return
156}
157
158// filterParamList applies filterType to each parameter type in fields.
159func (r *reader) filterParamList(fields *ast.FieldList) {
160	if fields != nil {
161		for _, f := range fields.List {
162			r.filterType(nil, f.Type)
163		}
164	}
165}
166
167// filterType strips any unexported struct fields or method types from typ
168// in place. If fields (or methods) have been removed, the corresponding
169// struct or interface type has the Incomplete field set to true.
170func (r *reader) filterType(parent *namedType, typ ast.Expr) {
171	switch t := typ.(type) {
172	case *ast.Ident:
173		// nothing to do
174	case *ast.ParenExpr:
175		r.filterType(nil, t.X)
176	case *ast.StarExpr: // possibly an embedded type literal
177		r.filterType(nil, t.X)
178	case *ast.UnaryExpr:
179		if t.Op == token.TILDE { // approximation element
180			r.filterType(nil, t.X)
181		}
182	case *ast.BinaryExpr:
183		if t.Op == token.OR { // union
184			r.filterType(nil, t.X)
185			r.filterType(nil, t.Y)
186		}
187	case *ast.ArrayType:
188		r.filterType(nil, t.Elt)
189	case *ast.StructType:
190		if r.filterFieldList(parent, t.Fields, nil) {
191			t.Incomplete = true
192		}
193	case *ast.FuncType:
194		r.filterParamList(t.TypeParams)
195		r.filterParamList(t.Params)
196		r.filterParamList(t.Results)
197	case *ast.InterfaceType:
198		if r.filterFieldList(parent, t.Methods, t) {
199			t.Incomplete = true
200		}
201	case *ast.MapType:
202		r.filterType(nil, t.Key)
203		r.filterType(nil, t.Value)
204	case *ast.ChanType:
205		r.filterType(nil, t.Value)
206	}
207}
208
209func (r *reader) filterSpec(spec ast.Spec) bool {
210	switch s := spec.(type) {
211	case *ast.ImportSpec:
212		// always keep imports so we can collect them
213		return true
214	case *ast.ValueSpec:
215		s.Values = filterExprList(s.Values, token.IsExported, true)
216		if len(s.Values) > 0 || s.Type == nil && len(s.Values) == 0 {
217			// If there are values declared on RHS, just replace the unexported
218			// identifiers on the LHS with underscore, so that it matches
219			// the sequence of expression on the RHS.
220			//
221			// Similarly, if there are no type and values, then this expression
222			// must be following an iota expression, where order matters.
223			if updateIdentList(s.Names) {
224				r.filterType(nil, s.Type)
225				return true
226			}
227		} else {
228			s.Names = filterIdentList(s.Names)
229			if len(s.Names) > 0 {
230				r.filterType(nil, s.Type)
231				return true
232			}
233		}
234	case *ast.TypeSpec:
235		// Don't filter type parameters here, by analogy with function parameters
236		// which are not filtered for top-level function declarations.
237		if name := s.Name.Name; token.IsExported(name) {
238			r.filterType(r.lookupType(s.Name.Name), s.Type)
239			return true
240		} else if IsPredeclared(name) {
241			if r.shadowedPredecl == nil {
242				r.shadowedPredecl = make(map[string]bool)
243			}
244			r.shadowedPredecl[name] = true
245		}
246	}
247	return false
248}
249
250// copyConstType returns a copy of typ with position pos.
251// typ must be a valid constant type.
252// In practice, only (possibly qualified) identifiers are possible.
253func copyConstType(typ ast.Expr, pos token.Pos) ast.Expr {
254	switch typ := typ.(type) {
255	case *ast.Ident:
256		return &ast.Ident{Name: typ.Name, NamePos: pos}
257	case *ast.SelectorExpr:
258		if id, ok := typ.X.(*ast.Ident); ok {
259			// presumably a qualified identifier
260			return &ast.SelectorExpr{
261				Sel: ast.NewIdent(typ.Sel.Name),
262				X:   &ast.Ident{Name: id.Name, NamePos: pos},
263			}
264		}
265	}
266	return nil // shouldn't happen, but be conservative and don't panic
267}
268
269func (r *reader) filterSpecList(list []ast.Spec, tok token.Token) []ast.Spec {
270	if tok == token.CONST {
271		// Propagate any type information that would get lost otherwise
272		// when unexported constants are filtered.
273		var prevType ast.Expr
274		for _, spec := range list {
275			spec := spec.(*ast.ValueSpec)
276			if spec.Type == nil && len(spec.Values) == 0 && prevType != nil {
277				// provide current spec with an explicit type
278				spec.Type = copyConstType(prevType, spec.Pos())
279			}
280			if hasExportedName(spec.Names) {
281				// exported names are preserved so there's no need to propagate the type
282				prevType = nil
283			} else {
284				prevType = spec.Type
285			}
286		}
287	}
288
289	j := 0
290	for _, s := range list {
291		if r.filterSpec(s) {
292			list[j] = s
293			j++
294		}
295	}
296	return list[0:j]
297}
298
299func (r *reader) filterDecl(decl ast.Decl) bool {
300	switch d := decl.(type) {
301	case *ast.GenDecl:
302		d.Specs = r.filterSpecList(d.Specs, d.Tok)
303		return len(d.Specs) > 0
304	case *ast.FuncDecl:
305		// ok to filter these methods early because any
306		// conflicting method will be filtered here, too -
307		// thus, removing these methods early will not lead
308		// to the false removal of possible conflicts
309		return token.IsExported(d.Name.Name)
310	}
311	return false
312}
313
314// fileExports removes unexported declarations from src in place.
315func (r *reader) fileExports(src *ast.File) {
316	j := 0
317	for _, d := range src.Decls {
318		if r.filterDecl(d) {
319			src.Decls[j] = d
320			j++
321		}
322	}
323	src.Decls = src.Decls[0:j]
324}
325