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.

Reply via email to