Help us shape the future of Go! Take the 2026 Go Developer Survey

Source file src/cmd/compile/internal/types2/range.go

     1  // Copyright 2025 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 range statements.
     6  
     7  package types2
     8  
     9  import (
    10  	"cmd/compile/internal/syntax"
    11  	"go/constant"
    12  	. "internal/types/errors"
    13  )
    14  
    15  // rangeStmt type-checks a range statement of form
    16  //
    17  //	for sKey, sValue = range rangeVar { ... }
    18  //
    19  // where sKey, sValue, sExtra may be nil. isDef indicates whether these
    20  // variables are assigned to only (=) or whether there is a short variable
    21  // declaration (:=). If the latter and there are no variables, an error is
    22  // reported at noNewVarPos.
    23  func (check *Checker) rangeStmt(inner stmtContext, rangeStmt *syntax.ForStmt, noNewVarPos poser, sKey, sValue, sExtra, rangeVar syntax.Expr, isDef bool) {
    24  	// check expression to iterate over
    25  	var x operand
    26  
    27  	// From the spec:
    28  	//   If the last iteration variable is the blank identifier, the range
    29  	//   clause is equivalent to the same clause without that identifier.
    30  	//
    31  	//   The range expression x is evaluated before beginning the loop,
    32  	//   with one exception: if at most one iteration variable is present
    33  	//   and x or len(x) is constant, the range expression is not evaluated.
    34  	// So we have to be careful not to evaluate the arg in the
    35  	// described situation, and a blank sValue must not count as present.
    36  
    37  	check.hasCallOrRecv = false
    38  	check.expr(nil, &x, rangeVar)
    39  
    40  	ident, _ := syntax.Unparen(sValue).(*syntax.Name)
    41  	noValue := sValue == nil || ident != nil && ident.Value == "_"
    42  
    43  	if isTypes2 && x.isValid() && noValue && !check.hasCallOrRecv {
    44  		if t, ok := arrayPtrDeref(x.typ().Underlying()).(*Array); ok {
    45  			for {
    46  				// Put constant info on the thing inside parentheses.
    47  				// That's where (*../noder/writer).expr expects it.
    48  				// See issue 73476.
    49  				p, ok := rangeVar.(*syntax.ParenExpr)
    50  				if !ok {
    51  					break
    52  				}
    53  				rangeVar = p.X
    54  			}
    55  			// Override type of rangeVar to be a constant
    56  			// (and thus side-effects will not be computed
    57  			// by the backend).
    58  			check.record(&operand{
    59  				mode_: constant_,
    60  				expr:  rangeVar,
    61  				typ_:  Typ[Int],
    62  				val:   constant.MakeInt64(t.len),
    63  				id:    x.id,
    64  			})
    65  		}
    66  	}
    67  
    68  	// determine key/value types
    69  	var key, val Type
    70  	if x.isValid() {
    71  		k, v, cause, ok := rangeKeyVal(check, x.typ(), func(v goVersion) bool {
    72  			return check.allowVersion(v)
    73  		})
    74  		switch {
    75  		case !ok && cause != "":
    76  			check.softErrorf(&x, InvalidRangeExpr, "cannot range over %s: %s", &x, cause)
    77  		case !ok:
    78  			check.softErrorf(&x, InvalidRangeExpr, "cannot range over %s", &x)
    79  		case k == nil && sKey != nil:
    80  			check.softErrorf(sKey, InvalidIterVar, "range over %s permits no iteration variables", &x)
    81  		case v == nil && sValue != nil:
    82  			check.softErrorf(sValue, InvalidIterVar, "range over %s permits only one iteration variable", &x)
    83  		case sExtra != nil:
    84  			check.softErrorf(sExtra, InvalidIterVar, "range clause permits at most two iteration variables")
    85  		}
    86  		key, val = k, v
    87  	}
    88  
    89  	// Open the for-statement block scope now, after the range clause.
    90  	// Iteration variables declared with := need to go in this scope (was go.dev/issue/51437).
    91  	check.openScope(rangeStmt, "range")
    92  	defer check.closeScope()
    93  
    94  	// check assignment to/declaration of iteration variables
    95  	// (irregular assignment, cannot easily map to existing assignment checks)
    96  
    97  	// lhs expressions and initialization value (rhs) types
    98  	lhs := [2]syntax.Expr{sKey, sValue} // sKey, sValue may be nil
    99  	rhs := [2]Type{key, val}            // key, val may be nil
   100  
   101  	rangeOverInt := isInteger(x.typ())
   102  
   103  	if isDef {
   104  		// short variable declaration
   105  		var vars []*Var
   106  		for i, lhs := range lhs {
   107  			if lhs == nil {
   108  				continue
   109  			}
   110  
   111  			// determine lhs variable
   112  			var obj *Var
   113  			if ident, _ := lhs.(*syntax.Name); ident != nil {
   114  				// declare new variable
   115  				name := ident.Value
   116  				obj = newVar(LocalVar, ident.Pos(), check.pkg, name, nil)
   117  				check.recordDef(ident, obj)
   118  				// _ variables don't count as new variables
   119  				if name != "_" {
   120  					vars = append(vars, obj)
   121  				}
   122  			} else {
   123  				check.errorf(lhs, InvalidSyntaxTree, "cannot declare %s", lhs)
   124  				obj = newVar(LocalVar, lhs.Pos(), check.pkg, "_", nil) // dummy variable
   125  			}
   126  			assert(obj.typ == nil)
   127  
   128  			// initialize lhs iteration variable, if any
   129  			typ := rhs[i]
   130  			if typ == nil || typ == Typ[Invalid] {
   131  				// typ == Typ[Invalid] can happen if allowVersion fails.
   132  				obj.typ = Typ[Invalid]
   133  				check.usedVars[obj] = true // don't complain about unused variable
   134  				continue
   135  			}
   136  
   137  			if rangeOverInt {
   138  				assert(i == 0) // at most one iteration variable (rhs[1] == nil or Typ[Invalid] for rangeOverInt)
   139  				check.initVar(obj, &x, "range clause")
   140  			} else {
   141  				var y operand
   142  				y.mode_ = value
   143  				y.expr = lhs // we don't have a better rhs expression to use here
   144  				y.typ_ = typ
   145  				check.initVar(obj, &y, "assignment") // error is on variable, use "assignment" not "range clause"
   146  			}
   147  			assert(obj.typ != nil)
   148  		}
   149  
   150  		// declare variables
   151  		if len(vars) > 0 {
   152  			scopePos := rangeStmt.Body.Pos()
   153  			for _, obj := range vars {
   154  				check.declare(check.scope, nil /* recordDef already called */, obj, scopePos)
   155  			}
   156  		} else {
   157  			check.error(noNewVarPos, NoNewVar, "no new variables on left side of :=")
   158  		}
   159  	} else if sKey != nil /* lhs[0] != nil */ {
   160  		// ordinary assignment
   161  		for i, lhs := range lhs {
   162  			if lhs == nil {
   163  				continue
   164  			}
   165  
   166  			// assign to lhs iteration variable, if any
   167  			typ := rhs[i]
   168  			if typ == nil || typ == Typ[Invalid] {
   169  				continue
   170  			}
   171  
   172  			if rangeOverInt {
   173  				assert(i == 0) // at most one iteration variable (rhs[1] == nil or Typ[Invalid] for rangeOverInt)
   174  				check.assignVar(lhs, nil, &x, "range clause")
   175  				// If the assignment succeeded, if x was untyped before, it now
   176  				// has a type inferred via the assignment. It must be an integer.
   177  				// (go.dev/issues/67027)
   178  				if x.isValid() && !isInteger(x.typ()) {
   179  					check.softErrorf(lhs, InvalidRangeExpr, "cannot use iteration variable of type %s", x.typ())
   180  				}
   181  			} else {
   182  				var y operand
   183  				y.mode_ = value
   184  				y.expr = lhs // we don't have a better rhs expression to use here
   185  				y.typ_ = typ
   186  				check.assignVar(lhs, nil, &y, "assignment") // error is on variable, use "assignment" not "range clause"
   187  			}
   188  		}
   189  	} else if rangeOverInt {
   190  		// If we don't have any iteration variables, we still need to
   191  		// check that a (possibly untyped) integer range expression x
   192  		// is valid.
   193  		// We do this by checking the assignment _ = x. This ensures
   194  		// that an untyped x can be converted to a value of its default
   195  		// type (rune or int).
   196  		check.assignment(&x, nil, "range clause")
   197  	}
   198  
   199  	check.stmt(inner, rangeStmt.Body)
   200  }
   201  
   202  // rangeKeyVal returns the key and value type produced by a range clause
   203  // over an expression of type orig.
   204  // If allowVersion != nil, it is used to check the required language version.
   205  // If the range clause is not permitted, rangeKeyVal returns ok = false.
   206  // When ok = false, rangeKeyVal may also return a reason in cause.
   207  // The check parameter is only used in case of an error; it may be nil.
   208  func rangeKeyVal(check *Checker, orig Type, allowVersion func(goVersion) bool) (key, val Type, cause string, ok bool) {
   209  	bad := func(cause string) (Type, Type, string, bool) {
   210  		return Typ[Invalid], Typ[Invalid], cause, false
   211  	}
   212  
   213  	rtyp, err := commonUnder(orig, func(t, u Type) *typeError {
   214  		// A channel must permit receive operations.
   215  		if ch, _ := u.(*Chan); ch != nil && ch.dir == SendOnly {
   216  			return typeErrorf("receive from send-only channel %s", t)
   217  		}
   218  		return nil
   219  	})
   220  	if rtyp == nil {
   221  		return bad(err.format(check))
   222  	}
   223  
   224  	switch typ := arrayPtrDeref(rtyp).(type) {
   225  	case *Basic:
   226  		if isString(typ) {
   227  			return Typ[Int], universeRune, "", true // use 'rune' name
   228  		}
   229  		if isInteger(typ) {
   230  			if allowVersion != nil && !allowVersion(go1_22) {
   231  				return bad("requires go1.22 or later")
   232  			}
   233  			return orig, nil, "", true
   234  		}
   235  	case *Array:
   236  		return Typ[Int], typ.elem, "", true
   237  	case *Slice:
   238  		return Typ[Int], typ.elem, "", true
   239  	case *Map:
   240  		return typ.key, typ.elem, "", true
   241  	case *Chan:
   242  		assert(typ.dir != SendOnly)
   243  		return typ.elem, nil, "", true
   244  	case *Signature:
   245  		if allowVersion != nil && !allowVersion(go1_23) {
   246  			return bad("requires go1.23 or later")
   247  		}
   248  		// check iterator arity
   249  		// TODO(gri) error messages could be less verbose (consider rangeStmt error and cause returned here)
   250  		switch {
   251  		case typ.Params().Len() != 1:
   252  			return bad("func must be func(yield func(...) bool): wrong argument count")
   253  		case typ.Results().Len() != 0:
   254  			return bad("func must be func(yield func(...) bool): unexpected results")
   255  		}
   256  		assert(typ.Recv() == nil)
   257  		// check iterator argument type
   258  		u, err := commonUnder(typ.Params().At(0).Type(), nil)
   259  		cb, _ := u.(*Signature)
   260  		switch {
   261  		case cb == nil:
   262  			if err != nil {
   263  				return bad(check.sprintf("func must be func(yield func(...) bool): in yield type, %s", err.format(check)))
   264  			} else {
   265  				return bad("func must be func(yield func(...) bool): argument is not func")
   266  			}
   267  		case cb.Params().Len() > 2:
   268  			return bad("func must be func(yield func(...) bool): yield func has too many parameters")
   269  		case cb.Results().Len() != 1 || !Identical(cb.Results().At(0).Type(), universeBool):
   270  			// see go.dev/issues/71131, go.dev/issues/71164
   271  			if cb.Results().Len() == 1 && isBoolean(cb.Results().At(0).Type()) {
   272  				return bad("func must be func(yield func(...) bool): yield func returns user-defined boolean, not bool")
   273  			} else {
   274  				return bad("func must be func(yield func(...) bool): yield func does not return bool")
   275  			}
   276  		case cb.Variadic():
   277  			return bad(check.sprintf("yield func of type %s cannot be variadic", cb))
   278  		}
   279  		assert(cb.Recv() == nil)
   280  		// determine key and value types, if any
   281  		if cb.Params().Len() >= 1 {
   282  			key = cb.Params().At(0).Type()
   283  		}
   284  		if cb.Params().Len() >= 2 {
   285  			val = cb.Params().At(1).Type()
   286  		}
   287  		return key, val, "", true
   288  	}
   289  	return
   290  }
   291  

View as plain text