Source file src/go/types/errors.go

     1  // Copyright 2012 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 various error reporters.
     6  
     7  package types
     8  
     9  import (
    10  	"bytes"
    11  	"errors"
    12  	"fmt"
    13  	"go/ast"
    14  	"go/token"
    15  	"strconv"
    16  	"strings"
    17  )
    18  
    19  func assert(p bool) {
    20  	if !p {
    21  		panic("assertion failed")
    22  	}
    23  }
    24  
    25  func unreachable() {
    26  	panic("unreachable")
    27  }
    28  
    29  func (check *Checker) qualifier(pkg *Package) string {
    30  	// Qualify the package unless it's the package being type-checked.
    31  	if pkg != check.pkg {
    32  		if check.pkgPathMap == nil {
    33  			check.pkgPathMap = make(map[string]map[string]bool)
    34  			check.seenPkgMap = make(map[*Package]bool)
    35  			check.markImports(check.pkg)
    36  		}
    37  		// If the same package name was used by multiple packages, display the full path.
    38  		if len(check.pkgPathMap[pkg.name]) > 1 {
    39  			return strconv.Quote(pkg.path)
    40  		}
    41  		return pkg.name
    42  	}
    43  	return ""
    44  }
    45  
    46  // markImports recursively walks pkg and its imports, to record unique import
    47  // paths in pkgPathMap.
    48  func (check *Checker) markImports(pkg *Package) {
    49  	if check.seenPkgMap[pkg] {
    50  		return
    51  	}
    52  	check.seenPkgMap[pkg] = true
    53  
    54  	forName, ok := check.pkgPathMap[pkg.name]
    55  	if !ok {
    56  		forName = make(map[string]bool)
    57  		check.pkgPathMap[pkg.name] = forName
    58  	}
    59  	forName[pkg.path] = true
    60  
    61  	for _, imp := range pkg.imports {
    62  		check.markImports(imp)
    63  	}
    64  }
    65  
    66  // check may be nil.
    67  func (check *Checker) sprintf(format string, args ...any) string {
    68  	var fset *token.FileSet
    69  	var qf Qualifier
    70  	if check != nil {
    71  		fset = check.fset
    72  		qf = check.qualifier
    73  	}
    74  	return sprintf(fset, qf, false, format, args...)
    75  }
    76  
    77  func sprintf(fset *token.FileSet, qf Qualifier, debug bool, format string, args ...any) string {
    78  	for i, arg := range args {
    79  		switch a := arg.(type) {
    80  		case nil:
    81  			arg = "<nil>"
    82  		case operand:
    83  			panic("got operand instead of *operand")
    84  		case *operand:
    85  			arg = operandString(a, qf)
    86  		case token.Pos:
    87  			if fset != nil {
    88  				arg = fset.Position(a).String()
    89  			}
    90  		case ast.Expr:
    91  			arg = ExprString(a)
    92  		case []ast.Expr:
    93  			var buf bytes.Buffer
    94  			buf.WriteByte('[')
    95  			writeExprList(&buf, a)
    96  			buf.WriteByte(']')
    97  			arg = buf.String()
    98  		case Object:
    99  			arg = ObjectString(a, qf)
   100  		case Type:
   101  			arg = typeString(a, qf, debug)
   102  		case []Type:
   103  			var buf bytes.Buffer
   104  			buf.WriteByte('[')
   105  			for i, x := range a {
   106  				if i > 0 {
   107  					buf.WriteString(", ")
   108  				}
   109  				buf.WriteString(typeString(x, qf, debug))
   110  			}
   111  			buf.WriteByte(']')
   112  			arg = buf.String()
   113  		case []*TypeParam:
   114  			var buf bytes.Buffer
   115  			buf.WriteByte('[')
   116  			for i, x := range a {
   117  				if i > 0 {
   118  					buf.WriteString(", ")
   119  				}
   120  				buf.WriteString(typeString(x, qf, debug)) // use typeString so we get subscripts when debugging
   121  			}
   122  			buf.WriteByte(']')
   123  			arg = buf.String()
   124  		}
   125  		args[i] = arg
   126  	}
   127  	return fmt.Sprintf(format, args...)
   128  }
   129  
   130  func (check *Checker) trace(pos token.Pos, format string, args ...any) {
   131  	fmt.Printf("%s:\t%s%s\n",
   132  		check.fset.Position(pos),
   133  		strings.Repeat(".  ", check.indent),
   134  		sprintf(check.fset, check.qualifier, true, format, args...),
   135  	)
   136  }
   137  
   138  // dump is only needed for debugging
   139  func (check *Checker) dump(format string, args ...any) {
   140  	fmt.Println(sprintf(check.fset, check.qualifier, true, format, args...))
   141  }
   142  
   143  func (check *Checker) err(err error) {
   144  	if err == nil {
   145  		return
   146  	}
   147  	var e Error
   148  	isInternal := errors.As(err, &e)
   149  	// Cheap trick: Don't report errors with messages containing
   150  	// "invalid operand" or "invalid type" as those tend to be
   151  	// follow-on errors which don't add useful information. Only
   152  	// exclude them if these strings are not at the beginning,
   153  	// and only if we have at least one error already reported.
   154  	isInvalidErr := isInternal && (strings.Index(e.Msg, "invalid operand") > 0 || strings.Index(e.Msg, "invalid type") > 0)
   155  	if check.firstErr != nil && isInvalidErr {
   156  		return
   157  	}
   158  
   159  	if isInternal {
   160  		e.Msg = stripAnnotations(e.Msg)
   161  		if check.errpos != nil {
   162  			// If we have an internal error and the errpos override is set, use it to
   163  			// augment our error positioning.
   164  			// TODO(rFindley) we may also want to augment the error message and refer
   165  			// to the position (pos) in the original expression.
   166  			span := spanOf(check.errpos)
   167  			e.Pos = span.pos
   168  			e.go116start = span.start
   169  			e.go116end = span.end
   170  		}
   171  		err = e
   172  	}
   173  
   174  	if check.firstErr == nil {
   175  		check.firstErr = err
   176  	}
   177  
   178  	if trace {
   179  		pos := e.Pos
   180  		msg := e.Msg
   181  		if !isInternal {
   182  			msg = err.Error()
   183  			pos = token.NoPos
   184  		}
   185  		check.trace(pos, "ERROR: %s", msg)
   186  	}
   187  
   188  	f := check.conf.Error
   189  	if f == nil {
   190  		panic(bailout{}) // report only first error
   191  	}
   192  	f(err)
   193  }
   194  
   195  func (check *Checker) newError(at positioner, code errorCode, soft bool, msg string) error {
   196  	span := spanOf(at)
   197  	return Error{
   198  		Fset:       check.fset,
   199  		Pos:        span.pos,
   200  		Msg:        msg,
   201  		Soft:       soft,
   202  		go116code:  code,
   203  		go116start: span.start,
   204  		go116end:   span.end,
   205  	}
   206  }
   207  
   208  // newErrorf creates a new Error, but does not handle it.
   209  func (check *Checker) newErrorf(at positioner, code errorCode, soft bool, format string, args ...any) error {
   210  	msg := check.sprintf(format, args...)
   211  	return check.newError(at, code, soft, msg)
   212  }
   213  
   214  func (check *Checker) error(at positioner, code errorCode, msg string) {
   215  	check.err(check.newError(at, code, false, msg))
   216  }
   217  
   218  func (check *Checker) errorf(at positioner, code errorCode, format string, args ...any) {
   219  	check.error(at, code, check.sprintf(format, args...))
   220  }
   221  
   222  func (check *Checker) softErrorf(at positioner, code errorCode, format string, args ...any) {
   223  	check.err(check.newErrorf(at, code, true, format, args...))
   224  }
   225  
   226  func (check *Checker) invalidAST(at positioner, format string, args ...any) {
   227  	check.errorf(at, 0, "invalid AST: "+format, args...)
   228  }
   229  
   230  func (check *Checker) invalidArg(at positioner, code errorCode, format string, args ...any) {
   231  	check.errorf(at, code, "invalid argument: "+format, args...)
   232  }
   233  
   234  func (check *Checker) invalidOp(at positioner, code errorCode, format string, args ...any) {
   235  	check.errorf(at, code, "invalid operation: "+format, args...)
   236  }
   237  
   238  // The positioner interface is used to extract the position of type-checker
   239  // errors.
   240  type positioner interface {
   241  	Pos() token.Pos
   242  }
   243  
   244  // posSpan holds a position range along with a highlighted position within that
   245  // range. This is used for positioning errors, with pos by convention being the
   246  // first position in the source where the error is known to exist, and start
   247  // and end defining the full span of syntax being considered when the error was
   248  // detected. Invariant: start <= pos < end || start == pos == end.
   249  type posSpan struct {
   250  	start, pos, end token.Pos
   251  }
   252  
   253  func (e posSpan) Pos() token.Pos {
   254  	return e.pos
   255  }
   256  
   257  // inNode creates a posSpan for the given node.
   258  // Invariant: node.Pos() <= pos < node.End() (node.End() is the position of the
   259  // first byte after node within the source).
   260  func inNode(node ast.Node, pos token.Pos) posSpan {
   261  	start, end := node.Pos(), node.End()
   262  	if debug {
   263  		assert(start <= pos && pos < end)
   264  	}
   265  	return posSpan{start, pos, end}
   266  }
   267  
   268  // atPos wraps a token.Pos to implement the positioner interface.
   269  type atPos token.Pos
   270  
   271  func (s atPos) Pos() token.Pos {
   272  	return token.Pos(s)
   273  }
   274  
   275  // spanOf extracts an error span from the given positioner. By default this is
   276  // the trivial span starting and ending at pos, but this span is expanded when
   277  // the argument naturally corresponds to a span of source code.
   278  func spanOf(at positioner) posSpan {
   279  	switch x := at.(type) {
   280  	case nil:
   281  		panic("nil positioner")
   282  	case posSpan:
   283  		return x
   284  	case ast.Node:
   285  		pos := x.Pos()
   286  		return posSpan{pos, pos, x.End()}
   287  	case *operand:
   288  		if x.expr != nil {
   289  			pos := x.Pos()
   290  			return posSpan{pos, pos, x.expr.End()}
   291  		}
   292  		return posSpan{token.NoPos, token.NoPos, token.NoPos}
   293  	default:
   294  		pos := at.Pos()
   295  		return posSpan{pos, pos, pos}
   296  	}
   297  }
   298  
   299  // stripAnnotations removes internal (type) annotations from s.
   300  func stripAnnotations(s string) string {
   301  	var b strings.Builder
   302  	for _, r := range s {
   303  		// strip #'s and subscript digits
   304  		if r < '₀' || '₀'+10 <= r { // '₀' == U+2080
   305  			b.WriteRune(r)
   306  		}
   307  	}
   308  	if b.Len() < len(s) {
   309  		return b.String()
   310  	}
   311  	return s
   312  }
   313  

View as plain text