| // 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 types2 |
| |
| import ( |
| "cmd/compile/internal/syntax" |
| "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, inst *syntax.IndexExpr) { |
| if !check.allowVersion(check.pkg, 1, 18) { |
| check.softErrorf(inst.Pos(), "function instantiation requires go1.18 or later") |
| } |
| |
| xlist := unpackExpr(inst.Index) |
| targs := check.typeList(xlist) |
| if targs == nil { |
| x.mode = invalid |
| x.expr = inst |
| return |
| } |
| assert(len(targs) == len(xlist)) |
| |
| // check number of type arguments (got) vs number of type parameters (want) |
| sig := x.typ.(*Signature) |
| got, want := len(targs), sig.TParams().Len() |
| if !useConstraintTypeInference && got != want || got > want { |
| check.errorf(xlist[got-1], "got %d type arguments but want %d", got, want) |
| x.mode = invalid |
| x.expr = inst |
| return |
| } |
| |
| // if we don't have enough type arguments, try type inference |
| inferred := false |
| if got < want { |
| targs = check.infer(inst.Pos(), sig.TParams().list(), targs, nil, nil, true) |
| if targs == nil { |
| // error was already reported |
| x.mode = invalid |
| x.expr = inst |
| return |
| } |
| got = len(targs) |
| inferred = true |
| } |
| assert(got == want) |
| |
| // determine argument positions (for error reporting) |
| poslist := make([]syntax.Pos, len(xlist)) |
| for i, x := range xlist { |
| poslist[i] = syntax.StartPos(x) |
| } |
| |
| // instantiate function signature |
| res := check.instantiate(x.Pos(), sig, targs, poslist).(*Signature) |
| assert(res.TParams().Len() == 0) // signature is not generic anymore |
| if inferred { |
| check.recordInferred(inst, targs, res) |
| } |
| x.typ = res |
| x.mode = value |
| x.expr = inst |
| } |
| |
| func (check *Checker) callExpr(x *operand, call *syntax.CallExpr) exprKind { |
| var inst *syntax.IndexExpr // function instantiation, if any |
| if iexpr, _ := call.Fun.(*syntax.IndexExpr); iexpr != nil { |
| if check.indexExpr(x, iexpr) { |
| // Delay function instantiation to argument checking, |
| // where we combine type and value arguments for type |
| // inference. |
| assert(x.mode == value) |
| inst = iexpr |
| } |
| x.expr = iexpr |
| check.record(x) |
| } else { |
| check.exprOrType(x, call.Fun) |
| } |
| |
| switch x.mode { |
| case invalid: |
| check.use(call.ArgList...) |
| x.expr = call |
| return statement |
| |
| case typexpr: |
| // conversion |
| T := x.typ |
| x.mode = invalid |
| switch n := len(call.ArgList); n { |
| case 0: |
| check.errorf(call, "missing argument in conversion to %s", T) |
| case 1: |
| check.expr(x, call.ArgList[0]) |
| if x.mode != invalid { |
| if t := asInterface(T); t != nil { |
| if t.IsConstraint() { |
| check.errorf(call, "cannot use interface %s in conversion (contains type list or is comparable)", T) |
| break |
| } |
| } |
| if call.HasDots { |
| check.errorf(call.ArgList[0], "invalid use of ... in type conversion to %s", T) |
| break |
| } |
| check.conversion(x, T) |
| } |
| default: |
| check.use(call.ArgList...) |
| check.errorf(call.ArgList[n-1], "too many arguments in conversion to %s", T) |
| } |
| x.expr = call |
| return conversion |
| |
| case builtin: |
| 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 |
| cgocall := x.mode == cgofunc |
| |
| sig := asSignature(x.typ) |
| if sig == nil { |
| check.errorf(x, invalidOp+"cannot call non-function %s", x) |
| x.mode = invalid |
| x.expr = call |
| return statement |
| } |
| |
| // evaluate type arguments, if any |
| var targs []Type |
| if inst != nil { |
| xlist := unpackExpr(inst.Index) |
| targs = check.typeList(xlist) |
| if targs == nil { |
| check.use(call.ArgList...) |
| 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.TParams().Len() |
| if got > want { |
| check.errorf(xlist[want], "got %d type arguments but want %d", got, want) |
| check.use(call.ArgList...) |
| x.mode = invalid |
| x.expr = call |
| return statement |
| } |
| } |
| |
| // evaluate arguments |
| args, _ := check.exprList(call.ArgList, false) |
| sig = check.arguments(call, sig, targs, args) |
| |
| // 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.TParams().Len() > 0 && isParameterized(sig.TParams().list(), x.typ) { |
| x.mode = invalid |
| } |
| |
| return statement |
| } |
| |
| func (check *Checker) exprList(elist []syntax.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) { |
| x.mode = value |
| xlist = append(xlist, &operand{mode: value, expr: e, typ: Typ[UntypedBool]}) |
| 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 |
| } |
| |
| func (check *Checker) arguments(call *syntax.CallExpr, sig *Signature, targs []Type, args []*operand) (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, "%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.HasDots |
| |
| // 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.ArgList) == 1 && nargs > 1 { |
| // f()... is not permitted if f() is multi-valued |
| //check.errorf(call.Ellipsis, "cannot use ... with %d-valued %s", nargs, call.ArgList[0]) |
| check.errorf(call, "cannot use ... with %d-valued %s", nargs, call.ArgList[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(call.Ellipsis, "cannot use ... in call to non-variadic %s", call.Fun) |
| check.errorf(call, "cannot use ... in call to non-variadic %s", call.Fun) |
| return |
| } |
| // standard_func(a, b, c) |
| } |
| |
| // check argument count |
| switch { |
| case nargs < npars: |
| check.errorf(call, "not enough arguments in call to %s", call.Fun) |
| return |
| case nargs > npars: |
| check.errorf(args[npars], "too many arguments in call to %s", call.Fun) // report at first extra argument |
| return |
| } |
| |
| // infer type arguments and instantiate signature if necessary |
| if sig.TParams().Len() > 0 { |
| if !check.allowVersion(check.pkg, 1, 18) { |
| if iexpr, _ := call.Fun.(*syntax.IndexExpr); iexpr != nil { |
| check.softErrorf(iexpr.Pos(), "function instantiation requires go1.18 or later") |
| } else { |
| check.softErrorf(call.Pos(), "implicit function instantiation requires go1.18 or later") |
| } |
| } |
| // TODO(gri) provide position information for targs so we can feed |
| // it to the instantiate call for better error reporting |
| targs := check.infer(call.Pos(), sig.TParams().list(), targs, sigParams, args, true) |
| if targs == nil { |
| return // error already reported |
| } |
| |
| // compute result signature |
| rsig = check.instantiate(call.Pos(), sig, targs, nil).(*Signature) |
| assert(rsig.TParams().Len() == 0) // signature is not generic anymore |
| check.recordInferred(call, 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.TParams().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 *syntax.SelectorExpr) { |
| // these must be declared before the "goto Error" statements |
| var ( |
| obj Object |
| index []int |
| indirect bool |
| ) |
| |
| sel := e.Sel.Value |
| // 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.(*syntax.Name); ok { |
| obj := check.lookup(ident.Value) |
| 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, "%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 { |
| if check.conf.CompilerErrorMessages { |
| check.errorf(e.Sel, "undefined: %s.%s", pkg.name, sel) |
| } else { |
| check.errorf(e.Sel, "%s not declared by package %s", sel, pkg.name) |
| } |
| } |
| goto Error |
| } |
| if !exp.Exported() { |
| check.errorf(e.Sel, "%s not exported by package %s", sel, pkg.name) |
| // ok to continue |
| } |
| } |
| check.recordUse(e.Sel, exp) |
| |
| // Simplified version of the code for *syntax.Names: |
| // - 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", posFor(e.Sel), exp) |
| unreachable() |
| } |
| x.expr = e |
| return |
| } |
| } |
| |
| check.exprOrType(x, e.X) |
| if x.mode == invalid { |
| goto Error |
| } |
| |
| check.instantiatedOperand(x) |
| |
| obj, index, indirect = LookupFieldOrMethod(x.typ, x.mode == variable, check.pkg, sel) |
| if obj == nil { |
| switch { |
| case index != nil: |
| // TODO(gri) should provide actual type where the conflict happens |
| check.errorf(e.Sel, "ambiguous selector %s.%s", x.expr, sel) |
| case indirect: |
| check.errorf(e.Sel, "cannot call pointer method %s on %s", sel, x.typ) |
| default: |
| var why string |
| if tpar := asTypeParam(x.typ); tpar != nil { |
| // Type parameter bounds don't specify fields, so don't mention "field". |
| if tname := tpar.iface().obj; tname != nil { |
| why = check.sprintf("interface %s has no method %s", tname.name, sel) |
| } else { |
| why = check.sprintf("type bound for %s has no method %s", x.typ, sel) |
| } |
| } 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. |
| 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, "%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.dump("### found method %s", m) |
| check.objDecl(m, nil) |
| // If m has a parameterized receiver type, infer the type arguments from |
| // the actual receiver provided and then substitute the type parameters in |
| // the signature accordingly. |
| // TODO(gri) factor this code out |
| sig := m.typ.(*Signature) |
| if sig.RParams().Len() > 0 { |
| // For inference to work, we must use the receiver type |
| // matching the receiver in the actual method declaration. |
| // If the method is embedded, the matching receiver is the |
| // embedded struct or interface that declared the method. |
| // Traverse the embedding to find that type (issue #44688). |
| recv := x.typ |
| for i := 0; i < len(index)-1; i++ { |
| // The embedded type is either a struct or a pointer to |
| // a struct except for the last one (which we don't need). |
| recv = asStruct(derefStructPtr(recv)).Field(index[i]).typ |
| } |
| //check.dump("### recv = %s", recv) |
| //check.dump("### method = %s rparams = %s tparams = %s", m, sig.rparams, sig.tparams) |
| // The method may have a pointer receiver, but the actually provided receiver |
| // may be a (hopefully addressable) non-pointer value, or vice versa. Here we |
| // only care about inferring receiver type parameters; to make the inference |
| // work, match up pointer-ness of receiver and argument. |
| if ptrRecv := isPointer(sig.recv.typ); ptrRecv != isPointer(recv) { |
| if ptrRecv { |
| recv = NewPointer(recv) |
| } else { |
| recv = recv.(*Pointer).base |
| } |
| } |
| // Disable reporting of errors during inference below. If we're unable to infer |
| // the receiver type arguments here, the receiver must be be otherwise invalid |
| // and an error has been reported elsewhere. |
| arg := operand{mode: variable, expr: x.expr, typ: recv} |
| targs := check.infer(m.pos, sig.RParams().list(), nil, NewTuple(sig.recv), []*operand{&arg}, false /* no error reporting */) |
| //check.dump("### inferred targs = %s", targs) |
| if targs == nil { |
| // We may reach here if there were other errors (see issue #40056). |
| goto Error |
| } |
| // Don't modify m. Instead - for now - make a copy of m and use that instead. |
| // (If we modify m, some tests will fail; possibly because the m is in use.) |
| // TODO(gri) investigate and provide a correct explanation here |
| copy := *m |
| copy.typ = check.subst(e.Pos(), m.typ, makeSubstMap(sig.RParams().list(), targs), nil) |
| obj = © |
| } |
| // TODO(gri) we also need to do substitution for parameterized interface methods |
| // (this breaks code in testdata/linalg.go2 at the moment) |
| // 12/20/2019: Is this TODO still correct? |
| } |
| |
| 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, "%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, "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) |
| |
| 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. |
| // TODO(gri) make this accept a []syntax.Expr and use an unpack function when we have a ListExpr? |
| func (check *Checker) use(arg ...syntax.Expr) { |
| var x operand |
| for _, e := range arg { |
| // Certain AST fields may legally be nil (e.g., the ast.SliceExpr.High field). |
| if e == nil { |
| continue |
| } |
| if l, _ := e.(*syntax.ListExpr); l != nil { |
| check.use(l.ElemList...) |
| continue |
| } |
| check.rawExpr(&x, e, nil) |
| } |
| } |
| |
| // 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 ...syntax.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).(*syntax.Name); ident != nil { |
| // never type-check the blank name on the lhs |
| if ident.Value == "_" { |
| continue |
| } |
| if _, obj := check.scope.LookupParent(ident.Value, 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) |
| if v != nil { |
| v.used = v_used // restore v.used |
| } |
| } |
| } |
| |
| // instantiatedOperand reports an error of x is an uninstantiated (generic) type and sets x.typ to Typ[Invalid]. |
| func (check *Checker) instantiatedOperand(x *operand) { |
| if x.mode == typexpr && isGeneric(x.typ) { |
| check.errorf(x, "cannot use generic type %s without instantiation", x.typ) |
| x.typ = Typ[Invalid] |
| } |
| } |