Source file src/go/doc/exports.go

     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  
     7  package doc
     8  
     9  import (
    10  	"go/ast"
    11  	"go/token"
    12  )
    13  
    14  // filterIdentList removes unexported names from list in place
    15  // and returns the resulting list.
    16  func 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  
    27  var underscore = ast.NewIdent("_")
    28  
    29  func 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  
    37  func 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.
    59  func 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.
    71  func 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.
    81  func 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.
   107  func (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.
   159  func (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.
   170  func (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  
   209  func (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.
   253  func 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  
   269  func (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  
   299  func (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.
   315  func (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  

View as plain text