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

View as plain text