| // 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, inst *ast.IndexExpr) { |
| xlist := typeparams.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), len(sig.tparams) |
| if got > want { |
| check.errorf(xlist[got-1], _Todo, "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, sig.tparams, 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) |
| // TODO(rFindley) use a positioner here? instantiate would need to be |
| // updated accordingly. |
| poslist := make([]token.Pos, len(xlist)) |
| for i, x := range xlist { |
| poslist[i] = x.Pos() |
| } |
| |
| // instantiate function signature |
| res := check.instantiate(x.Pos(), sig, targs, poslist).(*Signature) |
| assert(res.tparams == nil) // 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 *ast.CallExpr) exprKind { |
| var inst *ast.IndexExpr |
| if iexpr, _ := call.Fun.(*ast.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.Args...) |
| x.expr = call |
| return statement |
| |
| case typexpr: |
| // 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 := asInterface(T); t != nil { |
| check.completeInterface(token.NoPos, t) |
| if t._IsConstraint() { |
| check.errorf(call, _Todo, "cannot use interface %s in conversion (contains type list 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: |
| 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.invalidOp(x, _InvalidCall, "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 := typeparams.UnpackExpr(inst.Index) |
| 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), len(sig.tparams) |
| if got > want { |
| check.errorf(xlist[want], _Todo, "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) |
| 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 && len(sig.tparams) > 0 && isParameterized(sig.tparams, 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) { |
| x.mode = value |
| 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 |
| } |
| |
| func (check *Checker) arguments(call *ast.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, 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 |
| switch { |
| case nargs < npars: |
| check.errorf(inNode(call, call.Rparen), _WrongArgCount, "not enough arguments in call to %s", call.Fun) |
| return |
| case nargs > npars: |
| check.errorf(args[npars], _WrongArgCount, "too many arguments in call to %s", call.Fun) // report at first extra argument |
| return |
| } |
| |
| // infer type arguments and instantiate signature if necessary |
| if len(sig.tparams) > 0 { |
| // TODO(gri) provide position information for targs so we can feed |
| // it to the instantiate call for better error reporting |
| targs := check.infer(call, sig.tparams, 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 == nil) // 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, targs)).(*Tuple) |
| } else { |
| sigParams = rsig.params |
| } |
| } |
| |
| // check arguments |
| for i, a := range args { |
| check.assignment(a, sigParams.vars[i].typ, check.sprintf("argument to %s", call.Fun)) |
| } |
| |
| 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) { |
| // 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) |
| if x.mode == invalid { |
| goto Error |
| } |
| |
| check.instantiatedOperand(x) |
| |
| obj, index, indirect = check.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, _AmbiguousSelector, "ambiguous selector %s.%s", x.expr, sel) |
| case indirect: |
| check.errorf(e.Sel, _InvalidMethodExpr, "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". |
| switch obj := tpar.Bound().obj.(type) { |
| case nil: |
| why = check.sprintf("type bound for %s has no method %s", x.typ, sel) |
| case *TypeName: |
| why = check.sprintf("interface %s has no method %s", obj.name, 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, _, _ = check.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 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 len(sig.rparams) > 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 |
| } |
| |
| // 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, sig.rparams, nil, NewTuple(sig.recv), []*operand{&arg}, false /* no error reporting */) |
| 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, targs)) |
| 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, _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) |
| |
| // the receiver type becomes the type of the first function |
| // argument of the method expression's function type |
| var params []*Var |
| sig := m.typ.(*Signature) |
| if sig.params != nil { |
| params = sig.params.vars |
| } |
| x.mode = value |
| x.typ = &Signature{ |
| tparams: sig.tparams, |
| params: NewTuple(append([]*Var{NewVar(token.NoPos, check.pkg, "_", x.typ)}, 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) |
| } |
| } |
| } |
| |
| // 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) |
| 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, _Todo, "cannot use generic type %s without instantiation", x.typ) |
| x.typ = Typ[Invalid] |
| } |
| } |