Black Lives Matter. Support the Equal Justice Initiative.

Source file src/go/types/builtins.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 typechecking of builtin function calls.
     6  
     7  package types
     8  
     9  import (
    10  	"go/ast"
    11  	"go/constant"
    12  	"go/token"
    13  )
    14  
    15  // builtin type-checks a call to the built-in specified by id and
    16  // reports whether the call is valid, with *x holding the result;
    17  // but x.expr is not set. If the call is invalid, the result is
    18  // false, and *x is undefined.
    19  //
    20  func (check *Checker) builtin(x *operand, call *ast.CallExpr, id builtinId) (_ bool) {
    21  	// append is the only built-in that permits the use of ... for the last argument
    22  	bin := predeclaredFuncs[id]
    23  	if call.Ellipsis.IsValid() && id != _Append {
    24  		check.invalidOp(atPos(call.Ellipsis),
    25  			_InvalidDotDotDot,
    26  			"invalid use of ... with built-in %s", bin.name)
    27  		check.use(call.Args...)
    28  		return
    29  	}
    30  
    31  	// For len(x) and cap(x) we need to know if x contains any function calls or
    32  	// receive operations. Save/restore current setting and set hasCallOrRecv to
    33  	// false for the evaluation of x so that we can check it afterwards.
    34  	// Note: We must do this _before_ calling unpack because unpack evaluates the
    35  	//       first argument before we even call arg(x, 0)!
    36  	if id == _Len || id == _Cap {
    37  		defer func(b bool) {
    38  			check.hasCallOrRecv = b
    39  		}(check.hasCallOrRecv)
    40  		check.hasCallOrRecv = false
    41  	}
    42  
    43  	// determine actual arguments
    44  	var arg getter
    45  	nargs := len(call.Args)
    46  	switch id {
    47  	default:
    48  		// make argument getter
    49  		arg, nargs, _ = unpack(func(x *operand, i int) { check.multiExpr(x, call.Args[i]) }, nargs, false)
    50  		if arg == nil {
    51  			return
    52  		}
    53  		// evaluate first argument, if present
    54  		if nargs > 0 {
    55  			arg(x, 0)
    56  			if x.mode == invalid {
    57  				return
    58  			}
    59  		}
    60  	case _Make, _New, _Offsetof, _Trace:
    61  		// arguments require special handling
    62  	}
    63  
    64  	// check argument count
    65  	{
    66  		msg := ""
    67  		if nargs < bin.nargs {
    68  			msg = "not enough"
    69  		} else if !bin.variadic && nargs > bin.nargs {
    70  			msg = "too many"
    71  		}
    72  		if msg != "" {
    73  			check.invalidOp(inNode(call, call.Rparen), _WrongArgCount, "%s arguments for %s (expected %d, found %d)", msg, call, bin.nargs, nargs)
    74  			return
    75  		}
    76  	}
    77  
    78  	switch id {
    79  	case _Append:
    80  		// append(s S, x ...T) S, where T is the element type of S
    81  		// spec: "The variadic function append appends zero or more values x to s of type
    82  		// S, which must be a slice type, and returns the resulting slice, also of type S.
    83  		// The values x are passed to a parameter of type ...T where T is the element type
    84  		// of S and the respective parameter passing rules apply."
    85  		S := x.typ
    86  		var T Type
    87  		if s, _ := S.Underlying().(*Slice); s != nil {
    88  			T = s.elem
    89  		} else {
    90  			check.invalidArg(x, _InvalidAppend, "%s is not a slice", x)
    91  			return
    92  		}
    93  
    94  		// remember arguments that have been evaluated already
    95  		alist := []operand{*x}
    96  
    97  		// spec: "As a special case, append also accepts a first argument assignable
    98  		// to type []byte with a second argument of string type followed by ... .
    99  		// This form appends the bytes of the string.
   100  		if nargs == 2 && call.Ellipsis.IsValid() {
   101  			if ok, _ := x.assignableTo(check, NewSlice(universeByte), nil); ok {
   102  				arg(x, 1)
   103  				if x.mode == invalid {
   104  					return
   105  				}
   106  				if isString(x.typ) {
   107  					if check.Types != nil {
   108  						sig := makeSig(S, S, x.typ)
   109  						sig.variadic = true
   110  						check.recordBuiltinType(call.Fun, sig)
   111  					}
   112  					x.mode = value
   113  					x.typ = S
   114  					break
   115  				}
   116  				alist = append(alist, *x)
   117  				// fallthrough
   118  			}
   119  		}
   120  
   121  		// check general case by creating custom signature
   122  		sig := makeSig(S, S, NewSlice(T)) // []T required for variadic signature
   123  		sig.variadic = true
   124  		check.arguments(x, call, sig, func(x *operand, i int) {
   125  			// only evaluate arguments that have not been evaluated before
   126  			if i < len(alist) {
   127  				*x = alist[i]
   128  				return
   129  			}
   130  			arg(x, i)
   131  		}, nargs)
   132  		// ok to continue even if check.arguments reported errors
   133  
   134  		x.mode = value
   135  		x.typ = S
   136  		if check.Types != nil {
   137  			check.recordBuiltinType(call.Fun, sig)
   138  		}
   139  
   140  	case _Cap, _Len:
   141  		// cap(x)
   142  		// len(x)
   143  		mode := invalid
   144  		var typ Type
   145  		var val constant.Value
   146  		switch typ = implicitArrayDeref(x.typ.Underlying()); t := typ.(type) {
   147  		case *Basic:
   148  			if isString(t) && id == _Len {
   149  				if x.mode == constant_ {
   150  					mode = constant_
   151  					val = constant.MakeInt64(int64(len(constant.StringVal(x.val))))
   152  				} else {
   153  					mode = value
   154  				}
   155  			}
   156  
   157  		case *Array:
   158  			mode = value
   159  			// spec: "The expressions len(s) and cap(s) are constants
   160  			// if the type of s is an array or pointer to an array and
   161  			// the expression s does not contain channel receives or
   162  			// function calls; in this case s is not evaluated."
   163  			if !check.hasCallOrRecv {
   164  				mode = constant_
   165  				if t.len >= 0 {
   166  					val = constant.MakeInt64(t.len)
   167  				} else {
   168  					val = constant.MakeUnknown()
   169  				}
   170  			}
   171  
   172  		case *Slice, *Chan:
   173  			mode = value
   174  
   175  		case *Map:
   176  			if id == _Len {
   177  				mode = value
   178  			}
   179  		}
   180  
   181  		if mode == invalid && typ != Typ[Invalid] {
   182  			code := _InvalidCap
   183  			if id == _Len {
   184  				code = _InvalidLen
   185  			}
   186  			check.invalidArg(x, code, "%s for %s", x, bin.name)
   187  			return
   188  		}
   189  
   190  		x.mode = mode
   191  		x.typ = Typ[Int]
   192  		x.val = val
   193  		if check.Types != nil && mode != constant_ {
   194  			check.recordBuiltinType(call.Fun, makeSig(x.typ, typ))
   195  		}
   196  
   197  	case _Close:
   198  		// close(c)
   199  		c, _ := x.typ.Underlying().(*Chan)
   200  		if c == nil {
   201  			check.invalidArg(x, _InvalidClose, "%s is not a channel", x)
   202  			return
   203  		}
   204  		if c.dir == RecvOnly {
   205  			check.invalidArg(x, _InvalidClose, "%s must not be a receive-only channel", x)
   206  			return
   207  		}
   208  
   209  		x.mode = novalue
   210  		if check.Types != nil {
   211  			check.recordBuiltinType(call.Fun, makeSig(nil, c))
   212  		}
   213  
   214  	case _Complex:
   215  		// complex(x, y floatT) complexT
   216  		var y operand
   217  		arg(&y, 1)
   218  		if y.mode == invalid {
   219  			return
   220  		}
   221  
   222  		// convert or check untyped arguments
   223  		d := 0
   224  		if isUntyped(x.typ) {
   225  			d |= 1
   226  		}
   227  		if isUntyped(y.typ) {
   228  			d |= 2
   229  		}
   230  		switch d {
   231  		case 0:
   232  			// x and y are typed => nothing to do
   233  		case 1:
   234  			// only x is untyped => convert to type of y
   235  			check.convertUntyped(x, y.typ)
   236  		case 2:
   237  			// only y is untyped => convert to type of x
   238  			check.convertUntyped(&y, x.typ)
   239  		case 3:
   240  			// x and y are untyped =>
   241  			// 1) if both are constants, convert them to untyped
   242  			//    floating-point numbers if possible,
   243  			// 2) if one of them is not constant (possible because
   244  			//    it contains a shift that is yet untyped), convert
   245  			//    both of them to float64 since they must have the
   246  			//    same type to succeed (this will result in an error
   247  			//    because shifts of floats are not permitted)
   248  			if x.mode == constant_ && y.mode == constant_ {
   249  				toFloat := func(x *operand) {
   250  					if isNumeric(x.typ) && constant.Sign(constant.Imag(x.val)) == 0 {
   251  						x.typ = Typ[UntypedFloat]
   252  					}
   253  				}
   254  				toFloat(x)
   255  				toFloat(&y)
   256  			} else {
   257  				check.convertUntyped(x, Typ[Float64])
   258  				check.convertUntyped(&y, Typ[Float64])
   259  				// x and y should be invalid now, but be conservative
   260  				// and check below
   261  			}
   262  		}
   263  		if x.mode == invalid || y.mode == invalid {
   264  			return
   265  		}
   266  
   267  		// both argument types must be identical
   268  		if !check.identical(x.typ, y.typ) {
   269  			check.invalidArg(x, _InvalidComplex, "mismatched types %s and %s", x.typ, y.typ)
   270  			return
   271  		}
   272  
   273  		// the argument types must be of floating-point type
   274  		if !isFloat(x.typ) {
   275  			check.invalidArg(x, _InvalidComplex, "arguments have type %s, expected floating-point", x.typ)
   276  			return
   277  		}
   278  
   279  		// if both arguments are constants, the result is a constant
   280  		if x.mode == constant_ && y.mode == constant_ {
   281  			x.val = constant.BinaryOp(constant.ToFloat(x.val), token.ADD, constant.MakeImag(constant.ToFloat(y.val)))
   282  		} else {
   283  			x.mode = value
   284  		}
   285  
   286  		// determine result type
   287  		var res BasicKind
   288  		switch x.typ.Underlying().(*Basic).kind {
   289  		case Float32:
   290  			res = Complex64
   291  		case Float64:
   292  			res = Complex128
   293  		case UntypedFloat:
   294  			res = UntypedComplex
   295  		default:
   296  			unreachable()
   297  		}
   298  		resTyp := Typ[res]
   299  
   300  		if check.Types != nil && x.mode != constant_ {
   301  			check.recordBuiltinType(call.Fun, makeSig(resTyp, x.typ, x.typ))
   302  		}
   303  
   304  		x.typ = resTyp
   305  
   306  	case _Copy:
   307  		// copy(x, y []T) int
   308  		var dst Type
   309  		if t, _ := x.typ.Underlying().(*Slice); t != nil {
   310  			dst = t.elem
   311  		}
   312  
   313  		var y operand
   314  		arg(&y, 1)
   315  		if y.mode == invalid {
   316  			return
   317  		}
   318  		var src Type
   319  		switch t := y.typ.Underlying().(type) {
   320  		case *Basic:
   321  			if isString(y.typ) {
   322  				src = universeByte
   323  			}
   324  		case *Slice:
   325  			src = t.elem
   326  		}
   327  
   328  		if dst == nil || src == nil {
   329  			check.invalidArg(x, _InvalidCopy, "copy expects slice arguments; found %s and %s", x, &y)
   330  			return
   331  		}
   332  
   333  		if !check.identical(dst, src) {
   334  			check.invalidArg(x, _InvalidCopy, "arguments to copy %s and %s have different element types %s and %s", x, &y, dst, src)
   335  			return
   336  		}
   337  
   338  		if check.Types != nil {
   339  			check.recordBuiltinType(call.Fun, makeSig(Typ[Int], x.typ, y.typ))
   340  		}
   341  		x.mode = value
   342  		x.typ = Typ[Int]
   343  
   344  	case _Delete:
   345  		// delete(m, k)
   346  		m, _ := x.typ.Underlying().(*Map)
   347  		if m == nil {
   348  			check.invalidArg(x, _InvalidDelete, "%s is not a map", x)
   349  			return
   350  		}
   351  		arg(x, 1) // k
   352  		if x.mode == invalid {
   353  			return
   354  		}
   355  
   356  		if ok, code := x.assignableTo(check, m.key, nil); !ok {
   357  			check.invalidArg(x, code, "%s is not assignable to %s", x, m.key)
   358  			return
   359  		}
   360  
   361  		x.mode = novalue
   362  		if check.Types != nil {
   363  			check.recordBuiltinType(call.Fun, makeSig(nil, m, m.key))
   364  		}
   365  
   366  	case _Imag, _Real:
   367  		// imag(complexT) floatT
   368  		// real(complexT) floatT
   369  
   370  		// convert or check untyped argument
   371  		if isUntyped(x.typ) {
   372  			if x.mode == constant_ {
   373  				// an untyped constant number can always be considered
   374  				// as a complex constant
   375  				if isNumeric(x.typ) {
   376  					x.typ = Typ[UntypedComplex]
   377  				}
   378  			} else {
   379  				// an untyped non-constant argument may appear if
   380  				// it contains a (yet untyped non-constant) shift
   381  				// expression: convert it to complex128 which will
   382  				// result in an error (shift of complex value)
   383  				check.convertUntyped(x, Typ[Complex128])
   384  				// x should be invalid now, but be conservative and check
   385  				if x.mode == invalid {
   386  					return
   387  				}
   388  			}
   389  		}
   390  
   391  		// the argument must be of complex type
   392  		if !isComplex(x.typ) {
   393  			code := _InvalidImag
   394  			if id == _Real {
   395  				code = _InvalidReal
   396  			}
   397  			check.invalidArg(x, code, "argument has type %s, expected complex type", x.typ)
   398  			return
   399  		}
   400  
   401  		// if the argument is a constant, the result is a constant
   402  		if x.mode == constant_ {
   403  			if id == _Real {
   404  				x.val = constant.Real(x.val)
   405  			} else {
   406  				x.val = constant.Imag(x.val)
   407  			}
   408  		} else {
   409  			x.mode = value
   410  		}
   411  
   412  		// determine result type
   413  		var res BasicKind
   414  		switch x.typ.Underlying().(*Basic).kind {
   415  		case Complex64:
   416  			res = Float32
   417  		case Complex128:
   418  			res = Float64
   419  		case UntypedComplex:
   420  			res = UntypedFloat
   421  		default:
   422  			unreachable()
   423  		}
   424  		resTyp := Typ[res]
   425  
   426  		if check.Types != nil && x.mode != constant_ {
   427  			check.recordBuiltinType(call.Fun, makeSig(resTyp, x.typ))
   428  		}
   429  
   430  		x.typ = resTyp
   431  
   432  	case _Make:
   433  		// make(T, n)
   434  		// make(T, n, m)
   435  		// (no argument evaluated yet)
   436  		arg0 := call.Args[0]
   437  		T := check.typ(arg0)
   438  		if T == Typ[Invalid] {
   439  			return
   440  		}
   441  
   442  		var min int // minimum number of arguments
   443  		switch T.Underlying().(type) {
   444  		case *Slice:
   445  			min = 2
   446  		case *Map, *Chan:
   447  			min = 1
   448  		default:
   449  			check.invalidArg(arg0, _InvalidMake, "cannot make %s; type must be slice, map, or channel", arg0)
   450  			return
   451  		}
   452  		if nargs < min || min+1 < nargs {
   453  			check.errorf(call, _WrongArgCount, "%v expects %d or %d arguments; found %d", call, min, min+1, nargs)
   454  			return
   455  		}
   456  		types := []Type{T}
   457  		var sizes []int64 // constant integer arguments, if any
   458  		for _, arg := range call.Args[1:] {
   459  			typ, size := check.index(arg, -1) // ok to continue with typ == Typ[Invalid]
   460  			types = append(types, typ)
   461  			if size >= 0 {
   462  				sizes = append(sizes, size)
   463  			}
   464  		}
   465  		if len(sizes) == 2 && sizes[0] > sizes[1] {
   466  			check.invalidArg(call.Args[1], _SwappedMakeArgs, "length and capacity swapped")
   467  			// safe to continue
   468  		}
   469  		x.mode = value
   470  		x.typ = T
   471  		if check.Types != nil {
   472  			check.recordBuiltinType(call.Fun, makeSig(x.typ, types...))
   473  		}
   474  
   475  	case _New:
   476  		// new(T)
   477  		// (no argument evaluated yet)
   478  		T := check.typ(call.Args[0])
   479  		if T == Typ[Invalid] {
   480  			return
   481  		}
   482  
   483  		x.mode = value
   484  		x.typ = &Pointer{base: T}
   485  		if check.Types != nil {
   486  			check.recordBuiltinType(call.Fun, makeSig(x.typ, T))
   487  		}
   488  
   489  	case _Panic:
   490  		// panic(x)
   491  		// record panic call if inside a function with result parameters
   492  		// (for use in Checker.isTerminating)
   493  		if check.sig != nil && check.sig.results.Len() > 0 {
   494  			// function has result parameters
   495  			p := check.isPanic
   496  			if p == nil {
   497  				// allocate lazily
   498  				p = make(map[*ast.CallExpr]bool)
   499  				check.isPanic = p
   500  			}
   501  			p[call] = true
   502  		}
   503  
   504  		check.assignment(x, &emptyInterface, "argument to panic")
   505  		if x.mode == invalid {
   506  			return
   507  		}
   508  
   509  		x.mode = novalue
   510  		if check.Types != nil {
   511  			check.recordBuiltinType(call.Fun, makeSig(nil, &emptyInterface))
   512  		}
   513  
   514  	case _Print, _Println:
   515  		// print(x, y, ...)
   516  		// println(x, y, ...)
   517  		var params []Type
   518  		if nargs > 0 {
   519  			params = make([]Type, nargs)
   520  			for i := 0; i < nargs; i++ {
   521  				if i > 0 {
   522  					arg(x, i) // first argument already evaluated
   523  				}
   524  				check.assignment(x, nil, "argument to "+predeclaredFuncs[id].name)
   525  				if x.mode == invalid {
   526  					// TODO(gri) "use" all arguments?
   527  					return
   528  				}
   529  				params[i] = x.typ
   530  			}
   531  		}
   532  
   533  		x.mode = novalue
   534  		if check.Types != nil {
   535  			check.recordBuiltinType(call.Fun, makeSig(nil, params...))
   536  		}
   537  
   538  	case _Recover:
   539  		// recover() interface{}
   540  		x.mode = value
   541  		x.typ = &emptyInterface
   542  		if check.Types != nil {
   543  			check.recordBuiltinType(call.Fun, makeSig(x.typ))
   544  		}
   545  
   546  	case _Alignof:
   547  		// unsafe.Alignof(x T) uintptr
   548  		check.assignment(x, nil, "argument to unsafe.Alignof")
   549  		if x.mode == invalid {
   550  			return
   551  		}
   552  
   553  		x.mode = constant_
   554  		x.val = constant.MakeInt64(check.conf.alignof(x.typ))
   555  		x.typ = Typ[Uintptr]
   556  		// result is constant - no need to record signature
   557  
   558  	case _Offsetof:
   559  		// unsafe.Offsetof(x T) uintptr, where x must be a selector
   560  		// (no argument evaluated yet)
   561  		arg0 := call.Args[0]
   562  		selx, _ := unparen(arg0).(*ast.SelectorExpr)
   563  		if selx == nil {
   564  			check.invalidArg(arg0, _BadOffsetofSyntax, "%s is not a selector expression", arg0)
   565  			check.use(arg0)
   566  			return
   567  		}
   568  
   569  		check.expr(x, selx.X)
   570  		if x.mode == invalid {
   571  			return
   572  		}
   573  
   574  		base := derefStructPtr(x.typ)
   575  		sel := selx.Sel.Name
   576  		obj, index, indirect := check.lookupFieldOrMethod(base, false, check.pkg, sel)
   577  		switch obj.(type) {
   578  		case nil:
   579  			check.invalidArg(x, _MissingFieldOrMethod, "%s has no single field %s", base, sel)
   580  			return
   581  		case *Func:
   582  			// TODO(gri) Using derefStructPtr may result in methods being found
   583  			// that don't actually exist. An error either way, but the error
   584  			// message is confusing. See: https://play.golang.org/p/al75v23kUy ,
   585  			// but go/types reports: "invalid argument: x.m is a method value".
   586  			check.invalidArg(arg0, _InvalidOffsetof, "%s is a method value", arg0)
   587  			return
   588  		}
   589  		if indirect {
   590  			check.invalidArg(x, _InvalidOffsetof, "field %s is embedded via a pointer in %s", sel, base)
   591  			return
   592  		}
   593  
   594  		// TODO(gri) Should we pass x.typ instead of base (and indirect report if derefStructPtr indirected)?
   595  		check.recordSelection(selx, FieldVal, base, obj, index, false)
   596  
   597  		offs := check.conf.offsetof(base, index)
   598  		x.mode = constant_
   599  		x.val = constant.MakeInt64(offs)
   600  		x.typ = Typ[Uintptr]
   601  		// result is constant - no need to record signature
   602  
   603  	case _Sizeof:
   604  		// unsafe.Sizeof(x T) uintptr
   605  		check.assignment(x, nil, "argument to unsafe.Sizeof")
   606  		if x.mode == invalid {
   607  			return
   608  		}
   609  
   610  		x.mode = constant_
   611  		x.val = constant.MakeInt64(check.conf.sizeof(x.typ))
   612  		x.typ = Typ[Uintptr]
   613  		// result is constant - no need to record signature
   614  
   615  	case _Assert:
   616  		// assert(pred) causes a typechecker error if pred is false.
   617  		// The result of assert is the value of pred if there is no error.
   618  		// Note: assert is only available in self-test mode.
   619  		if x.mode != constant_ || !isBoolean(x.typ) {
   620  			check.invalidArg(x, _Test, "%s is not a boolean constant", x)
   621  			return
   622  		}
   623  		if x.val.Kind() != constant.Bool {
   624  			check.errorf(x, _Test, "internal error: value of %s should be a boolean constant", x)
   625  			return
   626  		}
   627  		if !constant.BoolVal(x.val) {
   628  			check.errorf(call, _Test, "%v failed", call)
   629  			// compile-time assertion failure - safe to continue
   630  		}
   631  		// result is constant - no need to record signature
   632  
   633  	case _Trace:
   634  		// trace(x, y, z, ...) dumps the positions, expressions, and
   635  		// values of its arguments. The result of trace is the value
   636  		// of the first argument.
   637  		// Note: trace is only available in self-test mode.
   638  		// (no argument evaluated yet)
   639  		if nargs == 0 {
   640  			check.dump("%v: trace() without arguments", call.Pos())
   641  			x.mode = novalue
   642  			break
   643  		}
   644  		var t operand
   645  		x1 := x
   646  		for _, arg := range call.Args {
   647  			check.rawExpr(x1, arg, nil) // permit trace for types, e.g.: new(trace(T))
   648  			check.dump("%v: %s", x1.Pos(), x1)
   649  			x1 = &t // use incoming x only for first argument
   650  		}
   651  		// trace is only available in test mode - no need to record signature
   652  
   653  	default:
   654  		unreachable()
   655  	}
   656  
   657  	return true
   658  }
   659  
   660  // makeSig makes a signature for the given argument and result types.
   661  // Default types are used for untyped arguments, and res may be nil.
   662  func makeSig(res Type, args ...Type) *Signature {
   663  	list := make([]*Var, len(args))
   664  	for i, param := range args {
   665  		list[i] = NewVar(token.NoPos, nil, "", Default(param))
   666  	}
   667  	params := NewTuple(list...)
   668  	var result *Tuple
   669  	if res != nil {
   670  		assert(!isUntyped(res))
   671  		result = NewTuple(NewVar(token.NoPos, nil, "", res))
   672  	}
   673  	return &Signature{params: params, results: result}
   674  }
   675  
   676  // implicitArrayDeref returns A if typ is of the form *A and A is an array;
   677  // otherwise it returns typ.
   678  //
   679  func implicitArrayDeref(typ Type) Type {
   680  	if p, ok := typ.(*Pointer); ok {
   681  		if a, ok := p.base.Underlying().(*Array); ok {
   682  			return a
   683  		}
   684  	}
   685  	return typ
   686  }
   687  
   688  // unparen returns e with any enclosing parentheses stripped.
   689  func unparen(e ast.Expr) ast.Expr {
   690  	for {
   691  		p, ok := e.(*ast.ParenExpr)
   692  		if !ok {
   693  			return e
   694  		}
   695  		e = p.X
   696  	}
   697  }
   698  

View as plain text