| // Copyright 2013 The Go Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style |
| // license that can be found in the LICENSE file. |
| |
| // This file implements typechecking of call and selector expressions. |
| |
| package types |
| |
| import ( |
| "go/ast" |
| "go/internal/typeparams" |
| "go/token" |
| "strings" |
| "unicode" |
| ) |
| |
| // funcInst type-checks a function instantiation inst and returns the result in x. |
| // The operand x must be the evaluation of inst.X and its type must be a signature. |
| func (check *Checker) funcInst(x *operand, ix *typeparams.IndexExpr) { |
| if !check.allowVersion(check.pkg, 1, 18) { |
| check.softErrorf(inNode(ix.Orig, ix.Lbrack), _UnsupportedFeature, "function instantiation requires go1.18 or later") |
| } |
| |
| targs := check.typeList(ix.Indices) |
| if targs == nil { |
| x.mode = invalid |
| x.expr = ix.Orig |
| return |
| } |
| assert(len(targs) == len(ix.Indices)) |
| |
| // check number of type arguments (got) vs number of type parameters (want) |
| sig := x.typ.(*Signature) |
| got, want := len(targs), sig.TypeParams().Len() |
| if got > want { |
| check.errorf(ix.Indices[got-1], _WrongTypeArgCount, "got %d type arguments but want %d", got, want) |
| x.mode = invalid |
| x.expr = ix.Orig |
| return |
| } |
| |
| if got < want { |
| targs = check.infer(ix.Orig, sig.TypeParams().list(), targs, nil, nil) |
| if targs == nil { |
| // error was already reported |
| x.mode = invalid |
| x.expr = ix.Orig |
| return |
| } |
| got = len(targs) |
| } |
| assert(got == want) |
| |
| // instantiate function signature |
| res := check.instantiateSignature(x.Pos(), sig, targs, ix.Indices) |
| assert(res.TypeParams().Len() == 0) // signature is not generic anymore |
| check.recordInstance(ix.Orig, targs, res) |
| x.typ = res |
| x.mode = value |
| x.expr = ix.Orig |
| } |
| |
| func (check *Checker) instantiateSignature(pos token.Pos, typ *Signature, targs []Type, xlist []ast.Expr) (res *Signature) { |
| assert(check != nil) |
| assert(len(targs) == typ.TypeParams().Len()) |
| |
| if trace { |
| check.trace(pos, "-- instantiating %s with %s", typ, targs) |
| check.indent++ |
| defer func() { |
| check.indent-- |
| check.trace(pos, "=> %s (under = %s)", res, res.Underlying()) |
| }() |
| } |
| |
| inst := check.instance(pos, typ, targs, check.bestContext(nil)).(*Signature) |
| assert(len(xlist) <= len(targs)) |
| |
| // verify instantiation lazily (was issue #50450) |
| check.later(func() { |
| tparams := typ.TypeParams().list() |
| if i, err := check.verify(pos, tparams, targs); err != nil { |
| // best position for error reporting |
| pos := pos |
| if i < len(xlist) { |
| pos = xlist[i].Pos() |
| } |
| check.softErrorf(atPos(pos), _InvalidTypeArg, "%s", err) |
| } else { |
| check.mono.recordInstance(check.pkg, pos, tparams, targs, xlist) |
| } |
| }) |
| |
| return inst |
| } |
| |
| func (check *Checker) callExpr(x *operand, call *ast.CallExpr) exprKind { |
| ix := typeparams.UnpackIndexExpr(call.Fun) |
| if ix != nil { |
| if check.indexExpr(x, ix) { |
| // Delay function instantiation to argument checking, |
| // where we combine type and value arguments for type |
| // inference. |
| assert(x.mode == value) |
| } else { |
| ix = nil |
| } |
| x.expr = call.Fun |
| check.record(x) |
| |
| } else { |
| check.exprOrType(x, call.Fun, true) |
| } |
| // x.typ may be generic |
| |
| switch x.mode { |
| case invalid: |
| check.use(call.Args...) |
| x.expr = call |
| return statement |
| |
| case typexpr: |
| // conversion |
| check.nonGeneric(x) |
| if x.mode == invalid { |
| return conversion |
| } |
| T := x.typ |
| x.mode = invalid |
| switch n := len(call.Args); n { |
| case 0: |
| check.errorf(inNode(call, call.Rparen), _WrongArgCount, "missing argument in conversion to %s", T) |
| case 1: |
| check.expr(x, call.Args[0]) |
| if x.mode != invalid { |
| if call.Ellipsis.IsValid() { |
| check.errorf(call.Args[0], _BadDotDotDotSyntax, "invalid use of ... in conversion to %s", T) |
| break |
| } |
| if t, _ := under(T).(*Interface); t != nil && !isTypeParam(T) { |
| if !t.IsMethodSet() { |
| check.errorf(call, _MisplacedConstraintIface, "cannot use interface %s in conversion (contains specific type constraints or is comparable)", T) |
| break |
| } |
| } |
| check.conversion(x, T) |
| } |
| default: |
| check.use(call.Args...) |
| check.errorf(call.Args[n-1], _WrongArgCount, "too many arguments in conversion to %s", T) |
| } |
| x.expr = call |
| return conversion |
| |
| case builtin: |
| // no need to check for non-genericity here |
| id := x.id |
| if !check.builtin(x, call, id) { |
| x.mode = invalid |
| } |
| x.expr = call |
| // a non-constant result implies a function call |
| if x.mode != invalid && x.mode != constant_ { |
| check.hasCallOrRecv = true |
| } |
| return predeclaredFuncs[id].kind |
| } |
| |
| // ordinary function/method call |
| // signature may be generic |
| cgocall := x.mode == cgofunc |
| |
| // a type parameter may be "called" if all types have the same signature |
| sig, _ := coreType(x.typ).(*Signature) |
| if sig == nil { |
| check.invalidOp(x, _InvalidCall, "cannot call non-function %s", x) |
| x.mode = invalid |
| x.expr = call |
| return statement |
| } |
| |
| // evaluate type arguments, if any |
| var xlist []ast.Expr |
| var targs []Type |
| if ix != nil { |
| xlist = ix.Indices |
| targs = check.typeList(xlist) |
| if targs == nil { |
| check.use(call.Args...) |
| x.mode = invalid |
| x.expr = call |
| return statement |
| } |
| assert(len(targs) == len(xlist)) |
| |
| // check number of type arguments (got) vs number of type parameters (want) |
| got, want := len(targs), sig.TypeParams().Len() |
| if got > want { |
| check.errorf(xlist[want], _WrongTypeArgCount, "got %d type arguments but want %d", got, want) |
| check.use(call.Args...) |
| x.mode = invalid |
| x.expr = call |
| return statement |
| } |
| } |
| |
| // evaluate arguments |
| args, _ := check.exprList(call.Args, false) |
| isGeneric := sig.TypeParams().Len() > 0 |
| sig = check.arguments(call, sig, targs, args, xlist) |
| |
| if isGeneric && sig.TypeParams().Len() == 0 { |
| // Update the recorded type of call.Fun to its instantiated type. |
| check.recordTypeAndValue(call.Fun, value, sig, nil) |
| } |
| |
| // determine result |
| switch sig.results.Len() { |
| case 0: |
| x.mode = novalue |
| case 1: |
| if cgocall { |
| x.mode = commaerr |
| } else { |
| x.mode = value |
| } |
| x.typ = sig.results.vars[0].typ // unpack tuple |
| default: |
| x.mode = value |
| x.typ = sig.results |
| } |
| x.expr = call |
| check.hasCallOrRecv = true |
| |
| // if type inference failed, a parametrized result must be invalidated |
| // (operands cannot have a parametrized type) |
| if x.mode == value && sig.TypeParams().Len() > 0 && isParameterized(sig.TypeParams().list(), x.typ) { |
| x.mode = invalid |
| } |
| |
| return statement |
| } |
| |
| func (check *Checker) exprList(elist []ast.Expr, allowCommaOk bool) (xlist []*operand, commaOk bool) { |
| switch len(elist) { |
| case 0: |
| // nothing to do |
| |
| case 1: |
| // single (possibly comma-ok) value, or function returning multiple values |
| e := elist[0] |
| var x operand |
| check.multiExpr(&x, e) |
| if t, ok := x.typ.(*Tuple); ok && x.mode != invalid { |
| // multiple values |
| xlist = make([]*operand, t.Len()) |
| for i, v := range t.vars { |
| xlist[i] = &operand{mode: value, expr: e, typ: v.typ} |
| } |
| break |
| } |
| |
| // exactly one (possibly invalid or comma-ok) value |
| xlist = []*operand{&x} |
| if allowCommaOk && (x.mode == mapindex || x.mode == commaok || x.mode == commaerr) { |
| x2 := &operand{mode: value, expr: e, typ: Typ[UntypedBool]} |
| if x.mode == commaerr { |
| x2.typ = universeError |
| } |
| xlist = append(xlist, x2) |
| commaOk = true |
| } |
| |
| default: |
| // multiple (possibly invalid) values |
| xlist = make([]*operand, len(elist)) |
| for i, e := range elist { |
| var x operand |
| check.expr(&x, e) |
| xlist[i] = &x |
| } |
| } |
| |
| return |
| } |
| |
| // xlist is the list of type argument expressions supplied in the source code. |
| func (check *Checker) arguments(call *ast.CallExpr, sig *Signature, targs []Type, args []*operand, xlist []ast.Expr) (rsig *Signature) { |
| rsig = sig |
| |
| // TODO(gri) try to eliminate this extra verification loop |
| for _, a := range args { |
| switch a.mode { |
| case typexpr: |
| check.errorf(a, 0, "%s used as value", a) |
| return |
| case invalid: |
| return |
| } |
| } |
| |
| // Function call argument/parameter count requirements |
| // |
| // | standard call | dotdotdot call | |
| // --------------+------------------+----------------+ |
| // standard func | nargs == npars | invalid | |
| // --------------+------------------+----------------+ |
| // variadic func | nargs >= npars-1 | nargs == npars | |
| // --------------+------------------+----------------+ |
| |
| nargs := len(args) |
| npars := sig.params.Len() |
| ddd := call.Ellipsis.IsValid() |
| |
| // set up parameters |
| sigParams := sig.params // adjusted for variadic functions (may be nil for empty parameter lists!) |
| adjusted := false // indicates if sigParams is different from t.params |
| if sig.variadic { |
| if ddd { |
| // variadic_func(a, b, c...) |
| if len(call.Args) == 1 && nargs > 1 { |
| // f()... is not permitted if f() is multi-valued |
| check.errorf(inNode(call, call.Ellipsis), _InvalidDotDotDot, "cannot use ... with %d-valued %s", nargs, call.Args[0]) |
| return |
| } |
| } else { |
| // variadic_func(a, b, c) |
| if nargs >= npars-1 { |
| // Create custom parameters for arguments: keep |
| // the first npars-1 parameters and add one for |
| // each argument mapping to the ... parameter. |
| vars := make([]*Var, npars-1) // npars > 0 for variadic functions |
| copy(vars, sig.params.vars) |
| last := sig.params.vars[npars-1] |
| typ := last.typ.(*Slice).elem |
| for len(vars) < nargs { |
| vars = append(vars, NewParam(last.pos, last.pkg, last.name, typ)) |
| } |
| sigParams = NewTuple(vars...) // possibly nil! |
| adjusted = true |
| npars = nargs |
| } else { |
| // nargs < npars-1 |
| npars-- // for correct error message below |
| } |
| } |
| } else { |
| if ddd { |
| // standard_func(a, b, c...) |
| check.errorf(inNode(call, call.Ellipsis), _NonVariadicDotDotDot, "cannot use ... in call to non-variadic %s", call.Fun) |
| return |
| } |
| // standard_func(a, b, c) |
| } |
| |
| // check argument count |
| if nargs != npars { |
| var at positioner = call |
| qualifier := "not enough" |
| if nargs > npars { |
| at = args[npars].expr // report at first extra argument |
| qualifier = "too many" |
| } else { |
| at = atPos(call.Rparen) // report at closing ) |
| } |
| // take care of empty parameter lists represented by nil tuples |
| var params []*Var |
| if sig.params != nil { |
| params = sig.params.vars |
| } |
| check.errorf(at, _WrongArgCount, "%s arguments in call to %s\n\thave %s\n\twant %s", |
| qualifier, call.Fun, |
| check.typesSummary(operandTypes(args), false), |
| check.typesSummary(varTypes(params), sig.variadic), |
| ) |
| return |
| } |
| |
| // infer type arguments and instantiate signature if necessary |
| if sig.TypeParams().Len() > 0 { |
| if !check.allowVersion(check.pkg, 1, 18) { |
| switch call.Fun.(type) { |
| case *ast.IndexExpr, *ast.IndexListExpr: |
| ix := typeparams.UnpackIndexExpr(call.Fun) |
| check.softErrorf(inNode(call.Fun, ix.Lbrack), _UnsupportedFeature, "function instantiation requires go1.18 or later") |
| default: |
| check.softErrorf(inNode(call, call.Lparen), _UnsupportedFeature, "implicit function instantiation requires go1.18 or later") |
| } |
| } |
| targs := check.infer(call, sig.TypeParams().list(), targs, sigParams, args) |
| if targs == nil { |
| return // error already reported |
| } |
| |
| // compute result signature |
| rsig = check.instantiateSignature(call.Pos(), sig, targs, xlist) |
| assert(rsig.TypeParams().Len() == 0) // signature is not generic anymore |
| check.recordInstance(call.Fun, targs, rsig) |
| |
| // Optimization: Only if the parameter list was adjusted do we |
| // need to compute it from the adjusted list; otherwise we can |
| // simply use the result signature's parameter list. |
| if adjusted { |
| sigParams = check.subst(call.Pos(), sigParams, makeSubstMap(sig.TypeParams().list(), targs), nil).(*Tuple) |
| } else { |
| sigParams = rsig.params |
| } |
| } |
| |
| // check arguments |
| if len(args) > 0 { |
| context := check.sprintf("argument to %s", call.Fun) |
| for i, a := range args { |
| check.assignment(a, sigParams.vars[i].typ, context) |
| } |
| } |
| |
| return |
| } |
| |
| var cgoPrefixes = [...]string{ |
| "_Ciconst_", |
| "_Cfconst_", |
| "_Csconst_", |
| "_Ctype_", |
| "_Cvar_", // actually a pointer to the var |
| "_Cfpvar_fp_", |
| "_Cfunc_", |
| "_Cmacro_", // function to evaluate the expanded expression |
| } |
| |
| func (check *Checker) selector(x *operand, e *ast.SelectorExpr, def *Named) { |
| // these must be declared before the "goto Error" statements |
| var ( |
| obj Object |
| index []int |
| indirect bool |
| ) |
| |
| sel := e.Sel.Name |
| // If the identifier refers to a package, handle everything here |
| // so we don't need a "package" mode for operands: package names |
| // can only appear in qualified identifiers which are mapped to |
| // selector expressions. |
| if ident, ok := e.X.(*ast.Ident); ok { |
| obj := check.lookup(ident.Name) |
| if pname, _ := obj.(*PkgName); pname != nil { |
| assert(pname.pkg == check.pkg) |
| check.recordUse(ident, pname) |
| pname.used = true |
| pkg := pname.imported |
| |
| var exp Object |
| funcMode := value |
| if pkg.cgo { |
| // cgo special cases C.malloc: it's |
| // rewritten to _CMalloc and does not |
| // support two-result calls. |
| if sel == "malloc" { |
| sel = "_CMalloc" |
| } else { |
| funcMode = cgofunc |
| } |
| for _, prefix := range cgoPrefixes { |
| // cgo objects are part of the current package (in file |
| // _cgo_gotypes.go). Use regular lookup. |
| _, exp = check.scope.LookupParent(prefix+sel, check.pos) |
| if exp != nil { |
| break |
| } |
| } |
| if exp == nil { |
| check.errorf(e.Sel, _UndeclaredImportedName, "%s not declared by package C", sel) |
| goto Error |
| } |
| check.objDecl(exp, nil) |
| } else { |
| exp = pkg.scope.Lookup(sel) |
| if exp == nil { |
| if !pkg.fake { |
| check.errorf(e.Sel, _UndeclaredImportedName, "%s not declared by package %s", sel, pkg.name) |
| } |
| goto Error |
| } |
| if !exp.Exported() { |
| check.errorf(e.Sel, _UnexportedName, "%s not exported by package %s", sel, pkg.name) |
| // ok to continue |
| } |
| } |
| check.recordUse(e.Sel, exp) |
| |
| // Simplified version of the code for *ast.Idents: |
| // - imported objects are always fully initialized |
| switch exp := exp.(type) { |
| case *Const: |
| assert(exp.Val() != nil) |
| x.mode = constant_ |
| x.typ = exp.typ |
| x.val = exp.val |
| case *TypeName: |
| x.mode = typexpr |
| x.typ = exp.typ |
| case *Var: |
| x.mode = variable |
| x.typ = exp.typ |
| if pkg.cgo && strings.HasPrefix(exp.name, "_Cvar_") { |
| x.typ = x.typ.(*Pointer).base |
| } |
| case *Func: |
| x.mode = funcMode |
| x.typ = exp.typ |
| if pkg.cgo && strings.HasPrefix(exp.name, "_Cmacro_") { |
| x.mode = value |
| x.typ = x.typ.(*Signature).results.vars[0].typ |
| } |
| case *Builtin: |
| x.mode = builtin |
| x.typ = exp.typ |
| x.id = exp.id |
| default: |
| check.dump("%v: unexpected object %v", e.Sel.Pos(), exp) |
| unreachable() |
| } |
| x.expr = e |
| return |
| } |
| } |
| |
| check.exprOrType(x, e.X, false) |
| switch x.mode { |
| case typexpr: |
| // don't crash for "type T T.x" (was issue #51509) |
| if def != nil && x.typ == def { |
| check.cycleError([]Object{def.obj}) |
| goto Error |
| } |
| case builtin: |
| // types2 uses the position of '.' for the error |
| check.errorf(e.Sel, _UncalledBuiltin, "cannot select on %s", x) |
| goto Error |
| case invalid: |
| goto Error |
| } |
| |
| obj, index, indirect = LookupFieldOrMethod(x.typ, x.mode == variable, check.pkg, sel) |
| if obj == nil { |
| // Don't report another error if the underlying type was invalid (issue #49541). |
| if under(x.typ) == Typ[Invalid] { |
| goto Error |
| } |
| |
| if index != nil { |
| // TODO(gri) should provide actual type where the conflict happens |
| check.errorf(e.Sel, _AmbiguousSelector, "ambiguous selector %s.%s", x.expr, sel) |
| goto Error |
| } |
| |
| if indirect { |
| check.errorf(e.Sel, _InvalidMethodExpr, "cannot call pointer method %s on %s", sel, x.typ) |
| goto Error |
| } |
| |
| var why string |
| if isInterfacePtr(x.typ) { |
| why = check.interfacePtrError(x.typ) |
| } else { |
| why = check.sprintf("type %s has no field or method %s", x.typ, sel) |
| // Check if capitalization of sel matters and provide better error message in that case. |
| // TODO(gri) This code only looks at the first character but LookupFieldOrMethod should |
| // have an (internal) mechanism for case-insensitive lookup that we should use |
| // instead (see types2). |
| if len(sel) > 0 { |
| var changeCase string |
| if r := rune(sel[0]); unicode.IsUpper(r) { |
| changeCase = string(unicode.ToLower(r)) + sel[1:] |
| } else { |
| changeCase = string(unicode.ToUpper(r)) + sel[1:] |
| } |
| if obj, _, _ = LookupFieldOrMethod(x.typ, x.mode == variable, check.pkg, changeCase); obj != nil { |
| why += ", but does have " + changeCase |
| } |
| } |
| } |
| check.errorf(e.Sel, _MissingFieldOrMethod, "%s.%s undefined (%s)", x.expr, sel, why) |
| goto Error |
| } |
| |
| // methods may not have a fully set up signature yet |
| if m, _ := obj.(*Func); m != nil { |
| check.objDecl(m, nil) |
| } |
| |
| if x.mode == typexpr { |
| // method expression |
| m, _ := obj.(*Func) |
| if m == nil { |
| // TODO(gri) should check if capitalization of sel matters and provide better error message in that case |
| check.errorf(e.Sel, _MissingFieldOrMethod, "%s.%s undefined (type %s has no method %s)", x.expr, sel, x.typ, sel) |
| goto Error |
| } |
| |
| check.recordSelection(e, MethodExpr, x.typ, m, index, indirect) |
| |
| sig := m.typ.(*Signature) |
| if sig.recv == nil { |
| check.error(e, _InvalidDeclCycle, "illegal cycle in method declaration") |
| goto Error |
| } |
| |
| // the receiver type becomes the type of the first function |
| // argument of the method expression's function type |
| var params []*Var |
| if sig.params != nil { |
| params = sig.params.vars |
| } |
| // Be consistent about named/unnamed parameters. This is not needed |
| // for type-checking, but the newly constructed signature may appear |
| // in an error message and then have mixed named/unnamed parameters. |
| // (An alternative would be to not print parameter names in errors, |
| // but it's useful to see them; this is cheap and method expressions |
| // are rare.) |
| name := "" |
| if len(params) > 0 && params[0].name != "" { |
| // name needed |
| name = sig.recv.name |
| if name == "" { |
| name = "_" |
| } |
| } |
| params = append([]*Var{NewVar(sig.recv.pos, sig.recv.pkg, name, x.typ)}, params...) |
| x.mode = value |
| x.typ = &Signature{ |
| tparams: sig.tparams, |
| params: NewTuple(params...), |
| results: sig.results, |
| variadic: sig.variadic, |
| } |
| |
| check.addDeclDep(m) |
| |
| } else { |
| // regular selector |
| switch obj := obj.(type) { |
| case *Var: |
| check.recordSelection(e, FieldVal, x.typ, obj, index, indirect) |
| if x.mode == variable || indirect { |
| x.mode = variable |
| } else { |
| x.mode = value |
| } |
| x.typ = obj.typ |
| |
| case *Func: |
| // TODO(gri) If we needed to take into account the receiver's |
| // addressability, should we report the type &(x.typ) instead? |
| check.recordSelection(e, MethodVal, x.typ, obj, index, indirect) |
| |
| // TODO(gri) The verification pass below is disabled for now because |
| // method sets don't match method lookup in some cases. |
| // For instance, if we made a copy above when creating a |
| // custom method for a parameterized received type, the |
| // method set method doesn't match (no copy there). There |
| /// may be other situations. |
| disabled := true |
| if !disabled && debug { |
| // Verify that LookupFieldOrMethod and MethodSet.Lookup agree. |
| // TODO(gri) This only works because we call LookupFieldOrMethod |
| // _before_ calling NewMethodSet: LookupFieldOrMethod completes |
| // any incomplete interfaces so they are available to NewMethodSet |
| // (which assumes that interfaces have been completed already). |
| typ := x.typ |
| if x.mode == variable { |
| // If typ is not an (unnamed) pointer or an interface, |
| // use *typ instead, because the method set of *typ |
| // includes the methods of typ. |
| // Variables are addressable, so we can always take their |
| // address. |
| if _, ok := typ.(*Pointer); !ok && !IsInterface(typ) { |
| typ = &Pointer{base: typ} |
| } |
| } |
| // If we created a synthetic pointer type above, we will throw |
| // away the method set computed here after use. |
| // TODO(gri) Method set computation should probably always compute |
| // both, the value and the pointer receiver method set and represent |
| // them in a single structure. |
| // TODO(gri) Consider also using a method set cache for the lifetime |
| // of checker once we rely on MethodSet lookup instead of individual |
| // lookup. |
| mset := NewMethodSet(typ) |
| if m := mset.Lookup(check.pkg, sel); m == nil || m.obj != obj { |
| check.dump("%v: (%s).%v -> %s", e.Pos(), typ, obj.name, m) |
| check.dump("%s\n", mset) |
| // Caution: MethodSets are supposed to be used externally |
| // only (after all interface types were completed). It's |
| // now possible that we get here incorrectly. Not urgent |
| // to fix since we only run this code in debug mode. |
| // TODO(gri) fix this eventually. |
| panic("method sets and lookup don't agree") |
| } |
| } |
| |
| x.mode = value |
| |
| // remove receiver |
| sig := *obj.typ.(*Signature) |
| sig.recv = nil |
| x.typ = &sig |
| |
| check.addDeclDep(obj) |
| |
| default: |
| unreachable() |
| } |
| } |
| |
| // everything went well |
| x.expr = e |
| return |
| |
| Error: |
| x.mode = invalid |
| x.expr = e |
| } |
| |
| // use type-checks each argument. |
| // Useful to make sure expressions are evaluated |
| // (and variables are "used") in the presence of other errors. |
| // The arguments may be nil. |
| func (check *Checker) use(arg ...ast.Expr) { |
| var x operand |
| for _, e := range arg { |
| // The nil check below is necessary since certain AST fields |
| // may legally be nil (e.g., the ast.SliceExpr.High field). |
| if e != nil { |
| check.rawExpr(&x, e, nil, false) |
| } |
| } |
| } |
| |
| // useLHS is like use, but doesn't "use" top-level identifiers. |
| // It should be called instead of use if the arguments are |
| // expressions on the lhs of an assignment. |
| // The arguments must not be nil. |
| func (check *Checker) useLHS(arg ...ast.Expr) { |
| var x operand |
| for _, e := range arg { |
| // If the lhs is an identifier denoting a variable v, this assignment |
| // is not a 'use' of v. Remember current value of v.used and restore |
| // after evaluating the lhs via check.rawExpr. |
| var v *Var |
| var v_used bool |
| if ident, _ := unparen(e).(*ast.Ident); ident != nil { |
| // never type-check the blank name on the lhs |
| if ident.Name == "_" { |
| continue |
| } |
| if _, obj := check.scope.LookupParent(ident.Name, token.NoPos); obj != nil { |
| // It's ok to mark non-local variables, but ignore variables |
| // from other packages to avoid potential race conditions with |
| // dot-imported variables. |
| if w, _ := obj.(*Var); w != nil && w.pkg == check.pkg { |
| v = w |
| v_used = v.used |
| } |
| } |
| } |
| check.rawExpr(&x, e, nil, false) |
| if v != nil { |
| v.used = v_used // restore v.used |
| } |
| } |
| } |