On Sun, Dec 30, 2018 at 12:05 PM Ian Lance Taylor <i...@golang.org> wrote:
> On Sat, Dec 29, 2018 at 11:12 PM Caleb Spare <cesp...@gmail.com> wrote: > > > > I noticed the following: > > > > package main > > > > func main() { > > x := float64(1 << 3) // fine (constant expression) > > > > v := uint(3) > > x = float64(1 << v) // invalid operation: 1 << uint(v) (shift of type > float64) > > > > _ = x > > } > > > > (playground: https://play.golang.org/p/xMhEvyMw0wg) > > > > It's clear from the spec why the first one works, but I was a little > surprised that the second one doesn't. As best I can tell, the spec > discusses this at https://golang.org/ref/spec#Operators: > > > > The right operand in a shift expression must have unsigned integer type > or be an untyped constant representable by a value of type uint. If the > left operand of a non-constant shift expression is an untyped constant, it > is first converted to the type it would assume if the shift expression were > replaced by its left operand alone. > > > > var s uint = 33 > > var i = 1<<s // 1 has type int > > var j int32 = 1<<s // 1 has type int32; j == 0 > > var k = uint64(1<<s) // 1 has type uint64; k == 1<<33 > > var m int = 1.0<<s // 1.0 has type int; m == 0 if ints are > 32bits in size > > var n = 1.0<<s == j // 1.0 has type int32; n == true > > var o = 1<<s == 2<<s // 1 and 2 have type int; o == true if > ints are 32bits in size > > var p = 1<<s == 1<<33 // illegal if ints are 32bits in size: 1 > has type int, but 1<<33 overflows int > > var u = 1.0<<s // illegal: 1.0 has type float64, cannot > shift > > var u1 = 1.0<<s != 0 // illegal: 1.0 has type float64, cannot > shift > > var u2 = 1<<s != 1.0 // illegal: 1 has type float64, cannot > shift > > var v float32 = 1<<s // illegal: 1 has type float32, cannot > shift > > var w int64 = 1.0<<33 // 1.0<<33 is a constant shift expression > > var x = a[1.0<<s] // 1.0 has type int; x == a[0] if ints are > 32bits in size > > var a = make([]byte, 1.0<<s) // 1.0 has type int; len(a) == 0 if ints > are 32bits in size > > > > I'm trying to understand the logic by which the 1 in float64(1 << v) is > converted to a float64. The last sentence says "[...] it is first converted > to the type it would assume if the shift expression were replaced by its > left operand alone", but if we wrote float64(1) would we really say that > the 1 is a float64? Clearly float64(1) is, as a whole, a typed float64 > constant, but I don't see how we can infer a type for 1 itself in that > context. > > > > The most similar of the above examples from the spec is this one: > > > > var v float32 = 1<<s // illegal: 1 has type float32, cannot > shift > > > > This seems logical: the whole RHS must evaluate to something assignable > to a float32 variable, so 1 is inferred to be a float32. But if we write > > > > var v = float32(1 << s) > > > > as in my example code then it doesn't really make sense to infer float32 > as the type for 1. In fact, given that the code is trying to convert 1 << s > to a float32, it seems better to infer that the type is not float32! > > > > I found a couple of related issues discussing this: > > > > https://github.com/golang/go/issues/13061#issuecomment-152297737 > > https://github.com/golang/go/issues/19963 > > > > So I have two questions: > > > > 1. Does the spec fully specify this behavior? (I think it ought to be > clarified.) > > 2. Should the spec be changed to make this behavior more sensible? > > > > Regarding (2), my suggested change would be for an untyped constant on > the LHS of a shift that is inside a conversion to be inferred to have its > default type rather than the conversion type: > > > > s := uint(3) > > _ = float64(1 << s) // used to be an error, ok with my suggested change > > _ = int(1.0 << s) // used to be ok, now an error with my suggested > change > > > > Given that it would break certain (very unusual) code, it would probably > have to be a Go 2 change. > > First let me say that there have been many hours of discussions on the > behavior of type inference for shift operations, and I don't think > anybody has any interest in reopening that discussion. > > I believe that the spec does fully specify the current behavior. You > even quote it: "it is first converted to the type it would assume if > the shift expression were replaced by its left operand alone." > Changing the shift expression, we get `x = float64(1)`. float64(1) is > a constant expression. The 1 is an untyped constant. An untyped > constant is converted to the type required by the expression. In this > case the expression requires a float64. So the 1 is a float64. Then > we reinsert the shift, and discover an attempt to shift a floating > point constant, which is an error. > > You suggest that in `float64(1)` the 1 doesn't really have a type, so > it should get the default type. But that would mean that `int64(1.2)` > would be the same as `int64(1)`, whereas today it is an error. That > said, this language change is still perhaps worth considering not > because of shifts, which are fairly uninteresting, but for other > reasons. See https://golang.org/issue/6923. > > Ian > Thanks Ian, that's interesting food for thought. It now seems that the case I encountered is just one particular manifestation of the semantics regarding conversion expressions with constant arguments that is more fully discussed in https://golang.org/issue/6923. It seems to me that the changes proposed there would also fix this shift case, so I'll follow along with that issue. Thanks for bringing it to my attention. Caleb -- You received this message because you are subscribed to the Google Groups "golang-nuts" group. To unsubscribe from this group and stop receiving emails from it, send an email to golang-nuts+unsubscr...@googlegroups.com. For more options, visit https://groups.google.com/d/optout.