Black Lives Matter. Support the Equal Justice Initiative.

Source file src/go/doc/exports.go

Documentation: go/doc

     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  // removeErrorField removes anonymous fields named "error" from an interface.
    83  // This is called when "error" has been determined to be a local name,
    84  // not the predeclared type.
    85  //
    86  func removeErrorField(ityp *ast.InterfaceType) {
    87  	list := ityp.Methods.List // we know that ityp.Methods != nil
    88  	j := 0
    89  	for _, field := range list {
    90  		keepField := true
    91  		if n := len(field.Names); n == 0 {
    92  			// anonymous field
    93  			if fname, _ := baseTypeName(field.Type); fname == "error" {
    94  				keepField = false
    95  			}
    96  		}
    97  		if keepField {
    98  			list[j] = field
    99  			j++
   100  		}
   101  	}
   102  	if j < len(list) {
   103  		ityp.Incomplete = true
   104  	}
   105  	ityp.Methods.List = list[0:j]
   106  }
   107  
   108  // filterFieldList removes unexported fields (field names) from the field list
   109  // in place and reports whether fields were removed. Anonymous fields are
   110  // recorded with the parent type. filterType is called with the types of
   111  // all remaining fields.
   112  //
   113  func (r *reader) filterFieldList(parent *namedType, fields *ast.FieldList, ityp *ast.InterfaceType) (removedFields bool) {
   114  	if fields == nil {
   115  		return
   116  	}
   117  	list := fields.List
   118  	j := 0
   119  	for _, field := range list {
   120  		keepField := false
   121  		if n := len(field.Names); n == 0 {
   122  			// anonymous field
   123  			fname := r.recordAnonymousField(parent, field.Type)
   124  			if token.IsExported(fname) {
   125  				keepField = true
   126  			} else if ityp != nil && fname == "error" {
   127  				// possibly the predeclared error interface; keep
   128  				// it for now but remember this interface so that
   129  				// it can be fixed if error is also defined locally
   130  				keepField = true
   131  				r.remember(ityp)
   132  			}
   133  		} else {
   134  			field.Names = filterIdentList(field.Names)
   135  			if len(field.Names) < n {
   136  				removedFields = true
   137  			}
   138  			if len(field.Names) > 0 {
   139  				keepField = true
   140  			}
   141  		}
   142  		if keepField {
   143  			r.filterType(nil, field.Type)
   144  			list[j] = field
   145  			j++
   146  		}
   147  	}
   148  	if j < len(list) {
   149  		removedFields = true
   150  	}
   151  	fields.List = list[0:j]
   152  	return
   153  }
   154  
   155  // filterParamList applies filterType to each parameter type in fields.
   156  //
   157  func (r *reader) filterParamList(fields *ast.FieldList) {
   158  	if fields != nil {
   159  		for _, f := range fields.List {
   160  			r.filterType(nil, f.Type)
   161  		}
   162  	}
   163  }
   164  
   165  // filterType strips any unexported struct fields or method types from typ
   166  // in place. If fields (or methods) have been removed, the corresponding
   167  // struct or interface type has the Incomplete field set to true.
   168  //
   169  func (r *reader) filterType(parent *namedType, typ ast.Expr) {
   170  	switch t := typ.(type) {
   171  	case *ast.Ident:
   172  		// nothing to do
   173  	case *ast.ParenExpr:
   174  		r.filterType(nil, t.X)
   175  	case *ast.ArrayType:
   176  		r.filterType(nil, t.Elt)
   177  	case *ast.StructType:
   178  		if r.filterFieldList(parent, t.Fields, nil) {
   179  			t.Incomplete = true
   180  		}
   181  	case *ast.FuncType:
   182  		r.filterParamList(t.Params)
   183  		r.filterParamList(t.Results)
   184  	case *ast.InterfaceType:
   185  		if r.filterFieldList(parent, t.Methods, t) {
   186  			t.Incomplete = true
   187  		}
   188  	case *ast.MapType:
   189  		r.filterType(nil, t.Key)
   190  		r.filterType(nil, t.Value)
   191  	case *ast.ChanType:
   192  		r.filterType(nil, t.Value)
   193  	}
   194  }
   195  
   196  func (r *reader) filterSpec(spec ast.Spec) bool {
   197  	switch s := spec.(type) {
   198  	case *ast.ImportSpec:
   199  		// always keep imports so we can collect them
   200  		return true
   201  	case *ast.ValueSpec:
   202  		s.Values = filterExprList(s.Values, token.IsExported, true)
   203  		if len(s.Values) > 0 || s.Type == nil && len(s.Values) == 0 {
   204  			// If there are values declared on RHS, just replace the unexported
   205  			// identifiers on the LHS with underscore, so that it matches
   206  			// the sequence of expression on the RHS.
   207  			//
   208  			// Similarly, if there are no type and values, then this expression
   209  			// must be following an iota expression, where order matters.
   210  			if updateIdentList(s.Names) {
   211  				r.filterType(nil, s.Type)
   212  				return true
   213  			}
   214  		} else {
   215  			s.Names = filterIdentList(s.Names)
   216  			if len(s.Names) > 0 {
   217  				r.filterType(nil, s.Type)
   218  				return true
   219  			}
   220  		}
   221  	case *ast.TypeSpec:
   222  		if name := s.Name.Name; token.IsExported(name) {
   223  			r.filterType(r.lookupType(s.Name.Name), s.Type)
   224  			return true
   225  		} else if name == "error" {
   226  			// special case: remember that error is declared locally
   227  			r.errorDecl = true
   228  		}
   229  	}
   230  	return false
   231  }
   232  
   233  // copyConstType returns a copy of typ with position pos.
   234  // typ must be a valid constant type.
   235  // In practice, only (possibly qualified) identifiers are possible.
   236  //
   237  func copyConstType(typ ast.Expr, pos token.Pos) ast.Expr {
   238  	switch typ := typ.(type) {
   239  	case *ast.Ident:
   240  		return &ast.Ident{Name: typ.Name, NamePos: pos}
   241  	case *ast.SelectorExpr:
   242  		if id, ok := typ.X.(*ast.Ident); ok {
   243  			// presumably a qualified identifier
   244  			return &ast.SelectorExpr{
   245  				Sel: ast.NewIdent(typ.Sel.Name),
   246  				X:   &ast.Ident{Name: id.Name, NamePos: pos},
   247  			}
   248  		}
   249  	}
   250  	return nil // shouldn't happen, but be conservative and don't panic
   251  }
   252  
   253  func (r *reader) filterSpecList(list []ast.Spec, tok token.Token) []ast.Spec {
   254  	if tok == token.CONST {
   255  		// Propagate any type information that would get lost otherwise
   256  		// when unexported constants are filtered.
   257  		var prevType ast.Expr
   258  		for _, spec := range list {
   259  			spec := spec.(*ast.ValueSpec)
   260  			if spec.Type == nil && len(spec.Values) == 0 && prevType != nil {
   261  				// provide current spec with an explicit type
   262  				spec.Type = copyConstType(prevType, spec.Pos())
   263  			}
   264  			if hasExportedName(spec.Names) {
   265  				// exported names are preserved so there's no need to propagate the type
   266  				prevType = nil
   267  			} else {
   268  				prevType = spec.Type
   269  			}
   270  		}
   271  	}
   272  
   273  	j := 0
   274  	for _, s := range list {
   275  		if r.filterSpec(s) {
   276  			list[j] = s
   277  			j++
   278  		}
   279  	}
   280  	return list[0:j]
   281  }
   282  
   283  func (r *reader) filterDecl(decl ast.Decl) bool {
   284  	switch d := decl.(type) {
   285  	case *ast.GenDecl:
   286  		d.Specs = r.filterSpecList(d.Specs, d.Tok)
   287  		return len(d.Specs) > 0
   288  	case *ast.FuncDecl:
   289  		// ok to filter these methods early because any
   290  		// conflicting method will be filtered here, too -
   291  		// thus, removing these methods early will not lead
   292  		// to the false removal of possible conflicts
   293  		return token.IsExported(d.Name.Name)
   294  	}
   295  	return false
   296  }
   297  
   298  // fileExports removes unexported declarations from src in place.
   299  //
   300  func (r *reader) fileExports(src *ast.File) {
   301  	j := 0
   302  	for _, d := range src.Decls {
   303  		if r.filterDecl(d) {
   304  			src.Decls[j] = d
   305  			j++
   306  		}
   307  	}
   308  	src.Decls = src.Decls[0:j]
   309  }
   310  

View as plain text