3

The title maybe vague because I don't know how to describe such situation.

// go 1.23.0

package main

import (
    "fmt"
)

func compare(num uint16, bit uint8) bool {
    // ok
    return num >= (1 << bit)
}

func main() {
    fmt.Println(compare(65535, 16)) // this line will print true

    var num uint16 = 65535
    var bit uint8 = 16
    // compile error: Invalid operation: num >= (1 << bit) (cannot convert the constant (1 << bit) to the type uint16)
    fmt.Println(num >= (1 << bit))
}

What's the difference between the two comparison expression?

4
  • "cannot convert the constant" - not sure, but what I am reading out of this is that the compiler in latter case is trying to precompute and inline the value while in the former case, it cannot do that (because it doesn't "know" the concrete value at compile-time). During inlining, the compiler seems to be lacking the ability to expand the type automatically. Commented Nov 14 at 7:28
  • Using go 1.25.3, I get true in both cases and no compile error. Commented Nov 14 at 7:40
  • Go Playground also doesn't repro: go.dev/play/p/XO00vIk_Odc with Go 1.25 Commented Nov 14 at 7:53
  • So no matter for readability or compatibility, it's better to do a explicit conversion when encounter such situation where different types of variables are taking a part in. Commented Nov 14 at 7:54

1 Answer 1

4

I think this is a bug in go1.23, apparently fixed in go1.24. Here's my justification (sorry, somewhat long and full of language lawyerisms).

First note that in a shift expression, if the left-hand value is an untyped constant, it gets the type that it would have if the constant would have if the shift was omitted. Since the context is num >= (1 << bit) that means 1 gets the type uint16 -- the type of num.

From https://go.dev/ref/spec#Operators

The right operand in a shift expression must have integer type [Go 1.13] 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 implicitly converted to the type it would assume if the shift expression were replaced by its left operand alone.

A constant expression isn't allowed to overflow or round except in some floating-point contexts, so if 1<<bit is a constant expression then the compiler error is correct; uint16(1) << 16 isn't valid it involves truncation (producing the value 0).

From https://go.dev/ref/spec#Constant_expressions

Constant expressions are always evaluated exactly; intermediate values and the constants themselves may require precision significantly larger than supported by any predeclared type in the language.

(I observe that the language here is ambiguous; there's something to be said for uint16(1) << 16 being evaluated exactly -- as 0, although I guess this is not the intention).

But, the language in the spec is quite clear about whether 1<<bit is a constant expression. From https://go.dev/ref/spec#Constant_expressions

Constant expressions may contain only constant operands and are evaluated at compile time.

So I think the error message reported by go1.23 is a mistake because 1<<bit is not a constant expression because bit is not a constant (as defined by the spec). My guess it's the result of an over-aggressive constant folding optimizer.

I had a quick skim through go issues from the go1.24 release period and didn't see any related fix, but perhaps I missed something.

Sign up to request clarification or add additional context in comments.

4 Comments

As you mentioned uint16(1) << 16, but it seems tend to be uint16(1 << 16) since the compiler said cannot convert the constant (1 << bit) to the type uint16. No matter what, from my POV, I agree with: "it's the result of an over-aggressive constant folding optimizer" in such context like main, I guess. And finally it was fixed in go 1.24 to produce the same behavior in different contexts.
Yes, the fact that the compiler is claiming to interpret (1<<bit) in this context as "the constant (1<<bit)" is strong evidence that there was a bug in this release of go. The language specification extracts in my answer are clear enough I think.
there is no mention of such a bug fix in any minor release of 1.23. The OP's program compiles fine with 1.23.8. The most likely explanation is the OP copy-pasted the wrong reproducer, unless someone can show otherwise.
oops, it's an IDE BUG.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.