...

Source file src/go/types/errors.go

Documentation: go/types

     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  	"fmt"
    12  	"go/ast"
    13  	"go/token"
    14  	. "internal/types/errors"
    15  	"runtime"
    16  	"strconv"
    17  	"strings"
    18  )
    19  
    20  func assert(p bool) {
    21  	if !p {
    22  		msg := "assertion failed"
    23  		// Include information about the assertion location. Due to panic recovery,
    24  		// this location is otherwise buried in the middle of the panicking stack.
    25  		if _, file, line, ok := runtime.Caller(1); ok {
    26  			msg = fmt.Sprintf("%s:%d: %s", file, line, msg)
    27  		}
    28  		panic(msg)
    29  	}
    30  }
    31  
    32  func unreachable() {
    33  	panic("unreachable")
    34  }
    35  
    36  // An error_ represents a type-checking error.
    37  // To report an error_, call Checker.report.
    38  type error_ struct {
    39  	desc []errorDesc
    40  	code Code
    41  	soft bool // TODO(gri) eventually determine this from an error code
    42  }
    43  
    44  // An errorDesc describes part of a type-checking error.
    45  type errorDesc struct {
    46  	posn   positioner
    47  	format string
    48  	args   []interface{}
    49  }
    50  
    51  func (err *error_) empty() bool {
    52  	return err.desc == nil
    53  }
    54  
    55  func (err *error_) pos() token.Pos {
    56  	if err.empty() {
    57  		return nopos
    58  	}
    59  	return err.desc[0].posn.Pos()
    60  }
    61  
    62  func (err *error_) msg(fset *token.FileSet, qf Qualifier) string {
    63  	if err.empty() {
    64  		return "no error"
    65  	}
    66  	var buf strings.Builder
    67  	for i := range err.desc {
    68  		p := &err.desc[i]
    69  		if i > 0 {
    70  			fmt.Fprint(&buf, "\n\t")
    71  			if p.posn.Pos().IsValid() {
    72  				fmt.Fprintf(&buf, "%s: ", fset.Position(p.posn.Pos()))
    73  			}
    74  		}
    75  		buf.WriteString(sprintf(fset, qf, false, p.format, p.args...))
    76  	}
    77  	return buf.String()
    78  }
    79  
    80  // String is for testing.
    81  func (err *error_) String() string {
    82  	if err.empty() {
    83  		return "no error"
    84  	}
    85  	return fmt.Sprintf("%d: %s", err.pos(), err.msg(nil, nil))
    86  }
    87  
    88  // errorf adds formatted error information to err.
    89  // It may be called multiple times to provide additional information.
    90  func (err *error_) errorf(at token.Pos, format string, args ...interface{}) {
    91  	err.desc = append(err.desc, errorDesc{atPos(at), format, args})
    92  }
    93  
    94  func (check *Checker) qualifier(pkg *Package) string {
    95  	// Qualify the package unless it's the package being type-checked.
    96  	if pkg != check.pkg {
    97  		if check.pkgPathMap == nil {
    98  			check.pkgPathMap = make(map[string]map[string]bool)
    99  			check.seenPkgMap = make(map[*Package]bool)
   100  			check.markImports(check.pkg)
   101  		}
   102  		// If the same package name was used by multiple packages, display the full path.
   103  		if len(check.pkgPathMap[pkg.name]) > 1 {
   104  			return strconv.Quote(pkg.path)
   105  		}
   106  		return pkg.name
   107  	}
   108  	return ""
   109  }
   110  
   111  // markImports recursively walks pkg and its imports, to record unique import
   112  // paths in pkgPathMap.
   113  func (check *Checker) markImports(pkg *Package) {
   114  	if check.seenPkgMap[pkg] {
   115  		return
   116  	}
   117  	check.seenPkgMap[pkg] = true
   118  
   119  	forName, ok := check.pkgPathMap[pkg.name]
   120  	if !ok {
   121  		forName = make(map[string]bool)
   122  		check.pkgPathMap[pkg.name] = forName
   123  	}
   124  	forName[pkg.path] = true
   125  
   126  	for _, imp := range pkg.imports {
   127  		check.markImports(imp)
   128  	}
   129  }
   130  
   131  // check may be nil.
   132  func (check *Checker) sprintf(format string, args ...any) string {
   133  	var fset *token.FileSet
   134  	var qf Qualifier
   135  	if check != nil {
   136  		fset = check.fset
   137  		qf = check.qualifier
   138  	}
   139  	return sprintf(fset, qf, false, format, args...)
   140  }
   141  
   142  func sprintf(fset *token.FileSet, qf Qualifier, tpSubscripts bool, format string, args ...any) string {
   143  	for i, arg := range args {
   144  		switch a := arg.(type) {
   145  		case nil:
   146  			arg = "<nil>"
   147  		case operand:
   148  			panic("got operand instead of *operand")
   149  		case *operand:
   150  			arg = operandString(a, qf)
   151  		case token.Pos:
   152  			if fset != nil {
   153  				arg = fset.Position(a).String()
   154  			}
   155  		case ast.Expr:
   156  			arg = ExprString(a)
   157  		case []ast.Expr:
   158  			var buf bytes.Buffer
   159  			buf.WriteByte('[')
   160  			writeExprList(&buf, a)
   161  			buf.WriteByte(']')
   162  			arg = buf.String()
   163  		case Object:
   164  			arg = ObjectString(a, qf)
   165  		case Type:
   166  			var buf bytes.Buffer
   167  			w := newTypeWriter(&buf, qf)
   168  			w.tpSubscripts = tpSubscripts
   169  			w.typ(a)
   170  			arg = buf.String()
   171  		case []Type:
   172  			var buf bytes.Buffer
   173  			w := newTypeWriter(&buf, qf)
   174  			w.tpSubscripts = tpSubscripts
   175  			buf.WriteByte('[')
   176  			for i, x := range a {
   177  				if i > 0 {
   178  					buf.WriteString(", ")
   179  				}
   180  				w.typ(x)
   181  			}
   182  			buf.WriteByte(']')
   183  			arg = buf.String()
   184  		case []*TypeParam:
   185  			var buf bytes.Buffer
   186  			w := newTypeWriter(&buf, qf)
   187  			w.tpSubscripts = tpSubscripts
   188  			buf.WriteByte('[')
   189  			for i, x := range a {
   190  				if i > 0 {
   191  					buf.WriteString(", ")
   192  				}
   193  				w.typ(x)
   194  			}
   195  			buf.WriteByte(']')
   196  			arg = buf.String()
   197  		}
   198  		args[i] = arg
   199  	}
   200  	return fmt.Sprintf(format, args...)
   201  }
   202  
   203  func (check *Checker) trace(pos token.Pos, format string, args ...any) {
   204  	fmt.Printf("%s:\t%s%s\n",
   205  		check.fset.Position(pos),
   206  		strings.Repeat(".  ", check.indent),
   207  		sprintf(check.fset, check.qualifier, true, format, args...),
   208  	)
   209  }
   210  
   211  // dump is only needed for debugging
   212  func (check *Checker) dump(format string, args ...any) {
   213  	fmt.Println(sprintf(check.fset, check.qualifier, true, format, args...))
   214  }
   215  
   216  // Report records the error pointed to by errp, setting check.firstError if
   217  // necessary.
   218  func (check *Checker) report(errp *error_) {
   219  	if errp.empty() {
   220  		panic("empty error details")
   221  	}
   222  
   223  	msg := errp.msg(check.fset, check.qualifier)
   224  	switch errp.code {
   225  	case InvalidSyntaxTree:
   226  		msg = "invalid AST: " + msg
   227  	case 0:
   228  		panic("no error code provided")
   229  	}
   230  
   231  	// If we have a URL for error codes, add a link to the first line.
   232  	if errp.code != 0 && check.conf._ErrorURL != "" {
   233  		u := fmt.Sprintf(check.conf._ErrorURL, errp.code)
   234  		if i := strings.Index(msg, "\n"); i >= 0 {
   235  			msg = msg[:i] + u + msg[i:]
   236  		} else {
   237  			msg += u
   238  		}
   239  	}
   240  
   241  	span := spanOf(errp.desc[0].posn)
   242  	e := Error{
   243  		Fset:       check.fset,
   244  		Pos:        span.pos,
   245  		Msg:        msg,
   246  		Soft:       errp.soft,
   247  		go116code:  errp.code,
   248  		go116start: span.start,
   249  		go116end:   span.end,
   250  	}
   251  
   252  	// Cheap trick: Don't report errors with messages containing
   253  	// "invalid operand" or "invalid type" as those tend to be
   254  	// follow-on errors which don't add useful information. Only
   255  	// exclude them if these strings are not at the beginning,
   256  	// and only if we have at least one error already reported.
   257  	isInvalidErr := strings.Index(e.Msg, "invalid operand") > 0 || strings.Index(e.Msg, "invalid type") > 0
   258  	if check.firstErr != nil && isInvalidErr {
   259  		return
   260  	}
   261  
   262  	e.Msg = stripAnnotations(e.Msg)
   263  	if check.errpos != nil {
   264  		// If we have an internal error and the errpos override is set, use it to
   265  		// augment our error positioning.
   266  		// TODO(rFindley) we may also want to augment the error message and refer
   267  		// to the position (pos) in the original expression.
   268  		span := spanOf(check.errpos)
   269  		e.Pos = span.pos
   270  		e.go116start = span.start
   271  		e.go116end = span.end
   272  	}
   273  	err := e
   274  
   275  	if check.firstErr == nil {
   276  		check.firstErr = err
   277  	}
   278  
   279  	if check.conf._Trace {
   280  		pos := e.Pos
   281  		msg := e.Msg
   282  		check.trace(pos, "ERROR: %s", msg)
   283  	}
   284  
   285  	f := check.conf.Error
   286  	if f == nil {
   287  		panic(bailout{}) // report only first error
   288  	}
   289  	f(err)
   290  }
   291  
   292  const (
   293  	invalidArg = "invalid argument: "
   294  	invalidOp  = "invalid operation: "
   295  )
   296  
   297  // newErrorf creates a new error_ for later reporting with check.report.
   298  func newErrorf(at positioner, code Code, format string, args ...any) *error_ {
   299  	return &error_{
   300  		desc: []errorDesc{{at, format, args}},
   301  		code: code,
   302  	}
   303  }
   304  
   305  func (check *Checker) error(at positioner, code Code, msg string) {
   306  	check.report(newErrorf(at, code, "%s", msg))
   307  }
   308  
   309  func (check *Checker) errorf(at positioner, code Code, format string, args ...any) {
   310  	check.report(newErrorf(at, code, format, args...))
   311  }
   312  
   313  func (check *Checker) softErrorf(at positioner, code Code, format string, args ...any) {
   314  	err := newErrorf(at, code, format, args...)
   315  	err.soft = true
   316  	check.report(err)
   317  }
   318  
   319  func (check *Checker) versionErrorf(at positioner, v goVersion, format string, args ...interface{}) {
   320  	msg := check.sprintf(format, args...)
   321  	var err *error_
   322  	err = newErrorf(at, UnsupportedFeature, "%s requires %s or later", msg, v)
   323  	check.report(err)
   324  }
   325  
   326  // The positioner interface is used to extract the position of type-checker
   327  // errors.
   328  type positioner interface {
   329  	Pos() token.Pos
   330  }
   331  
   332  // posSpan holds a position range along with a highlighted position within that
   333  // range. This is used for positioning errors, with pos by convention being the
   334  // first position in the source where the error is known to exist, and start
   335  // and end defining the full span of syntax being considered when the error was
   336  // detected. Invariant: start <= pos < end || start == pos == end.
   337  type posSpan struct {
   338  	start, pos, end token.Pos
   339  }
   340  
   341  func (e posSpan) Pos() token.Pos {
   342  	return e.pos
   343  }
   344  
   345  // inNode creates a posSpan for the given node.
   346  // Invariant: node.Pos() <= pos < node.End() (node.End() is the position of the
   347  // first byte after node within the source).
   348  func inNode(node ast.Node, pos token.Pos) posSpan {
   349  	start, end := node.Pos(), node.End()
   350  	if debug {
   351  		assert(start <= pos && pos < end)
   352  	}
   353  	return posSpan{start, pos, end}
   354  }
   355  
   356  // atPos wraps a token.Pos to implement the positioner interface.
   357  type atPos token.Pos
   358  
   359  func (s atPos) Pos() token.Pos {
   360  	return token.Pos(s)
   361  }
   362  
   363  // spanOf extracts an error span from the given positioner. By default this is
   364  // the trivial span starting and ending at pos, but this span is expanded when
   365  // the argument naturally corresponds to a span of source code.
   366  func spanOf(at positioner) posSpan {
   367  	switch x := at.(type) {
   368  	case nil:
   369  		panic("nil positioner")
   370  	case posSpan:
   371  		return x
   372  	case ast.Node:
   373  		pos := x.Pos()
   374  		return posSpan{pos, pos, x.End()}
   375  	case *operand:
   376  		if x.expr != nil {
   377  			pos := x.Pos()
   378  			return posSpan{pos, pos, x.expr.End()}
   379  		}
   380  		return posSpan{nopos, nopos, nopos}
   381  	default:
   382  		pos := at.Pos()
   383  		return posSpan{pos, pos, pos}
   384  	}
   385  }
   386  
   387  // stripAnnotations removes internal (type) annotations from s.
   388  func stripAnnotations(s string) string {
   389  	var buf strings.Builder
   390  	for _, r := range s {
   391  		// strip #'s and subscript digits
   392  		if r < '₀' || '₀'+10 <= r { // '₀' == U+2080
   393  			buf.WriteRune(r)
   394  		}
   395  	}
   396  	if buf.Len() < len(s) {
   397  		return buf.String()
   398  	}
   399  	return s
   400  }
   401  

View as plain text