crystal-lang / crystal

The Crystal Programming Language
https://crystal-lang.org
Apache License 2.0
19.49k stars 1.62k forks source link

Compiler error doing BigInt shifts: 3.to_big_i << n #15201

Closed jzakiya closed 6 days ago

jzakiya commented 6 days ago

This compiles fine.

Crystal 1.14.0 [dacd97bcc] (2024-10-09)

LLVM: 18.1.6
Default target: x86_64-unknown-linux-gnu
def mpgen(p, steps = 1u64)
  puts "Starting Mp gen exponent = #{p}"
  puts "Starting Mp gen process at step #{steps}"
  r = (1.to_big_i << p) - 1
  modpnk = modpn = 3.to_big_i << p
  (steps-1).times { modpnk = (modpnk << 2) + modpn }
  primes_checked = 0u64
  loop do
    mp = modpnk + r
    print "\rsteps = #{steps}"
    p = mp.bit_length.to_u64
    if prime?(p)
      primes_checked += 1
      break if mp_prime?(p, mp)
    end
    steps += 1
    modpnk = (modpnk << 2) + modpn
  end
  print "\rsteps = #{steps}\n"
  puts "Primes checked = #{primes_checked}"
  puts "Next Mp prime = #{p}"
  p
end

Then I changed the code to this:

def mpgen(n, steps = 1u64)
  puts "Starting Mp gen exponent = #{n}"
  puts "Starting Mp gen process at step #{steps}"
  primes_checked = 0u64
  loop do
    mp = (3.to_big_i << n) + (1.to_big_i << n) - 1
    print "\rsteps = #{steps}"
    p = mp.bit_length.to_u64
    if prime?(p)
      primes_checked += 1
      break if mp_prime?(p, mp)
    end
    steps += 1
    n += 2
  end
  print "\rsteps = #{steps}\n"
  puts "Primes checked = #{primes_checked}"
  puts "Next Mp prime = #{p}"
  p
end

And now get this error.

Showing last frame. Use --error-trace for full trace.

In MPgenerate1.cr:40:25

 40 | mp = (3.to_big_i << n) + (1.to_big_i << n) - 1
                          ^
Error: expected argument #1 to 'BigInt#<<' to be Int, not (Tuple() | UInt64)

Overloads are:
 - BigInt#<<(other : Int)
 - Int#<<(count : Int)

When I change the order of those 2 terms it still points to the first n.

This has to be a compiler error, right?

jzakiya commented 6 days ago

Here's a gist file of all the code to compile to produce the error.

https://gist.github.com/jzakiya/11607c333f6cec2207c348b58535d7d5

Blacksmoke16 commented 6 days ago

The compiler is technically right here. There's a few things going on, but I don't really think this is a compiler bug at least. Adding method parameter and return type restrictions makes the problem more clear.

  1. Using loop do creates its own scope within the block, so the compiler has to handle the case where it doesn't execute I guess, using while true does not suffer from this and works just fine.
  2. Your variable p is basically conflicting with the ::p() method which returns the arguments passed to it, and since its only assigned in the loop do, if that doesn't execute the compiler will call ::p() with no arguments so it returns an empty tuple, which without a type restriction on the method or n parameter, is allowed to pass thru, turning the type of n in to Tuple() | UInt64.

Closing as I don't think there's any real compiler bug here.